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

Unverified Commit 78ff171e authored by derfelot's avatar derfelot
Browse files

input: Add clearpad driver from Sony kernel

Taken from Sony 47.2.A.10.107 stock kernel
parent 3aa41146
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -633,6 +633,26 @@ config TOUCHSCREEN_MIGOR
	  To compile this driver as a module, choose M here: the
	  module will be called migor_ts.

config TOUCHSCREEN_CLEARPAD
	tristate "Synaptics ClearPad"
	default n
	help
		Say Y here to enable Synaptics ClearPad touchscreen.

config TOUCHSCREEN_CLEARPAD_I2C
	tristate "Synaptics ClearPad I2C"
	depends on I2C && TOUCHSCREEN_CLEARPAD
	default n
	help
		Say Y here to enable Synaptics ClearPad I2C touchscreen.

config TOUCHSCREEN_CLEARPAD_RMI_DEV
	tristate "Synaptics ClearPad RMI Dev"
	depends on GPIO_SYSFS && TOUCHSCREEN_CLEARPAD && TOUCHSCREEN_CLEARPAD_I2C
	default n
	help
		Say Y here to enable Synaptics ClearPad RMI dev.

config TOUCHSCREEN_TOUCHRIGHT
	tristate "Touchright serial touchscreen"
	select SERIO
+3 −0
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@ obj-$(CONFIG_TOUCHSCREEN_ATMEL_MAXTOUCH_TS) += atmel_maxtouch_ts.o
obj-$(CONFIG_TOUCHSCREEN_AUO_PIXCIR)	+= auo-pixcir-ts.o
obj-$(CONFIG_TOUCHSCREEN_BU21013)	+= bu21013_ts.o
obj-$(CONFIG_TOUCHSCREEN_CHIPONE_ICN8318)	+= chipone_icn8318.o
obj-$(CONFIG_TOUCHSCREEN_CLEARPAD)	+= clearpad_core.o
obj-$(CONFIG_TOUCHSCREEN_CLEARPAD_I2C)	+= clearpad_i2c.o
obj-$(CONFIG_TOUCHSCREEN_CLEARPAD_RMI_DEV)	+= clearpad_rmi_dev.o
obj-$(CONFIG_TOUCHSCREEN_CY8CTMG110)	+= cy8ctmg110_ts.o
obj-$(CONFIG_TOUCHSCREEN_CYTTSP_CORE)	+= cyttsp_core.o
obj-$(CONFIG_TOUCHSCREEN_CYTTSP_I2C)	+= cyttsp_i2c.o cyttsp_i2c_common.o
+9646 −0

File added.

File size exceeds preview limit.

+343 −0
Original line number Diff line number Diff line
/* linux/drivers/input/touchscreen/clearpad_i2c.c
 *
 * Copyright (c) 2011 Synaptics Incorporated
 * Copyright (c) 2011 Unixphere
 *
 * Author: Yusuke Yoshimura <Yusuke.Yoshimura@sonyericsson.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2, as
 * published by the Free Software Foundation.
 */
/*
 * Copyright (C) 2016 Sony Mobile Communications Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2, as
 * published by the Free Software Foundation.
 */

#include <linux/async.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/clearpad.h>

#define CLEARPAD_PAGE_SELECT_REGISTER 0xff
#define CLEARPAD_REG(addr) ((addr) & 0xff)
#define CLEARPAD_PAGE(addr) (((addr) >> 8) & 0xff)

struct clearpad_i2c_t {
	struct platform_device *pdev;
	unsigned int page;
	struct mutex page_mutex;
};

static int clearpad_i2c_set_page(struct device *dev, u8 page)
{
	struct clearpad_i2c_t *this = dev_get_drvdata(dev);
	char txbuf[2] = {CLEARPAD_PAGE_SELECT_REGISTER, page};
	int rc = 0;

	rc = i2c_master_send(to_i2c_client(dev), txbuf, sizeof(txbuf));
	if (rc != sizeof(txbuf)) {
		dev_err(dev,
			"%s: set page failed: %d.", __func__, rc);
		rc = (rc < 0) ? rc : -EIO;
		goto exit;
	}
	this->page = page;
	rc = 0;
exit:
	return rc;
}

