All of lore.kernel.org
 help / color / mirror / Atom feed
From: Christopher Heiny <cheiny@synaptics.com>
To: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Jean Delvare <khali@linux-fr.org>,
	Linux Kernel <linux-kernel@vger.kernel.org>,
	Linux Input <linux-input@vger.kernel.org>,
	Christopher Heiny <cheiny@synaptics.com>,
	Allie Xiong <axiong@synaptics.com>, Vivian Ly <vly@synaptics.com>,
	Daniel Rosenberg <daniel.rosenberg@synaptics.com>,
	Alexandra Chin <alexandra.chin@tw.synaptics.com>,
	Joerie de Gram <j.de.gram@gmail.com>,
	Wolfram Sang <w.sang@pengutronix.de>,
	Mathieu Poirier <mathieu.poirier@linaro.org>,
	Linus Walleij <linus.walleij@stericsson.com>,
	Naveen Kumar Gaddipati <naveen.gaddipati@stericsson.com>
Subject: [RFC PATCH 03/06] input/rmi4: I2C physical interface
Date: Fri, 16 Nov 2012 19:58:51 -0800	[thread overview]
Message-ID: <1353124734-16803-4-git-send-email-cheiny@synaptics.com> (raw)
In-Reply-To: <1353124734-16803-1-git-send-email-cheiny@synaptics.com>


rmi_i2c.c abstracts an RMI4 device on some arbitrary I2C bus as a logical
device in the RMI bus.  It handles reads/writes from/to the RMI4 devices,
and manages the page select register setting (since the meaning of page
select is dependent on the physical layer used to communicate with the RMi4
device).


Signed-off-by: Christopher Heiny <cheiny@synaptics.com>

Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Linus Walleij <linus.walleij@stericsson.com>
Cc: Naveen Kumar Gaddipati <naveen.gaddipati@stericsson.com>
Cc: Joeri de Gram <j.de.gram@gmail.com>

Acked-by: Jean Delvare <khali@linux-fr.org>

---

 drivers/input/rmi4/rmi_i2c.c |  490 ++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 490 insertions(+), 0 deletions(-)

