From 47fbe4cc3a5370e8fb37ba4393b35c39fed71f57 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Tue, 11 Feb 2020 19:00:59 +0200 Subject: [PATCH 01/16] tsl2580: Add a simple driver for tsl2580 Driver consists of the source file, dts overlay and registers description. When the device is detected, it's initialized with a default configuration, that can be seen in the source file. It also has a single sysfs entry, which reads from the device its id, and prints what type of device is attached, if it's supported. Signed-off-by: Andrii Revva --- tsl2580/Makefile | 9 ++ tsl2580/tsl2580.c | 186 +++++++++++++++++++++++++++++++++++++++++ tsl2580/tsl2580.dts | 22 +++++ tsl2580/tsl2580_regs.h | 55 ++++++++++++ 4 files changed, 272 insertions(+) create mode 100644 tsl2580/Makefile create mode 100644 tsl2580/tsl2580.c create mode 100644 tsl2580/tsl2580.dts create mode 100644 tsl2580/tsl2580_regs.h 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..4b0411e --- /dev/null +++ b/tsl2580/tsl2580.c @@ -0,0 +1,186 @@ +#include +#include +#include +#include +#include + +#include "tsl2580_regs.h" + +#define TSL2580_CLASS_NAME "tsl2580" + +struct tsl2580_dev { + struct i2c_client *client; +}; + +static struct tsl2580_dev mydev; +static struct class *tsl2580_class; + +static ssize_t who_am_i_show(struct class *class, struct class_attribute *attr, char *buf); +static int tsl2580_probe(struct i2c_client *client, const struct i2c_device_id *id); +static int tsl2580_remove(struct i2c_client *client); + +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 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 (ret < 0) + return ret; + /* Enable TSL2580 */ + ret = i2c_smbus_write_byte(dev->client, TSL2580_CTRL_POWER | TSL2580_CTRL_ADC_EN); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte(dev->client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_TIMING_REG); + if (ret < 0) + return ret; + /* 148 integration cycles by default */ + ret = i2c_smbus_write_byte(dev->client, TSL2580_TIMING_148); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte(dev->client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ANALOG_REG); + if (ret < 0) + return ret; + /* 16x analog gain by default */ + ret = i2c_smbus_write_byte(dev->client, TSL2580_ANALOG_GAIN_16X); + if (ret < 0) + return ret; + + return 0; +} + +static int tsl2580_probe(struct i2c_client *client, + const struct i2c_device_id *device_id) +{ + int ret; + 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 (ret < 0) { + pr_err("tsl2580: error %d occured 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; + } + ret = tsl2580_default_config(&mydev); + if (ret < 0) { + pr_err("tsl2580: failed to configure the device\n"); + return ret; + } + return 0; +} + +static int tsl2580_remove(struct i2c_client *client) +{ + pr_info("tsl2580: remove\n"); + return 0; +} + + +static ssize_t who_am_i_show(struct class *class, struct class_attribute *attr, char *buf) +{ + char *type; + u8 id; + s32 ret; + + ret = i2c_smbus_write_byte(mydev.client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ID_REG); + if (ret < 0) { + pr_err("tsl2580: error %d occured writing to the device\n", ret); + return ret; + } + + id = i2c_smbus_read_byte(mydev.client); + id &= 0xF0; + if (id == TSL2580_LOW_ID) + type = "2580\n"; + else if (id == TSL2581_LOW_ID) + type = "2581\n"; + else + type = "Unknow device type\n"; + ret = sprintf(buf, type); + + return ret; +} + +static int __init tsl2580_init(void) +{ + int ret; + + ret = i2c_add_driver(&tsl2580_i2c_driver); + if (ret < 0) { + pr_err("tsl2580: failed to add an i2c driver; error %d\n", ret); + return ret; + } + pr_info("tsl2580: i2c driver created\n"); + + 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 %ld\n", PTR_ERR(tsl2580_class)); + goto err; + } + + ret = class_create_file(tsl2580_class, &class_attr_who_am_i); + if (ret < 0) { + pr_err("tsl2580: failed to create a who_am_i class attribute; error %d\n", ret); + goto err; + } + + pr_info("tsl2580: init succeded\n"); + + return 0; +err: + class_remove_file(tsl2580_class, &class_attr_who_am_i); + class_destroy(tsl2580_class); + return ret; +} + +static void __exit tsl2580_exit(void) +{ + pr_info("tsl2580: enter exit\n"); + class_remove_file(tsl2580_class, &class_attr_who_am_i); + 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..4819756 --- /dev/null +++ b/tsl2580/tsl2580_regs.h @@ -0,0 +1,55 @@ +#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_2X 0x1 +#define TSL2580_ANALOG_GAIN_16X 0x2 +#define TSL2580_ANALOG_GAIN_111X 0x3 + +#endif From 5bf9a5f566b02ce8af4e31f7f136eaaade1d599a Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Tue, 11 Feb 2020 20:13:32 +0200 Subject: [PATCH 02/16] tsl2580: Fix some code style issues Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index 4b0411e..3e0cbd7 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 #include #include #include @@ -15,8 +16,11 @@ struct tsl2580_dev { static struct tsl2580_dev mydev; static struct class *tsl2580_class; -static ssize_t who_am_i_show(struct class *class, struct class_attribute *attr, char *buf); -static int tsl2580_probe(struct i2c_client *client, const struct i2c_device_id *id); +static ssize_t who_am_i_show(struct class *class, + struct class_attribute *attr, + char *buf); +static int tsl2580_probe(struct i2c_client *client, + const struct i2c_device_id *id); static int tsl2580_remove(struct i2c_client *client); static const struct i2c_device_id tsl2580_i2c_id[] = { @@ -51,21 +55,25 @@ static int __must_check tsl2580_default_config(struct tsl2580_dev *dev) if (!dev->client) return -EINVAL; - ret = i2c_smbus_write_byte(dev->client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_CTRL_REG); + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG| TSL2580_TRNS_BLOCK | TSL2580_CTRL_REG); if (ret < 0) return ret; /* Enable TSL2580 */ - ret = i2c_smbus_write_byte(dev->client, TSL2580_CTRL_POWER | TSL2580_CTRL_ADC_EN); + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CTRL_POWER | TSL2580_CTRL_ADC_EN); if (ret < 0) return ret; - ret = i2c_smbus_write_byte(dev->client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_TIMING_REG); + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_TIMING_REG); if (ret < 0) return ret; /* 148 integration cycles by default */ ret = i2c_smbus_write_byte(dev->client, TSL2580_TIMING_148); if (ret < 0) return ret; - ret = i2c_smbus_write_byte(dev->client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ANALOG_REG); + ret = i2c_smbus_write_byte(dev->client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ANALOG_REG); if (ret < 0) return ret; /* 16x analog gain by default */ @@ -85,9 +93,10 @@ static int tsl2580_probe(struct i2c_client *client, 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); + ret = i2c_smbus_write_byte(mydev.client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ID_REG); if (ret < 0) { - pr_err("tsl2580: error %d occured writing to the device\n", ret); + pr_err("tsl2580: error %d writing to the device\n", ret); return ret; } id = i2c_smbus_read_byte(mydev.client); @@ -111,15 +120,18 @@ static int tsl2580_remove(struct i2c_client *client) } -static ssize_t who_am_i_show(struct class *class, struct class_attribute *attr, char *buf) +static ssize_t who_am_i_show(struct class *class, + struct class_attribute *attr, + char *buf) { char *type; u8 id; s32 ret; - ret = i2c_smbus_write_byte(mydev.client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ID_REG); + ret = i2c_smbus_write_byte(mydev.client, + TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ID_REG); if (ret < 0) { - pr_err("tsl2580: error %d occured writing to the device\n", ret); + pr_err("tsl2580: error %d writing to the device\n", ret); return ret; } @@ -142,7 +154,7 @@ static int __init tsl2580_init(void) ret = i2c_add_driver(&tsl2580_i2c_driver); if (ret < 0) { - pr_err("tsl2580: failed to add an i2c driver; error %d\n", ret); + pr_err("tsl2580: error %d adding an i2c driver\n", ret); return ret; } pr_info("tsl2580: i2c driver created\n"); @@ -150,13 +162,14 @@ static int __init tsl2580_init(void) 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 %ld\n", PTR_ERR(tsl2580_class)); + pr_err("tsl2580: failed to create a class; error %ld\n", + PTR_ERR(tsl2580_class)); goto err; } ret = class_create_file(tsl2580_class, &class_attr_who_am_i); if (ret < 0) { - pr_err("tsl2580: failed to create a who_am_i class attribute; error %d\n", ret); + pr_err("tsl2580: error %d creating a who_am_i attribute\n", ret); goto err; } From c60ffce00a1c869af949031e6e5816743cdbfef5 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Tue, 11 Feb 2020 21:28:25 +0200 Subject: [PATCH 03/16] tsl2580: Add attribute to read ADC0 channel Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index 3e0cbd7..eba8a75 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -16,9 +16,13 @@ struct tsl2580_dev { static struct tsl2580_dev mydev; static struct class *tsl2580_class; +static s32 tsl2580_read_adc0(struct tsl2580_dev *dev); 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 int tsl2580_probe(struct i2c_client *client, const struct i2c_device_id *id); static int tsl2580_remove(struct i2c_client *client); @@ -48,6 +52,30 @@ static struct i2c_driver tsl2580_i2c_driver = { 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 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 low; + high = i2c_smbus_read_byte(dev->client); + if (high < 0) + return high; + /* FIXME: assumes little endian */ + return (low | (high << 16)); +} static int __must_check tsl2580_default_config(struct tsl2580_dev *dev) { @@ -148,6 +176,22 @@ static ssize_t who_am_i_show(struct class *class, return ret; } +static ssize_t adc0_show(struct class *class, + struct class_attribute *attr, + char *buf) +{ + s32 res; + + res = tsl2580_read_adc0(&mydev); + if (res < 0) { + pr_err("tsl2580: error %d reading adc0\n", res); + return res; + } + res = sprintf(buf, "%d\n", res); + return res; +} + + static int __init tsl2580_init(void) { int ret; @@ -172,12 +216,18 @@ static int __init tsl2580_init(void) 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 (ret < 0) { + pr_err("tsl2580: error %d creating a adc0 attribute\n", ret); + goto err; + } pr_info("tsl2580: init succeded\n"); return 0; err: class_remove_file(tsl2580_class, &class_attr_who_am_i); + class_remove_file(tsl2580_class, &class_attr_adc0); class_destroy(tsl2580_class); return ret; } @@ -186,6 +236,7 @@ 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_destroy(tsl2580_class); i2c_del_driver(&tsl2580_i2c_driver); pr_info("tsl2580: exit\n"); From 3da0075e05900184ea9d51fde91fa7c58af35782 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Tue, 11 Feb 2020 21:44:01 +0200 Subject: [PATCH 04/16] tsl2580: Add an attribute to read ADC1 channel Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index eba8a75..96f3d0a 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -17,12 +17,16 @@ static struct tsl2580_dev mydev; static struct class *tsl2580_class; static s32 tsl2580_read_adc0(struct tsl2580_dev *dev); +static s32 tsl2580_read_adc1(struct tsl2580_dev *dev); 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 int tsl2580_probe(struct i2c_client *client, const struct i2c_device_id *id); static int tsl2580_remove(struct i2c_client *client); @@ -54,6 +58,31 @@ 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 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 low; + high = i2c_smbus_read_byte(dev->client); + if (high < 0) + return high; + /* FIXME: assumes little endian */ + return (low | (high << 16)); + +} static s32 tsl2580_read_adc0(struct tsl2580_dev *dev) { @@ -191,6 +220,22 @@ static ssize_t adc0_show(struct class *class, return res; } +static ssize_t adc1_show(struct class *class, + struct class_attribute *attr, + char *buf) +{ + s32 res; + + res = tsl2580_read_adc1(&mydev); + if (res < 0) { + pr_err("tsl2580: error %d reading adc0\n", res); + return res; + } + res = sprintf(buf, "%d\n", res); + return res; +} + + static int __init tsl2580_init(void) { @@ -218,9 +263,13 @@ static int __init tsl2580_init(void) } ret = class_create_file(tsl2580_class, &class_attr_adc0); if (ret < 0) { - pr_err("tsl2580: error %d creating a adc0 attribute\n", ret); + pr_err("tsl2580: error %d creating an adc0 attribute\n", ret); goto err; } + ret = class_create_file(tsl2580_class, &class_attr_adc1); + if (ret < 0) { + pr_err("tsl2580: error %d creating an adc1 attribute\n", ret); + } pr_info("tsl2580: init succeded\n"); @@ -228,6 +277,7 @@ static int __init tsl2580_init(void) 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_destroy(tsl2580_class); return ret; } @@ -237,6 +287,7 @@ 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_destroy(tsl2580_class); i2c_del_driver(&tsl2580_i2c_driver); pr_info("tsl2580: exit\n"); From a741c711ea96271f9f96f2b8780fdf4a6c534e72 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Wed, 12 Feb 2020 14:29:01 +0200 Subject: [PATCH 05/16] tsl2580: Add a lux attribute in the sysfs class It seems like a redundant thing, because to precicely calculate lux values from adc0 and adc1 it's beneficial to have floating point arithmetic. So it would be nice to pass this obligation to user space. The method to approximately calculate lux values comes from the datasheet and it is _absolutely_ arcane and obscure. Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 140 +++++++++++++++++++++++++++++++++++++++-- tsl2580/tsl2580_regs.h | 2 +- 2 files changed, 136 insertions(+), 6 deletions(-) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index 96f3d0a..d8c7f89 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -9,6 +9,32 @@ #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 */ + struct tsl2580_dev { struct i2c_client *client; }; @@ -18,6 +44,7 @@ static struct class *tsl2580_class; 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 ssize_t who_am_i_show(struct class *class, struct class_attribute *attr, char *buf); @@ -27,6 +54,9 @@ static ssize_t adc0_show(struct class *class, 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 int tsl2580_probe(struct i2c_client *client, const struct i2c_device_id *id); static int tsl2580_remove(struct i2c_client *client); @@ -60,6 +90,86 @@ 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 s32 tsl2580_read_lux(struct tsl2580_dev *dev) +{ + s32 low, high; + u8 gain, ic; + u32 sc0, sc1, ratio; + + low = high = gain = ic = sc0 = sc1 = ratio = 0; + + low = tsl2580_read_adc0(dev); + if (low < 0) + return low; + high = tsl2580_read_adc1(dev); + if (high < 0) + return high; + 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; + 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) { @@ -75,7 +185,7 @@ static s32 tsl2580_read_adc1(struct tsl2580_dev *dev) high = i2c_smbus_write_byte(dev->client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_DATA1HIGH_REG); if (high < 0) - return low; + return high; high = i2c_smbus_read_byte(dev->client); if (high < 0) return high; @@ -98,7 +208,7 @@ static s32 tsl2580_read_adc0(struct tsl2580_dev *dev) high = i2c_smbus_write_byte(dev->client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_DATA0HIGH_REG); if (high < 0) - return low; + return high; high = i2c_smbus_read_byte(dev->client); if (high < 0) return high; @@ -235,7 +345,21 @@ static ssize_t adc1_show(struct class *class, return res; } +static ssize_t lux_show(struct class *class, + struct class_attribute *attr, + char *buf) +{ + s32 res; + res = tsl2580_read_lux(&mydev); + if (res < 0) { + pr_err("tsl2580: error %d reading lux\n", res); + return res; + } + res = sprintf(buf, "%d\n", res); + + return res; +} static int __init tsl2580_init(void) { @@ -251,11 +375,9 @@ static int __init tsl2580_init(void) 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 %ld\n", - PTR_ERR(tsl2580_class)); + pr_err("tsl2580: failed to create a class; error %ld\n", ret); goto err; } - ret = class_create_file(tsl2580_class, &class_attr_who_am_i); if (ret < 0) { pr_err("tsl2580: error %d creating a who_am_i attribute\n", ret); @@ -269,6 +391,12 @@ static int __init tsl2580_init(void) ret = class_create_file(tsl2580_class, &class_attr_adc1); if (ret < 0) { pr_err("tsl2580: error %d creating an adc1 attribute\n", ret); + goto err; + } + ret = class_create_file(tsl2580_class, &class_attr_lux); + if (ret < 0) { + pr_err("tsl2580: error %d creating a lux attribute\n", ret); + goto err; } pr_info("tsl2580: init succeded\n"); @@ -278,6 +406,7 @@ static int __init tsl2580_init(void) 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; } @@ -288,6 +417,7 @@ static void __exit tsl2580_exit(void) 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); i2c_del_driver(&tsl2580_i2c_driver); pr_info("tsl2580: exit\n"); diff --git a/tsl2580/tsl2580_regs.h b/tsl2580/tsl2580_regs.h index 4819756..2af18de 100644 --- a/tsl2580/tsl2580_regs.h +++ b/tsl2580/tsl2580_regs.h @@ -48,7 +48,7 @@ /* Analog register gain options */ #define TSL2580_ANALOG_GAIN_1X 0x0 -#define TSL2580_ANALOG_GAIN_2X 0x1 +#define TSL2580_ANALOG_GAIN_8X 0x1 #define TSL2580_ANALOG_GAIN_16X 0x2 #define TSL2580_ANALOG_GAIN_111X 0x3 From 5a7d1a7a4d9a116c139baf4e9046d03948d6c533 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Wed, 12 Feb 2020 18:22:32 +0200 Subject: [PATCH 06/16] tsl2580: Add caching of read values Three additional fields were declared in the tsl2580 device struct to store previously read values. Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index d8c7f89..55826b5 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -37,6 +37,9 @@ struct tsl2580_dev { struct i2c_client *client; + u16 data_adc0; + u16 data_adc1; + u16 data_lux; }; static struct tsl2580_dev mydev; @@ -326,6 +329,7 @@ static ssize_t adc0_show(struct class *class, pr_err("tsl2580: error %d reading adc0\n", res); return res; } + mydev.data_adc0 = (u16)res; res = sprintf(buf, "%d\n", res); return res; } @@ -341,6 +345,7 @@ static ssize_t adc1_show(struct class *class, pr_err("tsl2580: error %d reading adc0\n", res); return res; } + mydev.data_adc1 = (u16)res; res = sprintf(buf, "%d\n", res); return res; } @@ -356,6 +361,7 @@ static ssize_t lux_show(struct class *class, pr_err("tsl2580: error %d reading lux\n", res); return res; } + mydev.data_lux = (u16)res; res = sprintf(buf, "%d\n", res); return res; From e1a586fc8e80e0cdcf0bafdbaf72e99d0647a8b2 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Fri, 14 Feb 2020 13:20:01 +0200 Subject: [PATCH 07/16] tsl2580: Make id cache when probing device Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index 55826b5..323f2f3 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -37,6 +37,7 @@ struct tsl2580_dev { struct i2c_client *client; + u8 dev_id; u16 data_adc0; u16 data_adc1; u16 data_lux; @@ -48,6 +49,7 @@ static struct class *tsl2580_class; 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_read_data(struct tsl2580_dev *dev); static ssize_t who_am_i_show(struct class *class, struct class_attribute *attr, char *buf); @@ -275,6 +277,7 @@ static int tsl2580_probe(struct i2c_client *client, pr_warn("tsl2580: wrong device id\n"); return -EINVAL; } + mydev.dev_id = id; ret = tsl2580_default_config(&mydev); if (ret < 0) { pr_err("tsl2580: failed to configure the device\n"); @@ -295,21 +298,11 @@ static ssize_t who_am_i_show(struct class *class, char *buf) { char *type; - u8 id; s32 ret; - - ret = i2c_smbus_write_byte(mydev.client, - TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ID_REG); - if (ret < 0) { - 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) + if (mydev.dev_id == TSL2580_LOW_ID) type = "2580\n"; - else if (id == TSL2581_LOW_ID) + else if (mydev.dev_id == TSL2581_LOW_ID) type = "2581\n"; else type = "Unknow device type\n"; @@ -381,7 +374,7 @@ static int __init tsl2580_init(void) 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 %ld\n", ret); + 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); From f45622a120b84afd11e8acc4edb82cac3de1377c Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Fri, 14 Feb 2020 13:46:20 +0200 Subject: [PATCH 08/16] tsl2580: Move device data read into one function Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 58 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index 323f2f3..a98f30c 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -49,7 +49,8 @@ static struct class *tsl2580_class; 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_read_data(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 ssize_t who_am_i_show(struct class *class, struct class_attribute *attr, char *buf); @@ -101,17 +102,22 @@ static struct class_attribute class_attr_lux = static s32 tsl2580_read_lux(struct tsl2580_dev *dev) { s32 low, high; - u8 gain, ic; - u32 sc0, sc1, ratio; - - low = high = gain = ic = sc0 = sc1 = ratio = 0; - 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) @@ -221,6 +227,25 @@ static s32 tsl2580_read_adc0(struct tsl2580_dev *dev) return (low | (high << 16)); } +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 __must_check tsl2580_default_config(struct tsl2580_dev *dev) { int ret; @@ -317,13 +342,12 @@ static ssize_t adc0_show(struct class *class, { s32 res; - res = tsl2580_read_adc0(&mydev); - if (res < 0) { + res = tsl2580_read_data(&mydev); + if (IS_ERR_VALUE(res)) { pr_err("tsl2580: error %d reading adc0\n", res); return res; } - mydev.data_adc0 = (u16)res; - res = sprintf(buf, "%d\n", res); + res = sprintf(buf, "%d\n", mydev.data_adc0); return res; } @@ -333,13 +357,12 @@ static ssize_t adc1_show(struct class *class, { s32 res; - res = tsl2580_read_adc1(&mydev); - if (res < 0) { + res = tsl2580_read_data(&mydev); + if (IS_ERR_VALUE(res)) { pr_err("tsl2580: error %d reading adc0\n", res); return res; } - mydev.data_adc1 = (u16)res; - res = sprintf(buf, "%d\n", res); + res = sprintf(buf, "%d\n", mydev.data_adc1); return res; } @@ -349,13 +372,12 @@ static ssize_t lux_show(struct class *class, { s32 res; - res = tsl2580_read_lux(&mydev); - if (res < 0) { + res = tsl2580_read_data(&mydev); + if (IS_ERR_VALUE(res)) { pr_err("tsl2580: error %d reading lux\n", res); return res; } - mydev.data_lux = (u16)res; - res = sprintf(buf, "%d\n", res); + res = sprintf(buf, "%d\n", mydev.data_lux); return res; } From 647d623b75d01c876e8c35b17a87fdfefe852d3e Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Fri, 14 Feb 2020 16:12:41 +0200 Subject: [PATCH 09/16] tsl2580: Move sysfs init in the probe function Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 87 ++++++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index a98f30c..947d7ec 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -51,6 +51,7 @@ 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_init_sysfs(void); static ssize_t who_am_i_show(struct class *class, struct class_attribute *attr, char *buf); @@ -281,6 +282,47 @@ static int __must_check tsl2580_default_config(struct tsl2580_dev *dev) 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 (ret < 0) { + 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 (ret < 0) { + pr_err("tsl2580: error %d creating an adc0 attribute\n", ret); + goto err; + } + ret = class_create_file(tsl2580_class, &class_attr_adc1); + if (ret < 0) { + pr_err("tsl2580: error %d creating an adc1 attribute\n", ret); + goto err; + } + ret = class_create_file(tsl2580_class, &class_attr_lux); + if (ret < 0) { + pr_err("tsl2580: error %d creating a lux 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) { @@ -308,6 +350,11 @@ static int tsl2580_probe(struct i2c_client *client, 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; + } return 0; } @@ -323,7 +370,6 @@ static ssize_t who_am_i_show(struct class *class, char *buf) { char *type; - s32 ret; if (mydev.dev_id == TSL2580_LOW_ID) type = "2580\n"; @@ -331,9 +377,8 @@ static ssize_t who_am_i_show(struct class *class, type = "2581\n"; else type = "Unknow device type\n"; - ret = sprintf(buf, type); - return ret; + return sprintf(buf, type); } static ssize_t adc0_show(struct class *class, @@ -387,49 +432,15 @@ static int __init tsl2580_init(void) int ret; ret = i2c_add_driver(&tsl2580_i2c_driver); - if (ret < 0) { + 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"); - 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 (ret < 0) { - 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 (ret < 0) { - pr_err("tsl2580: error %d creating an adc0 attribute\n", ret); - goto err; - } - ret = class_create_file(tsl2580_class, &class_attr_adc1); - if (ret < 0) { - pr_err("tsl2580: error %d creating an adc1 attribute\n", ret); - goto err; - } - ret = class_create_file(tsl2580_class, &class_attr_lux); - if (ret < 0) { - pr_err("tsl2580: error %d creating a lux attribute\n", ret); - goto err; - } - pr_info("tsl2580: init succeded\n"); 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 void __exit tsl2580_exit(void) From 40a3dce5a49dd1ef7269bce72bfd5a19a8e90733 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Sat, 15 Feb 2020 09:58:49 +0200 Subject: [PATCH 10/16] sync: Move interaction with device into thread Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 100 +++++++++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index 947d7ec..c0db85f 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include "tsl2580_regs.h" @@ -41,6 +44,11 @@ struct tsl2580_dev { 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; }; static struct tsl2580_dev mydev; @@ -51,6 +59,7 @@ 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, @@ -151,6 +160,7 @@ static s32 tsl2580_calc_lux(struct tsl2580_dev *dev, s32 low, s32 high) 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; @@ -202,7 +212,7 @@ static s32 tsl2580_read_adc1(struct tsl2580_dev *dev) if (high < 0) return high; /* FIXME: assumes little endian */ - return (low | (high << 16)); + return (low | (high << 8)); } @@ -225,7 +235,7 @@ static s32 tsl2580_read_adc0(struct tsl2580_dev *dev) if (high < 0) return high; /* FIXME: assumes little endian */ - return (low | (high << 16)); + return (low | (high << 8)); } static int tsl2580_read_data(struct tsl2580_dev *dev) @@ -247,6 +257,25 @@ static int tsl2580_read_data(struct tsl2580_dev *dev) 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; + + ret = tsl2580_read_data(&mydev); + 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; @@ -255,28 +284,28 @@ static int __must_check tsl2580_default_config(struct tsl2580_dev *dev) return -EINVAL; ret = i2c_smbus_write_byte(dev->client, TSL2580_CMD_REG| TSL2580_TRNS_BLOCK | TSL2580_CTRL_REG); - if (ret < 0) + if (IS_ERR_VALUE(ret)) return ret; /* Enable TSL2580 */ ret = i2c_smbus_write_byte(dev->client, TSL2580_CTRL_POWER | TSL2580_CTRL_ADC_EN); - if (ret < 0) + if (IS_ERR_VALUE(ret)) return ret; ret = i2c_smbus_write_byte(dev->client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_TIMING_REG); - if (ret < 0) + if (IS_ERR_VALUE(ret)) return ret; /* 148 integration cycles by default */ ret = i2c_smbus_write_byte(dev->client, TSL2580_TIMING_148); - if (ret < 0) + if (IS_ERR_VALUE(ret)) return ret; ret = i2c_smbus_write_byte(dev->client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ANALOG_REG); - if (ret < 0) + if (IS_ERR_VALUE(ret)) return ret; /* 16x analog gain by default */ ret = i2c_smbus_write_byte(dev->client, TSL2580_ANALOG_GAIN_16X); - if (ret < 0) + if (IS_ERR_VALUE(ret)) return ret; return 0; @@ -293,22 +322,22 @@ static int tsl2580_init_sysfs(void) goto err; } ret = class_create_file(tsl2580_class, &class_attr_who_am_i); - if (ret < 0) { + 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 (ret < 0) { + 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 (ret < 0) { + 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 (ret < 0) { + if (IS_ERR_VALUE(ret)) { pr_err("tsl2580: error %d creating a lux attribute\n", ret); goto err; } @@ -334,7 +363,7 @@ static int tsl2580_probe(struct i2c_client *client, mydev.client = client; ret = i2c_smbus_write_byte(mydev.client, TSL2580_CMD_REG | TSL2580_TRNS_BLOCK | TSL2580_ID_REG); - if (ret < 0) { + if (IS_ERR_VALUE(ret)) { pr_err("tsl2580: error %d writing to the device\n", ret); return ret; } @@ -346,7 +375,7 @@ static int tsl2580_probe(struct i2c_client *client, } mydev.dev_id = id; ret = tsl2580_default_config(&mydev); - if (ret < 0) { + if (IS_ERR_VALUE(ret)) { pr_err("tsl2580: failed to configure the device\n"); return ret; } @@ -355,12 +384,24 @@ static int tsl2580_probe(struct i2c_client *client, pr_err("tsl2580: error %d initializing sysfs\n", ret); return ret; } + 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"); return 0; } static int tsl2580_remove(struct i2c_client *client) { - pr_info("tsl2580: remove\n"); + 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); + } + pr_info("tsl2580: remove end\n"); + return 0; } @@ -386,13 +427,14 @@ static ssize_t adc0_show(struct class *class, char *buf) { s32 res; - - res = tsl2580_read_data(&mydev); - if (IS_ERR_VALUE(res)) { - pr_err("tsl2580: error %d reading adc0\n", res); - return res; + + if (atomic_read(&mydev.data_access_state) == 0) { + atomic_set(&mydev.data_access_state, 1); + wake_up(&mydev.data_access_queue); } + wait_for_completion(&mydev.data_access_completion); res = sprintf(buf, "%d\n", mydev.data_adc0); + return res; } @@ -402,12 +444,14 @@ static ssize_t adc1_show(struct class *class, { s32 res; - res = tsl2580_read_data(&mydev); - if (IS_ERR_VALUE(res)) { - pr_err("tsl2580: error %d reading adc0\n", res); - return res; + + if (atomic_read(&mydev.data_access_state) == 0) { + atomic_set(&mydev.data_access_state, 1); + wake_up(&mydev.data_access_queue); } + wait_for_completion(&mydev.data_access_completion); res = sprintf(buf, "%d\n", mydev.data_adc1); + return res; } @@ -417,11 +461,11 @@ static ssize_t lux_show(struct class *class, { s32 res; - res = tsl2580_read_data(&mydev); - if (IS_ERR_VALUE(res)) { - pr_err("tsl2580: error %d reading lux\n", res); - return res; + if (atomic_read(&mydev.data_access_state) == 0) { + atomic_set(&mydev.data_access_state, 1); + wake_up(&mydev.data_access_queue); } + wait_for_completion(&mydev.data_access_completion); res = sprintf(buf, "%d\n", mydev.data_lux); return res; From 170165759edf559a29586fbb2a701d62d1060ea7 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Sat, 15 Feb 2020 13:32:28 +0200 Subject: [PATCH 11/16] sync: Add protection from concurrent access A mutex is used to make protection. The mutex use is coarse, since this particular protection isn't really neccessary, because only one thread is writing data to structure, and other threads wait for completeion. Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index c0db85f..0d67113 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "tsl2580_regs.h" @@ -49,6 +50,7 @@ struct tsl2580_dev { atomic_t data_access_state; atomic_t thread_stop; struct completion data_access_completion; + struct mutex data_access_mutex; }; static struct tsl2580_dev mydev; @@ -266,7 +268,9 @@ static int tsl2580_read_task(void *data) if (atomic_read(&mydev.thread_stop)) break; + mutex_lock(&mydev.data_access_mutex); ret = tsl2580_read_data(&mydev); + mutex_unlock(&mydev.data_access_mutex); if (IS_ERR_VALUE(ret)) return ret; @@ -384,6 +388,7 @@ static int tsl2580_probe(struct i2c_client *client, pr_err("tsl2580: error %d initializing sysfs\n", ret); return ret; } + mutex_init(&mydev.data_access_mutex); init_completion(&mydev.data_access_completion); init_waitqueue_head(&mydev.data_access_queue); atomic_set(&mydev.data_access_state, 0); From f57048d294d93ca59cab6bc02dc612dbc751e64c Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Sat, 15 Feb 2020 16:20:15 +0200 Subject: [PATCH 12/16] sync: Limit interaction with device By default, the device can be accessed every second, although it can be changed with 'rt' attribute in sysfs. Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 98 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index 0d67113..e12dec8 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -8,6 +8,9 @@ #include #include #include +#include +#include +#include #include "tsl2580_regs.h" @@ -39,6 +42,8 @@ #define ODFN_B5C 0x0000 /* 0.00 * 2 ^ LUX_SCALE */ #define ODFN_M5C 0x0000 /* 0.00 * 2 ^ LUX_SCALE */ +static u64 read_threashold = HZ; + struct tsl2580_dev { struct i2c_client *client; u8 dev_id; @@ -51,11 +56,14 @@ struct tsl2580_dev { atomic_t thread_stop; struct completion data_access_completion; struct mutex data_access_mutex; + struct timespec64 read_ts; + struct mutex ts_access; }; static struct tsl2580_dev mydev; static struct class *tsl2580_class; +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); @@ -75,6 +83,13 @@ static ssize_t adc1_show(struct class *class, 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); @@ -110,6 +125,8 @@ 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 s32 tsl2580_read_lux(struct tsl2580_dev *dev) { @@ -267,6 +284,9 @@ static int tsl2580_read_task(void *data) 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); @@ -345,6 +365,11 @@ static int tsl2580_init_sysfs(void) 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: @@ -389,11 +414,13 @@ static int tsl2580_probe(struct i2c_client *client, return ret; } 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); return 0; } @@ -427,53 +454,65 @@ static ssize_t who_am_i_show(struct class *class, 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_read(&mydev.data_access_state) == 0) { + atomic_set(&mydev.data_access_state, 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) { - s32 res; - - if (atomic_read(&mydev.data_access_state) == 0) { - atomic_set(&mydev.data_access_state, 1); - wake_up(&mydev.data_access_queue); - } - wait_for_completion(&mydev.data_access_completion); - res = sprintf(buf, "%d\n", mydev.data_adc0); + tsl2580_prepare_read(); + return sprintf(buf, "%d\n", mydev.data_adc0); - return res; } static ssize_t adc1_show(struct class *class, struct class_attribute *attr, char *buf) { - s32 res; - - - if (atomic_read(&mydev.data_access_state) == 0) { - atomic_set(&mydev.data_access_state, 1); - wake_up(&mydev.data_access_queue); - } - wait_for_completion(&mydev.data_access_completion); - res = sprintf(buf, "%d\n", mydev.data_adc1); - - return res; + 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) { - s32 res; + tsl2580_prepare_read(); + return sprintf(buf, "%d\n", mydev.data_lux); +} - if (atomic_read(&mydev.data_access_state) == 0) { - atomic_set(&mydev.data_access_state, 1); - wake_up(&mydev.data_access_queue); - } - wait_for_completion(&mydev.data_access_completion); - res = sprintf(buf, "%d\n", mydev.data_lux); - - return res; +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) @@ -499,6 +538,7 @@ static void __exit tsl2580_exit(void) 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"); From bd38acaae676f5a2e896eb91e8c9360a7950b8b6 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Sat, 15 Feb 2020 18:12:21 +0200 Subject: [PATCH 13/16] irq: Update regs file to include irq config Signed-off-by: Andrii Revva --- tsl2580/tsl2580_regs.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tsl2580/tsl2580_regs.h b/tsl2580/tsl2580_regs.h index 2af18de..980f52a 100644 --- a/tsl2580/tsl2580_regs.h +++ b/tsl2580/tsl2580_regs.h @@ -52,4 +52,31 @@ #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_THL 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 + #endif From 16b0fd34dd8b1b00be10341836e11af458a18584 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Sat, 15 Feb 2020 18:25:10 +0200 Subject: [PATCH 14/16] irq: Add interrupt config for device Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 43 +++++++++++++++++++++++++++++++++++++++++- tsl2580/tsl2580_regs.h | 2 +- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index e12dec8..9fc3894 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -42,6 +42,10 @@ #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 500 +#define INT_HIGH_TH 10000 + static u64 read_threashold = HZ; struct tsl2580_dev { @@ -331,7 +335,44 @@ static int __must_check tsl2580_default_config(struct tsl2580_dev *dev) 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; } diff --git a/tsl2580/tsl2580_regs.h b/tsl2580/tsl2580_regs.h index 980f52a..3c1e493 100644 --- a/tsl2580/tsl2580_regs.h +++ b/tsl2580/tsl2580_regs.h @@ -63,7 +63,7 @@ /* Interrupt register persistance select */ #define TSL2580_INT_PRST_ADC 0x00 -#define TSL2580_INT_PRST_THL 0x01 +#define TSL2580_INT_PRST_TH 0x01 #define TSL2580_INT_PRST_2 0x02 #define TSL2580_INT_PRST_3 0x03 #define TSL2580_INT_PRST_4 0x04 From 444498d5d68ebd2f279e81b5c43db800b433f7a9 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Sun, 16 Feb 2020 13:05:56 +0200 Subject: [PATCH 15/16] irq: Add interrupt registration Interrupt handles is called only once, because the interrupt must be reset for the device, and it must be done in non-irq context. It must be deffered into either tasklet or workqueue. Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 33 +++++++++++++++++++++++++++++++-- tsl2580/tsl2580_regs.h | 5 +++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index 9fc3894..0e7c4c8 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -11,6 +11,10 @@ #include #include #include +#include +#include +#include +#include #include "tsl2580_regs.h" @@ -43,8 +47,10 @@ #define ODFN_M5C 0x0000 /* 0.00 * 2 ^ LUX_SCALE */ /* Random threashold values for interrupt */ -#define INT_LOW_TH 500 -#define INT_HIGH_TH 10000 +#define INT_LOW_TH 1000 +#define INT_HIGH_TH 1000 + +#define INT_GPIO 4 static u64 read_threashold = HZ; @@ -97,6 +103,7 @@ static ssize_t rt_store(struct class *class, 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}, @@ -132,6 +139,13 @@ static struct class_attribute class_attr_lux = static struct class_attribute class_attr_rt = __ATTR(rt, 00644, rt_show, rt_store); +static irqreturn_t tsl2580_isr(int irq, void *data) +{ + pr_info("tsl2580: interrupt\n"); + //i2c_smbus_write_byte(data, TSL2580_CMD_REG | TSL2580_TRNS_SPECIAL | TSL2580_CMD_INT_CLR); + return IRQ_HANDLED; +} + static s32 tsl2580_read_lux(struct tsl2580_dev *dev) { s32 low, high; @@ -427,6 +441,8 @@ static int tsl2580_probe(struct i2c_client *client, { int ret; u8 id; + int irq_n; + struct gpio_desc *gp; pr_info("tsl2580: i2c client address is 0x%X\n", client->addr); @@ -462,6 +478,19 @@ static int tsl2580_probe(struct i2c_client *client, 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); + 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; } diff --git a/tsl2580/tsl2580_regs.h b/tsl2580/tsl2580_regs.h index 3c1e493..483741d 100644 --- a/tsl2580/tsl2580_regs.h +++ b/tsl2580/tsl2580_regs.h @@ -79,4 +79,9 @@ #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 From b6b4697176f706591c4798c4488767bd12799bb2 Mon Sep 17 00:00:00 2001 From: Andrii Revva Date: Sun, 16 Feb 2020 16:11:51 +0200 Subject: [PATCH 16/16] irq: Add bottom half for interrupt handling Workqueue is used to deffer interrupt work. It uses the same thread to access device as sysfs intefaces. Signed-off-by: Andrii Revva --- tsl2580/tsl2580.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/tsl2580/tsl2580.c b/tsl2580/tsl2580.c index 0e7c4c8..b1ff2a5 100644 --- a/tsl2580/tsl2580.c +++ b/tsl2580/tsl2580.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "tsl2580_regs.h" @@ -47,8 +48,8 @@ #define ODFN_M5C 0x0000 /* 0.00 * 2 ^ LUX_SCALE */ /* Random threashold values for interrupt */ -#define INT_LOW_TH 1000 -#define INT_HIGH_TH 1000 +#define INT_LOW_TH 0 +#define INT_HIGH_TH 10000 #define INT_GPIO 4 @@ -68,11 +69,13 @@ struct tsl2580_dev { 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); @@ -139,10 +142,15 @@ static struct class_attribute class_attr_lux = 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) { - pr_info("tsl2580: interrupt\n"); - //i2c_smbus_write_byte(data, TSL2580_CMD_REG | TSL2580_TRNS_SPECIAL | TSL2580_CMD_INT_CLR); + schedule_work(&mydev.isr_bh); return IRQ_HANDLED; } @@ -439,10 +447,8 @@ static int tsl2580_init_sysfs(void) static int tsl2580_probe(struct i2c_client *client, const struct i2c_device_id *device_id) { - int ret; + int ret, irq_n; u8 id; - int irq_n; - struct gpio_desc *gp; pr_info("tsl2580: i2c client address is 0x%X\n", client->addr); @@ -470,6 +476,7 @@ static int tsl2580_probe(struct i2c_client *client, 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); @@ -478,7 +485,10 @@ static int tsl2580_probe(struct i2c_client *client, 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); - devm_gpio_request_one(&client->dev, INT_GPIO, GPIOF_ACTIVE_LOW | GPIOF_OPEN_DRAIN, "INT"); + + /* 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)) { @@ -502,6 +512,7 @@ static int tsl2580_remove(struct i2c_client *client) 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; @@ -535,10 +546,8 @@ static void tsl2580_prepare_read(void) ktime_get_real_ts64(&ts); if (timespec64_to_ns(&ts) > (tmp + read_threashold)) { - if (atomic_read(&mydev.data_access_state) == 0) { - atomic_set(&mydev.data_access_state, 1); + if (atomic_add_unless(&mydev.data_access_state, 1, 1)) wake_up(&mydev.data_access_queue); - } wait_for_completion(&mydev.data_access_completion); } }