static int clearpad_i2c_read(struct device *dev, u16 addr, u8 *buf, u8 len)
{
	struct clearpad_i2c_t *this = dev_get_drvdata(dev);
	s32 rc = 0;
	u8 page = CLEARPAD_PAGE(addr);
	u8 reg = CLEARPAD_REG(addr);
	int rsize = I2C_SMBUS_BLOCK_MAX;
	int off;

	mutex_lock(&this->page_mutex);

	if (page != this->page) {
		rc = clearpad_i2c_set_page(dev, page);
		if (rc < 0)
			goto exit;
	}

	for (off = 0; off < len; off += rsize) {
		if (len < off + I2C_SMBUS_BLOCK_MAX)
			rsize = len - off;
		rc = i2c_smbus_read_i2c_block_data(to_i2c_client(dev),
				reg + off, rsize, &buf[off]);
		if (rc != rsize) {
			dev_err(dev, "%s: rc = %d\n", __func__, rc);
			if (rc > 0) {
				off += rc;
				break;
			}
			goto exit;
		}
	}
	rc = off;
exit:
	mutex_unlock(&this->page_mutex);
	return rc;
}

static int clearpad_i2c_write(struct device *dev, u16 addr,
			      const u8 *buf, u8 len)
{
	struct clearpad_i2c_t *this = dev_get_drvdata(dev);
	int rc = 0;
	u8 reg = CLEARPAD_REG(addr);
	u8 i;

	mutex_lock(&this->page_mutex);

	if (CLEARPAD_PAGE(addr) != this->page) {
		rc = clearpad_i2c_set_page(dev, CLEARPAD_PAGE(addr));
		if (rc < 0)
			goto exit;
	}

	for (i = 0; i < len; i++) {
		rc = i2c_smbus_write_byte_data(to_i2c_client(dev),
				reg + i, buf[i]);
		if (rc)
			goto exit;
	}
	rc = i;
exit:
	mutex_unlock(&this->page_mutex);
	return rc;
}

static int clearpad_i2c_read_block(struct device *dev, u16 addr, u8 *buf,
		int len)
{
	struct clearpad_i2c_t *this = dev_get_drvdata(dev);
	u8 txbuf[1] = {addr & 0xff};
	int rc = 0;

	mutex_lock(&this->page_mutex);

	if (CLEARPAD_PAGE(addr) != this->page) {
		rc = clearpad_i2c_set_page(dev, CLEARPAD_PAGE(addr));
		if (rc < 0)
			goto exit;
	}

	rc = i2c_master_send(to_i2c_client(dev), txbuf, sizeof(txbuf));
	if (rc != sizeof(txbuf)) {
		rc = (rc < 0) ? rc : -EIO;
		goto exit;
	}

	rc = i2c_master_recv(to_i2c_client(dev), buf, len);
	if (rc < 0)
		dev_err(dev, "%s: rc = %d\n", __func__, rc);
exit:
	mutex_unlock(&this->page_mutex);
	return rc;
}

static int clearpad_i2c_write_block(struct device *dev, u16 addr, const u8 *buf,
		int len)
{
	struct clearpad_i2c_t *this = dev_get_drvdata(dev);
	u8 txbuf[len + 1];
	int rc = 0;

	txbuf[0] = addr & 0xff;
	memcpy(txbuf + 1, buf, len);

	mutex_lock(&this->page_mutex);

	if (CLEARPAD_PAGE(addr) != this->page) {
		rc = clearpad_i2c_set_page(dev, CLEARPAD_PAGE(addr));
		if (rc < 0)
			goto exit;
	}

	rc = i2c_master_send(to_i2c_client(dev), txbuf, sizeof(txbuf));
	if (rc < 0)
		dev_err(dev, "%s: rc = %d\n", __func__, rc);
	else
		rc -= 1;
exit:
	mutex_unlock(&this->page_mutex);
	return rc;
}

static struct clearpad_bus_data_t clearpad_i2c_bus_data = {
	.bustype	= BUS_I2C,
	.set_page	= clearpad_i2c_set_page,
	.read		= clearpad_i2c_read,
	.write		= clearpad_i2c_write,
	.read_block	= clearpad_i2c_read_block,
	.write_block	= clearpad_i2c_write_block,
};

#ifdef CONFIG_OF
static int clearpad_parse_dt(struct device *dev,
		struct clearpad_platform_data_t *pdata)
{
	struct device_node *np = dev->of_node;

	pdata->irq_gpio = of_get_named_gpio_flags(np, "synaptics,irq_gpio",
			0, &pdata->irq_gpio_flags);
	return 0;
}
#else
static int clearpad_parse_dt(struct device *dev,
		struct clearpad_platform_data *pdata)
{
	return -ENODEV;
}
#endif