diff --git a/drivers/input/rmi4/rmi_i2c.c b/drivers/input/rmi4/rmi_i2c.c
new file mode 100644
index 0000000..ca32101
--- /dev/null
+++ b/drivers/input/rmi4/rmi_i2c.c
@@ -0,0 +1,490 @@
+/*
+ * Copyright (c) 2011, 2012 Synaptics Incorporated
+ * Copyright (c) 2011 Unixphere
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kconfig.h>
+#include <linux/lockdep.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/rmi.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include "rmi_driver.h"
+
+#define BUFFER_SIZE_INCREMENT 32
+/**
+ * struct rmi_i2c_data - stores information for i2c communication
+ *
+ * @page_mutex: Locks current page to avoid changing pages in unexpected ways.
+ * @page: Keeps track of the current virtual page
+ * @phys: Pointer to the physical interface
+ *
+ * @tx_buf: Buffer used for transmitting data to the sensor over i2c.
+ * @tx_buf_size: Size of the buffer
+ * @debug_buf: Buffer used for exposing buffer contents using dev_dbg
+ * @debug_buf_size: Size of the debug buffer.
+ *
+ * @comms_debug: Latest data read/written for debugging I2C communications
+ * @debugfs_comms: Debugfs file for debugging I2C communications
+ *
+ */
+struct rmi_i2c_data {
+	struct mutex page_mutex;
+	int page;
+	struct rmi_phys_device *phys;
+
+	u8 *tx_buf;
+	int tx_buf_size;
+	u8 *debug_buf;
+	int debug_buf_size;
+
+	bool comms_debug;
+#ifdef CONFIG_RMI4_DEBUG
+	struct dentry *debugfs_comms;
+#endif
+};
+
+#ifdef CONFIG_RMI4_DEBUG
+
+
+/**
+ * struct i2c_debugfs_data - stores information for debugfs
+ *
+ * @done: Indicates that we are done reading debug data. Subsequent reads
+ * will return EOF.
+ * @i2c_data: Pointer to the i2c data
+ *
+ */
+struct i2c_debugfs_data {
+	bool done;
+	struct rmi_i2c_data *i2c_data;
+};
+
+static int debug_open(struct inode *inodep, struct file *filp)
+{
+	struct i2c_debugfs_data *data;
+
+	data = kzalloc(sizeof(struct i2c_debugfs_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->i2c_data = inodep->i_private;
+	filp->private_data = data;
+	return 0;
+}
+
+static int debug_release(struct inode *inodep, struct file *filp)
+{
+	kfree(filp->private_data);
+	return 0;
+}
+
+static ssize_t comms_debug_read(struct file *filp, char __user *buffer,
+		size_t size, loff_t *offset) {
+	int retval;
+	char *local_buf;
+	struct i2c_debugfs_data *dfs = filp->private_data;
+	struct rmi_i2c_data *data = dfs->i2c_data;
+
+	if (dfs->done)
+		return 0;
+
+	local_buf = kcalloc(size, sizeof(u8), GFP_KERNEL);
+	if (!local_buf)
+		return -ENOMEM;
+
+	dfs->done = 1;
+
+	retval = snprintf(local_buf, PAGE_SIZE, "%u\n", data->comms_debug);
+
+	if (retval <= 0 || copy_to_user(buffer, local_buf, retval))
+		retval = -EFAULT;
+	kfree(local_buf);
+
+	return retval;
+}
+
+static ssize_t comms_debug_write(struct file *filp, const char __user *buffer,
+			   size_t size, loff_t *offset) {
+	int retval;
+	char *local_buf;
+	unsigned int new_value;
+	struct i2c_debugfs_data *dfs = filp->private_data;
+	struct rmi_i2c_data *data = dfs->i2c_data;
+
+	local_buf = kcalloc(size, sizeof(u8), GFP_KERNEL);
+	if (!local_buf)
+		return -ENOMEM;
+	retval = copy_from_user(local_buf, buffer, size);
+	if (retval) {
+		kfree(local_buf);
+		return -EFAULT;
+	}
+
+	retval = sscanf(local_buf, "%u", &new_value);
+	kfree(local_buf);
+	if (retval != 1 || new_value > 1)
+		return -EINVAL;
+
+	data->comms_debug = new_value;
+
+	return size;
+}
+
+
+static const struct file_operations comms_debug_fops = {
+	.owner = THIS_MODULE,
+	.open = debug_open,
+	.release = debug_release,
+	.read = comms_debug_read,
+	.write = comms_debug_write,
+};
+
+static int setup_debugfs(struct rmi_device *rmi_dev, struct rmi_i2c_data *data)
+{
+	if (!rmi_dev->debugfs_root)
+		return -ENODEV;
+
+	data->debugfs_comms = debugfs_create_file("comms_debug", RMI_RW_ATTR,
+			rmi_dev->debugfs_root, data, &comms_debug_fops);
+	if (!data->debugfs_comms || IS_ERR(data->debugfs_comms)) {
+		dev_warn(&rmi_dev->dev, "Failed to create debugfs comms_debug.\n");
+		data->debugfs_comms = NULL;
+	}
+
+	return 0;
+}
+
+static void teardown_debugfs(struct rmi_i2c_data *data)
+{
+	if (data->debugfs_comms)
+		debugfs_remove(data->debugfs_comms);
+}
+#endif
+
+#define COMMS_DEBUG(data) (IS_ENABLED(CONFIG_RMI4_DEBUG) && data->comms_debug)
+
+#define RMI_PAGE_SELECT_REGISTER 0xff
+#define RMI_I2C_PAGE(addr) (((addr) >> 8) & 0xff)
+
+static char *phys_proto_name = "i2c";
+
+/*
+ * rmi_set_page - Set RMI page
+ * @phys: The pointer to the rmi_phys_device struct
+ * @page: The new page address.
+ *
+ * RMI devices have 16-bit addressing, but some of the physical
+ * implementations (like SMBus) only have 8-bit addressing. So RMI implements
+ * a page address at 0xff of every page so we can reliable page addresses
+ * every 256 registers.
+ *
+ * The page_mutex lock must be held when this function is entered.
+ *
+ * Returns zero on success, non-zero on failure.
+ */
+static int rmi_set_page(struct rmi_phys_device *phys, u8 page)
+{
+	struct i2c_client *client = to_i2c_client(phys->dev);
+	struct rmi_i2c_data *data = phys->data;
+	u8 txbuf[2] = {RMI_PAGE_SELECT_REGISTER, page};
+	int retval;
+
+	if (COMMS_DEBUG(data))
+		dev_dbg(&client->dev, "writes 3 bytes: %02x %02x\n",
+			txbuf[0], txbuf[1]);
+	phys->info.tx_count++;
+	phys->info.tx_bytes += sizeof(txbuf);
+	retval = i2c_master_send(client, txbuf, sizeof(txbuf));
+	if (retval != sizeof(txbuf)) {
+		phys->info.tx_errs++;
+		dev_err(&client->dev,
+			"%s: set page failed: %d.", __func__, retval);
+		return (retval < 0) ? retval : -EIO;
+	}
+	data->page = page;
+	return 0;
+}
+
+static int copy_to_debug_buf(struct device *dev, struct rmi_i2c_data *data,
+			     const u8 *buf, const int len) {
+	int i;
+	int n = 0;
+	char *temp;
+	int dbg_size = 3 * len + 1;
+
+	if (!data->debug_buf || data->debug_buf_size < dbg_size) {
+		if (data->debug_buf)
+			devm_kfree(dev, data->debug_buf);
+		data->debug_buf_size = dbg_size + BUFFER_SIZE_INCREMENT;
+		data->debug_buf = devm_kzalloc(dev, data->debug_buf_size,
+					       GFP_KERNEL);
+		if (!data->debug_buf) {
+			data->debug_buf_size = 0;
+			return -ENOMEM;
+		}
+	}
+	temp = data->debug_buf;
+
+	for (i = 0; i < len; i++) {
+		n = sprintf(temp, " %02x", buf[i]);
+		temp += n;
+	}
+
+	return 0;
+}
+
+static int rmi_i2c_write_block(struct rmi_phys_device *phys, u16 addr,
+			       const void *buf, const int len)
+{
+	struct i2c_client *client = to_i2c_client(phys->dev);
+	struct rmi_i2c_data *data = phys->data;
+	int retval;
+	int tx_size = len + 1;
+
+	mutex_lock(&data->page_mutex);
+
+	if (!data->tx_buf || data->tx_buf_size < tx_size) {
+		if (data->tx_buf)
+			devm_kfree(&client->dev, data->tx_buf);
+		data->tx_buf_size = tx_size + BUFFER_SIZE_INCREMENT;
+		data->tx_buf = devm_kzalloc(&client->dev, data->tx_buf_size,
+					    GFP_KERNEL);
+		if (!data->tx_buf) {
+			data->tx_buf_size = 0;
+			retval = -ENOMEM;
+			goto exit;
+		}
+	}
+	data->tx_buf[0] = addr & 0xff;
+	memcpy(data->tx_buf + 1, buf, len);
+
+	if (RMI_I2C_PAGE(addr) != data->page) {
+		retval = rmi_set_page(phys, RMI_I2C_PAGE(addr));
+		if (retval < 0)
+			goto exit;
+	}
+
+	if (COMMS_DEBUG(data)) {
+		retval = copy_to_debug_buf(&client->dev, data, (u8 *) buf, len);
+		if (!retval)
+			dev_dbg(&client->dev, "writes %d bytes at %#06x:%s\n",
+				len, addr, data->debug_buf);
+	}
+
+	phys->info.tx_count++;
+	phys->info.tx_bytes += tx_size;
+	retval = i2c_master_send(client, data->tx_buf, tx_size);
+	if (retval < 0)
+		phys->info.tx_errs++;
+	else
+		retval--; /* don't count the address byte */
+
+exit:
+	mutex_unlock(&data->page_mutex);
+	return retval;
+}
+
+
+static int rmi_i2c_read_block(struct rmi_phys_device *phys, u16 addr,
+			      void *buf, const int len)
+{
+	struct i2c_client *client = to_i2c_client(phys->dev);
+	struct rmi_i2c_data *data = phys->data;
+	u8 txbuf[1] = {addr & 0xff};
+	int retval;
+
+	mutex_lock(&data->page_mutex);
+
+	if (RMI_I2C_PAGE(addr) != data->page) {
+		retval = rmi_set_page(phys, RMI_I2C_PAGE(addr));
+		if (retval < 0)
+			goto exit;
+	}
+
+	if (COMMS_DEBUG(data))
+		dev_dbg(&client->dev, "writes 1 bytes: %02x\n", txbuf[0]);
+
+	phys->info.tx_count++;
+	phys->info.tx_bytes += sizeof(txbuf);
+	retval = i2c_master_send(client, txbuf, sizeof(txbuf));
+	if (retval != sizeof(txbuf)) {
+		phys->info.tx_errs++;
+		retval = (retval < 0) ? retval : -EIO;
+		goto exit;
+	}
+
+	retval = i2c_master_recv(client, (u8 *) buf, len);
+
+	phys->info.rx_count++;
+	phys->info.rx_bytes += len;
+	if (retval < 0)
+		phys->info.rx_errs++;
+	else if (COMMS_DEBUG(data)) {
+		retval = copy_to_debug_buf(&client->dev, data, (u8 *) buf, len);
+		if (!retval)
+			dev_dbg(&client->dev, "read %d bytes at %#06x:%s\n",
+				len, addr, data->debug_buf);
+	}
+
+exit:
+	mutex_unlock(&data->page_mutex);
+	return retval;
+}
+
+static int __devinit rmi_i2c_probe(struct i2c_client *client,
+				  const struct i2c_device_id *id)
+{
+	struct rmi_phys_device *rmi_phys;
+	struct rmi_i2c_data *data;
+	struct rmi_device_platform_data *pdata = client->dev.platform_data;
+	int retval;
+
+	if (!pdata) {
+		dev_err(&client->dev, "no platform data\n");
+		return -EINVAL;
+	}
+	dev_info(&client->dev, "Probing %s at %#02x (IRQ %d).\n",
+		pdata->sensor_name ? pdata->sensor_name : "-no name-",
+		client->addr, pdata->attn_gpio);
+
+	if (pdata->gpio_config) {
+		dev_info(&client->dev, "Configuring GPIOs.\n");
+		retval = pdata->gpio_config(pdata->gpio_data, true);
+		if (retval < 0) {
+			dev_err(&client->dev, "Failed to configure GPIOs, code: %d.\n",
+				retval);
+			return retval;
+		}
+		dev_info(&client->dev, "Done with GPIO configuration.\n");
+	}
+
+	retval = i2c_check_functionality(client->adapter, I2C_FUNC_I2C);
+	if (!retval) {
+		dev_err(&client->dev, "i2c_check_functionality error %d.\n",
+			retval);
+		return retval;
+	}
+
+	rmi_phys = devm_kzalloc(&client->dev, sizeof(struct rmi_phys_device),
+				GFP_KERNEL);
+
+	if (!rmi_phys)
+		return -ENOMEM;
+
+	data = devm_kzalloc(&client->dev, sizeof(struct rmi_i2c_data),
+				GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->phys = rmi_phys;
+
+	rmi_phys->data = data;
+	rmi_phys->dev = &client->dev;
+
+	rmi_phys->write_block = rmi_i2c_write_block;
+	rmi_phys->read_block = rmi_i2c_read_block;
+	rmi_phys->info.proto = phys_proto_name;
+
+	mutex_init(&data->page_mutex);
+
+	/* Setting the page to zero will (a) make sure the PSR is in a
+	 * known state, and (b) make sure we can talk to the device.
+	 */
+	retval = rmi_set_page(rmi_phys, 0);
+	if (retval) {
+		dev_err(&client->dev, "Failed to set page select to 0.\n");
+		return retval;
+	}
+
+	retval = rmi_register_phys_device(rmi_phys);
+	if (retval) {
+		dev_err(&client->dev,
+			"failed to register physical driver at 0x%.2X.\n",
+			client->addr);
+		goto err_gpio;
+	}
+	i2c_set_clientdata(client, rmi_phys);
+
+	if (IS_ENABLED(CONFIG_RMI4_DEBUG))
+		retval = setup_debugfs(rmi_phys->rmi_dev, data);
+
+	dev_info(&client->dev, "registered rmi i2c driver at %#04x.\n",
+			client->addr);
+	return 0;
+
+err_gpio:
+	if (pdata->gpio_config)
+		pdata->gpio_config(pdata->gpio_data, false);
+	return retval;
+}
+
+static int __devexit rmi_i2c_remove(struct i2c_client *client)
+{
+	struct rmi_phys_device *phys = i2c_get_clientdata(client);
+	struct rmi_device_platform_data *pd = client->dev.platform_data;
+
+	if (IS_ENABLED(CONFIG_RMI4_DEBUG))
+		teardown_debugfs(phys->data);
+
+	rmi_unregister_phys_device(phys);
+
+	if (pd->gpio_config)
+		pd->gpio_config(&pd->gpio_data, false);
+
+	return 0;
+}
+
+static const struct i2c_device_id rmi_id[] = {
+	{ "rmi_i2c", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, rmi_id);
+
+static struct i2c_driver rmi_i2c_driver = {
+	.driver = {
+		.owner	= THIS_MODULE,
+		.name	= "rmi_i2c"
+	},
+	.id_table	= rmi_id,
+	.probe		= rmi_i2c_probe,
+	.remove		= __devexit_p(rmi_i2c_remove),
+};
+
+static int __init rmi_i2c_init(void)
+{
+	return i2c_add_driver(&rmi_i2c_driver);
+}
+
+static void __exit rmi_i2c_exit(void)
+{
+	i2c_del_driver(&rmi_i2c_driver);
+}
+
+module_init(rmi_i2c_init);
+module_exit(rmi_i2c_exit);
+
+MODULE_AUTHOR("Christopher Heiny <cheiny@synaptics.com>");
+MODULE_DESCRIPTION("RMI I2C driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(RMI_DRIVER_VERSION);

  parent reply	other threads:[~2012-11-17  4:00 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-11-17  3:58 [RFC PATCH 00/06] input: Synaptics RMI4 Touchscreen Driver Christopher Heiny
2012-11-17  3:58 ` [RFC PATCH 01/06] input/rmi4: Public header and documentation Christopher Heiny
2012-11-17 22:41   ` Linus Walleij
2012-11-17  3:58 ` [RFC PATCH 02/06] input/rmi4: Core files Christopher Heiny
2012-11-17 21:54   ` Greg Kroah-Hartman
2012-11-20  4:56     ` Christopher Heiny
2012-11-17 22:45   ` Linus Walleij
2012-11-26 18:41   ` Benjamin Tissoires
2012-11-26 22:54     ` Christopher Heiny
2012-11-27  9:33       ` Benjamin Tissoires
2012-11-17  3:58 ` Christopher Heiny [this message]
2012-11-17 22:47   ` [RFC PATCH 03/06] input/rmi4: I2C physical interface Linus Walleij
2012-11-17  3:58 ` [RFC PATCH 04/06] input/rmi4: Config files and makefiles Christopher Heiny
2012-11-17 22:50   ` Linus Walleij
2012-11-17  3:58 ` [RFC PATCH 05/06] input/rmi4: F01 - device control Christopher Heiny
2012-11-17  3:58   ` Christopher Heiny
2012-11-17 22:56   ` Linus Walleij
2012-11-26  9:40   ` Dmitry Torokhov
2012-11-26  9:40     ` Dmitry Torokhov
2012-11-26 22:31     ` Christopher Heiny
2012-11-27  9:29       ` Dmitry Torokhov
2012-11-28  2:52         ` Christopher Heiny
2012-11-17  3:58 ` [RFC PATCH 06/06] input/rmi4: F11 - 2D touch interface Christopher Heiny
2012-11-17 22:54   ` Linus Walleij
2012-11-27 13:01   ` Benjamin Tissoires
  -- strict thread matches above, loose matches on Subject: below --
2012-10-06  4:09 [RFC PATCH 00/06] input: Synaptics RMI4 Touchscreen Driver Christopher Heiny
2012-10-06  4:10 ` [RFC PATCH 03/06] input/rmi4: I2C physical interface Christopher Heiny
2012-10-09  9:05   ` Linus Walleij
2012-10-11  4:21     ` Christopher Heiny

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1353124734-16803-4-git-send-email-cheiny@synaptics.com \
    --to=cheiny@synaptics.com \
    --cc=alexandra.chin@tw.synaptics.com \
    --cc=axiong@synaptics.com \
    --cc=daniel.rosenberg@synaptics.com \
    --cc=dmitry.torokhov@gmail.com \
    --cc=j.de.gram@gmail.com \
    --cc=khali@linux-fr.org \
    --cc=linus.walleij@stericsson.com \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mathieu.poirier@linaro.org \
    --cc=naveen.gaddipati@stericsson.com \
    --cc=vly@synaptics.com \
    --cc=w.sang@pengutronix.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.