diff options
author | David Brownell <dbrownell@users.sourceforge.net> | 2007-01-18 00:45:48 -0500 |
---|---|---|
committer | Dmitry Torokhov <dtor@insightbb.com> | 2007-01-18 00:45:48 -0500 |
commit | 2c8dc071517ec2843869024dc82be2e246f41064 (patch) | |
tree | 793c90a34b40d9f2a60eb108387b1077ec9e8861 /drivers/input/touchscreen/ads7846.c | |
parent | 15e3589e59c35ed33823dda3d38ad171222b83b4 (diff) | |
download | lwn-2c8dc071517ec2843869024dc82be2e246f41064.tar.gz lwn-2c8dc071517ec2843869024dc82be2e246f41064.zip |
Input: ads7846 - be more compatible with the hwmon framework
- Hook up to hwmon
* show sensor attributes only if hwmon is present
* ... and the board's reference voltage is known
* otherwise be just a touchscreen
- Report voltages per hwmon convention
* measure in millivolts
* voltages are named in[0-8]_input (ugh)
* for 7846 chips, properly range-adjust vBATT/in1_input
Battery measurements help during recharge monitoring. On OSK/Mistral,
the measured voltage agreed with a multimeter to several decimal places.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
Diffstat (limited to 'drivers/input/touchscreen/ads7846.c')
-rw-r--r-- | drivers/input/touchscreen/ads7846.c | 306 |
1 files changed, 217 insertions, 89 deletions
diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c index b18c63a3f2ab..cd251efda410 100644 --- a/drivers/input/touchscreen/ads7846.c +++ b/drivers/input/touchscreen/ads7846.c @@ -17,8 +17,9 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ -#include <linux/device.h> +#include <linux/hwmon.h> #include <linux/init.h> +#include <linux/err.h> #include <linux/delay.h> #include <linux/input.h> #include <linux/interrupt.h> @@ -69,7 +70,7 @@ struct ts_event { u16 x; u16 y; u16 z1, z2; - int ignore; + int ignore; }; struct ads7846 { @@ -77,7 +78,12 @@ struct ads7846 { char phys[32]; struct spi_device *spi; + +#if defined(CONFIG_HWMON) || defined(CONFIG_HWMON_MODULE) struct attribute_group *attr_group; + struct class_device *hwmon; +#endif + u16 model; u16 vref_delay_usecs; u16 x_plate_ohms; @@ -170,7 +176,12 @@ struct ads7846 { /* * Non-touchscreen sensors only use single-ended conversions. + * The range is GND..vREF. The ads7843 and ads7835 must use external vREF; + * ads7846 lets that pin be unconnected, to use internal vREF. */ +static unsigned vREF_mV; +module_param(vREF_mV, uint, 0); +MODULE_PARM_DESC(vREF_mV, "external vREF voltage, in milliVolts"); struct ser_req { u8 ref_on; @@ -198,50 +209,55 @@ static int ads7846_read12_ser(struct device *dev, unsigned command) struct ser_req *req = kzalloc(sizeof *req, GFP_KERNEL); int status; int sample; - int i; + int use_internal; if (!req) return -ENOMEM; spi_message_init(&req->msg); - /* activate reference, so it has time to settle; */ - req->ref_on = REF_ON; - req->xfer[0].tx_buf = &req->ref_on; - req->xfer[0].len = 1; - req->xfer[1].rx_buf = &req->scratch; - req->xfer[1].len = 2; - - /* - * for external VREF, 0 usec (and assume it's always on); - * for 1uF, use 800 usec; - * no cap, 100 usec. - */ - req->xfer[1].delay_usecs = ts->vref_delay_usecs; + /* FIXME boards with ads7846 might use external vref instead ... */ + use_internal = (ts->model == 7846); + + /* maybe turn on internal vREF, and let it settle */ + if (use_internal) { + req->ref_on = REF_ON; + req->xfer[0].tx_buf = &req->ref_on; + req->xfer[0].len = 1; + spi_message_add_tail(&req->xfer[0], &req->msg); + + req->xfer[1].rx_buf = &req->scratch; + req->xfer[1].len = 2; + + /* for 1uF, settle for 800 usec; no cap, 100 usec. */ + req->xfer[1].delay_usecs = ts->vref_delay_usecs; + spi_message_add_tail(&req->xfer[1], &req->msg); + } /* take sample */ req->command = (u8) command; req->xfer[2].tx_buf = &req->command; req->xfer[2].len = 1; + spi_message_add_tail(&req->xfer[2], &req->msg); + req->xfer[3].rx_buf = &req->sample; req->xfer[3].len = 2; + spi_message_add_tail(&req->xfer[3], &req->msg); /* REVISIT: take a few more samples, and compare ... */ - /* turn off reference */ - req->ref_off = REF_OFF; - req->xfer[4].tx_buf = &req->ref_off; - req->xfer[4].len = 1; - req->xfer[5].rx_buf = &req->scratch; - req->xfer[5].len = 2; - - CS_CHANGE(req->xfer[5]); - - /* group all the transfers together, so we can't interfere with - * reading touchscreen state; disable penirq while sampling - */ - for (i = 0; i < 6; i++) - spi_message_add_tail(&req->xfer[i], &req->msg); + /* maybe off internal vREF */ + if (use_internal) { + req->ref_off = REF_OFF; + req->xfer[4].tx_buf = &req->ref_off; + req->xfer[4].len = 1; + spi_message_add_tail(&req->xfer[4], &req->msg); + + req->xfer[5].rx_buf = &req->scratch; + req->xfer[5].len = 2; + CS_CHANGE(req->xfer[5]); + spi_message_add_tail(&req->xfer[5], &req->msg); + } ts->irq_disabled = 1; disable_irq(spi->irq); @@ -261,25 +277,173 @@ static int ads7846_read12_ser(struct device *dev, unsigned command) return status ? status : sample; } -#define SHOW(name) static ssize_t \ +#if defined(CONFIG_HWMON) || defined(CONFIG_HWMON_MODULE) + +#define SHOW(name, var, adjust) static ssize_t \ name ## _show(struct device *dev, struct device_attribute *attr, char *buf) \ { \ + struct ads7846 *ts = dev_get_drvdata(dev); \ ssize_t v = ads7846_read12_ser(dev, \ - READ_12BIT_SER(name) | ADS_PD10_ALL_ON); \ + READ_12BIT_SER(var) | ADS_PD10_ALL_ON); \ if (v < 0) \ return v; \ - return sprintf(buf, "%u\n", (unsigned) v); \ + return sprintf(buf, "%u\n", adjust(ts, v)); \ } \ static DEVICE_ATTR(name, S_IRUGO, name ## _show, NULL); -SHOW(temp0) -SHOW(temp1) -SHOW(vaux) -SHOW(vbatt) + +/* Sysfs conventions report temperatures in millidegrees Celcius. + * ADS7846 could use the low-accuracy two-sample scheme, but can't do the high + * accuracy scheme without calibration data. For now we won't try either; + * userspace sees raw sensor values, and must scale/calibrate appropriately. + */ +static inline unsigned null_adjust(struct ads7846 *ts, ssize_t v) +{ + return v; +} + +SHOW(temp0, temp0, null_adjust) /* temp1_input */ +SHOW(temp1, temp1, null_adjust) /* temp2_input */ + + +/* sysfs conventions report voltages in millivolts. We can convert voltages + * if we know vREF. userspace may need to scale vAUX to match the board's + * external resistors; we assume that vBATT only uses the internal ones. + */ +static inline unsigned vaux_adjust(struct ads7846 *ts, ssize_t v) +{ + unsigned retval = v; + + /* external resistors may scale vAUX into 0..vREF */ + retval *= vREF_mV; + retval = retval >> 12; + return retval; +} + +static inline unsigned vbatt_adjust(struct ads7846 *ts, ssize_t v) +{ + unsigned retval = vaux_adjust(ts, v); + + /* ads7846 has a resistor ladder to scale this signal down */ + if (ts->model == 7846) + retval *= 4; + return retval; +} + +SHOW(in0_input, vaux, vaux_adjust) +SHOW(in1_input, vbatt, vbatt_adjust) + + +static struct attribute *ads7846_attributes[] = { + &dev_attr_temp0.attr, + &dev_attr_temp1.attr, + &dev_attr_in0_input.attr, + &dev_attr_in1_input.attr, + NULL, +}; + +static struct attribute_group ads7846_attr_group = { + .attrs = ads7846_attributes, +}; + +static struct attribute *ads7843_attributes[] = { + &dev_attr_in0_input.attr, + &dev_attr_in1_input.attr, + NULL, +}; + +static struct attribute_group ads7843_attr_group = { + .attrs = ads7843_attributes, +}; + +static struct attribute *ads7845_attributes[] = { + &dev_attr_in0_input.attr, + NULL, +}; + +static struct attribute_group ads7845_attr_group = { + .attrs = ads7845_attributes, +}; + +static int ads784x_hwmon_register(struct spi_device *spi, struct ads7846 *ts) +{ + struct class_device *hwmon; + int err; + + /* hwmon sensors need a reference voltage */ + switch (ts->model) { + case 7846: + if (!vREF_mV) { + dev_dbg(&spi->dev, "assuming 2.5V internal vREF\n"); + vREF_mV = 2500; + } + break; + case 7845: + case 7843: + if (!vREF_mV) { + dev_warn(&spi->dev, + "external vREF for ADS%d not specified\n", + ts->model); + return 0; + } + break; + } + + /* different chips have different sensor groups */ + switch (ts->model) { + case 7846: + ts->attr_group = &ads7846_attr_group; + break; + case 7845: + ts->attr_group = &ads7845_attr_group; + break; + case 7843: + ts->attr_group = &ads7843_attr_group; + break; + default: + dev_dbg(&spi->dev, "ADS%d not recognized\n", ts->model); + return 0; + } + + err = sysfs_create_group(&spi->dev.kobj, ts->attr_group); + if (err) + return err; + + hwmon = hwmon_device_register(&spi->dev); + if (IS_ERR(hwmon)) { + sysfs_remove_group(&spi->dev.kobj, ts->attr_group); + return PTR_ERR(hwmon); + } + + ts->hwmon = hwmon; + return 0; +} + +static void ads784x_hwmon_unregister(struct spi_device *spi, + struct ads7846 *ts) +{ + if (ts->hwmon) { + sysfs_remove_group(&spi->dev.kobj, ts->attr_group); + hwmon_device_unregister(ts->hwmon); + } +} + +#else +static inline int ads784x_hwmon_register(struct spi_device *spi, + struct ads7846 *ts) +{ + return 0; +} + +static inline void ads784x_hwmon_unregister(struct spi_device *spi, + struct ads7846 *ts) +{ +} +#endif static int is_pen_down(struct device *dev) { - struct ads7846 *ts = dev_get_drvdata(dev); + struct ads7846 *ts = dev_get_drvdata(dev); return ts->pendown; } @@ -323,46 +487,14 @@ static ssize_t ads7846_disable_store(struct device *dev, static DEVICE_ATTR(disable, 0664, ads7846_disable_show, ads7846_disable_store); -static struct attribute *ads7846_attributes[] = { - &dev_attr_temp0.attr, - &dev_attr_temp1.attr, - &dev_attr_vbatt.attr, - &dev_attr_vaux.attr, - &dev_attr_pen_down.attr, - &dev_attr_disable.attr, - NULL, -}; - -static struct attribute_group ads7846_attr_group = { - .attrs = ads7846_attributes, -}; - -/* - * ads7843/7845 don't have temperature sensors, and - * use the other sensors a bit differently too - */ - -static struct attribute *ads7843_attributes[] = { - &dev_attr_vbatt.attr, - &dev_attr_vaux.attr, +static struct attribute *ads784x_attributes[] = { &dev_attr_pen_down.attr, &dev_attr_disable.attr, NULL, }; -static struct attribute_group ads7843_attr_group = { - .attrs = ads7843_attributes, -}; - -static struct attribute *ads7845_attributes[] = { - &dev_attr_vaux.attr, - &dev_attr_pen_down.attr, - &dev_attr_disable.attr, - NULL, -}; - -static struct attribute_group ads7845_attr_group = { - .attrs = ads7845_attributes, +static struct attribute_group ads784x_attr_group = { + .attrs = ads784x_attributes, }; /*--------------------------------------------------------------------------*/ @@ -886,28 +1018,21 @@ static int __devinit ads7846_probe(struct spi_device *spi) goto err_cleanup_filter; } + err = ads784x_hwmon_register(spi, ts); + if (err) + goto err_free_irq; + dev_info(&spi->dev, "touchscreen, irq %d\n", spi->irq); - /* take a first sample, leaving nPENIRQ active; avoid + /* take a first sample, leaving nPENIRQ active and vREF off; avoid * the touchscreen, in case it's not connected. */ (void) ads7846_read12_ser(&spi->dev, READ_12BIT_SER(vaux) | ADS_PD10_ALL_ON); - switch (ts->model) { - case 7846: - ts->attr_group = &ads7846_attr_group; - break; - case 7845: - ts->attr_group = &ads7845_attr_group; - break; - default: - ts->attr_group = &ads7843_attr_group; - break; - } - err = sysfs_create_group(&spi->dev.kobj, ts->attr_group); + err = sysfs_create_group(&spi->dev.kobj, &ads784x_attr_group); if (err) - goto err_free_irq; + goto err_remove_hwmon; err = input_register_device(input_dev); if (err) @@ -916,7 +1041,9 @@ static int __devinit ads7846_probe(struct spi_device *spi) return 0; err_remove_attr_group: - sysfs_remove_group(&spi->dev.kobj, ts->attr_group); + sysfs_remove_group(&spi->dev.kobj, &ads784x_attr_group); + err_remove_hwmon: + ads784x_hwmon_unregister(spi, ts); err_free_irq: free_irq(spi->irq, ts); err_cleanup_filter: @@ -932,11 +1059,12 @@ static int __devexit ads7846_remove(struct spi_device *spi) { struct ads7846 *ts = dev_get_drvdata(&spi->dev); + ads784x_hwmon_unregister(spi, ts); input_unregister_device(ts->input); ads7846_suspend(spi, PMSG_SUSPEND); - sysfs_remove_group(&spi->dev.kobj, ts->attr_group); + sysfs_remove_group(&spi->dev.kobj, &ads784x_attr_group); free_irq(ts->spi->irq, ts); /* suspend left the IRQ disabled */ |