From jic23@cam.ac.uk Tue Aug 18 10:25:48 2009 From: Jonathan Cameron Date: Tue, 18 Aug 2009 18:06:21 +0100 Subject: Staging: IIO: tsl2561 digital light sensor core support To: greg@kroah.com Cc: jic23@cam.ac.uk Message-ID: <1250615193-32501-4-git-send-email-jic23@cam.ac.uk> This is a pretty minimalist example of an IIO driver. Signed-off-by: Jonathan Cameron Signed-off-by: Greg Kroah-Hartman --- drivers/staging/iio/Kconfig | 1 drivers/staging/iio/Makefile | 1 drivers/staging/iio/light/Kconfig | 13 + drivers/staging/iio/light/Makefile | 5 drivers/staging/iio/light/light.h | 12 + drivers/staging/iio/light/tsl2561.c | 276 ++++++++++++++++++++++++++++++++++++ 6 files changed, 307 insertions(+), 1 deletion(-) --- a/drivers/staging/iio/Kconfig +++ b/drivers/staging/iio/Kconfig @@ -12,5 +12,6 @@ menuconfig IIO if IIO source "drivers/staging/iio/adc/Kconfig" +source "drivers/staging/iio/light/Kconfig" endif # IIO --- /dev/null +++ b/drivers/staging/iio/light/Kconfig @@ -0,0 +1,13 @@ +# +# Light sensors +# +comment "Light sensors" + +config TSL2561 + tristate "TAOS TSL2561 light-to-digital convertor" + depends on I2C + help + Say yes bere to build support for the TAOS light to digital + convertor. This chip has two light sensors. One is broadband + including infrared whilst the other measures only infrared. + Provides direct access via sysfs. --- /dev/null +++ b/drivers/staging/iio/light/light.h @@ -0,0 +1,12 @@ +#include "../sysfs.h" + +/* Light to digital sensor attributes */ + +#define IIO_DEV_ATTR_LIGHT_INFRARED(_num, _show, _addr) \ + IIO_DEVICE_ATTR(light_infrared##_num, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LIGHT_BROAD(_num, _show, _addr) \ + IIO_DEVICE_ATTR(light_broadspectrum##_num, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LIGHT_VISIBLE(_num, _show, _addr) \ + IIO_DEVICE_ATTR(light_visible##_num, S_IRUGO, _show, NULL, _addr) --- /dev/null +++ b/drivers/staging/iio/light/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for industrial I/O Light sensors +# + +obj-$(CONFIG_TSL2561) += tsl2561.o --- /dev/null +++ b/drivers/staging/iio/light/tsl2561.c @@ -0,0 +1,276 @@ +/* + * tsl2561.c - Linux kernel modules for light to digital convertor + * + * Copyright (C) 2008-2009 Jonathan Cameron + * + * 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. + * + * Some portions based upon the tsl2550 driver. + * + * This driver could probably be adapted easily to talk to the tsl2560 (smbus) + * + * Needs some work to support the events this can generate. + * Todo: Implement interrupt handling. Currently a hardware bug means + * this isn't available on my test board. + */ + +#include +#include +#include +#include "../iio.h" +#include "../sysfs.h" +#include "light.h" + +#define TSL2561_CONTROL_REGISTER 0x00 +#define TSL2561_TIMING_REGISTER 0x01 +#define TSL2561_THRESHLOW_LOW_REGISTER 0x02 +#define TSL2561_THRESHLOW_HIGH_REGISTER 0x03 +#define TSL2561_THRESHHIGH_LOW_REGISTER 0x04 +#define TSL2561_THRESHHIGH_HIGH_REGISTER 0x05 +#define TSL2561_INT_CONTROL_REGISTER 0x06 + +#define TSL2561_INT_REG_INT_OFF 0x00 +#define TSL2561_INT_REG_INT_LEVEL 0x08 +#define TSL2561_INT_REG_INT_SMBUS 0x10 +#define TSL2561_INT_REG_INT_TEST 0x18 + +#define TSL2561_ID_REGISTER 0x0A + +#define TSL2561_DATA_0_LOW 0x0C +#define TSL2561_DATA_1_LOW 0x0E + +/* Control Register Values */ +#define TSL2561_CONT_REG_PWR_ON 0x03 +#define TSL2561_CONT_REG_PWR_OFF 0x00 + +/** + * struct tsl2561_state - device specific state + * @indio_dev: the industrialio I/O info structure + * @client: i2c client + * @command_buf: single command buffer used for all operations + * @command_buf_lock: ensure unique access to command_buf + */ +struct tsl2561_state { + struct iio_dev *indio_dev; + struct i2c_client *client; + struct tsl2561_command *command_buf; + struct mutex command_buf_lock; +}; + +/** + * struct tsl2561_command - command byte for smbus + * @address: register address + * @block: is this a block r/w + * @word: is this a word r/w + * @clear: set to 1 to clear pending interrupt + * @cmd: select the command register - always 1. + */ +struct tsl2561_command { + unsigned int address:4; + unsigned int block:1; + unsigned int word:1; + unsigned int clear:1; + unsigned int cmd:1; +}; + +static inline void tsl2561_init_command_buf(struct tsl2561_command *buf) +{ + buf->address = 0; + buf->block = 0; + buf->word = 0; + buf->clear = 0; + buf->cmd = 1; +} + +static ssize_t tsl2561_read_val(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret = 0, data; + ssize_t len = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2561_state *st = indio_dev->dev_data; + + mutex_lock(&st->command_buf_lock); + st->command_buf->cmd = 1; + st->command_buf->word = 1; + st->command_buf->address = this_attr->address; + + data = i2c_smbus_read_word_data(st->client, *(char *)(st->command_buf)); + if (data < 0) { + ret = data; + goto error_ret; + } + len = sprintf(buf, "%u\n", data); + +error_ret: + mutex_unlock(&st->command_buf_lock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_LIGHT_INFRARED(0, tsl2561_read_val, TSL2561_DATA_0_LOW); +static IIO_DEV_ATTR_LIGHT_BROAD(0, tsl2561_read_val, TSL2561_DATA_1_LOW); + +static struct attribute *tsl2561_attributes[] = { + &iio_dev_attr_light_infrared0.dev_attr.attr, + &iio_dev_attr_light_broadspectrum0.dev_attr.attr, + NULL, +}; + +static const struct attribute_group tsl2561_attribute_group = { + .attrs = tsl2561_attributes, +}; + +static int tsl2561_initialize(struct tsl2561_state *st) +{ + int err; + + mutex_lock(&st->command_buf_lock); + st->command_buf->word = 0; + st->command_buf->block = 0; + st->command_buf->address = TSL2561_CONTROL_REGISTER; + err = i2c_smbus_write_byte_data(st->client, *(char *)(st->command_buf), + TSL2561_CONT_REG_PWR_ON); + if (err) + goto error_ret; + + st->command_buf->address = TSL2561_INT_CONTROL_REGISTER; + err = i2c_smbus_write_byte_data(st->client, *(char *)(st->command_buf), + TSL2561_INT_REG_INT_TEST); + +error_ret: + mutex_unlock(&st->command_buf_lock); + + return err; +} + +static int tsl2561_powerdown(struct i2c_client *client) +{ + int err; + struct tsl2561_command Command = { + .cmd = 1, + .clear = 0, + .word = 0, + .block = 0, + .address = TSL2561_CONTROL_REGISTER, + }; + + err = i2c_smbus_write_byte_data(client, *(char *)(&Command), + TSL2561_CONT_REG_PWR_OFF); + return (err < 0) ? err : 0; +} +static int __devinit tsl2561_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0, regdone = 0; + struct tsl2561_state *st = kzalloc(sizeof(*st), GFP_KERNEL); + + if (st == NULL) { + ret = -ENOMEM; + goto error_ret; + } + i2c_set_clientdata(client, st); + st->client = client; + mutex_init(&st->command_buf_lock); + + st->command_buf = kmalloc(sizeof(*st->command_buf), GFP_KERNEL); + if (st->command_buf == NULL) { + ret = -ENOMEM; + goto error_free_state; + } + tsl2561_init_command_buf(st->command_buf); + + st->indio_dev = iio_allocate_device(); + if (st->indio_dev == NULL) { + ret = -ENOMEM; + goto error_free_command_buf; + } + st->indio_dev->attrs = &tsl2561_attribute_group; + st->indio_dev->dev.parent = &client->dev; + st->indio_dev->dev_data = (void *)(st); + st->indio_dev->driver_module = THIS_MODULE; + st->indio_dev->modes = INDIO_DIRECT_MODE; + ret = iio_device_register(st->indio_dev); + if (ret) + goto error_free_iiodev; + regdone = 1; + /* Intialize the chip */ + ret = tsl2561_initialize(st); + if (ret) + goto error_unregister_iiodev; + + return 0; +error_unregister_iiodev: +error_free_iiodev: + if (regdone) + iio_device_unregister(st->indio_dev); + else + iio_free_device(st->indio_dev); +error_free_command_buf: + kfree(st->command_buf); +error_free_state: + kfree(st); +error_ret: + return ret; + +} + +static int __devexit tsl2561_remove(struct i2c_client *client) +{ + struct tsl2561_state *st = i2c_get_clientdata(client); + + iio_device_unregister(st->indio_dev); + kfree(st); + + return tsl2561_powerdown(client); +} + +static unsigned short normal_i2c[] = { 0x29, 0x39, 0x49, I2C_CLIENT_END }; + +I2C_CLIENT_INSMOD; + +static const struct i2c_device_id tsl2561_id[] = { + { "tsl2561", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsl2561_id); + + +static struct i2c_driver tsl2561_driver = { + .driver = { + .name = "tsl2561", + }, + .probe = tsl2561_probe, + .remove = __devexit_p(tsl2561_remove), + .id_table = tsl2561_id, +}; + +static __init int tsl2561_init(void) +{ + return i2c_add_driver(&tsl2561_driver); +} +module_init(tsl2561_init); + +static __exit void tsl2561_exit(void) +{ + i2c_del_driver(&tsl2561_driver); +} +module_exit(tsl2561_exit); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("TSL2561 light sensor driver"); +MODULE_LICENSE("GPL"); --- a/drivers/staging/iio/Makefile +++ b/drivers/staging/iio/Makefile @@ -5,4 +5,5 @@ obj-$(CONFIG_IIO) += industrialio.o industrialio-y := industrialio-core.o -obj-y += adc/ \ No newline at end of file +obj-y += adc/ +obj-y += light/ \ No newline at end of file