static int clearpad_i2c_probe(struct i2c_client *client,
				      const struct i2c_device_id *id)
{
	struct clearpad_data_t clearpad_data = {
		.pdata = NULL,
		.bdata = &clearpad_i2c_bus_data,
		.probe_retry = 0,
#ifdef CONFIG_TOUCHSCREEN_CLEARPAD_RMI_DEV
		.rmi_dev = NULL,
#endif
	};
	struct clearpad_i2c_t *this;
	int rc;

	if (client->dev.of_node) {
		clearpad_data.pdata = devm_kzalloc(&client->dev,
				sizeof(struct clearpad_platform_data_t),
				GFP_KERNEL);
		if (!clearpad_data.pdata) {
			dev_err(&client->dev, "failed to allocate memory\n");
			rc = -ENOMEM;
			goto exit;
		}
		rc = clearpad_parse_dt(&client->dev, clearpad_data.pdata);
		if (rc) {
			dev_err(&client->dev, "failed to parse device tree\n");
			goto exit;
		}
	} else {
		clearpad_data.pdata = client->dev.platform_data;
	}

	this = kzalloc(sizeof(struct clearpad_i2c_t), GFP_KERNEL);
	if (!this) {
		rc = -ENOMEM;
		goto exit;
	}

	dev_set_drvdata(&client->dev, this);

	mutex_init(&this->page_mutex);

	this->pdev = platform_device_alloc(CLEARPAD_NAME, -1);
	if (!this->pdev) {
		rc = -ENOMEM;
		goto err_free;
	}
	clearpad_data.bdata->dev = &client->dev;
	clearpad_data.bdata->of_node = client->dev.of_node;
	this->pdev->dev.parent = &client->dev;
	rc = platform_device_add_data(this->pdev,
			&clearpad_data, sizeof(clearpad_data));
	if (rc)
		goto err_device_put;

	rc = platform_device_add(this->pdev);
	if (rc)
		goto err_device_put;

	dev_info(&client->dev, "%s: success\n", __func__);
	goto exit;

err_device_put:
	platform_device_put(this->pdev);
err_free:
	dev_set_drvdata(&client->dev, NULL);
	kfree(this);
exit:
	return rc;
}

static int clearpad_i2c_remove(struct i2c_client *client)
{
	struct clearpad_i2c_t *this = dev_get_drvdata(&client->dev);
	platform_device_unregister(this->pdev);
	dev_set_drvdata(&client->dev, NULL);
	kfree(this);
	return 0;
}

static const struct i2c_device_id clearpad_id[] = {
	{ CLEARPADI2C_NAME, 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, clearpad_id);

#ifdef CONFIG_OF
static struct of_device_id clearpad_match_table[] = {
	{ .compatible = "synaptics,clearpad", },
	{ },
};
#else
#define clearpad_match_table NULL
#endif

static struct i2c_driver clearpad_i2c_driver = {
	.driver = {
		.owner	= THIS_MODULE,
		.name	= CLEARPADI2C_NAME,
		.of_match_table = clearpad_match_table,
	},
	.id_table	= clearpad_id,
	.probe		= clearpad_i2c_probe,
	.remove		= clearpad_i2c_remove,
};

#ifndef MODULE
void clearpad_i2c_init_async(void *unused, async_cookie_t cookie)
{
	int rc;

	rc = i2c_add_driver(&clearpad_i2c_driver);
	if (rc != 0)
		pr_err("Clearpad I2C registration failed rc = %d\n", rc);
}
#endif

static int __init clearpad_i2c_init(void)
{
#ifdef MODULE
	return i2c_add_driver(&clearpad_i2c_driver);
#else
	async_schedule(clearpad_i2c_init_async, NULL);
	return 0;
#endif
}

static void __exit clearpad_i2c_exit(void)
{
	i2c_del_driver(&clearpad_i2c_driver);
}

MODULE_DESCRIPTION(CLEARPADI2C_NAME "ClearPad I2C Driver");
MODULE_LICENSE("GPL v2");

module_init(clearpad_i2c_init);
module_exit(clearpad_i2c_exit);
+506 −0
Original line number Diff line number Diff line
/* linux/drivers/input/touchscreen/clearpad_rmi_dev.c
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2, as
 * published by the Free Software Foundation.
 */
/*
 * Copyright (C) 2016 Sony Mobile Communications Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2, as
 * published by the Free Software Foundation.
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/syscalls.h>
#include <linux/clearpad.h>

#define CHAR_DEVICE_NAME "rmi"
#define DEVICE_CLASS_NAME "rmidev"

#define RMI_CHAR_DEV_TMPBUF_SZ (1024 * 8)
#define RMI_REG_ADDR_PAGE_SELECT 0xFF
#define REG_ADDR_LIMIT 0xFFFF

struct rmidev_data {
	/* mutex for file operation*/
	struct mutex file_mutex;
	/* main char dev structure */
	struct cdev main_dev;

	/* pointer to the corresponding RMI4 device.  We use this to do */
	/* read, write, etc. */

	struct platform_device *pdev;
	struct clearpad_platform_data_t *pdata;
	struct clearpad_bus_data_t *bdata;
	/* reference count */
	int ref_count;

	struct class *device_class;

	unsigned char tmpbuf[RMI_CHAR_DEV_TMPBUF_SZ];
};

