Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f6811370 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'chrome-platform-for-linus-4.17' of...

Merge tag 'chrome-platform-for-linus-4.17' of git://git.kernel.org/pub/scm/linux/kernel/git/bleung/chrome-platform

Pull chrome platform updates from Benson Leung:

 - a series from Dmitry to remove platform data from chromeos_laptop.c,
   which was the only user of platform data for the atmel_mxt_ts driver.

 - a series to clean up sysfs and debugfs for cros_ec

 - other misc cleanups

* tag 'chrome-platform-for-linus-4.17' of git://git.kernel.org/pub/scm/linux/kernel/git/bleung/chrome-platform: (22 commits)
  platform/chrome: mfd/cros_ec_dev: Add sysfs entry to set keyboard wake lid angle
  platform/chrome: cros_ec_debugfs: Add PD port info to debugfs
  platform/chrome: cros_ec_debugfs: Use octal permissions '0444'
  platform/chrome: cros_ec_sysfs: use permission-specific DEVICE_ATTR variants
  platform/chrome: cros_ec_sysfs: introduce to_cros_ec_dev define.
  platform/chrome: cros_ec_sysfs: Modify error handling
  platform/chrome: cros_ec_lpc: Add support for Google devices using custom coreboot firmware
  platform/chrome: cros_ec_lpc: wake up from s2idle on Chrome EC
  Input: atmel_mxt_ts - remove platform data support
  platform/chrome: chromeos_laptop - discard data for unneeded boards
  platform/chrome: chromeos_laptop - use device properties for Pixel
  platform/chrome: chromeos_laptop - rely on I2C to set up interrupt trigger
  platform/chrome: chromeos_laptop - use I2C notifier to create devices
  platform/chrome: chromeos_laptop - parse DMI IRQ data once
  platform/chrome: chromeos_laptop - rework i2c peripherals initialization
  platform/chrome: chromeos_laptop - factor out getting IRQ from DMI
  platform/chrome: chromeos_laptop - introduce pr_fmt()
  platform/chrome: chromeos_laptop - stop setting suspend mode for Atmel devices
  platform/chrome: chromeos_laptop - add SPDX identifier
  Input: atmel_mxt_ts - switch ChromeOS ACPI devices to generic props
  ...
parents ca4e7c51 c171d3b8
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -2426,7 +2426,6 @@ T: git git://github.com/ndyer/linux.git
S:	Maintained
F:	Documentation/devicetree/bindings/input/atmel,maxtouch.txt
F:	drivers/input/touchscreen/atmel_mxt_ts.c
F:	include/linux/platform_data/atmel_mxt_ts.h

ATMEL SAMA5D2 ADC DRIVER
M:	Ludovic Desroches <ludovic.desroches@microchip.com>
+123 −108
Original line number Diff line number Diff line
@@ -23,12 +23,13 @@
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/platform_data/atmel_mxt_ts.h>
#include <linux/input/mt.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/gpio/consumer.h>
#include <linux/property.h>
#include <asm/unaligned.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
@@ -268,12 +269,16 @@ static const struct v4l2_file_operations mxt_video_fops = {
	.poll = vb2_fop_poll,
};

enum mxt_suspend_mode {
	MXT_SUSPEND_DEEP_SLEEP	= 0,
	MXT_SUSPEND_T9_CTRL	= 1,
};

