Skip to content
Snippets Groups Projects
Select Git revision
  • 1bdec5d9818c47e080c19784dfd25d1d9c20807e
  • openEuler-1.0-LTS default protected
  • openEuler-22.09
  • OLK-5.10
  • openEuler-22.03-LTS
  • openEuler-22.03-LTS-Ascend
  • master
  • openEuler-22.03-LTS-LoongArch-NW
  • openEuler-22.09-HCK
  • openEuler-20.03-LTS-SP3
  • openEuler-21.09
  • openEuler-21.03
  • openEuler-20.09
  • 4.19.90-2210.5.0
  • 5.10.0-123.0.0
  • 5.10.0-60.63.0
  • 5.10.0-60.62.0
  • 4.19.90-2210.4.0
  • 5.10.0-121.0.0
  • 5.10.0-60.61.0
  • 4.19.90-2210.3.0
  • 5.10.0-60.60.0
  • 5.10.0-120.0.0
  • 5.10.0-60.59.0
  • 5.10.0-119.0.0
  • 4.19.90-2210.2.0
  • 4.19.90-2210.1.0
  • 5.10.0-118.0.0
  • 5.10.0-106.19.0
  • 5.10.0-60.58.0
  • 4.19.90-2209.6.0
  • 5.10.0-106.18.0
  • 5.10.0-106.17.0
33 results

ili210x.c