/*store dynamically allocated major number of char device*/
static int rmidev_major_num;


static struct class *rmidev_device_class;

static int rmi_read_block(struct rmidev_data *data, u16 addr, u8 *buf, int len)
{
	return data->bdata->read_block(data->bdata->dev, addr, buf, len);
}

static int rmi_write_block(struct rmidev_data *data, u16 addr, const u8 *buf,
		int len)
{
	return data->bdata->write_block(data->bdata->dev, addr, buf, len);
}

/* file operations for RMI char device */

/*
 * rmidev_llseek: - use to setup register address
 *
 * @filp: file structure for seek
 * @off: offset
 *       if whence == SEEK_SET,
 *       high 16 bits: page address
 *       low 16 bits: register address
 *
 *       if whence == SEEK_CUR,
 *       offset from current position
 *
 *       if whence == SEEK_END,
 *       offset from END(0xFFFF)
 *
 * @whence: SEEK_SET , SEEK_CUR or SEEK_END
 */
static loff_t rmidev_llseek(struct file *filp, loff_t off, int whence)
{
	loff_t newpos;
	struct rmidev_data *data = filp->private_data;

	if (IS_ERR(data)) {
		pr_err("%s: pointer of char device is invalid", __func__);
		newpos = -EBADF;
		goto exit;
	}

	mutex_lock(&(data->file_mutex));

	switch (whence) {
	case SEEK_SET:
		newpos = off;
		break;

	case SEEK_CUR:
		newpos = filp->f_pos + off;
		break;

	case SEEK_END:
		newpos = REG_ADDR_LIMIT + off;
		break;

	default:		/* can't happen */
		newpos = -EINVAL;
		goto clean_up;
	}

	if (newpos < 0 || newpos > REG_ADDR_LIMIT) {
		dev_err(&data->pdev->dev, "newpos 0x%04x is invalid.\n",
			(unsigned int)newpos);
		newpos = -EINVAL;
		goto clean_up;
	}

	filp->f_pos = newpos;

clean_up:
	mutex_unlock(&(data->file_mutex));
exit:
	return newpos;
}

/*
 *  rmidev_read: - use to read data from RMI stream
 *
 *  @filp: file structure for read
 *  @buf: user-level buffer pointer
 *
 *  @count: number of byte read
 *  @f_pos: offset (starting register address)
 *
 *	@return number of bytes read into user buffer (buf) if succeeds
 *          negative number if error occurs.
 */
static ssize_t rmidev_read(struct file *filp, char __user *buf,
		size_t count, loff_t *f_pos)
{
	struct rmidev_data *data = filp->private_data;
	ssize_t retval  = 0;

	if (*f_pos > REG_ADDR_LIMIT) {
		retval = -EINVAL;
		goto exit;
	}

	/* limit offset to REG_ADDR_LIMIT-1 */
	if (count > (REG_ADDR_LIMIT - *f_pos))
		count = REG_ADDR_LIMIT - *f_pos;
	if (count > RMI_CHAR_DEV_TMPBUF_SZ)
		count = RMI_CHAR_DEV_TMPBUF_SZ;

	if (count == 0)
		goto exit;

	if (IS_ERR(data)) {
		pr_err("%s: pointer of char device is invalid", __func__);
		retval = -EBADF;
		goto exit;
	}

	mutex_lock(&(data->file_mutex));

	retval = rmi_read_block(data, *f_pos, data->tmpbuf, count);

	if (retval < 0)
		goto clean_up;
	else
		*f_pos += retval;

	if (copy_to_user(buf, data->tmpbuf, count))
		retval = -EFAULT;

clean_up:

	mutex_unlock(&(data->file_mutex));
exit:
	return retval;
}

