Hi Christopher,
Please find my [incomplete] comments below.
On Wed, Dec 21, 2011 at 06:09:53PM -0800, Christopher Heiny wrote:Signed-off-by: Christopher Heiny<cheiny@xxxxxxxxxxxxx>
Cc: Dmitry Torokhov<dmitry.torokhov@xxxxxxxxx>
Cc: Linus Walleij<linus.walleij@xxxxxxxxxxxxxx>
Cc: Naveen Kumar Gaddipati<naveen.gaddipati@xxxxxxxxxxxxxx>
Cc: Joeri de Gram<j.de.gram@xxxxxxxxx>
---
drivers/input/rmi4/rmi_bus.c | 436 ++++++++++++
drivers/input/rmi4/rmi_driver.c | 1488 +++++++++++++++++++++++++++++++++++++++
drivers/input/rmi4/rmi_driver.h | 97 +++
3 files changed, 2021 insertions(+), 0 deletions(-)
diff --git a/drivers/input/rmi4/rmi_bus.c b/drivers/input/rmi4/rmi_bus.c
+static int rmi_bus_match(struct device *dev, struct device_driver *driver)
+{
+ struct rmi_driver *rmi_driver;
+ struct rmi_device *rmi_dev;
+ struct rmi_device_platform_data *pdata;
+
+ rmi_driver = to_rmi_driver(driver);
+ rmi_dev = to_rmi_device(dev);
+ pdata = to_rmi_platform_data(rmi_dev);
+ dev_dbg(dev, "Matching %s.\n", pdata->sensor_name);
+
+ if (!strcmp(pdata->driver_name, rmi_driver->driver.name)) {
+ rmi_dev->driver = rmi_driver;
+ dev_dbg(dev, "%s: Match %s to %s succeeded.\n", __func__,
+ pdata->driver_name, rmi_driver->driver.name);
+ return 1;
+ }
+
+ dev_err(dev, "%s: Match %s to %s failed.\n", __func__,
+ pdata->driver_name, rmi_driver->driver.name);
Why is this an error? dev_vdbg() at most, better yet just remove it.
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int rmi_bus_suspend(struct device *dev)
+{
+#ifdef GENERIC_SUBSYS_PM_OPS
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ if (pm&& pm->suspend)
+ return pm->suspend(dev);
+#endif
+
+ return 0;
+}
+
+static int rmi_bus_resume(struct device *dev)
+{
+#ifdef GENERIC_SUBSYS_PM_OPS
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ if (pm&& pm->resume)
+ return pm->resume(dev);
+#endif
+
+ return 0;
+}
+#endif
These are not needed if you switch to generic_subsys_pm_ops.
+int rmi_register_phys_device(struct rmi_phys_device *phys)
+{
+ struct rmi_device_platform_data *pdata = phys->dev->platform_data;
+ struct rmi_device *rmi_dev;
+
+ if (!pdata) {
+ dev_err(phys->dev, "no platform data!\n");
+ return -EINVAL;
+ }
+
+ rmi_dev = kzalloc(sizeof(struct rmi_device), GFP_KERNEL);
+ if (!rmi_dev)
+ return -ENOMEM;
+
+ rmi_dev->phys = phys;
+ rmi_dev->dev.bus =&rmi_bus_type;
+
+ mutex_lock(&rmi_bus_mutex);
+ rmi_dev->number = physical_device_count;
+ physical_device_count++;
+ mutex_unlock(&rmi_bus_mutex);
Do
rmi_dev->number = atomic_inc_return(&rmi_no) - 1;
and stick "static atomic_t rmi_no = ATOMIC_INIT(0)"; at the beginning
of the function. Then you don't need to take mutex here. Do you need
rmi_dev->number?
+
+ dev_set_name(&rmi_dev->dev, "sensor%02d", rmi_dev->number);
+ pr_debug("%s: Registered %s as %s.\n", __func__, pdata->sensor_name,
+ dev_name(&rmi_dev->dev));
+
+ phys->rmi_dev = rmi_dev;
+ return device_register(&rmi_dev->dev);
+}
+EXPORT_SYMBOL(rmi_register_phys_device);
+
+void rmi_unregister_phys_device(struct rmi_phys_device *phys)
+{
+ struct rmi_device *rmi_dev = phys->rmi_dev;
+
+ device_unregister(&rmi_dev->dev);
+ kfree(rmi_dev);
This is lifetime rules violation; rmi_dev->dev might still be referenced
and you are freeing it. Please provide proper release function.
+}
+EXPORT_SYMBOL(rmi_unregister_phys_device);
+
+int rmi_register_driver(struct rmi_driver *driver)
+{
+ driver->driver.bus =&rmi_bus_type;
+ return driver_register(&driver->driver);
+}
+EXPORT_SYMBOL(rmi_register_driver);
+
+static int __rmi_driver_remove(struct device *dev, void *data)
+{
+ struct rmi_driver *driver = data;
+ struct rmi_device *rmi_dev = to_rmi_device(dev);
+
+ if (rmi_dev->driver == driver)
+ rmi_dev->driver = NULL;
No cleanup whatsoever?
+
+ return 0;
+}
+
+void rmi_unregister_driver(struct rmi_driver *driver)
+{
+ bus_for_each_dev(&rmi_bus_type, NULL, driver, __rmi_driver_remove);
Why don't you rely on driver core to unbind devices upon driver removal
instead of rolling your own (and highly likely broken) implementation.
[snip]
+ driver_unregister(&driver->driver);
+}
+EXPORT_SYMBOL(rmi_unregister_driver);
+
+
+void rmi_unregister_function_driver(struct rmi_function_handler *fh)
+{
+ struct rmi_function_list *entry, *n;
+
+ /* notify devices of the removal of the function handler */
+ bus_for_each_dev(&rmi_bus_type, NULL, fh, __rmi_bus_fh_remove);
+
+ list_for_each_entry_safe(entry, n,&rmi_supported_functions.list,
+ list) {
+ if (entry->fh->func == fh->func) {
+ list_del(&entry->list);
+ kfree(entry);
+ }
+ }
You are still rolling partly your own infrastructure. It looks like you
need 2 types of devices on rmi bus - composite RMI device and sensor
device, see struct device_type. Make 2 of those and match drivers
depending on the device type. Then driver core will maintain all the
lists for you.
+
+}
+EXPORT_SYMBOL(rmi_unregister_function_driver);
+
+struct rmi_function_handler *rmi_get_function_handler(int id)
+{
+ struct rmi_function_list *entry;
+
+ list_for_each_entry(entry,&rmi_supported_functions.list, list)
+ if (entry->fh->func == id)
+ return entry->fh;
+
+ return NULL;
+}
+EXPORT_SYMBOL(rmi_get_function_handler);
+
+static void rmi_release_character_device(struct device *dev)
+{
+ dev_dbg(dev, "%s: Called.\n", __func__);
+ return;
No, no, no. Do not try to shut up warnings from device core; they are
there for a reason.
+}
+
+int rmi_register_character_driver(struct rmi_char_driver *char_driver)
+{
+ struct rmi_character_driver_list *entry;
+ int retval;
+
+ pr_debug("%s: Registering character driver %s.\n", __func__,
+ char_driver->driver.name);
+
+ char_driver->driver.bus =&rmi_bus_type;
+ INIT_LIST_HEAD(&char_driver->devices);
+ retval = driver_register(&char_driver->driver);
+ if (retval) {
+ pr_err("%s: Failed to register %s, code: %d.\n", __func__,
+ char_driver->driver.name, retval);
+ return retval;
+ }
+
+ entry = kzalloc(sizeof(struct rmi_character_driver_list), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+ entry->cd = char_driver;
+
+ mutex_lock(&rmi_bus_mutex);
+ list_add_tail(&entry->list,&rmi_character_drivers.list);
+ mutex_unlock(&rmi_bus_mutex);
+
+ /* notify devices of the removal of the function handler */
+ bus_for_each_dev(&rmi_bus_type, NULL, char_driver,
+ rmi_register_character_device);
Hmm, thisi is very roundabout way of attaching RMI chardevice... Does it
even work if driver [re]appears after rmi_dev module was loaded?
IFF we agree on keeping rmi_dev interface then I think something more
elegant could be cooked via bus's blocking notifier.
+
+static int __init rmi_bus_init(void)
+{
+ int error;
+
+ mutex_init(&rmi_bus_mutex);
+ INIT_LIST_HEAD(&rmi_supported_functions.list);
+ INIT_LIST_HEAD(&rmi_character_drivers.list);
+
+ error = bus_register(&rmi_bus_type);
+ if (error< 0) {
+ pr_err("%s: error registering the RMI bus: %d\n", __func__,
+ error);
+ return error;
+ }
+ pr_info("%s: successfully registered RMI bus.\n", __func__);
This is all useless noise. Just do:
return bus_register(&rmi_bus_type);
+
+ return 0;
+}
+
+static void __exit rmi_bus_exit(void)
+{
+ struct rmi_function_list *entry, *n;
+
+ list_for_each_entry_safe(entry, n,&rmi_supported_functions.list,
+ list) {
+ list_del(&entry->list);
+ kfree(entry);
+ }
How can this list be non-free? Your bus code is reference by function
drivers so module count is non zero until all such drivers are unloaded,
and therefore rmi_bus_exit() can not be called.
+
+ bus_unregister(&rmi_bus_type);
+}
+
+module_init(rmi_bus_init);
+module_exit(rmi_bus_exit);
+
+MODULE_AUTHOR("Eric Andersson<eric.andersson@xxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("RMI bus");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c
new file mode 100644
index 0000000..07097bb
--- /dev/null
+++ b/drivers/input/rmi4/rmi_driver.c
@@ -0,0 +1,1488 @@
+/*
+ * Copyright (c) 2011 Synaptics Incorporated
+ * Copyright (c) 2011 Unixphere
+ *
+ * This driver adds support for generic RMI4 devices from Synpatics. It
+ * implements the mandatory f01 RMI register and depends on the presence of
+ * other required RMI functions.
+ *
+ * The RMI4 specification can be found here (URL split after files/ for
+ * style reasons):
+ * http://www.synaptics.com/sites/default/files/
+ * 511-000136-01-Rev-E-RMI4%20Intrfacing%20Guide.pdf
+ *
+ * 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/delay.h>
+#include<linux/device.h>
+#include<linux/module.h>
+#include<linux/device.h>
+#include<linux/slab.h>
+#include<linux/list.h>
+#include<linux/pm.h>
+#include<linux/rmi.h>
+#include "rmi_driver.h"
+
+#define DELAY_DEBUG 0
+#define REGISTER_DEBUG 0
+
+#define PDT_END_SCAN_LOCATION 0x0005
+#define PDT_PROPERTIES_LOCATION 0x00EF
+#define BSR_LOCATION 0x00FE
+#define HAS_BSR_MASK 0x20
+#define HAS_NONSTANDARD_PDT_MASK 0x40
+#define RMI4_END_OF_PDT(id) ((id) == 0x00 || (id) == 0xff)
+#define RMI4_MAX_PAGE 0xff
+#define RMI4_PAGE_SIZE 0x100
+
+#define RMI_DEVICE_RESET_CMD 0x01
+#define DEFAULT_RESET_DELAY_MS 20
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void rmi_driver_early_suspend(struct early_suspend *h);
+static void rmi_driver_late_resume(struct early_suspend *h);
+#endif
Does not appear to be in mainline; please trim.
+
+/* sysfs files for attributes for driver values. */
+static ssize_t rmi_driver_hasbsr_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
Well, if it does not have bsr why do you even create bsr attribute?
+
+static ssize_t rmi_driver_bsr_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+static ssize_t rmi_driver_bsr_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+
+static ssize_t rmi_driver_enabled_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+
+static ssize_t rmi_driver_enabled_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
Should rely on load/unload or bind/unbind; no need for yet another
mechanism.
+
+static ssize_t rmi_driver_phys_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+
+static ssize_t rmi_driver_version_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
Should be /sys/module/xxx/version already.
+const char *buf, size_t count);
+#if REGISTER_DEBUG
+static ssize_t rmi_driver_reg_store(struct device *dev,
+ struct device_attribute *attr,
+
debugfs
+#endif
+
+#if DELAY_DEBUG
+static ssize_t rmi_delay_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+
+static ssize_t rmi_delay_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
debugfs
+ dev_dbg(dev, "%s: Creating sysfs files.", __func__);
+ for (attr_count = 0; attr_count< ARRAY_SIZE(attrs); attr_count++) {
+ error = device_create_file(dev,&attrs[attr_count]);
+ if (error< 0) {
+ dev_err(dev, "%s: Failed to create sysfs file %s.\n",
+ __func__, attrs[attr_count].attr.name);
+ goto err_free_data;
+ }
+ }
Use attribute group or driver infrastructure for registering common
attributes.
+
+ __mutex_init(&data->irq_mutex, "irq_mutex",&data->irq_key);
Why not standard mutex_init()?
+ data->current_irq_mask = kzalloc(sizeof(u8)*data->num_of_irq_regs,
Spaces around arithmetic and other operations are appreciated.
+ GFP_KERNEL);
+ if (!data->current_irq_mask) {
+ dev_err(dev, "Failed to allocate current_irq_mask.\n");
+ error = -ENOMEM;
+ goto err_free_data;
+ }
+ error = rmi_read_block(rmi_dev,
+ data->f01_container->fd.control_base_addr+1,
+ data->current_irq_mask, data->num_of_irq_regs);
+ if (error< 0) {
+ dev_err(dev, "%s: Failed to read current IRQ mask.\n",
+ __func__);
+ goto err_free_data;
+ }
+ data->irq_mask_store = kzalloc(sizeof(u8)*data->num_of_irq_regs,
+ GFP_KERNEL);
+ if (!data->irq_mask_store) {
+ dev_err(dev, "Failed to allocate mask store.\n");
+ error = -ENOMEM;
+ goto err_free_data;
+ }
+
+#ifdef CONFIG_PM
+ data->pm_data = pdata->pm_data;
+ data->pre_suspend = pdata->pre_suspend;
+ data->post_resume = pdata->post_resume;
Is it really platform dependent?
+
+ mutex_init(&data->suspend_mutex);
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ rmi_dev->early_suspend_handler.level =
+ EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
+ rmi_dev->early_suspend_handler.suspend = rmi_driver_early_suspend;
+ rmi_dev->early_suspend_handler.resume = rmi_driver_late_resume;
+ register_early_suspend(&rmi_dev->early_suspend_handler);
Not in mainline.
+#endif /* CONFIG_HAS_EARLYSUSPEND */
+#endif /* CONFIG_PM */
+ data->enabled = true;
+
+ dev_info(dev, "connected RMI device manufacturer: %s product: %s\n",
+ data->manufacturer_id == 1 ? "synaptics" : "unknown",
+ data->product_id);
+
+ return 0;
+
+ err_free_data:
+ rmi_free_function_list(rmi_dev);
+ for (attr_count--; attr_count>= 0; attr_count--)
+ device_remove_file(dev,&attrs[attr_count]);
+ if (data) {
You exit earlier if data is NULL.
+ if (data->f01_container)
+ kfree(data->f01_container->irq_mask);
+ kfree(data->irq_mask_store);
+ kfree(data->current_irq_mask);
+ kfree(data);
+ rmi_set_driverdata(rmi_dev, NULL);
+ }
+ return error;
+}
+
+#ifdef CONFIG_PM
+static int rmi_driver_suspend(struct device *dev)
+{
+ struct rmi_device *rmi_dev;
+ struct rmi_driver_data *data;
+ struct rmi_function_container *entry;
+ int retval = 0;
+
+ rmi_dev = to_rmi_device(dev);
+ data = rmi_get_driverdata(rmi_dev);
+
+ mutex_lock(&data->suspend_mutex);
+ if (data->suspended)
+ goto exit;
+
+#ifndef CONFIG_HAS_EARLYSUSPEND
+ if (data->pre_suspend) {
+ retval = data->pre_suspend(data->pm_data);
+ if (retval)
+ goto exit;
+ }
+#endif /* !CONFIG_HAS_EARLYSUSPEND */
+
+ list_for_each_entry(entry,&data->rmi_functions.list, list)
+ if (entry->fh&& entry->fh->suspend) {
+ retval = entry->fh->suspend(entry);
+ if (retval< 0)
+ goto exit;
+ }
+
+ if (data->f01_container&& data->f01_container->fh
+ && data->f01_container->fh->suspend) {
+ retval = data->f01_container->fh->suspend(data->f01_container);
+ if (retval< 0)
+ goto exit;
+ }
+ data->suspended = true;
+
+exit:
+ mutex_unlock(&data->suspend_mutex);
+ return retval;
Once it is better integrated in driver core this will be much simpler.
+}
+
+static int rmi_driver_resume(struct device *dev)
+{
+ struct rmi_device *rmi_dev;
+ struct rmi_driver_data *data;
+ struct rmi_function_container *entry;
+ int retval = 0;
+
+ rmi_dev = to_rmi_device(dev);
+ data = rmi_get_driverdata(rmi_dev);
+
+ mutex_lock(&data->suspend_mutex);
+ if (!data->suspended)
+ goto exit;
+
+ if (data->f01_container&& data->f01_container->fh
+ && data->f01_container->fh->resume) {
+ retval = data->f01_container->fh->resume(data->f01_container);
+ if (retval< 0)
+ goto exit;
+ }
+
+ list_for_each_entry(entry,&data->rmi_functions.list, list)
+ if (entry->fh&& entry->fh->resume) {
+ retval = entry->fh->resume(entry);
+ if (retval< 0)
+ goto exit;
+ }
+
+#ifndef CONFIG_HAS_EARLYSUSPEND
+ if (data->post_resume) {
+ retval = data->post_resume(data->pm_data);
+ if (retval)
+ goto exit;
+ }
+#endif /* !CONFIG_HAS_EARLYSUSPEND */
+
+ data->suspended = false;
+
+exit:
+ mutex_unlock(&data->suspend_mutex);
+ return retval;
This one too.
+
+static ssize_t rmi_delay_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct rmi_device *rmi_dev;
+ struct rmi_device_platform_data *pdata;
+
+ rmi_dev = to_rmi_device(dev);
+ pdata = rmi_dev->phys->dev->platform_data;
+
+ return snprintf(buf, PAGE_SIZE, "%d %d %d %d %d\n",
+ pdata->spi_data.read_delay_us, pdata->spi_data.write_delay_us,
+ pdata->spi_data.block_delay_us,
+ pdata->spi_data.pre_delay_us, pdata->spi_data.post_delay_us);
This violates "one value per attribute" principle. Also it does not look
like it is essential for device operation but rather diagnostic
facility. Switch to debugfs?
+}
+#endif
+
+static ssize_t rmi_driver_phys_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct rmi_device *rmi_dev;
+ struct rmi_phys_info *info;
+
+ rmi_dev = to_rmi_device(dev);
+ info =&rmi_dev->phys->info;
+
+ return snprintf(buf, PAGE_SIZE, "%-5s %ld %ld %ld %ld %ld %ld %ld\n",
+ info->proto ? info->proto : "unk",
+ info->tx_count, info->tx_bytes, info->tx_errs,
+ info->rx_count, info->rx_bytes, info->rx_errs,
+ info->attn_count);
Same as delay.