diff --git a/tsl2580/Makefile b/tsl2580/Makefile new file mode 100644 index 0000000..cd8aa45 --- /dev/null +++ b/tsl2580/Makefile @@ -0,0 +1,9 @@ +obj-m += tsl2580.o +KERN_DIR ?= /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C $(KERN_DIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERN_DIR) M=$(PWD) clean + diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c new file mode 100644 index 0000000..b1ff2a5 --- /dev/null +++ b/tsl2580/tsl2580.c @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tsl2580_regs.h" + +#define TSL2580_CLASS_NAME "tsl2580" + +/* Scale factors for lux approximation */ +#define LUX_SCALE 16 +#define RATIO_SCALE 9 +#define ADC_SCALE 16 + +/* 128x gain scaling factors */ +#define ADC0_SCALE_111X 107 +#define ADC1_SCALE_111X 115 + +/* Not sure if the preprocessor performs floating point evaluation */ +#define ODFN_K1C 0x009A /* 0.30 * 2 ^ RATIO_SCALE */ +#define ODFN_B1C 0x2148 /* 0.130 * 2 ^ LUX_SCALE */ +#define ODFN_M1C 0x3D71 /* 0.240 * 2 ^ LUX_SCALE */ +#define ODFN_K2C 0x00C3 /* 0.38 * 2 ^ RATIO_SCALE */ +#define ODFN_B2C 0x2A37 /* 0.1649 * 2 ^ LUX_SCALE */ +#define ODFN_M2C 0x5B30 /* 0.3562 * 2 ^ LUX_SCALE */ +#define ODFN_K3C 0x00E6 /* 0.45 * 2 ^ RATIO_SCALE */ +#define ODFN_B3C 0x18EF /* 0.0974 * 2 ^ LUX_SCALE */ +#define ODFN_M3C 0x2DB9 /* 0.1786 * 2 ^ LUX_SCALE */ +#define ODFN_K4C 0x0114 /* 0.54 * 2 ^ RATIO_SCALE */ +#define ODFN_B4C 0x0FDF /* 0.062 * 2 ^ LUX_SCALE */ +#define ODFN_M4C 0x199A /* 0.10 * 2 ^ LUX_SCALE */ +#define ODFN_K5C 0x0114 /* 0.54 * 2 ^ RATIO_SCALE */ +#define ODFN_B5C 0x0000 /* 0.00 * 2 ^ LUX_SCALE */ +#define ODFN_M5C 0x0000 /* 0.00 * 2 ^ LUX_SCALE */ + +/* Random threashold values for interrupt */ +#define INT_LOW_TH 0 +#define INT_HIGH_TH 10000 + +#define INT_GPIO 4 + +static u64 read_threashold = HZ; + +struct tsl2580_dev { + struct i2c_client *client; + u8 dev_id; + u16 data_adc0; + u16 data_adc1; + u16 data_lux; + struct task_struct *read_data_task; + wait_queue_head_t data_access_queue; + atomic_t data_access_state; + atomic_t thread_stop; + struct completion data_access_completion; + struct mutex data_access_mutex; + struct timespec64 read_ts; + struct mutex ts_access; + struct work_struct isr_bh; +}; + +static struct tsl2580_dev mydev; +static struct class *tsl2580_class; + +static void tsl2580_isr_bh(struct work_struct *ws); +static void tsl2580_prepare_read(void); +static s32 tsl2580_read_adc0(struct tsl2580_dev *dev); +static s32 tsl2580_read_adc1(struct tsl2580_dev *dev); +static s32 tsl2580_read_lux(struct tsl2580_dev *dev); +static s32 tsl2580_calc_lux(struct tsl2580_dev *dev, s32 low, s32 high); +static int tsl2580_read_data(struct tsl2580_dev *dev); +static int tsl2580_read_task(void *data); +static int tsl2580_init_sysfs(void); +static ssize_t who_am_i_show(struct class *class, + struct class_attribute *attr, + char *buf); +static ssize_t adc0_show(struct class *class, + struct class_attribute *attr, + char *buf); +static ssize_t adc1_show(struct class *class, + struct class_attribute *attr, + char *buf); +static ssize_t lux_show(struct class *class, + struct class_attribute *attr, + char *buf); +static ssize_t rt_show(struct class *class, + struct class_attribute *attr, + char *buf); +static ssize_t rt_store(struct class *class, + struct class_attribute *attr, + const char *buf, + size_t size); +static int tsl2580_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int tsl2580_remove(struct i2c_client *client); +static irqreturn_t tsl2580_isr(int irq, void *data); + +static const struct i2c_device_id tsl2580_i2c_id[] = { + {"tsl2580", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, tsl2580_i2c_id); + +static const struct of_device_id tsl2580_of_id[] = { + {.compatible = "gl,tsl2580"}, + {}, +}; +MODULE_DEVICE_TABLE(of, tsl2580_of_id); + +static struct i2c_driver tsl2580_i2c_driver = { + .driver = { + .name = "tsl2580", + .of_match_table = tsl2580_of_id, + .owner = THIS_MODULE, + }, + .probe = tsl2580_probe, + .remove = tsl2580_remove, + .id_table = tsl2580_i2c_id, +}; + +static struct class_attribute class_attr_who_am_i = + __ATTR(who_am_i, 00644, who_am_i_show, NULL); +static struct class_attribute class_attr_adc0 = + __ATTR(adc0, 00644, adc0_show, NULL); +static struct class_attribute class_attr_adc1 = + __ATTR(adc1, 00644, adc1_show, NULL); +static struct class_attribute class_attr_lux = + __ATTR(lux, 00644, lux_show, NULL); +static struct class_attribute class_attr_rt = + __ATTR(rt, 00644, rt_show, rt_store); + +static void tsl2580_isr_bh(struct work_struct *work) +{ + i2c_smbus_write_byte(mydev.client, TSL2580_CMD_REG | TSL2580_TRNS_SPECIAL | TSL2580_CMD_INT_CLR); + tsl2580_prepare_read(); +} + +static irqreturn_t tsl2580_isr(int irq, void *data) +{ + schedule_work(&mydev.isr_bh); + return IRQ_HANDLED; +} + +static s32 tsl2580_read_lux(struct tsl2580_dev *dev) +{ + s32 low, high; + low = tsl2580_read_adc0(dev); + if (low < 0) + return low; + high = tsl2580_read_adc1(dev); + if (high < 0) + return high; + return tsl2580_calc_lux(dev, low, high); +} + +static s32 tsl2580_calc_lux(struct tsl2580_dev *dev, s32 low, s32 high) +{ + u8 gain, ic; + u32 sc0, sc1, ratio; + + gain = ic = sc0 = sc1 = ratio = 0; + + gain = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ANALOG_REG); + if (gain < 0) + return gain; + gain = i2c_smbus_read_byte(dev->client); + if (gain < 0) + return gain; + ic = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_TIMING_REG); + if (ic < 0) + return ic; + ic = i2c_smbus_read_byte(dev->client); + + /* Have no idea why it's like this. + * Datasheet suggests particularly this formula. + */ + if (ic == TSL2580_TIMING_148) + sc0 = (1 << ADC_SCALE); + else + sc0 = (TSL2580_TIMING_148 << ADC_SCALE) / ic; + + switch(gain) { + case TSL2580_ANALOG_GAIN_1X: + sc1 = sc0; + break; + case TSL2580_ANALOG_GAIN_8X: + sc0 = sc0 >> 3; + sc1 = sc0; + break; + case TSL2580_ANALOG_GAIN_16X: + sc0 = sc0 >> 4; + sc1 = sc0; + break; + case TSL2580_ANALOG_GAIN_111X: + sc1 = sc0 / ADC1_SCALE_111X; + sc0 = sc0 / ADC0_SCALE_111X; + break; + } + low = (sc0 * low) >> ADC_SCALE; + high = (sc1 * high) >> ADC_SCALE; + if (low) + ratio = (high << (RATIO_SCALE + 1)) / low; + ratio = (ratio + 1) >> 1; + if (ratio <= ODFN_K1C) { + sc0 = ODFN_B1C; + sc1 = ODFN_M1C; + } else if (ratio <= ODFN_K2C) { + sc0 = ODFN_B2C; + sc1 = ODFN_M2C; + } else if (ratio <= ODFN_K3C) { + sc0 = ODFN_B3C; + sc1 = ODFN_M3C; + } else if (ratio <= ODFN_K4C) { + sc0 = ODFN_B4C; + sc1 = ODFN_M4C; + } else { + sc0 = ODFN_B5C; + sc1 = ODFN_M5C; + } + low = (low * sc0) - (high * sc1); + low += (1 << (LUX_SCALE - 1)); + + return low >> LUX_SCALE; +} + +static s32 tsl2580_read_adc1(struct tsl2580_dev *dev) +{ + s32 low, high; + + low = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_DATA1LOW_REG); + if (low < 0) + return low; + low = i2c_smbus_read_byte(dev->client); + if (low < 0) + return low; + high = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_DATA1HIGH_REG); + if (high < 0) + return high; + high = i2c_smbus_read_byte(dev->client); + if (high < 0) + return high; + /* FIXME: assumes little endian */ + return (low | (high << 8)); + +} + +static s32 tsl2580_read_adc0(struct tsl2580_dev *dev) +{ + s32 low, high; + + low = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_DATA0LOW_REG); + if (low < 0) + return low; + low = i2c_smbus_read_byte(dev->client); + if (low < 0) + return low; + high = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_DATA0HIGH_REG); + if (high < 0) + return high; + high = i2c_smbus_read_byte(dev->client); + if (high < 0) + return high; + /* FIXME: assumes little endian */ + return (low | (high << 8)); +} + +static int tsl2580_read_data(struct tsl2580_dev *dev) +{ + s32 res; + + res = tsl2580_read_adc0(dev); + if (IS_ERR_VALUE(res)) + return res; + mydev.data_adc0 = res; + res = tsl2580_read_adc1(dev); + if (IS_ERR_VALUE(res)) + return res; + mydev.data_adc1 = res; + res = tsl2580_calc_lux(dev, mydev.data_adc0, mydev.data_adc1); + if (IS_ERR_VALUE(res)) + return res; + mydev.data_lux = res; + return 0; +} + +static int tsl2580_read_task(void *data) +{ + int ret; + + while (!atomic_read(&mydev.thread_stop)) { + wait_event(mydev.data_access_queue, atomic_read(&mydev.data_access_state)); + if (atomic_read(&mydev.thread_stop)) + break; + mutex_lock(&mydev.ts_access); + ktime_get_real_ts64(&mydev.read_ts); + mutex_unlock(&mydev.ts_access); + + mutex_lock(&mydev.data_access_mutex); + ret = tsl2580_read_data(&mydev); + mutex_unlock(&mydev.data_access_mutex); + if (IS_ERR_VALUE(ret)) + return ret; + + complete_all(&mydev.data_access_completion); + atomic_set(&mydev.data_access_state, 0); + } + return 0; +} + +static int __must_check tsl2580_default_config(struct tsl2580_dev *dev) +{ + int ret; + + if (!dev->client) + return -EINVAL; + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG| TSL2580_TRNS_BLOCK | TSL2580_CTRL_REG); + if (IS_ERR_VALUE(ret)) + return ret; + /* Enable TSL2580 */ + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CTRL_POWER | TSL2580_CTRL_ADC_EN); + if (IS_ERR_VALUE(ret)) + return ret; + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_TIMING_REG); + if (IS_ERR_VALUE(ret)) + return ret; + /* 148 integration cycles by default */ + ret = i2c_smbus_write_byte(dev->client, TSL2580_TIMING_148); + if (IS_ERR_VALUE(ret)) + return ret; + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ANALOG_REG); + if (IS_ERR_VALUE(ret)) + return ret; + /* 16x analog gain by default */ + ret = i2c_smbus_write_byte(dev->client, TSL2580_ANALOG_GAIN_16X); + if (IS_ERR_VALUE(ret)) + return ret; + /* Interrupt config */ + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_INT_REG); + if (IS_ERR_VALUE(ret)) + return ret; + ret = i2c_smbus_write_byte(dev->client, + TSL2580_INT_CTRL_LEVEL | TSL2580_INT_PRST_TH); + if (IS_ERR_VALUE(ret)) + return ret; + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_INT_THLLOW_REG); + if (IS_ERR_VALUE(ret)) + return ret; + ret = i2c_smbus_write_byte(dev->client, INT_LOW_TH & 0xFF); + if (IS_ERR_VALUE(ret)) + return ret; + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_INT_THLHIGH_REG); + if (IS_ERR_VALUE(ret)) + return ret; + ret = i2c_smbus_write_byte(dev->client, (INT_LOW_TH >> 8)); + if (IS_ERR_VALUE(ret)) + return ret; + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_INT_THHLOW_REG); + if (IS_ERR_VALUE(ret)) + return ret; + ret = i2c_smbus_write_byte(dev->client, (INT_HIGH_TH & 0xFF)); + if (IS_ERR_VALUE(ret)) + return ret; + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_INT_THHHIGH_REG); + if (IS_ERR_VALUE(ret)) + return ret; + ret = i2c_smbus_write_byte(dev->client, (INT_HIGH_TH >> 8)); + if (IS_ERR_VALUE(ret)) + return ret; + + return 0; +} + +static int tsl2580_init_sysfs(void) +{ + int ret; + + tsl2580_class = class_create(THIS_MODULE, TSL2580_CLASS_NAME); + if (IS_ERR(tsl2580_class)) { + ret = PTR_ERR(tsl2580_class); + pr_err("tsl2580: failed to create a class; error %d\n", ret); + goto err; + } + ret = class_create_file(tsl2580_class, &class_attr_who_am_i); + if (IS_ERR_VALUE(ret)) { + pr_err("tsl2580: error %d creating a who_am_i attribute\n", ret); + goto err; + } + ret = class_create_file(tsl2580_class, &class_attr_adc0); + if (IS_ERR_VALUE(ret)) { + pr_err("tsl2580: error %d creating an adc0 attribute\n", ret); + goto err; + } + ret = class_create_file(tsl2580_class, &class_attr_adc1); + if (IS_ERR_VALUE(ret)) { + pr_err("tsl2580: error %d creating an adc1 attribute\n", ret); + goto err; + } + ret = class_create_file(tsl2580_class, &class_attr_lux); + if (IS_ERR_VALUE(ret)) { + pr_err("tsl2580: error %d creating a lux attribute\n", ret); + goto err; + } + ret = class_create_file(tsl2580_class, &class_attr_rt); + if (IS_ERR_VALUE(ret)) { + pr_err("tsl2580: error %d creating rt attribute\n", ret); + goto err; + } + + return 0; +err: + class_remove_file(tsl2580_class, &class_attr_who_am_i); + class_remove_file(tsl2580_class, &class_attr_adc0); + class_remove_file(tsl2580_class, &class_attr_adc1); + class_remove_file(tsl2580_class, &class_attr_lux); + class_destroy(tsl2580_class); + return ret; +} + +static int tsl2580_probe(struct i2c_client *client, + const struct i2c_device_id *device_id) +{ + int ret, irq_n; + u8 id; + + pr_info("tsl2580: i2c client address is 0x%X\n", client->addr); + + mydev.client = client; + ret = i2c_smbus_write_byte(mydev.client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ID_REG); + if (IS_ERR_VALUE(ret)) { + pr_err("tsl2580: error %d writing to the device\n", ret); + return ret; + } + id = i2c_smbus_read_byte(mydev.client); + id &= 0xF0; + if (id != TSL2580_LOW_ID && id != TSL2581_LOW_ID) { + pr_warn("tsl2580: wrong device id\n"); + return -EINVAL; + } + mydev.dev_id = id; + ret = tsl2580_default_config(&mydev); + if (IS_ERR_VALUE(ret)) { + pr_err("tsl2580: failed to configure the device\n"); + return ret; + } + ret = tsl2580_init_sysfs(); + if (IS_ERR_VALUE(ret)) { + pr_err("tsl2580: error %d initializing sysfs\n", ret); + return ret; + } + INIT_WORK(&mydev.isr_bh, tsl2580_isr_bh); + mutex_init(&mydev.data_access_mutex); + mutex_init(&mydev.ts_access); + init_completion(&mydev.data_access_completion); + init_waitqueue_head(&mydev.data_access_queue); + atomic_set(&mydev.data_access_state, 0); + atomic_set(&mydev.thread_stop, 0); + mydev.read_data_task = kthread_run(tsl2580_read_task, NULL, "tsl2580_read_thread"); + read_threashold = jiffies_to_nsecs(read_threashold); + + /* IRQ binding to specific GPIO */ + devm_gpio_request_one(&client->dev, + INT_GPIO, GPIOF_ACTIVE_LOW | GPIOF_OPEN_DRAIN, "INT"); + gpio_direction_input(INT_GPIO); + irq_n = gpio_to_irq(INT_GPIO); + if (IS_ERR_VALUE(irq_n)) { + pr_err("tsl2580: error %d getting irq for gpio\n", ret); + return ret; + } + ret = devm_request_irq(&client->dev, irq_n, tsl2580_isr, + IRQF_TRIGGER_FALLING, "tsl2580", client); + if (IS_ERR_VALUE(ret)) { + pr_err("tsl2580: error %d requesting irq\n", ret); + return ret; + } + return 0; +} + +static int tsl2580_remove(struct i2c_client *client) +{ + pr_info("tsl2580: remove start\n"); + if (mydev.read_data_task) { + atomic_set(&mydev.thread_stop, 1); + atomic_set(&mydev.data_access_state, 1); + wake_up(&mydev.data_access_queue); + } + i2c_smbus_write_byte(client, TSL2580_CMD_REG | TSL2580_TRNS_SPECIAL | TSL2580_CMD_INT_CLR); + pr_info("tsl2580: remove end\n"); + + return 0; +} + + +static ssize_t who_am_i_show(struct class *class, + struct class_attribute *attr, + char *buf) +{ + char *type; + + if (mydev.dev_id == TSL2580_LOW_ID) + type = "2580\n"; + else if (mydev.dev_id == TSL2581_LOW_ID) + type = "2581\n"; + else + type = "Unknow device type\n"; + + return sprintf(buf, type); +} + +static void tsl2580_prepare_read(void) +{ + struct timespec64 ts; + u64 tmp; + + mutex_lock(&mydev.ts_access); + tmp = timespec64_to_ns(&mydev.read_ts); + mutex_unlock(&mydev.ts_access); + + ktime_get_real_ts64(&ts); + if (timespec64_to_ns(&ts) > (tmp + read_threashold)) { + if (atomic_add_unless(&mydev.data_access_state, 1, 1)) + wake_up(&mydev.data_access_queue); + wait_for_completion(&mydev.data_access_completion); + } +} + +static ssize_t adc0_show(struct class *class, + struct class_attribute *attr, + char *buf) +{ + tsl2580_prepare_read(); + return sprintf(buf, "%d\n", mydev.data_adc0); + +} + +static ssize_t adc1_show(struct class *class, + struct class_attribute *attr, + char *buf) +{ + tsl2580_prepare_read(); + return sprintf(buf, "%d\n", mydev.data_adc1); +} + +static ssize_t lux_show(struct class *class, + struct class_attribute *attr, + char *buf) +{ + tsl2580_prepare_read(); + return sprintf(buf, "%d\n", mydev.data_lux); +} + +static ssize_t rt_show(struct class *class, + struct class_attribute *attr, + char *buf) +{ + return sprintf(buf, "%llu\n", nsecs_to_jiffies64(read_threashold)); +} + +static ssize_t rt_store(struct class *class, + struct class_attribute *attr, + const char *buf, + size_t size) +{ + sscanf(buf, "%llu", &read_threashold); + read_threashold = jiffies_to_nsecs(read_threashold); + return size; +} + +static int __init tsl2580_init(void) +{ + int ret; + + ret = i2c_add_driver(&tsl2580_i2c_driver); + if (IS_ERR_VALUE(ret)) { + pr_err("tsl2580: error %d adding an i2c driver\n", ret); + return ret; + } + pr_info("tsl2580: i2c driver created\n"); + + pr_info("tsl2580: init succeded\n"); + + return 0; +} + +static void __exit tsl2580_exit(void) +{ + pr_info("tsl2580: enter exit\n"); + class_remove_file(tsl2580_class, &class_attr_who_am_i); + class_remove_file(tsl2580_class, &class_attr_adc0); + class_remove_file(tsl2580_class, &class_attr_adc1); + class_remove_file(tsl2580_class, &class_attr_lux); + class_remove_file(tsl2580_class, &class_attr_rt); + class_destroy(tsl2580_class); + i2c_del_driver(&tsl2580_i2c_driver); + pr_info("tsl2580: exit\n"); +} + +module_init(tsl2580_init); +module_exit(tsl2580_exit); + +MODULE_AUTHOR("Andrii Revva "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TSL2580 driver"); diff --git a/tsl2580/tsl2580.dts b/tsl2580/tsl2580.dts new file mode 100644 index 0000000..c787ff8 --- /dev/null +++ b/tsl2580/tsl2580.dts @@ -0,0 +1,22 @@ +/dts-v1/; +/plugin/; + +/ { + + fragment@0 { + target = <&i2c1>; + + __overlay__ { + #address-cells = < 1 >; + #size-cells = < 0 >; + tsl2580@39 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "gl,tsl2580"; + status = "ok"; + reg = <0x39>; + }; + }; + }; + +}; diff --git a/tsl2580/tsl2580_regs.h b/tsl2580/tsl2580_regs.h new file mode 100644 index 0000000..483741d --- /dev/null +++ b/tsl2580/tsl2580_regs.h @@ -0,0 +1,87 @@ +#ifndef __TSL2580_REGS_H +#define __TSL2580_REGS_H + +/* High 4 bits of device id */ +#define TSL2580_LOW_ID 0b10000000 +#define TSL2581_LOW_ID 0b10010000 + +/* TSL2580 registers */ +#define TSL2580_CMD_REG 0x80 +#define TSL2580_CTRL_REG 0x00 +#define TSL2580_TIMING_REG 0x01 +#define TSL2580_INT_REG 0x02 +#define TSL2580_INT_THLLOW_REG 0x03 +#define TSL2580_INT_THLHIGH_REG 0x04 +#define TSL2580_INT_THHLOW_REG 0x05 +#define TSL2580_INT_THHHIGH_REG 0x06 +#define TSL2580_ANALOG_REG 0x07 +#define TSL2580_ID_REG 0x12 +#define TSL2580_CONSTANT_REG 0x13 +#define TSL2580_DATA0LOW_REG 0x14 +#define TSL2580_DATA0HIGH_REG 0x15 +#define TSL2580_DATA1LOW_REG 0x16 +#define TSL2580_DATA1HIGH_REG 0x17 +#define TSL2580_TIMERLOW_REG 0x18 +#define TSL2580_TIMERHIGH_REG 0x19 + +/* Type of transactions */ +#define TSL2580_TRNS_BYTE 0x00 +#define TSL2580_TRNS_WORD 0x20 +#define TSL2580_TRNS_BLOCK 0x40 +#define TSL2580_TRNS_SPECIAL 0x60 + +/* Control register commands */ +#define TSL2580_CTRL_POWER 0x1 +#define TSL2580_CTRL_ADC_EN 0x2 +#define TSL2580_CTRL_ADC_VALID 0x10 +#define TSL2580_CTRL_ADC_INTR 0x20 + +/* Timing register integration cycles (must be in 2's complement) */ +#define TSL2580_TIMING_MANUAL 0 +#define TSL2580_TIMING_1 (u8)(~(1) + 1) +#define TSL2580_TIMING_2 (u8)(~(2) + 1) +#define TSL2580_TIMING_19 (u8)(~(19) + 1) +#define TSL2580_TIMING_37 (u8)(~(37) + 1) +#define TSL2580_TIMING_74 (u8)(~(74) + 1) +#define TSL2580_TIMING_148 (u8)(~(148) + 1) +#define TSL2580_TIMING_255 (u8)(~(255) + 1) + +/* Analog register gain options */ +#define TSL2580_ANALOG_GAIN_1X 0x0 +#define TSL2580_ANALOG_GAIN_8X 0x1 +#define TSL2580_ANALOG_GAIN_16X 0x2 +#define TSL2580_ANALOG_GAIN_111X 0x3 + +/* Interrupt register control select */ +#define TSL2580_INT_CTRL_DISABLE 0x00 +#define TSL2580_INT_CTRL_LEVEL (0b01 << 4) +#define TSL2580_INT_CTRL_SMB (0b10 << 4) +#define TSL2580_INT_CTRL_TEST (0b11 << 4) + +/* Interrupt register stop ADC integration on interrupt */ +#define TSL2580_INT_CTRL_INTR_STOP 1 << 6 + +/* Interrupt register persistance select */ +#define TSL2580_INT_PRST_ADC 0x00 +#define TSL2580_INT_PRST_TH 0x01 +#define TSL2580_INT_PRST_2 0x02 +#define TSL2580_INT_PRST_3 0x03 +#define TSL2580_INT_PRST_4 0x04 +#define TSL2580_INT_PRST_5 0x05 +#define TSL2580_INT_PRST_6 0x06 +#define TSL2580_INT_PRST_7 0x07 +#define TSL2580_INT_PRST_8 0x08 +#define TSL2580_INT_PRST_9 0x09 +#define TSL2580_INT_PRST_10 0x0A +#define TSL2580_INT_PRST_11 0x0B +#define TSL2580_INT_PRST_12 0x0C +#define TSL2580_INT_PRST_13 0x0D +#define TSL2580_INT_PRST_14 0x0E +#define TSL2580_INT_PRST_15 0x0F + +/* Command register special function */ +#define TSL2580_CMD_INT_CLR 0x01 +#define TSL2580_CMD_STOP_MI 0x02 +#define TSL2580_CMD_START_MI 0x03 + +#endif