/*
 * rmidev_write: - use to write data into RMI stream
 *
 * @filep : file structure for write
 * @buf: user-level buffer pointer contains data to be written
 * @count: number of byte be be written
 * @f_pos: offset (starting register address)
 *
 * @return number of bytes written from user buffer (buf) if succeeds
 *         negative number if error occurs.
 */
static ssize_t rmidev_write(struct file *filp, const char __user *buf,
		size_t count, loff_t *f_pos)
{
	struct rmidev_data *data = filp->private_data;
	ssize_t retval  = 0;

	if (*f_pos > REG_ADDR_LIMIT) {
		retval = -EINVAL;
		goto exit;
	}

	/* limit offset to REG_ADDR_LIMIT-1 */
	if (count > (REG_ADDR_LIMIT - *f_pos))
		count = REG_ADDR_LIMIT - *f_pos;
	if (count > RMI_CHAR_DEV_TMPBUF_SZ)
		count = RMI_CHAR_DEV_TMPBUF_SZ;

	if (count == 0)
		goto exit;

	if (IS_ERR(data)) {
		pr_err("%s: pointer of char device is invalid", __func__);
		retval = -EBADF;
		goto exit;
	}

	if (copy_from_user(data->tmpbuf, buf, count)) {
		retval = -EFAULT;
		goto exit;
	}

	mutex_lock(&(data->file_mutex));

	retval = rmi_write_block(data, *f_pos, data->tmpbuf, (int)count);

	if (retval >= 0)
		*f_pos += count;

	mutex_unlock(&(data->file_mutex));
exit:
	return retval;
}

/*
 * rmidev_open: - get a new handle for from RMI stream
 * @inp : inode struture
 * @filp: file structure for read/write
 *
 * @return 0 if succeeds
 */
static int rmidev_open(struct inode *inp, struct file *filp)
{
	struct rmidev_data *data = container_of(inp->i_cdev,
			struct rmidev_data, main_dev);
	int retval = 0;

	filp->private_data = data;

	if (!data->pdata) {
		retval = -EACCES;
		goto exit;
	}

	mutex_lock(&(data->file_mutex));
	if (data->ref_count < 1)
		data->ref_count++;
	else
		retval = -EACCES;

	mutex_unlock(&(data->file_mutex));
exit:
	return retval;
}

/*
 *  rmidev_release: - release an existing handle
 *  @inp: inode structure
 *  @filp: file structure for read/write
 *
 *  @return 0 if succeeds
 */
static int rmidev_release(struct inode *inp, struct file *filp)
{
	struct rmidev_data *data = container_of(inp->i_cdev,
			struct rmidev_data, main_dev);
	int retval = 0;

	if (!data->pdev) {
		retval = -EACCES;
		goto exit;
	}

	mutex_lock(&(data->file_mutex));

	data->ref_count--;
	if (data->ref_count < 0)
		data->ref_count = 0;

	mutex_unlock(&(data->file_mutex));
exit:
	return retval;
}

static const struct file_operations rmidev_fops = {
	.owner =    THIS_MODULE,
	.llseek =   rmidev_llseek,
	.read =     rmidev_read,
	.write =    rmidev_write,
	.open =     rmidev_open,
	.release =  rmidev_release,
};

/*
 * rmi_char_dev_clean_up - release memory or unregister driver
 * @rmi_char_dev: rmi_char_dev structure
 *
 */
static void rmidev_device_cleanup(struct rmidev_data *data)
{
	dev_t devno;

	/* Get rid of our char dev entries */
	if (data) {
		devno = data->main_dev.dev;

		if (data->device_class)
			device_destroy(data->device_class, devno);

		cdev_del(&data->main_dev);

		/* cleanup_module is never called if registering failed */
		unregister_chrdev_region(devno, 1);
		pr_debug("%s: rmidev device is removed\n", __func__);
	}
}

/*
 * rmi_char_devnode - return device permission
 *
 * @dev: char device structure
 * @mode: file permission
 *
 */
static char *rmi_char_devnode(struct device *dev, umode_t *mode)
{
	char *pret = NULL;

	if (mode) {
		/**mode = 0600*/
		*mode = S_IRUSR|S_IWUSR;
		pret = kasprintf(GFP_KERNEL, "rmi/%s", dev_name(dev));
	}
	return pret;
}