Blame
  • user avatar
    Marek Vasut authored and Dmitry Torokhov committed
    Convert the driver to devm_request_irq(), drop the related unmanaged
    deregistration code and add ili210x_irq_teardown() to tear the IRQ
    down and cancel possible touchscreen pending work.
    
    Signed-off-by: default avatarMarek Vasut <marex@denx.de>
    Signed-off-by: default avatarDmitry Torokhov <dmitry.torokhov@gmail.com>
    1bdec5d9
    History
    ili210x.c 7.87 KiB
    #include <linux/module.h>
    #include <linux/i2c.h>
    #include <linux/interrupt.h>
    #include <linux/slab.h>
    #include <linux/input.h>
    #include <linux/input/mt.h>
    #include <linux/delay.h>
    #include <linux/workqueue.h>
    #include <linux/gpio/consumer.h>
    
    #define MAX_TOUCHES		2
    #define DEFAULT_POLL_PERIOD	20
    
    /* Touchscreen commands */
    #define REG_TOUCHDATA		0x10
    #define REG_PANEL_INFO		0x20
    #define REG_FIRMWARE_VERSION	0x40
    #define REG_CALIBRATE		0xcc
    
    struct finger {
    	u8 x_low;
    	u8 x_high;
    	u8 y_low;
    	u8 y_high;
    } __packed;
    
    struct touchdata {
    	u8 status;
    	struct finger finger[MAX_TOUCHES];
    } __packed;
    
    struct panel_info {
    	struct finger finger_max;
    	u8 xchannel_num;
    	u8 ychannel_num;
    } __packed;
    
    struct firmware_version {
    	u8 id;
    	u8 major;
    	u8 minor;
    } __packed;
    
    struct ili210x {
    	struct i2c_client *client;
    	struct input_dev *input;
    	unsigned int poll_period;
    	struct delayed_work dwork;
    	struct gpio_desc *reset_gpio;
    };
    
    static int ili210x_read_reg(struct i2c_client *client, u8 reg, void *buf,
    			    size_t len)
    {
    	struct i2c_msg msg[2] = {
    		{
    			.addr	= client->addr,
    			.flags	= 0,
    			.len	= 1,
    			.buf	= &reg,
    		},
    		{
    			.addr	= client->addr,
    			.flags	= I2C_M_RD,
    			.len	= len,
    			.buf	= buf,
    		}
    	};
    
    	if (i2c_transfer(client->adapter, msg, 2) != 2) {
    		dev_err(&client->dev, "i2c transfer failed\n");
    		return -EIO;
    	}
    
    	return 0;
    }
    
    static void ili210x_report_events(struct input_dev *input,
    				  const struct touchdata *touchdata)
    {
    	int i;
    	bool touch;
    	unsigned int x, y;
    	const struct finger *finger;
    
    	for (i = 0; i < MAX_TOUCHES; i++) {
    		input_mt_slot(input, i);
    
    		finger = &touchdata->finger[i];
    
    		touch = touchdata->status & (1 << i);
    		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
    		if (touch) {
    			x = finger->x_low | (finger->x_high << 8);
    			y = finger->y_low | (finger->y_high << 8);
    
    			input_report_abs(input, ABS_MT_POSITION_X, x);
    			input_report_abs(input, ABS_MT_POSITION_Y, y);
    		}
    	}
    
    	input_mt_report_pointer_emulation(input, false);
    	input_sync(input);
    }
    
    static void ili210x_work(struct work_struct *work)
    {
    	struct ili210x *priv = container_of(work, struct ili210x,
    					    dwork.work);
    	struct i2c_client *client = priv->client;
    	struct touchdata touchdata;
    	int error;
    
    	error = ili210x_read_reg(client, REG_TOUCHDATA,
    				 &touchdata, sizeof(touchdata));
    	if (error) {
    		dev_err(&client->dev,
    			"Unable to get touchdata, err = %d\n", error);
    		return;
    	}
    
    	ili210x_report_events(priv->input, &touchdata);
    
    	if (touchdata.status & 0xf3)
    		schedule_delayed_work(&priv->dwork,
    				      msecs_to_jiffies(priv->poll_period));
    }
    
    static irqreturn_t ili210x_irq(int irq, void *irq_data)
    {
    	struct ili210x *priv = irq_data;
    
    	schedule_delayed_work(&priv->dwork, 0);
    
    	return IRQ_HANDLED;
    }
    
    static ssize_t ili210x_calibrate(struct device *dev,
    				 struct device_attribute *attr,
    				 const char *buf, size_t count)
    {
    	struct i2c_client *client = to_i2c_client(dev);
    	struct ili210x *priv = i2c_get_clientdata(client);
    	unsigned long calibrate;
    	int rc;
    	u8 cmd = REG_CALIBRATE;
    
    	if (kstrtoul(buf, 10, &calibrate))
    		return -EINVAL;
    
    	if (calibrate > 1)
    		return -EINVAL;
    
    	if (calibrate) {
    		rc = i2c_master_send(priv->client, &cmd, sizeof(cmd));
    		if (rc != sizeof(cmd))
    			return -EIO;
    	}
    
    	return count;
    }
    static DEVICE_ATTR(calibrate, S_IWUSR, NULL, ili210x_calibrate);
    
    static struct attribute *ili210x_attributes[] = {
    	&dev_attr_calibrate.attr,
    	NULL,
    };
    
    static const struct attribute_group ili210x_attr_group = {
    	.attrs = ili210x_attributes,
    };
    
    static void ili210x_power_down(void *data)
    {
    	struct gpio_desc *reset_gpio = data;
    
    	gpiod_set_value_cansleep(reset_gpio, 1);
    }
    
    static void ili210x_cancel_work(void *data)
    {
    	struct ili210x *priv = data;
    
    	cancel_delayed_work_sync(&priv->dwork);
    }
    
    static int ili210x_i2c_probe(struct i2c_client *client,
    				       const struct i2c_device_id *id)
    {
    	struct device *dev = &client->dev;
    	struct ili210x *priv;
    	struct gpio_desc *reset_gpio;
    	struct input_dev *input;
    	struct panel_info panel;
    	struct firmware_version firmware;
    	int xmax, ymax;
    	int error;
    
    	dev_dbg(dev, "Probing for ILI210X I2C Touschreen driver");
    
    	if (client->irq <= 0) {
    		dev_err(dev, "No IRQ!\n");
    		return -EINVAL;
    	}
    
    	reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
    	if (IS_ERR(reset_gpio))
    		return PTR_ERR(reset_gpio);
    
    	if (reset_gpio) {
    		error = devm_add_action_or_reset(dev, ili210x_power_down,
    						 reset_gpio);
    		if (error)
    			return error;
    
    		usleep_range(50, 100);
    		gpiod_set_value_cansleep(reset_gpio, 0);
    		msleep(100);
    	}
    
    	/* Get firmware version */
    	error = ili210x_read_reg(client, REG_FIRMWARE_VERSION,
    				 &firmware, sizeof(firmware));
    	if (error) {
    		dev_err(dev, "Failed to get firmware version, err: %d\n",
    			error);
    		return error;
    	}
    
    	/* get panel info */
    	error = ili210x_read_reg(client, REG_PANEL_INFO, &panel, sizeof(panel));
    	if (error) {
    		dev_err(dev, "Failed to get panel information, err: %d\n",
    			error);
    		return error;
    	}
    
    	xmax = panel.finger_max.x_low | (panel.finger_max.x_high << 8);
    	ymax = panel.finger_max.y_low | (panel.finger_max.y_high << 8);
    
    	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
    	if (!priv)
    		return -ENOMEM;
    
    	input = devm_input_allocate_device(dev);
    	if (!input)
    		return -ENOMEM;
    
    	priv->client = client;
    	priv->input = input;
    	priv->poll_period = DEFAULT_POLL_PERIOD;
    	INIT_DELAYED_WORK(&priv->dwork, ili210x_work);
    	priv->reset_gpio = reset_gpio;
    
    	/* Setup input device */
    	input->name = "ILI210x Touchscreen";
    	input->id.bustype = BUS_I2C;
    	input->dev.parent = dev;
    
    	__set_bit(EV_SYN, input->evbit);
    	__set_bit(EV_KEY, input->evbit);
    	__set_bit(EV_ABS, input->evbit);
    	__set_bit(BTN_TOUCH, input->keybit);
    
    	/* Single touch */
    	input_set_abs_params(input, ABS_X, 0, xmax, 0, 0);
    	input_set_abs_params(input, ABS_Y, 0, ymax, 0, 0);
    
    	/* Multi touch */
    	input_mt_init_slots(input, MAX_TOUCHES, 0);
    	input_set_abs_params(input, ABS_MT_POSITION_X, 0, xmax, 0, 0);
    	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ymax, 0, 0);
    
    	i2c_set_clientdata(client, priv);
    
    	error = devm_add_action(dev, ili210x_cancel_work, priv);
    	if (error)
    		return error;
    
    	error = devm_request_irq(dev, client->irq, ili210x_irq, 0,
    				 client->name, priv);
    	if (error) {
    		dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n",
    			error);
    		return error;
    	}
    
    	error = sysfs_create_group(&dev->kobj, &ili210x_attr_group);
    	if (error) {
    		dev_err(dev, "Unable to create sysfs attributes, err: %d\n",
    			error);
    		return error;
    	}
    
    	error = input_register_device(priv->input);
    	if (error) {
    		dev_err(dev, "Cannot register input device, err: %d\n", error);
    		goto err_remove_sysfs;
    	}
    
    	device_init_wakeup(dev, 1);
    
    	dev_dbg(dev,
    		"ILI210x initialized (IRQ: %d), firmware version %d.%d.%d",
    		client->irq, firmware.id, firmware.major, firmware.minor);
    
    	return 0;
    
    err_remove_sysfs:
    	sysfs_remove_group(&dev->kobj, &ili210x_attr_group);
    	return error;
    }
    
    static int ili210x_i2c_remove(struct i2c_client *client)
    {
    	sysfs_remove_group(&client->dev.kobj, &ili210x_attr_group);
    
    	return 0;
    }
    
    static int __maybe_unused ili210x_i2c_suspend(struct device *dev)
    {
    	struct i2c_client *client = to_i2c_client(dev);
    
    	if (device_may_wakeup(&client->dev))
    		enable_irq_wake(client->irq);
    
    	return 0;
    }
    
    static int __maybe_unused ili210x_i2c_resume(struct device *dev)
    {
    	struct i2c_client *client = to_i2c_client(dev);
    
    	if (device_may_wakeup(&client->dev))
    		disable_irq_wake(client->irq);
    
    	return 0;
    }
    
    static SIMPLE_DEV_PM_OPS(ili210x_i2c_pm,
    			 ili210x_i2c_suspend, ili210x_i2c_resume);
    
    static const struct i2c_device_id ili210x_i2c_id[] = {
    	{ "ili210x", 0 },
    	{ }
    };
    MODULE_DEVICE_TABLE(i2c, ili210x_i2c_id);
    
    static struct i2c_driver ili210x_ts_driver = {
    	.driver = {
    		.name = "ili210x_i2c",
    		.pm = &ili210x_i2c_pm,
    	},
    	.id_table = ili210x_i2c_id,
    	.probe = ili210x_i2c_probe,
    	.remove = ili210x_i2c_remove,
    };
    
    module_i2c_driver(ili210x_ts_driver);
    
    MODULE_AUTHOR("Olivier Sobrie <olivier@sobrie.be>");
    MODULE_DESCRIPTION("ILI210X I2C Touchscreen Driver");
    MODULE_LICENSE("GPL");