/* Each client has this additional data */
struct mxt_data {
	struct i2c_client *client;
	struct input_dev *input_dev;
	char phys[64];		/* device physical location */
	const struct mxt_platform_data *pdata;
	struct mxt_object *object_table;
	struct mxt_info info;
	unsigned int irq;
@@ -324,6 +329,11 @@ struct mxt_data {

	/* for config update handling */
	struct completion crc_completion;

	u32 *t19_keymap;
	unsigned int t19_num_keys;

	enum mxt_suspend_mode suspend_mode;
};

struct mxt_vb2_buffer {
@@ -742,15 +752,14 @@ static int mxt_write_object(struct mxt_data *data,
static void mxt_input_button(struct mxt_data *data, u8 *message)
{
	struct input_dev *input = data->input_dev;
	const struct mxt_platform_data *pdata = data->pdata;
	int i;

	for (i = 0; i < pdata->t19_num_keys; i++) {
		if (pdata->t19_keymap[i] == KEY_RESERVED)
	for (i = 0; i < data->t19_num_keys; i++) {
		if (data->t19_keymap[i] == KEY_RESERVED)
			continue;

		/* Active-low switch */
		input_report_key(input, pdata->t19_keymap[i],
		input_report_key(input, data->t19_keymap[i],
				 !(message[1] & BIT(i)));
	}
}
@@ -758,7 +767,7 @@ static void mxt_input_button(struct mxt_data *data, u8 *message)
static void mxt_input_sync(struct mxt_data *data)
{
	input_mt_report_pointer_emulation(data->input_dev,
					  data->pdata->t19_num_keys);
					  data->t19_num_keys);
	input_sync(data->input_dev);
}

@@ -1858,7 +1867,6 @@ static void mxt_input_close(struct input_dev *dev);
static void mxt_set_up_as_touchpad(struct input_dev *input_dev,
				   struct mxt_data *data)
{
	const struct mxt_platform_data *pdata = data->pdata;
	int i;

	input_dev->name = "Atmel maXTouch Touchpad";
@@ -1872,15 +1880,14 @@ static void mxt_set_up_as_touchpad(struct input_dev *input_dev,
	input_abs_set_res(input_dev, ABS_MT_POSITION_Y,
			  MXT_PIXELS_PER_MM);

	for (i = 0; i < pdata->t19_num_keys; i++)
		if (pdata->t19_keymap[i] != KEY_RESERVED)
	for (i = 0; i < data->t19_num_keys; i++)
		if (data->t19_keymap[i] != KEY_RESERVED)
			input_set_capability(input_dev, EV_KEY,
					     pdata->t19_keymap[i]);
					     data->t19_keymap[i]);
}

static int mxt_initialize_input_device(struct mxt_data *data)
{
	const struct mxt_platform_data *pdata = data->pdata;
	struct device *dev = &data->client->dev;
	struct input_dev *input_dev;
	int error;
@@ -1946,7 +1953,7 @@ static int mxt_initialize_input_device(struct mxt_data *data)
	}

	/* If device has buttons we assume it is a touchpad */
	if (pdata->t19_num_keys) {
	if (data->t19_num_keys) {
		mxt_set_up_as_touchpad(input_dev, data);
		mt_flags |= INPUT_MT_POINTER;
	} else {
@@ -2868,7 +2875,7 @@ static const struct attribute_group mxt_attr_group = {

static void mxt_start(struct mxt_data *data)
{
	switch (data->pdata->suspend_mode) {
	switch (data->suspend_mode) {
	case MXT_SUSPEND_T9_CTRL:
		mxt_soft_reset(data);

@@ -2886,12 +2893,11 @@ static void mxt_start(struct mxt_data *data)
		mxt_t6_command(data, MXT_COMMAND_CALIBRATE, 1, false);
		break;
	}

}

static void mxt_stop(struct mxt_data *data)
{
	switch (data->pdata->suspend_mode) {
	switch (data->suspend_mode) {
	case MXT_SUSPEND_T9_CTRL:
		/* Touch disable */
		mxt_write_object(data,
@@ -2921,55 +2927,49 @@ static void mxt_input_close(struct input_dev *dev)
	mxt_stop(data);
}

#ifdef CONFIG_OF
static const struct mxt_platform_data *mxt_parse_dt(struct i2c_client *client)
static int mxt_parse_device_properties(struct mxt_data *data)
{
	struct mxt_platform_data *pdata;
	struct device_node *np = client->dev.of_node;
	static const char keymap_property[] = "linux,gpio-keymap";
	struct device *dev = &data->client->dev;
	u32 *keymap;
	int proplen, ret;

	if (!np)
		return ERR_PTR(-ENOENT);

	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
	if (!pdata)
		return ERR_PTR(-ENOMEM);
	int n_keys;
	int error;

	if (of_find_property(np, "linux,gpio-keymap", &proplen)) {
		pdata->t19_num_keys = proplen / sizeof(u32);
	if (device_property_present(dev, keymap_property)) {
		n_keys = device_property_read_u32_array(dev, keymap_property,
							NULL, 0);
		if (n_keys <= 0) {
			error = n_keys < 0 ? n_keys : -EINVAL;
			dev_err(dev, "invalid/malformed '%s' property: %d\n",
				keymap_property, error);
			return error;
		}

		keymap = devm_kzalloc(&client->dev,
				pdata->t19_num_keys * sizeof(keymap[0]),
		keymap = devm_kmalloc_array(dev, n_keys, sizeof(*keymap),
					    GFP_KERNEL);
		if (!keymap)
			return ERR_PTR(-ENOMEM);

		ret = of_property_read_u32_array(np, "linux,gpio-keymap",
						 keymap, pdata->t19_num_keys);
		if (ret)
			dev_warn(&client->dev,
				 "Couldn't read linux,gpio-keymap: %d\n", ret);
			return -ENOMEM;

		pdata->t19_keymap = keymap;
		error = device_property_read_u32_array(dev, keymap_property,
						       keymap, n_keys);
		if (error) {
			dev_err(dev, "failed to parse '%s' property: %d\n",
				keymap_property, error);
			return error;
		}

	pdata->suspend_mode = MXT_SUSPEND_DEEP_SLEEP;

	return pdata;
		data->t19_keymap = keymap;
		data->t19_num_keys = n_keys;
	}
#else
static const struct mxt_platform_data *mxt_parse_dt(struct i2c_client *client)
{
	return ERR_PTR(-ENOENT);

	return 0;
}
#endif

#ifdef CONFIG_ACPI

struct mxt_acpi_platform_data {
	const char *hid;
	struct mxt_platform_data pdata;
	const struct property_entry *props;
};

static unsigned int samus_touchpad_buttons[] = {
@@ -2979,14 +2979,16 @@ static unsigned int samus_touchpad_buttons[] = {
	BTN_LEFT
};

static const struct property_entry samus_touchpad_props[] = {
	PROPERTY_ENTRY_U32_ARRAY("linux,gpio-keymap", samus_touchpad_buttons),
	{ }
};

static struct mxt_acpi_platform_data samus_platform_data[] = {
	{
		/* Touchpad */
		.hid	= "ATML0000",
		.pdata	= {
			.t19_num_keys	= ARRAY_SIZE(samus_touchpad_buttons),
			.t19_keymap	= samus_touchpad_buttons,
		},
		.props	= samus_touchpad_props,
	},
	{
		/* Touchscreen */
@@ -3004,14 +3006,16 @@ static unsigned int chromebook_tp_buttons[] = {
	BTN_LEFT
};

static const struct property_entry chromebook_tp_props[] = {
	PROPERTY_ENTRY_U32_ARRAY("linux,gpio-keymap", chromebook_tp_buttons),
	{ }
};

static struct mxt_acpi_platform_data chromebook_platform_data[] = {
	{
		/* Touchpad */
		.hid	= "ATML0000",
		.pdata	= {
			.t19_num_keys	= ARRAY_SIZE(chromebook_tp_buttons),
			.t19_keymap	= chromebook_tp_buttons,
		},
		.props	= chromebook_tp_props,
	},
	{
		/* Touchscreen */
@@ -3041,83 +3045,85 @@ static const struct dmi_system_id mxt_dmi_table[] = {
	{ }
};

static const struct mxt_platform_data *mxt_parse_acpi(struct i2c_client *client)
static int mxt_prepare_acpi_properties(struct i2c_client *client)
{
	struct acpi_device *adev;
	const struct dmi_system_id *system_id;
	const struct mxt_acpi_platform_data *acpi_pdata;

	/*
	 * Ignore ACPI devices representing bootloader mode.
	 *
	 * This is a bit of a hack: Google Chromebook BIOS creates ACPI
	 * devices for both application and bootloader modes, but we are
	 * interested in application mode only (if device is in bootloader
	 * mode we'll end up switching into application anyway). So far
	 * application mode addresses were all above 0x40, so we'll use it
	 * as a threshold.
	 */
	if (client->addr < 0x40)
		return ERR_PTR(-ENXIO);

	adev = ACPI_COMPANION(&client->dev);
	if (!adev)
		return ERR_PTR(-ENOENT);
		return -ENOENT;

	system_id = dmi_first_match(mxt_dmi_table);
	if (!system_id)
		return ERR_PTR(-ENOENT);
		return -ENOENT;

	acpi_pdata = system_id->driver_data;
	if (!acpi_pdata)
		return ERR_PTR(-ENOENT);
		return -ENOENT;

	while (acpi_pdata->hid) {
		if (!strcmp(acpi_device_hid(adev), acpi_pdata->hid))
			return &acpi_pdata->pdata;
		if (!strcmp(acpi_device_hid(adev), acpi_pdata->hid)) {
			/*
			 * Remove previously installed properties if we
			 * are probing this device not for the very first
			 * time.
			 */
			device_remove_properties(&client->dev);

			/*
			 * Now install the platform-specific properties
			 * that are missing from ACPI.
			 */
			device_add_properties(&client->dev, acpi_pdata->props);
			break;
		}

		acpi_pdata++;
	}

	return ERR_PTR(-ENOENT);
	return 0;
}
#else
static const struct mxt_platform_data *mxt_parse_acpi(struct i2c_client *client)
static int mxt_prepare_acpi_properties(struct i2c_client *client)
{
	return ERR_PTR(-ENOENT);
	return -ENOENT;
}
#endif

static const struct mxt_platform_data *
mxt_get_platform_data(struct i2c_client *client)
static const struct dmi_system_id chromebook_T9_suspend_dmi[] = {
	{
	const struct mxt_platform_data *pdata;

	pdata = dev_get_platdata(&client->dev);
	if (pdata)
		return pdata;

	pdata = mxt_parse_dt(client);
	if (!IS_ERR(pdata) || PTR_ERR(pdata) != -ENOENT)
		return pdata;

	pdata = mxt_parse_acpi(client);
	if (!IS_ERR(pdata) || PTR_ERR(pdata) != -ENOENT)
		return pdata;

	dev_err(&client->dev, "No platform data specified\n");
	return ERR_PTR(-EINVAL);
}
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
			DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
		},
	},
	{
		.matches = {
			DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"),
		},
	},
	{ }
};

static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct mxt_data *data;
	const struct mxt_platform_data *pdata;
	int error;

	pdata = mxt_get_platform_data(client);
	if (IS_ERR(pdata))
		return PTR_ERR(pdata);
	/*
	 * Ignore ACPI devices representing bootloader mode.
	 *
	 * This is a bit of a hack: Google Chromebook BIOS creates ACPI
	 * devices for both application and bootloader modes, but we are
	 * interested in application mode only (if device is in bootloader
	 * mode we'll end up switching into application anyway). So far
	 * application mode addresses were all above 0x40, so we'll use it
	 * as a threshold.
	 */
	if (ACPI_COMPANION(&client->dev) && client->addr < 0x40)
		return -ENXIO;

	data = devm_kzalloc(&client->dev, sizeof(struct mxt_data), GFP_KERNEL);
	if (!data)
@@ -3127,7 +3133,6 @@ static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id)
		 client->adapter->nr, client->addr);

	data->client = client;
	data->pdata = pdata;
	data->irq = client->irq;
	i2c_set_clientdata(client, data);

@@ -3135,6 +3140,17 @@ static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id)
	init_completion(&data->reset_completion);
	init_completion(&data->crc_completion);

	data->suspend_mode = dmi_check_system(chromebook_T9_suspend_dmi) ?
		MXT_SUSPEND_T9_CTRL : MXT_SUSPEND_DEEP_SLEEP;

	error = mxt_prepare_acpi_properties(client);
	if (error && error != -ENOENT)
		return error;

	error = mxt_parse_device_properties(data);
	if (error)
		return error;

	data->reset_gpio = devm_gpiod_get_optional(&client->dev,
						   "reset", GPIOD_OUT_LOW);
	if (IS_ERR(data->reset_gpio)) {
@@ -3144,8 +3160,7 @@ static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id)
	}

	error = devm_request_threaded_irq(&client->dev, client->irq,
					  NULL, mxt_interrupt,
					  pdata->irqflags | IRQF_ONESHOT,
					  NULL, mxt_interrupt, IRQF_ONESHOT,
					  client->name, data);
	if (error) {
		dev_err(&client->dev, "Failed to register interrupt\n");
@@ -3265,7 +3280,7 @@ MODULE_DEVICE_TABLE(i2c, mxt_id);
static struct i2c_driver mxt_driver = {
	.driver = {
		.name	= "atmel_mxt_ts",
		.of_match_table = of_match_ptr(mxt_of_match),
		.of_match_table = mxt_of_match,
		.acpi_match_table = ACPI_PTR(mxt_acpi_id),
		.pm	= &mxt_pm_ops,
	},
+13 −18
Original line number Diff line number Diff line
@@ -305,8 +305,8 @@ static void cros_ec_sensors_register(struct cros_ec_dev *ec)

	resp = (struct ec_response_motion_sense *)msg->data;
	sensor_num = resp->dump.sensor_count;
	/* Allocate 2 extra sensors in case lid angle or FIFO are needed */
	sensor_cells = kzalloc(sizeof(struct mfd_cell) * (sensor_num + 2),
	/* Allocate 1 extra sensors in FIFO are needed */
	sensor_cells = kzalloc(sizeof(struct mfd_cell) * (sensor_num + 1),
			       GFP_KERNEL);
	if (sensor_cells == NULL)
		goto error;
@@ -362,16 +362,10 @@ static void cros_ec_sensors_register(struct cros_ec_dev *ec)
		sensor_type[resp->info.type]++;
		id++;
	}
	if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2) {
		sensor_platforms[id].sensor_num = sensor_num;

		sensor_cells[id].name = "cros-ec-angle";
		sensor_cells[id].id = 0;
		sensor_cells[id].platform_data = &sensor_platforms[id];
		sensor_cells[id].pdata_size =
			sizeof(struct cros_ec_sensor_platform);
		id++;
	}
	if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2)
		ec->has_kb_wake_angle = true;

	if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) {
		sensor_cells[id].name = "cros-ec-ring";
		id++;
@@ -424,6 +418,14 @@ static int ec_device_probe(struct platform_device *pdev)
		goto failed;
	}

	/* check whether this EC is a sensor hub. */
	if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE))
		cros_ec_sensors_register(ec);

	/* Take control of the lightbar from the EC. */
	lb_manual_suspend_ctrl(ec, 1);

	/* We can now add the sysfs class, we know which parameter to show */
	retval = cdev_device_add(&ec->cdev, &ec->class_dev);
	if (retval) {
		dev_err(dev, "cdev_device_add failed => %d\n", retval);
@@ -433,13 +435,6 @@ static int ec_device_probe(struct platform_device *pdev)
	if (cros_ec_debugfs_init(ec))
		dev_warn(dev, "failed to create debugfs directory\n");

	/* check whether this EC is a sensor hub. */
	if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE))
		cros_ec_sensors_register(ec);

	/* Take control of the lightbar from the EC. */
	lb_manual_suspend_ctrl(ec, 1);

	return 0;

failed:
+456 −440

File changed.

Preview size limit exceeded, changes collapsed.

+74 −2
Original line number Diff line number Diff line
@@ -211,6 +211,58 @@ static int cros_ec_console_log_release(struct inode *inode, struct file *file)
	return 0;
}

static ssize_t cros_ec_pdinfo_read(struct file *file,
				   char __user *user_buf,
				   size_t count,
				   loff_t *ppos)
{
	char read_buf[EC_USB_PD_MAX_PORTS * 40], *p = read_buf;
	struct cros_ec_debugfs *debug_info = file->private_data;
	struct cros_ec_device *ec_dev = debug_info->ec->ec_dev;
	struct {
		struct cros_ec_command msg;
		union {
			struct ec_response_usb_pd_control_v1 resp;
			struct ec_params_usb_pd_control params;
		};
	} __packed ec_buf;
	struct cros_ec_command *msg;
	struct ec_response_usb_pd_control_v1 *resp;
	struct ec_params_usb_pd_control *params;
	int i;

	msg = &ec_buf.msg;
	params = (struct ec_params_usb_pd_control *)msg->data;
	resp = (struct ec_response_usb_pd_control_v1 *)msg->data;

	msg->command = EC_CMD_USB_PD_CONTROL;
	msg->version = 1;
	msg->insize = sizeof(*resp);
	msg->outsize = sizeof(*params);

	/*
	 * Read status from all PD ports until failure, typically caused
	 * by attempting to read status on a port that doesn't exist.
	 */
	for (i = 0; i < EC_USB_PD_MAX_PORTS; ++i) {
		params->port = i;
		params->role = 0;
		params->mux = 0;
		params->swap = 0;

		if (cros_ec_cmd_xfer_status(ec_dev, msg) < 0)
			break;

		p += scnprintf(p, sizeof(read_buf) + read_buf - p,
			       "p%d: %s en:%.2x role:%.2x pol:%.2x\n", i,
			       resp->state, resp->enabled, resp->role,
			       resp->polarity);
	}

	return simple_read_from_buffer(user_buf, count, ppos,
				       read_buf, p - read_buf);
}

const struct file_operations cros_ec_console_log_fops = {
	.owner = THIS_MODULE,
	.open = cros_ec_console_log_open,
@@ -220,6 +272,13 @@ const struct file_operations cros_ec_console_log_fops = {
	.release = cros_ec_console_log_release,
};

const struct file_operations cros_ec_pdinfo_fops = {
	.owner = THIS_MODULE,
	.open = simple_open,
	.read = cros_ec_pdinfo_read,
	.llseek = default_llseek,
};

static int ec_read_version_supported(struct cros_ec_dev *ec)
{
	struct ec_params_get_cmd_versions_v1 *params;
@@ -288,7 +347,7 @@ static int cros_ec_create_console_log(struct cros_ec_debugfs *debug_info)
	init_waitqueue_head(&debug_info->log_wq);

	if (!debugfs_create_file("console_log",
				 S_IFREG | S_IRUGO,
				 S_IFREG | 0444,
				 debug_info->dir,
				 debug_info,
				 &cros_ec_console_log_fops))
@@ -341,7 +400,7 @@ static int cros_ec_create_panicinfo(struct cros_ec_debugfs *debug_info)
	debug_info->panicinfo_blob.size = ret;

	if (!debugfs_create_blob("panicinfo",
				 S_IFREG | S_IRUGO,
				 S_IFREG | 0444,
				 debug_info->dir,
				 &debug_info->panicinfo_blob)) {
		ret = -ENOMEM;
@@ -355,6 +414,15 @@ static int cros_ec_create_panicinfo(struct cros_ec_debugfs *debug_info)
	return ret;
}

static int cros_ec_create_pdinfo(struct cros_ec_debugfs *debug_info)
{
	if (!debugfs_create_file("pdinfo", 0444, debug_info->dir, debug_info,
				 &cros_ec_pdinfo_fops))
		return -ENOMEM;

	return 0;
}

int cros_ec_debugfs_init(struct cros_ec_dev *ec)
{
	struct cros_ec_platform *ec_platform = dev_get_platdata(ec->dev);
@@ -379,6 +447,10 @@ int cros_ec_debugfs_init(struct cros_ec_dev *ec)
	if (ret)
		goto remove_debugfs;

	ret = cros_ec_create_pdinfo(debug_info);
	if (ret)
		goto remove_debugfs;

	ec->debug_info = debug_info;

	return 0;
Loading