static int rmidev_init_device(struct rmidev_data *data)
{
	dev_t dev_no;
	int retval;
	struct device *device_ptr;

	if (rmidev_major_num) {
		dev_no = MKDEV(rmidev_major_num, 0);
		retval = register_chrdev_region(dev_no, 1, CHAR_DEVICE_NAME);
	} else {
		retval = alloc_chrdev_region(&dev_no, 0, 1, CHAR_DEVICE_NAME);
		/* let kernel allocate a major for us */
		rmidev_major_num = MAJOR(dev_no);
		dev_info(&data->pdev->dev, "Major number of rmidev: %d\n",
				 rmidev_major_num);
	}
	if (retval < 0) {
		dev_err(&data->pdev->dev,
			"Failed to register or allocate char dev, code %d.\n",
				retval);
		goto exit;
	} else {
		dev_info(&data->pdev->dev, "Allocated rmidev %d %d.\n",
			 MAJOR(dev_no), MINOR(dev_no));
	}

	mutex_init(&data->file_mutex);

	cdev_init(&data->main_dev, &rmidev_fops);

	retval = cdev_add(&data->main_dev, dev_no, 1);
	if (retval) {
		dev_err(&data->pdev->dev, "Error %d adding rmi_char_dev.\n",
			retval);
		rmidev_device_cleanup(data);
		goto exit;
	}

	dev_set_name(&data->pdev->dev, "rmidev%d", MINOR(dev_no));
	data->device_class = rmidev_device_class;
	device_ptr = device_create(
			data->device_class,
			NULL, dev_no, NULL,
			CHAR_DEVICE_NAME"%d",
			MINOR(dev_no));

	if (IS_ERR(device_ptr)) {
		dev_err(&data->pdev->dev,
			"Failed to create rmi device.\n");
		rmidev_device_cleanup(data);
		retval = -ENODEV;
	}
exit:
	return retval;
}

static int rmi_dev_probe(struct platform_device *pdev)
{

	struct clearpad_data_t *cdata = pdev->dev.platform_data;
	struct rmidev_data *data;
	int retval = 0;

	data = kzalloc(sizeof(struct rmidev_data), GFP_KERNEL);
	if (!data) {
		retval = -ENOMEM;
		goto exit;
	}

	dev_set_drvdata(&pdev->dev, data);
	data->pdev = pdev;
	data->pdata = cdata->pdata;
	if (!data->pdata) {
		dev_err(&data->pdev->dev, "no platform data\n");
		retval = -EINVAL;
		goto err_free;
	}
	data->bdata = cdata->bdata;
	if (!data->bdata) {
		dev_err(&data->pdev->dev, "no bus data\n");
		retval = -EINVAL;
		goto err_free;
	}

	retval = rmidev_init_device(data);
	if (!retval)
		goto exit;

err_free:
	dev_set_drvdata(&pdev->dev, NULL);
	kfree(data);
exit:
	return retval;
}

static int rmi_dev_remove(struct platform_device *pdev)
{
	struct rmidev_data *data = dev_get_drvdata(&pdev->dev);
	dev_set_drvdata(&pdev->dev, NULL);
	kfree(data);
	return 0;
}

static struct platform_driver rmidev_driver = {
	.driver = {
		.name = CLEARPAD_RMI_DEV_NAME,
		.owner	= THIS_MODULE,
	},
	.probe		= rmi_dev_probe,
	.remove		= rmi_dev_remove,
};

static int __init rmidev_init(void)
{
	int retval = 0;

	pr_debug("%s: rmi_dev initialization.\n", __func__);

	/* create device node */
	rmidev_device_class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);

	if (IS_ERR(rmidev_device_class)) {
		pr_err("%s: ERROR - Failed to create /dev/%s.\n", __func__,
			CHAR_DEVICE_NAME);
		retval = -ENODEV;
		goto exit;
	}
	/* setup permission */
	rmidev_device_class->devnode = rmi_char_devnode;

	retval = platform_driver_register(&rmidev_driver);
exit:
	return retval;
}

static void __exit rmidev_exit(void)
{
	pr_debug("%s: exiting.\n", __func__);
	platform_driver_unregister(&rmidev_driver);
	class_unregister(rmidev_device_class);
	class_destroy(rmidev_device_class);
}

module_init(rmidev_init);
module_exit(rmidev_exit);

MODULE_AUTHOR("Christopher Heiny <cheiny@synaptics.com>");
MODULE_DESCRIPTION("RMI4 Char Device");
MODULE_LICENSE("GPL");
Loading