From 92f0afb5b2be2e137ff223654af51f521dd74c3a Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Sun, 18 Sep 2016 15:42:15 -0700 Subject: iio: adc: ti-adc161s626: add regulator support Allow IIO_CHAN_INFO_SCALE and IIO_CHAN_INFO_OFFSET attributes for processing by checking voltage from a regulator. Signed-off-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/ti-adc161s626.txt | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/iio/adc/ti-adc161s626.txt b/Documentation/devicetree/bindings/iio/adc/ti-adc161s626.txt index 9ed2315781e4..3d25011f0c99 100644 --- a/Documentation/devicetree/bindings/iio/adc/ti-adc161s626.txt +++ b/Documentation/devicetree/bindings/iio/adc/ti-adc161s626.txt @@ -3,6 +3,7 @@ Required properties: - compatible: Should be "ti,adc141s626" or "ti,adc161s626" - reg: spi chip select number for the device + - vdda-supply: supply voltage to VDDA pin Recommended properties: - spi-max-frequency: Definition as per @@ -11,6 +12,7 @@ Recommended properties: Example: adc@0 { compatible = "ti,adc161s626"; + vdda-supply = <&vdda_fixed>; reg = <0>; spi-max-frequency = <4300000>; }; -- cgit v1.2.3 From 67e17300dc1d76091d2d513d6aa57e50af2c9648 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Sat, 24 Sep 2016 21:03:00 -0700 Subject: iio: potentiostat: add LMP91000 support Add support for the LMP91000 potentiostat which is used for chemical sensing applications. Signed-off-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- .../bindings/iio/potentiostat/lmp91000.txt | 30 ++ drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/potentiostat/Kconfig | 22 + drivers/iio/potentiostat/Makefile | 6 + drivers/iio/potentiostat/lmp91000.c | 446 +++++++++++++++++++++ 6 files changed, 506 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/potentiostat/lmp91000.txt create mode 100644 drivers/iio/potentiostat/Kconfig create mode 100644 drivers/iio/potentiostat/Makefile create mode 100644 drivers/iio/potentiostat/lmp91000.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/iio/potentiostat/lmp91000.txt b/Documentation/devicetree/bindings/iio/potentiostat/lmp91000.txt new file mode 100644 index 000000000000..b9b621e94cd7 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/potentiostat/lmp91000.txt @@ -0,0 +1,30 @@ +* Texas Instruments LMP91000 potentiostat + +http://www.ti.com/lit/ds/symlink/lmp91000.pdf + +Required properties: + + - compatible: should be "ti,lmp91000" + - reg: the I2C address of the device + - io-channels: the phandle of the iio provider + + - ti,external-tia-resistor: if the property ti,tia-gain-ohm is not defined this + needs to be set to signal that an external resistor value is being used. + +Optional properties: + + - ti,tia-gain-ohm: ohm value of the internal resistor for the transimpedance + amplifier. Must be 2750, 3500, 7000, 14000, 35000, 120000, or 350000 ohms. + + - ti,rload-ohm: ohm value of the internal resistor load applied to the gas + sensor. Must be 10, 33, 50, or 100 (default) ohms. + +Example: + +lmp91000@48 { + compatible = "ti,lmp91000"; + reg = <0x48>; + ti,tia-gain-ohm = <7500>; + ti,rload = <100>; + io-channels = <&adc>; +}; diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 6743b18194fb..a31a8cf2c330 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -87,6 +87,7 @@ if IIO_TRIGGER source "drivers/iio/trigger/Kconfig" endif #IIO_TRIGGER source "drivers/iio/potentiometer/Kconfig" +source "drivers/iio/potentiostat/Kconfig" source "drivers/iio/pressure/Kconfig" source "drivers/iio/proximity/Kconfig" source "drivers/iio/temperature/Kconfig" diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 87e4c4369e2f..2b6e2762a886 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -29,6 +29,7 @@ obj-y += light/ obj-y += magnetometer/ obj-y += orientation/ obj-y += potentiometer/ +obj-y += potentiostat/ obj-y += pressure/ obj-y += proximity/ obj-y += temperature/ diff --git a/drivers/iio/potentiostat/Kconfig b/drivers/iio/potentiostat/Kconfig new file mode 100644 index 000000000000..1e3baf2cc97d --- /dev/null +++ b/drivers/iio/potentiostat/Kconfig @@ -0,0 +1,22 @@ +# +# Potentiostat drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Digital potentiostats" + +config LMP91000 + tristate "Texas Instruments LMP91000 potentiostat driver" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_BUFFER_CB + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Texas Instruments + LMP91000 digital potentiostat chip. + + To compile this driver as a module, choose M here: the + module will be called lmp91000 + +endmenu diff --git a/drivers/iio/potentiostat/Makefile b/drivers/iio/potentiostat/Makefile new file mode 100644 index 000000000000..64d315ef4449 --- /dev/null +++ b/drivers/iio/potentiostat/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for industrial I/O potentiostat drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_LMP91000) += lmp91000.o diff --git a/drivers/iio/potentiostat/lmp91000.c b/drivers/iio/potentiostat/lmp91000.c new file mode 100644 index 000000000000..e22714365022 --- /dev/null +++ b/drivers/iio/potentiostat/lmp91000.c @@ -0,0 +1,446 @@ +/* + * lmp91000.c - Support for Texas Instruments digital potentiostats + * + * Copyright (C) 2016 Matt Ranostay + * + * 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. + * + * TODO: bias voltage + polarity control, and multiple chip support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LMP91000_REG_LOCK 0x01 +#define LMP91000_REG_TIACN 0x10 +#define LMP91000_REG_TIACN_GAIN_SHIFT 2 + +#define LMP91000_REG_REFCN 0x11 +#define LMP91000_REG_REFCN_EXT_REF 0x20 +#define LMP91000_REG_REFCN_50_ZERO 0x80 + +#define LMP91000_REG_MODECN 0x12 +#define LMP91000_REG_MODECN_3LEAD 0x03 +#define LMP91000_REG_MODECN_TEMP 0x07 + +#define LMP91000_DRV_NAME "lmp91000" + +static const int lmp91000_tia_gain[] = { 0, 2750, 3500, 7000, 14000, 35000, + 120000, 350000 }; + +static const int lmp91000_rload[] = { 10, 33, 50, 100 }; + +#define LMP91000_TEMP_BASE -40 + +static const u16 lmp91000_temp_lut[] = { + 1875, 1867, 1860, 1852, 1844, 1836, 1828, 1821, 1813, 1805, + 1797, 1789, 1782, 1774, 1766, 1758, 1750, 1742, 1734, 1727, + 1719, 1711, 1703, 1695, 1687, 1679, 1671, 1663, 1656, 1648, + 1640, 1632, 1624, 1616, 1608, 1600, 1592, 1584, 1576, 1568, + 1560, 1552, 1544, 1536, 1528, 1520, 1512, 1504, 1496, 1488, + 1480, 1472, 1464, 1456, 1448, 1440, 1432, 1424, 1415, 1407, + 1399, 1391, 1383, 1375, 1367, 1359, 1351, 1342, 1334, 1326, + 1318, 1310, 1302, 1293, 1285, 1277, 1269, 1261, 1253, 1244, + 1236, 1228, 1220, 1212, 1203, 1195, 1187, 1179, 1170, 1162, + 1154, 1146, 1137, 1129, 1121, 1112, 1104, 1096, 1087, 1079, + 1071, 1063, 1054, 1046, 1038, 1029, 1021, 1012, 1004, 996, + 987, 979, 971, 962, 954, 945, 937, 929, 920, 912, + 903, 895, 886, 878, 870, 861 }; + +static const struct regmap_config lmp91000_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +struct lmp91000_data { + struct regmap *regmap; + struct device *dev; + + struct iio_trigger *trig; + struct iio_cb_buffer *cb_buffer; + struct iio_channel *adc_chan; + + struct completion completion; + u8 chan_select; + + u32 buffer[4]; /* 64-bit data + 64-bit timestamp */ +}; + +static const struct iio_chan_spec lmp91000_channels[] = { + { /* chemical channel mV */ + .type = IIO_VOLTAGE, + .channel = 0, + .address = LMP91000_REG_MODECN_3LEAD, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), + { /* temperature channel mV */ + .type = IIO_TEMP, + .channel = 1, + .address = LMP91000_REG_MODECN_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, + }, +}; + +static int lmp91000_read(struct lmp91000_data *data, int channel, int *val) +{ + int state, ret; + + ret = regmap_read(data->regmap, LMP91000_REG_MODECN, &state); + if (ret) + return -EINVAL; + + ret = regmap_write(data->regmap, LMP91000_REG_MODECN, channel); + if (ret) + return -EINVAL; + + /* delay till first temperature reading is complete */ + if ((state != channel) && (channel == LMP91000_REG_MODECN_TEMP)) + usleep_range(3000, 4000); + + data->chan_select = channel != LMP91000_REG_MODECN_3LEAD; + + iio_trigger_poll_chained(data->trig); + + ret = wait_for_completion_timeout(&data->completion, HZ); + reinit_completion(&data->completion); + + if (!ret) + return -ETIMEDOUT; + + *val = data->buffer[data->chan_select]; + + return 0; +} + +static irqreturn_t lmp91000_buffer_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct lmp91000_data *data = iio_priv(indio_dev); + int ret, val; + + memset(data->buffer, 0, sizeof(data->buffer)); + + ret = lmp91000_read(data, LMP91000_REG_MODECN_3LEAD, &val); + if (!ret) { + data->buffer[0] = val; + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_get_time_ns(indio_dev)); + } + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int lmp91000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lmp91000_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: { + int ret = iio_channel_start_all_cb(data->cb_buffer); + + if (ret) + return ret; + + ret = lmp91000_read(data, chan->address, val); + + iio_channel_stop_all_cb(data->cb_buffer); + + if (ret) + return ret; + + if (mask == IIO_CHAN_INFO_PROCESSED) { + int tmp, i; + + ret = iio_convert_raw_to_processed(data->adc_chan, + *val, &tmp, 1); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(lmp91000_temp_lut); i++) + if (lmp91000_temp_lut[i] < tmp) + break; + + *val = (LMP91000_TEMP_BASE + i) * 1000; + } + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OFFSET: + return iio_read_channel_offset(data->adc_chan, val, val2); + case IIO_CHAN_INFO_SCALE: + return iio_read_channel_scale(data->adc_chan, val, val2); + } + + return -EINVAL; +} + +static const struct iio_info lmp91000_info = { + .driver_module = THIS_MODULE, + .read_raw = lmp91000_read_raw, +}; + +static int lmp91000_read_config(struct lmp91000_data *data) +{ + struct device *dev = data->dev; + struct device_node *np = dev->of_node; + unsigned int reg, val; + int i, ret; + + ret = of_property_read_u32(np, "ti,tia-gain-ohm", &val); + if (ret) { + if (of_property_read_bool(np, "ti,external-tia-resistor")) + val = 0; + else { + dev_err(dev, "no ti,tia-gain-ohm defined"); + return ret; + } + } + + ret = -EINVAL; + for (i = 0; i < ARRAY_SIZE(lmp91000_tia_gain); i++) { + if (lmp91000_tia_gain[i] == val) { + reg = i << LMP91000_REG_TIACN_GAIN_SHIFT; + ret = 0; + break; + } + } + + if (ret) { + dev_err(dev, "invalid ti,tia-gain-ohm %d\n", val); + return ret; + } + + ret = of_property_read_u32(np, "ti,rload-ohm", &val); + if (ret) { + val = 100; + dev_info(dev, "no ti,rload-ohm defined, default to %d\n", val); + } + + ret = -EINVAL; + for (i = 0; i < ARRAY_SIZE(lmp91000_rload); i++) { + if (lmp91000_rload[i] == val) { + reg |= i; + ret = 0; + break; + } + } + + if (ret) { + dev_err(dev, "invalid ti,rload-ohm %d\n", val); + return ret; + } + + regmap_write(data->regmap, LMP91000_REG_LOCK, 0); + regmap_write(data->regmap, LMP91000_REG_TIACN, reg); + regmap_write(data->regmap, LMP91000_REG_REFCN, LMP91000_REG_REFCN_EXT_REF + | LMP91000_REG_REFCN_50_ZERO); + regmap_write(data->regmap, LMP91000_REG_LOCK, 1); + + return 0; +} + +static int lmp91000_buffer_cb(const void *val, void *private) +{ + struct iio_dev *indio_dev = private; + struct lmp91000_data *data = iio_priv(indio_dev); + + data->buffer[data->chan_select] = *((int *)val); + complete_all(&data->completion); + + return 0; +} + +static const struct iio_trigger_ops lmp91000_trigger_ops = { + .owner = THIS_MODULE, +}; + + +static int lmp91000_buffer_preenable(struct iio_dev *indio_dev) +{ + struct lmp91000_data *data = iio_priv(indio_dev); + + return iio_channel_start_all_cb(data->cb_buffer); +} + +static int lmp91000_buffer_predisable(struct iio_dev *indio_dev) +{ + struct lmp91000_data *data = iio_priv(indio_dev); + + iio_channel_stop_all_cb(data->cb_buffer); + + return 0; +} + +static const struct iio_buffer_setup_ops lmp91000_buffer_setup_ops = { + .preenable = lmp91000_buffer_preenable, + .postenable = iio_triggered_buffer_postenable, + .predisable = lmp91000_buffer_predisable, +}; + +static int lmp91000_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct lmp91000_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &lmp91000_info; + indio_dev->channels = lmp91000_channels; + indio_dev->num_channels = ARRAY_SIZE(lmp91000_channels); + indio_dev->name = LMP91000_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + i2c_set_clientdata(client, indio_dev); + + data = iio_priv(indio_dev); + data->dev = dev; + data->regmap = devm_regmap_init_i2c(client, &lmp91000_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "regmap initialization failed.\n"); + return PTR_ERR(data->regmap); + } + + data->trig = devm_iio_trigger_alloc(data->dev, "%s-mux%d", + indio_dev->name, indio_dev->id); + if (!data->trig) { + dev_err(dev, "cannot allocate iio trigger.\n"); + return -ENOMEM; + } + + data->trig->ops = &lmp91000_trigger_ops; + data->trig->dev.parent = dev; + init_completion(&data->completion); + + ret = lmp91000_read_config(data); + if (ret) + return ret; + + ret = iio_trigger_set_immutable(iio_channel_cb_get_iio_dev(data->cb_buffer), + data->trig); + if (ret) { + dev_err(dev, "cannot set immutable trigger.\n"); + return ret; + } + + ret = iio_trigger_register(data->trig); + if (ret) { + dev_err(dev, "cannot register iio trigger.\n"); + return ret; + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &lmp91000_buffer_handler, + &lmp91000_buffer_setup_ops); + if (ret) + goto error_unreg_trigger; + + data->cb_buffer = iio_channel_get_all_cb(dev, &lmp91000_buffer_cb, + indio_dev); + + if (IS_ERR(data->cb_buffer)) { + if (PTR_ERR(data->cb_buffer) == -ENODEV) + ret = -EPROBE_DEFER; + else + ret = PTR_ERR(data->cb_buffer); + + goto error_unreg_buffer; + } + + data->adc_chan = iio_channel_cb_get_channels(data->cb_buffer); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_cb_buffer; + + return 0; + +error_unreg_cb_buffer: + iio_channel_release_all_cb(data->cb_buffer); + +error_unreg_buffer: + iio_triggered_buffer_cleanup(indio_dev); + +error_unreg_trigger: + iio_trigger_unregister(data->trig); + + return ret; +} + +static int lmp91000_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct lmp91000_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + iio_channel_stop_all_cb(data->cb_buffer); + iio_channel_release_all_cb(data->cb_buffer); + + iio_triggered_buffer_cleanup(indio_dev); + iio_trigger_unregister(data->trig); + + return 0; +} + +static const struct of_device_id lmp91000_of_match[] = { + { .compatible = "ti,lmp91000", }, + { }, +}; +MODULE_DEVICE_TABLE(of, lmp91000_of_match); + +static const struct i2c_device_id lmp91000_id[] = { + { "lmp91000", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, lmp91000_id); + +static struct i2c_driver lmp91000_driver = { + .driver = { + .name = LMP91000_DRV_NAME, + .of_match_table = of_match_ptr(lmp91000_of_match), + }, + .probe = lmp91000_probe, + .remove = lmp91000_remove, + .id_table = lmp91000_id, +}; +module_i2c_driver(lmp91000_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("LMP91000 digital potentiostat"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 1a8f324aa1f2237caef1c6633734785bbdcffeed Mon Sep 17 00:00:00 2001 From: William Breathitt Gray Date: Wed, 28 Sep 2016 13:59:49 -0400 Subject: iio: Implement counter channel type and info constants Quadrature encoders, such as rotary encoders and linear encoders, are devices which are capable of encoding the relative position and direction of motion of a shaft. This patch introduces several IIO constants for supporting quadrature encoder counter devices. IIO_COUNT: Current count (main data provided by the counter device) IIO_INDEX: Counter device index value Signed-off-by: William Breathitt Gray Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 18 ++++++++++++++++++ drivers/iio/industrialio-core.c | 2 ++ include/uapi/linux/iio/types.h | 2 ++ 3 files changed, 22 insertions(+) (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index fee35c00cc4e..b8f220f978dd 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -329,6 +329,7 @@ What: /sys/bus/iio/devices/iio:deviceX/in_pressure_scale What: /sys/bus/iio/devices/iio:deviceX/in_humidityrelative_scale What: /sys/bus/iio/devices/iio:deviceX/in_velocity_sqrt(x^2+y^2+z^2)_scale What: /sys/bus/iio/devices/iio:deviceX/in_illuminance_scale +What: /sys/bus/iio/devices/iio:deviceX/in_countY_scale KernelVersion: 2.6.35 Contact: linux-iio@vger.kernel.org Description: @@ -1579,3 +1580,20 @@ Contact: linux-iio@vger.kernel.org Description: Raw (unscaled no offset etc.) electric conductivity reading that can be processed to siemens per meter. + +What: /sys/bus/iio/devices/iio:deviceX/in_countY_raw +KernelVersion: 4.9 +Contact: linux-iio@vger.kernel.org +Description: + Raw counter device counts from channel Y. For quadrature + counters, multiplication by an available [Y]_scale results in + the counts of a single quadrature signal phase from channel Y. + +What: /sys/bus/iio/devices/iio:deviceX/in_indexY_raw +KernelVersion: 4.9 +Contact: linux-iio@vger.kernel.org +Description: + Raw counter device index value from channel Y. This attribute + provides an absolute positional reference (e.g. a pulse once per + revolution) which may be used to home positional systems as + required. diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index fc340ed3dca1..649725bc15c1 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -81,6 +81,8 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_PH] = "ph", [IIO_UVINDEX] = "uvindex", [IIO_ELECTRICALCONDUCTIVITY] = "electricalconductivity", + [IIO_COUNT] = "count", + [IIO_INDEX] = "index", }; static const char * const iio_modifier_names[] = { diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index 22e5e589a274..e54d14a7f876 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -40,6 +40,8 @@ enum iio_chan_type { IIO_PH, IIO_UVINDEX, IIO_ELECTRICALCONDUCTIVITY, + IIO_COUNT, + IIO_INDEX, }; enum iio_modifier { -- cgit v1.2.3 From 28e5d3bb0325e71ef9b53a9cb4242cdfb55fd8c5 Mon Sep 17 00:00:00 2001 From: William Breathitt Gray Date: Wed, 28 Sep 2016 14:00:01 -0400 Subject: iio: 104-quad-8: Add IIO support for the ACCES 104-QUAD-8 The ACCES 104-QUAD-8 is a general purpose quadrature encoder counter/interface board. The 104-QUAD-8 is capable of monitoring the outputs of eight encoders via four on-board LSI/CSI LS7266R1 24-bit dual-axis quadrature counter chips. Core functions handled by the LS7266R1, such as direction and total count, are available. Performing a write to a counter's IIO_CHAN_INFO_RAW sets the counter and also clears the counter's respective error flag. Although the counters have a 25-bit range, only the lower 24 bits may be set, either directly or via a counter's preset attribute. Interrupts are not supported by this driver. This driver adds IIO support for the ACCES 104-QUAD-8 and ACCES 104-QUAD-4. The base port addresses for the devices may be configured via the base array module parameter. Signed-off-by: William Breathitt Gray Signed-off-by: Jonathan Cameron --- .../ABI/testing/sysfs-bus-iio-counter-104-quad-8 | 125 +++++ MAINTAINERS | 6 + drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/counter/104-quad-8.c | 593 +++++++++++++++++++++ drivers/iio/counter/Kconfig | 24 + drivers/iio/counter/Makefile | 7 + 7 files changed, 757 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-counter-104-quad-8 create mode 100644 drivers/iio/counter/104-quad-8.c create mode 100644 drivers/iio/counter/Kconfig create mode 100644 drivers/iio/counter/Makefile (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-counter-104-quad-8 b/Documentation/ABI/testing/sysfs-bus-iio-counter-104-quad-8 new file mode 100644 index 000000000000..ba676520b953 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-counter-104-quad-8 @@ -0,0 +1,125 @@ +What: /sys/bus/iio/devices/iio:deviceX/in_count_count_direction_available +What: /sys/bus/iio/devices/iio:deviceX/in_count_count_mode_available +What: /sys/bus/iio/devices/iio:deviceX/in_count_noise_error_available +What: /sys/bus/iio/devices/iio:deviceX/in_count_quadrature_mode_available +What: /sys/bus/iio/devices/iio:deviceX/in_index_index_polarity_available +What: /sys/bus/iio/devices/iio:deviceX/in_index_synchronous_mode_available +KernelVersion: 4.9 +Contact: linux-iio@vger.kernel.org +Description: + Discrete set of available values for the respective counter + configuration are listed in this file. + +What: /sys/bus/iio/devices/iio:deviceX/in_countY_count_direction +KernelVersion: 4.9 +Contact: linux-iio@vger.kernel.org +Description: + Read-only attribute that indicates whether the counter for + channel Y is counting up or down. + +What: /sys/bus/iio/devices/iio:deviceX/in_countY_count_mode +KernelVersion: 4.9 +Contact: linux-iio@vger.kernel.org +Description: + Count mode for channel Y. Four count modes are available: + normal, range limit, non-recycle, and modulo-n. The preset value + for channel Y is used by the count mode where required. + + Normal: + Counting is continuous in either direction. + + Range Limit: + An upper or lower limit is set, mimicking limit switches + in the mechanical counterpart. The upper limit is set to + the preset value, while the lower limit is set to 0. The + counter freezes at count = preset when counting up, and + at count = 0 when counting down. At either of these + limits, the counting is resumed only when the count + direction is reversed. + + Non-recycle: + Counter is disabled whenever a 24-bit count overflow or + underflow takes place. The counter is re-enabled when a + new count value is loaded to the counter via a preset + operation or write to raw. + + Modulo-N: + A count boundary is set between 0 and the preset value. + The counter is reset to 0 at count = preset when + counting up, while the counter is set to the preset + value at count = 0 when counting down; the counter does + not freeze at the bundary points, but counts + continuously throughout. + +What: /sys/bus/iio/devices/iio:deviceX/in_countY_noise_error +KernelVersion: 4.9 +Contact: linux-iio@vger.kernel.org +Description: + Read-only attribute that indicates whether excessive noise is + present at the channel Y count inputs in quadrature clock mode; + irrelevant in non-quadrature clock mode. + +What: /sys/bus/iio/devices/iio:deviceX/in_countY_preset +KernelVersion: 4.9 +Contact: linux-iio@vger.kernel.org +Description: + If the counter device supports preset registers, the preset + count for channel Y is provided by this attribute. + +What: /sys/bus/iio/devices/iio:deviceX/in_countY_quadrature_mode +KernelVersion: 4.9 +Contact: linux-iio@vger.kernel.org +Description: + Configure channel Y counter for non-quadrature or quadrature + clock mode. Selecting non-quadrature clock mode will disable + synchronous load mode. In quadrature clock mode, the channel Y + scale attribute selects the encoder phase division (scale of 1 + selects full-cycle, scale of 0.5 selects half-cycle, scale of + 0.25 selects quarter-cycle) processed by the channel Y counter. + + Non-quadrature: + The filter and decoder circuit are bypassed. Encoder A + input serves as the count input and B as the UP/DOWN + direction control input, with B = 1 selecting UP Count + mode and B = 0 selecting Down Count mode. + + Quadrature: + Encoder A and B inputs are digitally filtered and + decoded for UP/DN clock. + +What: /sys/bus/iio/devices/iio:deviceX/in_countY_set_to_preset_on_index +KernelVersion: 4.9 +Contact: linux-iio@vger.kernel.org +Description: + Whether to set channel Y counter with channel Y preset value + when channel Y index input is active, or continuously count. + Valid attribute values are boolean. + +What: /sys/bus/iio/devices/iio:deviceX/in_indexY_index_polarity +KernelVersion: 4.9 +Contact: linux-iio@vger.kernel.org +Description: + Active level of channel Y index input; irrelevant in + non-synchronous load mode. + +What: /sys/bus/iio/devices/iio:deviceX/in_indexY_synchronous_mode +KernelVersion: 4.9 +Contact: linux-iio@vger.kernel.org +Description: + Configure channel Y counter for non-synchronous or synchronous + load mode. Synchronous load mode cannot be selected in + non-quadrature clock mode. + + Non-synchronous: + A logic low level is the active level at this index + input. The index function (as enabled via + set_to_preset_on_index) is performed directly on the + active level of the index input. + + Synchronous: + Intended for interfacing with encoder Index output in + quadrature clock mode. The active level is configured + via index_polarity. The index function (as enabled via + set_to_preset_on_index) is performed synchronously with + the quadrature clock on the active level of the index + input. diff --git a/MAINTAINERS b/MAINTAINERS index e013c2be6d23..982dff301045 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -255,6 +255,12 @@ L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-104-idio-16.c +ACCES 104-QUAD-8 IIO DRIVER +M: William Breathitt Gray +L: linux-iio@vger.kernel.org +S: Maintained +F: drivers/iio/counter/104-quad-8.c + ACENIC DRIVER M: Jes Sorensen L: linux-acenic@sunsite.dk diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index a31a8cf2c330..a918270d6f54 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -73,6 +73,7 @@ source "drivers/iio/adc/Kconfig" source "drivers/iio/amplifiers/Kconfig" source "drivers/iio/chemical/Kconfig" source "drivers/iio/common/Kconfig" +source "drivers/iio/counter/Kconfig" source "drivers/iio/dac/Kconfig" source "drivers/iio/dummy/Kconfig" source "drivers/iio/frequency/Kconfig" diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 2b6e2762a886..33fa4026f92c 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -18,6 +18,7 @@ obj-y += amplifiers/ obj-y += buffer/ obj-y += chemical/ obj-y += common/ +obj-y += counter/ obj-y += dac/ obj-y += dummy/ obj-y += gyro/ diff --git a/drivers/iio/counter/104-quad-8.c b/drivers/iio/counter/104-quad-8.c new file mode 100644 index 000000000000..2d2ee353dde7 --- /dev/null +++ b/drivers/iio/counter/104-quad-8.c @@ -0,0 +1,593 @@ +/* + * IIO driver for the ACCES 104-QUAD-8 + * Copyright (C) 2016 William Breathitt Gray + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * 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. + * + * This driver supports the ACCES 104-QUAD-8 and ACCES 104-QUAD-4. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define QUAD8_EXTENT 32 + +static unsigned int base[max_num_isa_dev(QUAD8_EXTENT)]; +static unsigned int num_quad8; +module_param_array(base, uint, &num_quad8, 0); +MODULE_PARM_DESC(base, "ACCES 104-QUAD-8 base addresses"); + +#define QUAD8_NUM_COUNTERS 8 + +/** + * struct quad8_iio - IIO device private data structure + * @preset: array of preset values + * @count_mode: array of count mode configurations + * @quadrature_mode: array of quadrature mode configurations + * @quadrature_scale: array of quadrature mode scale configurations + * @ab_enable: array of A and B inputs enable configurations + * @preset_enable: array of set_to_preset_on_index attribute configurations + * @synchronous_mode: array of index function synchronous mode configurations + * @index_polarity: array of index function polarity configurations + * @base: base port address of the IIO device + */ +struct quad8_iio { + unsigned int preset[QUAD8_NUM_COUNTERS]; + unsigned int count_mode[QUAD8_NUM_COUNTERS]; + unsigned int quadrature_mode[QUAD8_NUM_COUNTERS]; + unsigned int quadrature_scale[QUAD8_NUM_COUNTERS]; + unsigned int ab_enable[QUAD8_NUM_COUNTERS]; + unsigned int preset_enable[QUAD8_NUM_COUNTERS]; + unsigned int synchronous_mode[QUAD8_NUM_COUNTERS]; + unsigned int index_polarity[QUAD8_NUM_COUNTERS]; + unsigned int base; +}; + +static int quad8_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel; + unsigned int flags; + unsigned int borrow; + unsigned int carry; + int i; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_INDEX) { + *val = !!(inb(priv->base + 0x16) & BIT(chan->channel)); + return IIO_VAL_INT; + } + + flags = inb(base_offset); + borrow = flags & BIT(0); + carry = !!(flags & BIT(1)); + + /* Borrow XOR Carry effectively doubles count range */ + *val = (borrow ^ carry) << 24; + + /* Reset Byte Pointer; transfer Counter to Output Latch */ + outb(0x11, base_offset + 1); + + for (i = 0; i < 3; i++) + *val |= (unsigned int)inb(base_offset) << (8 * i); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_ENABLE: + *val = priv->ab_enable[chan->channel]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 1; + *val2 = priv->quadrature_scale[chan->channel]; + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static int quad8_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel; + int i; + unsigned int ior_cfg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_INDEX) + return -EINVAL; + + /* Only 24-bit values are supported */ + if ((unsigned int)val > 0xFFFFFF) + return -EINVAL; + + /* Reset Byte Pointer */ + outb(0x01, base_offset + 1); + + /* Counter can only be set via Preset Register */ + for (i = 0; i < 3; i++) + outb(val >> (8 * i), base_offset); + + /* Transfer Preset Register to Counter */ + outb(0x08, base_offset + 1); + + /* Reset Byte Pointer */ + outb(0x01, base_offset + 1); + + /* Set Preset Register back to original value */ + val = priv->preset[chan->channel]; + for (i = 0; i < 3; i++) + outb(val >> (8 * i), base_offset); + + /* Reset Borrow, Carry, Compare, and Sign flags */ + outb(0x02, base_offset + 1); + /* Reset Error flag */ + outb(0x06, base_offset + 1); + + return 0; + case IIO_CHAN_INFO_ENABLE: + /* only boolean values accepted */ + if (val < 0 || val > 1) + return -EINVAL; + + priv->ab_enable[chan->channel] = val; + + ior_cfg = val | priv->preset_enable[chan->channel] << 1; + + /* Load I/O control configuration */ + outb(0x40 | ior_cfg, base_offset); + + return 0; + case IIO_CHAN_INFO_SCALE: + /* Quadrature scaling only available in quadrature mode */ + if (!priv->quadrature_mode[chan->channel] && (val2 || val != 1)) + return -EINVAL; + + /* Only three gain states (1, 0.5, 0.25) */ + if (val == 1 && !val2) + priv->quadrature_scale[chan->channel] = 0; + else if (!val) + switch (val2) { + case 500000: + priv->quadrature_scale[chan->channel] = 1; + break; + case 250000: + priv->quadrature_scale[chan->channel] = 2; + break; + default: + return -EINVAL; + } + else + return -EINVAL; + + return 0; + } + + return -EINVAL; +} + +static const struct iio_info quad8_info = { + .driver_module = THIS_MODULE, + .read_raw = quad8_read_raw, + .write_raw = quad8_write_raw +}; + +static ssize_t quad8_read_preset(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", priv->preset[chan->channel]); +} + +static ssize_t quad8_write_preset(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, const char *buf, size_t len) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel; + unsigned int preset; + int ret; + int i; + + ret = kstrtouint(buf, 0, &preset); + if (ret) + return ret; + + /* Only 24-bit values are supported */ + if (preset > 0xFFFFFF) + return -EINVAL; + + priv->preset[chan->channel] = preset; + + /* Reset Byte Pointer */ + outb(0x01, base_offset + 1); + + /* Set Preset Register */ + for (i = 0; i < 3; i++) + outb(preset >> (8 * i), base_offset); + + return len; +} + +static ssize_t quad8_read_set_to_preset_on_index(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + priv->preset_enable[chan->channel]); +} + +static ssize_t quad8_write_set_to_preset_on_index(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel; + bool preset_enable; + int ret; + unsigned int ior_cfg; + + ret = kstrtobool(buf, &preset_enable); + if (ret) + return ret; + + priv->preset_enable[chan->channel] = preset_enable; + + ior_cfg = priv->ab_enable[chan->channel] | + (unsigned int)preset_enable << 1; + + /* Load I/O control configuration to Input / Output Control Register */ + outb(0x40 | ior_cfg, base_offset); + + return len; +} + +static const char *const quad8_noise_error_states[] = { + "No excessive noise is present at the count inputs", + "Excessive noise is present at the count inputs" +}; + +static int quad8_get_noise_error(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel + 1; + + return !!(inb(base_offset) & BIT(4)); +} + +static const struct iio_enum quad8_noise_error_enum = { + .items = quad8_noise_error_states, + .num_items = ARRAY_SIZE(quad8_noise_error_states), + .get = quad8_get_noise_error +}; + +static const char *const quad8_count_direction_states[] = { + "down", + "up" +}; + +static int quad8_get_count_direction(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel + 1; + + return !!(inb(base_offset) & BIT(5)); +} + +static const struct iio_enum quad8_count_direction_enum = { + .items = quad8_count_direction_states, + .num_items = ARRAY_SIZE(quad8_count_direction_states), + .get = quad8_get_count_direction +}; + +static const char *const quad8_count_modes[] = { + "normal", + "range limit", + "non-recycle", + "modulo-n" +}; + +static int quad8_set_count_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int count_mode) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + unsigned int mode_cfg = count_mode << 1; + const int base_offset = priv->base + 2 * chan->channel + 1; + + priv->count_mode[chan->channel] = count_mode; + + /* Add quadrature mode configuration */ + if (priv->quadrature_mode[chan->channel]) + mode_cfg |= (priv->quadrature_scale[chan->channel] + 1) << 3; + + /* Load mode configuration to Counter Mode Register */ + outb(0x20 | mode_cfg, base_offset); + + return 0; +} + +static int quad8_get_count_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return priv->count_mode[chan->channel]; +} + +static const struct iio_enum quad8_count_mode_enum = { + .items = quad8_count_modes, + .num_items = ARRAY_SIZE(quad8_count_modes), + .set = quad8_set_count_mode, + .get = quad8_get_count_mode +}; + +static const char *const quad8_synchronous_modes[] = { + "non-synchronous", + "synchronous" +}; + +static int quad8_set_synchronous_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int synchronous_mode) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const unsigned int idr_cfg = synchronous_mode | + priv->index_polarity[chan->channel] << 1; + const int base_offset = priv->base + 2 * chan->channel + 1; + + /* Index function must be non-synchronous in non-quadrature mode */ + if (synchronous_mode && !priv->quadrature_mode[chan->channel]) + return -EINVAL; + + priv->synchronous_mode[chan->channel] = synchronous_mode; + + /* Load Index Control configuration to Index Control Register */ + outb(0x40 | idr_cfg, base_offset); + + return 0; +} + +static int quad8_get_synchronous_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return priv->synchronous_mode[chan->channel]; +} + +static const struct iio_enum quad8_synchronous_mode_enum = { + .items = quad8_synchronous_modes, + .num_items = ARRAY_SIZE(quad8_synchronous_modes), + .set = quad8_set_synchronous_mode, + .get = quad8_get_synchronous_mode +}; + +static const char *const quad8_quadrature_modes[] = { + "non-quadrature", + "quadrature" +}; + +static int quad8_set_quadrature_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int quadrature_mode) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + unsigned int mode_cfg = priv->count_mode[chan->channel] << 1; + const int base_offset = priv->base + 2 * chan->channel + 1; + + if (quadrature_mode) + mode_cfg |= (priv->quadrature_scale[chan->channel] + 1) << 3; + else { + /* Quadrature scaling only available in quadrature mode */ + priv->quadrature_scale[chan->channel] = 0; + + /* Synchronous function not supported in non-quadrature mode */ + if (priv->synchronous_mode[chan->channel]) + quad8_set_synchronous_mode(indio_dev, chan, 0); + } + + priv->quadrature_mode[chan->channel] = quadrature_mode; + + /* Load mode configuration to Counter Mode Register */ + outb(0x20 | mode_cfg, base_offset); + + return 0; +} + +static int quad8_get_quadrature_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return priv->quadrature_mode[chan->channel]; +} + +static const struct iio_enum quad8_quadrature_mode_enum = { + .items = quad8_quadrature_modes, + .num_items = ARRAY_SIZE(quad8_quadrature_modes), + .set = quad8_set_quadrature_mode, + .get = quad8_get_quadrature_mode +}; + +static const char *const quad8_index_polarity_modes[] = { + "negative", + "positive" +}; + +static int quad8_set_index_polarity(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int index_polarity) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const unsigned int idr_cfg = priv->synchronous_mode[chan->channel] | + index_polarity << 1; + const int base_offset = priv->base + 2 * chan->channel + 1; + + priv->index_polarity[chan->channel] = index_polarity; + + /* Load Index Control configuration to Index Control Register */ + outb(0x40 | idr_cfg, base_offset); + + return 0; +} + +static int quad8_get_index_polarity(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return priv->index_polarity[chan->channel]; +} + +static const struct iio_enum quad8_index_polarity_enum = { + .items = quad8_index_polarity_modes, + .num_items = ARRAY_SIZE(quad8_index_polarity_modes), + .set = quad8_set_index_polarity, + .get = quad8_get_index_polarity +}; + +static const struct iio_chan_spec_ext_info quad8_count_ext_info[] = { + { + .name = "preset", + .shared = IIO_SEPARATE, + .read = quad8_read_preset, + .write = quad8_write_preset + }, + { + .name = "set_to_preset_on_index", + .shared = IIO_SEPARATE, + .read = quad8_read_set_to_preset_on_index, + .write = quad8_write_set_to_preset_on_index + }, + IIO_ENUM("noise_error", IIO_SEPARATE, &quad8_noise_error_enum), + IIO_ENUM_AVAILABLE("noise_error", &quad8_noise_error_enum), + IIO_ENUM("count_direction", IIO_SEPARATE, &quad8_count_direction_enum), + IIO_ENUM_AVAILABLE("count_direction", &quad8_count_direction_enum), + IIO_ENUM("count_mode", IIO_SEPARATE, &quad8_count_mode_enum), + IIO_ENUM_AVAILABLE("count_mode", &quad8_count_mode_enum), + IIO_ENUM("quadrature_mode", IIO_SEPARATE, &quad8_quadrature_mode_enum), + IIO_ENUM_AVAILABLE("quadrature_mode", &quad8_quadrature_mode_enum), + {} +}; + +static const struct iio_chan_spec_ext_info quad8_index_ext_info[] = { + IIO_ENUM("synchronous_mode", IIO_SEPARATE, + &quad8_synchronous_mode_enum), + IIO_ENUM_AVAILABLE("synchronous_mode", &quad8_synchronous_mode_enum), + IIO_ENUM("index_polarity", IIO_SEPARATE, &quad8_index_polarity_enum), + IIO_ENUM_AVAILABLE("index_polarity", &quad8_index_polarity_enum), + {} +}; + +#define QUAD8_COUNT_CHAN(_chan) { \ + .type = IIO_COUNT, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_ENABLE) | BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = quad8_count_ext_info, \ + .indexed = 1 \ +} + +#define QUAD8_INDEX_CHAN(_chan) { \ + .type = IIO_INDEX, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .ext_info = quad8_index_ext_info, \ + .indexed = 1 \ +} + +static const struct iio_chan_spec quad8_channels[] = { + QUAD8_COUNT_CHAN(0), QUAD8_INDEX_CHAN(0), + QUAD8_COUNT_CHAN(1), QUAD8_INDEX_CHAN(1), + QUAD8_COUNT_CHAN(2), QUAD8_INDEX_CHAN(2), + QUAD8_COUNT_CHAN(3), QUAD8_INDEX_CHAN(3), + QUAD8_COUNT_CHAN(4), QUAD8_INDEX_CHAN(4), + QUAD8_COUNT_CHAN(5), QUAD8_INDEX_CHAN(5), + QUAD8_COUNT_CHAN(6), QUAD8_INDEX_CHAN(6), + QUAD8_COUNT_CHAN(7), QUAD8_INDEX_CHAN(7) +}; + +static int quad8_probe(struct device *dev, unsigned int id) +{ + struct iio_dev *indio_dev; + struct quad8_iio *priv; + int i, j; + unsigned int base_offset; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + if (!devm_request_region(dev, base[id], QUAD8_EXTENT, + dev_name(dev))) { + dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", + base[id], base[id] + QUAD8_EXTENT); + return -EBUSY; + } + + indio_dev->info = &quad8_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = ARRAY_SIZE(quad8_channels); + indio_dev->channels = quad8_channels; + indio_dev->name = dev_name(dev); + + priv = iio_priv(indio_dev); + priv->base = base[id]; + + /* Reset all counters and disable interrupt function */ + outb(0x01, base[id] + 0x11); + /* Set initial configuration for all counters */ + for (i = 0; i < QUAD8_NUM_COUNTERS; i++) { + base_offset = base[id] + 2 * i; + /* Reset Byte Pointer */ + outb(0x01, base_offset + 1); + /* Reset Preset Register */ + for (j = 0; j < 3; j++) + outb(0x00, base_offset); + /* Reset Borrow, Carry, Compare, and Sign flags */ + outb(0x04, base_offset + 1); + /* Reset Error flag */ + outb(0x06, base_offset + 1); + /* Binary encoding; Normal count; non-quadrature mode */ + outb(0x20, base_offset + 1); + /* Disable A and B inputs; preset on index; FLG1 as Carry */ + outb(0x40, base_offset + 1); + /* Disable index function; negative index polarity */ + outb(0x60, base_offset + 1); + } + /* Enable all counters */ + outb(0x00, base[id] + 0x11); + + return devm_iio_device_register(dev, indio_dev); +} + +static struct isa_driver quad8_driver = { + .probe = quad8_probe, + .driver = { + .name = "104-quad-8" + } +}; + +module_isa_driver(quad8_driver, num_quad8); + +MODULE_AUTHOR("William Breathitt Gray "); +MODULE_DESCRIPTION("ACCES 104-QUAD-8 IIO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/counter/Kconfig b/drivers/iio/counter/Kconfig new file mode 100644 index 000000000000..44627f6e4861 --- /dev/null +++ b/drivers/iio/counter/Kconfig @@ -0,0 +1,24 @@ +# +# Counter devices +# +# When adding new entries keep the list in alphabetical order + +menu "Counters" + +config 104_QUAD_8 + tristate "ACCES 104-QUAD-8 driver" + depends on X86 && ISA_BUS_API + help + Say yes here to build support for the ACCES 104-QUAD-8 quadrature + encoder counter/interface device family (104-QUAD-8, 104-QUAD-4). + + Performing a write to a counter's IIO_CHAN_INFO_RAW sets the counter and + also clears the counter's respective error flag. Although the counters + have a 25-bit range, only the lower 24 bits may be set, either directly + or via a counter's preset attribute. Interrupts are not supported by + this driver. + + The base port addresses for the devices may be configured via the base + array module parameter. + +endmenu diff --git a/drivers/iio/counter/Makefile b/drivers/iio/counter/Makefile new file mode 100644 index 000000000000..007e88411648 --- /dev/null +++ b/drivers/iio/counter/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for IIO counter devices +# + +# When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_104_QUAD_8) += 104-quad-8.o -- cgit v1.2.3 From 34cf779d3cdc565bed108e70fe6e194d5acdc097 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 8 Oct 2016 14:34:23 +0200 Subject: dt: bindings: i2c/trivial-devices.txt: Add 2 iio supported accelerometers Add compatible strings for 2 accelerometers which follow the trivial i2c device bindings and have been supported by the iio subsystem for a while now. Signed-off-by: Hans de Goede Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/i2c/trivial-devices.txt | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt index 1416c6a0d2cd..fc78f9a68bf0 100644 --- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt +++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt @@ -44,6 +44,7 @@ epson,rx8025 High-Stability. I2C-Bus INTERFACE REAL TIME CLOCK MODULE epson,rx8581 I2C-BUS INTERFACE REAL TIME CLOCK MODULE fsl,mag3110 MAG3110: Xtrinsic High Accuracy, 3D Magnetometer fsl,mc13892 MC13892: Power Management Integrated Circuit (PMIC) for i.MX35/51 +fsl,mma7660 MMA7660FC: 3-Axis Orientation/Motion Detection Sensor fsl,mma8450 MMA8450Q: Xtrinsic Low-power, 3-axis Xtrinsic Accelerometer fsl,mpl3115 MPL3115: Absolute Digital Pressure Sensor fsl,mpr121 MPR121: Proximity Capacitive Touch Sensor Controller @@ -58,6 +59,7 @@ maxim,max1237 Low-Power, 4-/12-Channel, 2-Wire Serial, 12-Bit ADCs maxim,max6625 9-Bit/12-Bit Temperature Sensors with I²C-Compatible Serial Interface mc,rv3029c2 Real Time Clock Module with I2C-Bus mcube,mc3230 mCube 3-axis 8-bit digital accelerometer +memsic,mxc6225 MEMSIC 2-axis 8-bit digital accelerometer microchip,mcp4531-502 Microchip 7-bit Single I2C Digital Potentiometer (5k) microchip,mcp4531-103 Microchip 7-bit Single I2C Digital Potentiometer (10k) microchip,mcp4531-503 Microchip 7-bit Single I2C Digital Potentiometer (50k) -- cgit v1.2.3 From 569906e2032ebbdd56e820ba60368cd803234cec Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 8 Oct 2016 14:34:24 +0200 Subject: iio: accel: Add driver for dmard10 3-axis Accelerometer Add a driver for the Domintech ARD10 3-axis Accelerometer, based on the android driver found here: https://github.com/domintech/dmard10 Signed-off-by: Hans de Goede Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/i2c/trivial-devices.txt | 1 + drivers/iio/accel/Kconfig | 10 + drivers/iio/accel/Makefile | 1 + drivers/iio/accel/dmard10.c | 266 +++++++++++++++++++++ 4 files changed, 278 insertions(+) create mode 100644 drivers/iio/accel/dmard10.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt index fc78f9a68bf0..b0a43955852c 100644 --- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt +++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt @@ -39,6 +39,7 @@ dallas,ds75 Digital Thermometer and Thermostat dlg,da9053 DA9053: flexible system level PMIC with multicore support dlg,da9063 DA9063: system PMIC for quad-core application processors domintech,dmard09 DMARD09: 3-axis Accelerometer +domintech,dmard10 DMARD10: 3-axis Accelerometer epson,rx8010 I2C-BUS INTERFACE REAL TIME CLOCK MODULE epson,rx8025 High-Stability. I2C-Bus INTERFACE REAL TIME CLOCK MODULE epson,rx8581 I2C-BUS INTERFACE REAL TIME CLOCK MODULE diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 2b791fe1e2bc..d59cfe7d4e5b 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -73,6 +73,16 @@ config DMARD09 Choosing M will build the driver as a module. If so, the module will be called dmard09. +config DMARD10 + tristate "Domintech DMARD10 3-axis Accelerometer Driver" + depends on I2C + help + Say yes here to get support for the Domintech DMARD10 3-axis + accelerometer. + + Choosing M will build the driver as a module. If so, the module + will be called dmard10. + config HID_SENSOR_ACCEL_3D depends on HID_SENSOR_HUB select IIO_BUFFER diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index f5d3ddee619e..cb0e04b2dcb6 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_BMC150_ACCEL_I2C) += bmc150-accel-i2c.o obj-$(CONFIG_BMC150_ACCEL_SPI) += bmc150-accel-spi.o obj-$(CONFIG_DMARD06) += dmard06.o obj-$(CONFIG_DMARD09) += dmard09.o +obj-$(CONFIG_DMARD10) += dmard10.o obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o obj-$(CONFIG_KXCJK1013) += kxcjk-1013.o obj-$(CONFIG_KXSD9) += kxsd9.o diff --git a/drivers/iio/accel/dmard10.c b/drivers/iio/accel/dmard10.c new file mode 100644 index 000000000000..b8736cc75656 --- /dev/null +++ b/drivers/iio/accel/dmard10.c @@ -0,0 +1,266 @@ +/** + * IIO driver for the 3-axis accelerometer Domintech ARD10. + * + * Copyright (c) 2016 Hans de Goede + * Copyright (c) 2012 Domintech Technology Co., Ltd + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#define DMARD10_REG_ACTR 0x00 +#define DMARD10_REG_AFEM 0x0c +#define DMARD10_REG_STADR 0x12 +#define DMARD10_REG_STAINT 0x1c +#define DMARD10_REG_MISC2 0x1f +#define DMARD10_REG_PD 0x21 + +#define DMARD10_MODE_OFF 0x00 +#define DMARD10_MODE_STANDBY 0x02 +#define DMARD10_MODE_ACTIVE 0x06 +#define DMARD10_MODE_READ_OTP 0x12 +#define DMARD10_MODE_RESET_DATA_PATH 0x82 + +/* AFEN set 1, ATM[2:0]=b'000 (normal), EN_Z/Y/X/T=1 */ +#define DMARD10_VALUE_AFEM_AFEN_NORMAL 0x8f +/* ODR[3:0]=b'0111 (100Hz), CCK[3:0]=b'0100 (204.8kHZ) */ +#define DMARD10_VALUE_CKSEL_ODR_100_204 0x74 +/* INTC[6:5]=b'00 */ +#define DMARD10_VALUE_INTC 0x00 +/* TAP1/TAP2 Average 2 */ +#define DMARD10_VALUE_TAPNS_AVE_2 0x11 + +#define DMARD10_VALUE_STADR 0x55 +#define DMARD10_VALUE_STAINT 0xaa +#define DMARD10_VALUE_MISC2_OSCA_EN 0x08 +#define DMARD10_VALUE_PD_RST 0x52 + +/* Offsets into the buffer read in dmard10_read_raw() */ +#define DMARD10_X_OFFSET 1 +#define DMARD10_Y_OFFSET 2 +#define DMARD10_Z_OFFSET 3 + +/* + * a value of + or -128 corresponds to + or - 1G + * scale = 9.81 / 128 = 0.076640625 + */ + +static const int dmard10_nscale = 76640625; + +#define DMARD10_CHANNEL(reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec dmard10_channels[] = { + DMARD10_CHANNEL(DMARD10_X_OFFSET, X), + DMARD10_CHANNEL(DMARD10_Y_OFFSET, Y), + DMARD10_CHANNEL(DMARD10_Z_OFFSET, Z), +}; + +struct dmard10_data { + struct i2c_client *client; +}; + +/* Init sequence taken from the android driver */ +static int dmard10_reset(struct i2c_client *client) +{ + unsigned char buffer[7]; + int ret; + + /* 1. Powerdown reset */ + ret = i2c_smbus_write_byte_data(client, DMARD10_REG_PD, + DMARD10_VALUE_PD_RST); + if (ret < 0) + return ret; + + /* + * 2. ACTR => Standby mode => Download OTP to parameter reg => + * Standby mode => Reset data path => Standby mode + */ + buffer[0] = DMARD10_REG_ACTR; + buffer[1] = DMARD10_MODE_STANDBY; + buffer[2] = DMARD10_MODE_READ_OTP; + buffer[3] = DMARD10_MODE_STANDBY; + buffer[4] = DMARD10_MODE_RESET_DATA_PATH; + buffer[5] = DMARD10_MODE_STANDBY; + ret = i2c_master_send(client, buffer, 6); + if (ret < 0) + return ret; + + /* 3. OSCA_EN = 1, TSTO = b'000 (INT1 = normal, TEST0 = normal) */ + ret = i2c_smbus_write_byte_data(client, DMARD10_REG_MISC2, + DMARD10_VALUE_MISC2_OSCA_EN); + if (ret < 0) + return ret; + + /* 4. AFEN = 1 (AFE will powerdown after ADC) */ + buffer[0] = DMARD10_REG_AFEM; + buffer[1] = DMARD10_VALUE_AFEM_AFEN_NORMAL; + buffer[2] = DMARD10_VALUE_CKSEL_ODR_100_204; + buffer[3] = DMARD10_VALUE_INTC; + buffer[4] = DMARD10_VALUE_TAPNS_AVE_2; + buffer[5] = 0x00; /* DLYC, no delay timing */ + buffer[6] = 0x07; /* INTD=1 push-pull, INTA=1 active high, AUTOT=1 */ + ret = i2c_master_send(client, buffer, 7); + if (ret < 0) + return ret; + + /* 5. Activation mode */ + ret = i2c_smbus_write_byte_data(client, DMARD10_REG_ACTR, + DMARD10_MODE_ACTIVE); + if (ret < 0) + return ret; + + return 0; +} + +/* Shutdown sequence taken from the android driver */ +static int dmard10_shutdown(struct i2c_client *client) +{ + unsigned char buffer[3]; + + buffer[0] = DMARD10_REG_ACTR; + buffer[1] = DMARD10_MODE_STANDBY; + buffer[2] = DMARD10_MODE_OFF; + + return i2c_master_send(client, buffer, 3); +} + +static int dmard10_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dmard10_data *data = iio_priv(indio_dev); + __le16 buf[4]; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Read 8 bytes starting at the REG_STADR register, trying to + * read the individual X, Y, Z registers will always read 0. + */ + ret = i2c_smbus_read_i2c_block_data(data->client, + DMARD10_REG_STADR, + sizeof(buf), (u8 *)buf); + if (ret < 0) + return ret; + ret = le16_to_cpu(buf[chan->address]); + *val = sign_extend32(ret, 12); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = dmard10_nscale; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info dmard10_info = { + .driver_module = THIS_MODULE, + .read_raw = dmard10_read_raw, +}; + +static int dmard10_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct iio_dev *indio_dev; + struct dmard10_data *data; + + /* These 2 registers have special POR reset values used for id */ + ret = i2c_smbus_read_byte_data(client, DMARD10_REG_STADR); + if (ret != DMARD10_VALUE_STADR) + return (ret < 0) ? ret : -ENODEV; + + ret = i2c_smbus_read_byte_data(client, DMARD10_REG_STAINT); + if (ret != DMARD10_VALUE_STAINT) + return (ret < 0) ? ret : -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) { + dev_err(&client->dev, "iio allocation failed!\n"); + return -ENOMEM; + } + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &dmard10_info; + indio_dev->name = "dmard10"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = dmard10_channels; + indio_dev->num_channels = ARRAY_SIZE(dmard10_channels); + + ret = dmard10_reset(client); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "device_register failed\n"); + dmard10_shutdown(client); + } + + return ret; +} + +static int dmard10_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + return dmard10_shutdown(client); +} + +#ifdef CONFIG_PM_SLEEP +static int dmard10_suspend(struct device *dev) +{ + return dmard10_shutdown(to_i2c_client(dev)); +} + +static int dmard10_resume(struct device *dev) +{ + return dmard10_reset(to_i2c_client(dev)); +} +#endif + +static SIMPLE_DEV_PM_OPS(dmard10_pm_ops, dmard10_suspend, dmard10_resume); + +static const struct i2c_device_id dmard10_i2c_id[] = { + {"dmard10", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, dmard10_i2c_id); + +static struct i2c_driver dmard10_driver = { + .driver = { + .name = "dmard10", + .pm = &dmard10_pm_ops, + }, + .probe = dmard10_probe, + .remove = dmard10_remove, + .id_table = dmard10_i2c_id, +}; + +module_i2c_driver(dmard10_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("Domintech ARD10 3-Axis Accelerometer driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 598893e9cb64d9c74152db6174c4d7a6036a84e8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 8 Oct 2016 14:34:25 +0200 Subject: iio: accel: Add driver for the MiraMEMS DA311 3-axis 12-bit digital accelerometer This driver is based on the DA311 Android driver which can be found here: https://git.matricom.net/Firmware/kernel_amlogic_meson-common/tree/1e70113a5befd07debb68f537156def84c5be57a/drivers/amlogic/input/sensor the mir3da_* files are the DA311 driver. Unfortunately there is no datasheet. Signed-off-by: Hans de Goede Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/i2c/trivial-devices.txt | 1 + drivers/iio/accel/Kconfig | 10 + drivers/iio/accel/Makefile | 1 + drivers/iio/accel/da311.c | 305 +++++++++++++++++++++ 4 files changed, 317 insertions(+) create mode 100644 drivers/iio/accel/da311.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt index b0a43955852c..decdd3e636bf 100644 --- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt +++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt @@ -125,6 +125,7 @@ microchip,mcp4662-502 Microchip 8-bit Dual I2C Digital Potentiometer with NV Mem microchip,mcp4662-103 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (10k) microchip,mcp4662-503 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (50k) microchip,mcp4662-104 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (100k) +miramems,da311 MiraMEMS DA311 3-axis 12-bit digital accelerometer national,lm63 Temperature sensor with integrated fan control national,lm75 I2C TEMP SENSOR national,lm80 Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index d59cfe7d4e5b..8945e70a797c 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -52,6 +52,16 @@ config BMC150_ACCEL_SPI tristate select REGMAP_SPI +config DA311 + tristate "MiraMEMS DA311 3-axis 12-bit digital accelerometer driver" + depends on I2C + help + Say yes here to build support for the MiraMEMS DA311 3-axis 12-bit + digital accelerometer. + + To compile this driver as a module, choose M here: the + module will be called da311. + config DMARD06 tristate "Domintech DMARD06 Digital Accelerometer Driver" depends on OF || COMPILE_TEST diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index cb0e04b2dcb6..ade13ae5d056 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_BMA220) += bma220_spi.o obj-$(CONFIG_BMC150_ACCEL) += bmc150-accel-core.o obj-$(CONFIG_BMC150_ACCEL_I2C) += bmc150-accel-i2c.o obj-$(CONFIG_BMC150_ACCEL_SPI) += bmc150-accel-spi.o +obj-$(CONFIG_DA311) += da311.o obj-$(CONFIG_DMARD06) += dmard06.o obj-$(CONFIG_DMARD09) += dmard09.o obj-$(CONFIG_DMARD10) += dmard10.o diff --git a/drivers/iio/accel/da311.c b/drivers/iio/accel/da311.c new file mode 100644 index 000000000000..537cfa8b6edf --- /dev/null +++ b/drivers/iio/accel/da311.c @@ -0,0 +1,305 @@ +/** + * IIO driver for the MiraMEMS DA311 3-axis accelerometer + * + * Copyright (c) 2016 Hans de Goede + * Copyright (c) 2011-2013 MiraMEMS Sensing Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#define DA311_CHIP_ID 0x13 + +/* + * Note register addressed go from 0 - 0x3f and then wrap. + * For some reason there are 2 banks with 0 - 0x3f addresses, + * rather then a single 0-0x7f bank. + */ + +/* Bank 0 regs */ +#define DA311_REG_BANK 0x0000 +#define DA311_REG_LDO_REG 0x0006 +#define DA311_REG_CHIP_ID 0x000f +#define DA311_REG_TEMP_CFG_REG 0x001f +#define DA311_REG_CTRL_REG1 0x0020 +#define DA311_REG_CTRL_REG3 0x0022 +#define DA311_REG_CTRL_REG4 0x0023 +#define DA311_REG_CTRL_REG5 0x0024 +#define DA311_REG_CTRL_REG6 0x0025 +#define DA311_REG_STATUS_REG 0x0027 +#define DA311_REG_OUT_X_L 0x0028 +#define DA311_REG_OUT_X_H 0x0029 +#define DA311_REG_OUT_Y_L 0x002a +#define DA311_REG_OUT_Y_H 0x002b +#define DA311_REG_OUT_Z_L 0x002c +#define DA311_REG_OUT_Z_H 0x002d +#define DA311_REG_INT1_CFG 0x0030 +#define DA311_REG_INT1_SRC 0x0031 +#define DA311_REG_INT1_THS 0x0032 +#define DA311_REG_INT1_DURATION 0x0033 +#define DA311_REG_INT2_CFG 0x0034 +#define DA311_REG_INT2_SRC 0x0035 +#define DA311_REG_INT2_THS 0x0036 +#define DA311_REG_INT2_DURATION 0x0037 +#define DA311_REG_CLICK_CFG 0x0038 +#define DA311_REG_CLICK_SRC 0x0039 +#define DA311_REG_CLICK_THS 0x003a +#define DA311_REG_TIME_LIMIT 0x003b +#define DA311_REG_TIME_LATENCY 0x003c +#define DA311_REG_TIME_WINDOW 0x003d + +/* Bank 1 regs */ +#define DA311_REG_SOFT_RESET 0x0105 +#define DA311_REG_OTP_XOFF_L 0x0110 +#define DA311_REG_OTP_XOFF_H 0x0111 +#define DA311_REG_OTP_YOFF_L 0x0112 +#define DA311_REG_OTP_YOFF_H 0x0113 +#define DA311_REG_OTP_ZOFF_L 0x0114 +#define DA311_REG_OTP_ZOFF_H 0x0115 +#define DA311_REG_OTP_XSO 0x0116 +#define DA311_REG_OTP_YSO 0x0117 +#define DA311_REG_OTP_ZSO 0x0118 +#define DA311_REG_OTP_TRIM_OSC 0x011b +#define DA311_REG_LPF_ABSOLUTE 0x011c +#define DA311_REG_TEMP_OFF1 0x0127 +#define DA311_REG_TEMP_OFF2 0x0128 +#define DA311_REG_TEMP_OFF3 0x0129 +#define DA311_REG_OTP_TRIM_THERM_H 0x011a + +/* + * a value of + or -1024 corresponds to + or - 1G + * scale = 9.81 / 1024 = 0.009580078 + */ + +static const int da311_nscale = 9580078; + +#define DA311_CHANNEL(reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec da311_channels[] = { + /* | 0x80 comes from the android driver */ + DA311_CHANNEL(DA311_REG_OUT_X_L | 0x80, X), + DA311_CHANNEL(DA311_REG_OUT_Y_L | 0x80, Y), + DA311_CHANNEL(DA311_REG_OUT_Z_L | 0x80, Z), +}; + +struct da311_data { + struct i2c_client *client; +}; + +static int da311_register_mask_write(struct i2c_client *client, u16 addr, + u8 mask, u8 data) +{ + int ret; + u8 tmp_data = 0; + + if (addr & 0xff00) { + /* Select bank 1 */ + ret = i2c_smbus_write_byte_data(client, DA311_REG_BANK, 0x01); + if (ret < 0) + return ret; + } + + if (mask != 0xff) { + ret = i2c_smbus_read_byte_data(client, addr); + if (ret < 0) + return ret; + tmp_data = ret; + } + + tmp_data &= ~mask; + tmp_data |= data & mask; + ret = i2c_smbus_write_byte_data(client, addr & 0xff, tmp_data); + if (ret < 0) + return ret; + + if (addr & 0xff00) { + /* Back to bank 0 */ + ret = i2c_smbus_write_byte_data(client, DA311_REG_BANK, 0x00); + if (ret < 0) + return ret; + } + + return 0; +} + +/* Init sequence taken from the android driver */ +static int da311_reset(struct i2c_client *client) +{ + const struct { + u16 addr; + u8 mask; + u8 data; + } init_data[] = { + { DA311_REG_TEMP_CFG_REG, 0xff, 0x08 }, + { DA311_REG_CTRL_REG5, 0xff, 0x80 }, + { DA311_REG_CTRL_REG4, 0x30, 0x00 }, + { DA311_REG_CTRL_REG1, 0xff, 0x6f }, + { DA311_REG_TEMP_CFG_REG, 0xff, 0x88 }, + { DA311_REG_LDO_REG, 0xff, 0x02 }, + { DA311_REG_OTP_TRIM_OSC, 0xff, 0x27 }, + { DA311_REG_LPF_ABSOLUTE, 0xff, 0x30 }, + { DA311_REG_TEMP_OFF1, 0xff, 0x3f }, + { DA311_REG_TEMP_OFF2, 0xff, 0xff }, + { DA311_REG_TEMP_OFF3, 0xff, 0x0f }, + }; + int i, ret; + + /* Reset */ + ret = da311_register_mask_write(client, DA311_REG_SOFT_RESET, + 0xff, 0xaa); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(init_data); i++) { + ret = da311_register_mask_write(client, + init_data[i].addr, + init_data[i].mask, + init_data[i].data); + if (ret < 0) + return ret; + } + + return 0; +} + +static int da311_enable(struct i2c_client *client, bool enable) +{ + u8 data = enable ? 0x00 : 0x20; + + return da311_register_mask_write(client, DA311_REG_TEMP_CFG_REG, + 0x20, data); +} + +static int da311_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct da311_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_data(data->client, chan->address); + if (ret < 0) + return ret; + /* + * Values are 12 bits, stored as 16 bits with the 4 + * least significant bits always 0. + */ + *val = (short)ret >> 4; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = da311_nscale; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info da311_info = { + .driver_module = THIS_MODULE, + .read_raw = da311_read_raw, +}; + +static int da311_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct iio_dev *indio_dev; + struct da311_data *data; + + ret = i2c_smbus_read_byte_data(client, DA311_REG_CHIP_ID); + if (ret != DA311_CHIP_ID) + return (ret < 0) ? ret : -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &da311_info; + indio_dev->name = "da311"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = da311_channels; + indio_dev->num_channels = ARRAY_SIZE(da311_channels); + + ret = da311_reset(client); + if (ret < 0) + return ret; + + ret = da311_enable(client, true); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "device_register failed\n"); + da311_enable(client, false); + } + + return ret; +} + +static int da311_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + return da311_enable(client, false); +} + +#ifdef CONFIG_PM_SLEEP +static int da311_suspend(struct device *dev) +{ + return da311_enable(to_i2c_client(dev), false); +} + +static int da311_resume(struct device *dev) +{ + return da311_enable(to_i2c_client(dev), true); +} +#endif + +static SIMPLE_DEV_PM_OPS(da311_pm_ops, da311_suspend, da311_resume); + +static const struct i2c_device_id da311_i2c_id[] = { + {"da311", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, da311_i2c_id); + +static struct i2c_driver da311_driver = { + .driver = { + .name = "da311", + .pm = &da311_pm_ops, + }, + .probe = da311_probe, + .remove = da311_remove, + .id_table = da311_i2c_id, +}; + +module_i2c_driver(da311_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("MiraMEMS DA311 3-Axis Accelerometer driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From f225951dbb9d02c80d09cb81b070b3af73a68de9 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 8 Oct 2016 20:15:27 +0200 Subject: iio: accel: Add driver for the MiraMEMS DA280 3-axis 14-bit digital accelerometer Add an iio driver for the MiraMEMS DA280 3-axis 14-bit accelerometer, as well as for the DA226 which is a fully compatible 2-axis version. Datasheets for the DA280 and DA226 can be found at the manufacturers site: http://www.miramems.com/en/products.asp?list=1 Signed-off-by: Hans de Goede Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/i2c/trivial-devices.txt | 2 + drivers/iio/accel/Kconfig | 10 ++ drivers/iio/accel/Makefile | 1 + drivers/iio/accel/da280.c | 183 +++++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 drivers/iio/accel/da280.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt index decdd3e636bf..307f13bc81f2 100644 --- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt +++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt @@ -125,6 +125,8 @@ microchip,mcp4662-502 Microchip 8-bit Dual I2C Digital Potentiometer with NV Mem microchip,mcp4662-103 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (10k) microchip,mcp4662-503 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (50k) microchip,mcp4662-104 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (100k) +miramems,da226 MiraMEMS DA226 2-axis 14-bit digital accelerometer +miramems,da280 MiraMEMS DA280 3-axis 14-bit digital accelerometer miramems,da311 MiraMEMS DA311 3-axis 12-bit digital accelerometer national,lm63 Temperature sensor with integrated fan control national,lm75 I2C TEMP SENSOR diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 8945e70a797c..878e3c9449de 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -52,6 +52,16 @@ config BMC150_ACCEL_SPI tristate select REGMAP_SPI +config DA280 + tristate "MiraMEMS DA280 3-axis 14-bit digital accelerometer driver" + depends on I2C + help + Say yes here to build support for the MiraMEMS DA280 3-axis 14-bit + digital accelerometer. + + To compile this driver as a module, choose M here: the + module will be called da280. + config DA311 tristate "MiraMEMS DA311 3-axis 12-bit digital accelerometer driver" depends on I2C diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index ade13ae5d056..0aa574957047 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_BMA220) += bma220_spi.o obj-$(CONFIG_BMC150_ACCEL) += bmc150-accel-core.o obj-$(CONFIG_BMC150_ACCEL_I2C) += bmc150-accel-i2c.o obj-$(CONFIG_BMC150_ACCEL_SPI) += bmc150-accel-spi.o +obj-$(CONFIG_DA280) += da280.o obj-$(CONFIG_DA311) += da311.o obj-$(CONFIG_DMARD06) += dmard06.o obj-$(CONFIG_DMARD09) += dmard09.o diff --git a/drivers/iio/accel/da280.c b/drivers/iio/accel/da280.c new file mode 100644 index 000000000000..ed8343aeac9c --- /dev/null +++ b/drivers/iio/accel/da280.c @@ -0,0 +1,183 @@ +/** + * IIO driver for the MiraMEMS DA280 3-axis accelerometer and + * IIO driver for the MiraMEMS DA226 2-axis accelerometer + * + * Copyright (c) 2016 Hans de Goede + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#define DA280_REG_CHIP_ID 0x01 +#define DA280_REG_ACC_X_LSB 0x02 +#define DA280_REG_ACC_Y_LSB 0x04 +#define DA280_REG_ACC_Z_LSB 0x06 +#define DA280_REG_MODE_BW 0x11 + +#define DA280_CHIP_ID 0x13 +#define DA280_MODE_ENABLE 0x1e +#define DA280_MODE_DISABLE 0x9e + +enum { da226, da280 }; + +/* + * a value of + or -4096 corresponds to + or - 1G + * scale = 9.81 / 4096 = 0.002395019 + */ + +static const int da280_nscale = 2395019; + +#define DA280_CHANNEL(reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec da280_channels[] = { + DA280_CHANNEL(DA280_REG_ACC_X_LSB, X), + DA280_CHANNEL(DA280_REG_ACC_Y_LSB, Y), + DA280_CHANNEL(DA280_REG_ACC_Z_LSB, Z), +}; + +struct da280_data { + struct i2c_client *client; +}; + +static int da280_enable(struct i2c_client *client, bool enable) +{ + u8 data = enable ? DA280_MODE_ENABLE : DA280_MODE_DISABLE; + + return i2c_smbus_write_byte_data(client, DA280_REG_MODE_BW, data); +} + +static int da280_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct da280_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_data(data->client, chan->address); + if (ret < 0) + return ret; + /* + * Values are 14 bits, stored as 16 bits with the 2 + * least significant bits always 0. + */ + *val = (short)ret >> 2; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = da280_nscale; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static const struct iio_info da280_info = { + .driver_module = THIS_MODULE, + .read_raw = da280_read_raw, +}; + +static int da280_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct iio_dev *indio_dev; + struct da280_data *data; + + ret = i2c_smbus_read_byte_data(client, DA280_REG_CHIP_ID); + if (ret != DA280_CHIP_ID) + return (ret < 0) ? ret : -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &da280_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = da280_channels; + if (id->driver_data == da226) { + indio_dev->name = "da226"; + indio_dev->num_channels = 2; + } else { + indio_dev->name = "da280"; + indio_dev->num_channels = 3; + } + + ret = da280_enable(client, true); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "device_register failed\n"); + da280_enable(client, false); + } + + return ret; +} + +static int da280_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + return da280_enable(client, false); +} + +#ifdef CONFIG_PM_SLEEP +static int da280_suspend(struct device *dev) +{ + return da280_enable(to_i2c_client(dev), false); +} + +static int da280_resume(struct device *dev) +{ + return da280_enable(to_i2c_client(dev), true); +} +#endif + +static SIMPLE_DEV_PM_OPS(da280_pm_ops, da280_suspend, da280_resume); + +static const struct i2c_device_id da280_i2c_id[] = { + { "da226", da226 }, + { "da280", da280 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, da280_i2c_id); + +static struct i2c_driver da280_driver = { + .driver = { + .name = "da280", + .pm = &da280_pm_ops, + }, + .probe = da280_probe, + .remove = da280_remove, + .id_table = da280_i2c_id, +}; + +module_i2c_driver(da280_driver); + +MODULE_AUTHOR("Hans de Goede "); +MODULE_DESCRIPTION("MiraMEMS DA280 3-Axis Accelerometer driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From eceb04609b3f7dc79b41e46ca7da7df85e0bd8fa Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 6 Oct 2016 19:06:50 +0200 Subject: dt: bindings: vendor-prefix: Add 3 accelerometer vendor prefixes Recently the kernel has gotten support in the iio subsystem for a number of new accelerometers from mCube, Memsic and MiraMEMS. This commit adds the vendor prefixes already used for these vendors in Documentation/devicetree/bindings/i2c/trivial-devices.txt to vendor-prefixes.txt. The "full" company names were taken from the company's websites: http://www.mcubemems.com/ http://www.memsic.com/ http://www.miramems.com/en/ Signed-off-by: Hans de Goede Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/vendor-prefixes.txt | 3 +++ 1 file changed, 3 insertions(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 0d9d4d80479c..ce955f1988f2 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -154,14 +154,17 @@ lsi LSI Corp. (LSI Logic) lltc Linear Technology Corporation marvell Marvell Technology Group Ltd. maxim Maxim Integrated Products +mcube mCube meas Measurement Specialties mediatek MediaTek Inc. melexis Melexis N.V. +memsic MEMSIC Inc. merrii Merrii Technology Co., Ltd. micrel Micrel Inc. microchip Microchip Technology Inc. micron Micron Technology Inc. minix MINIX Technology Ltd. +miramems MiraMEMS Sensing Technology Co., Ltd. mitsubishi Mitsubishi Electric Corporation mosaixtech Mosaix Technologies, Inc. moxa Moxa -- cgit v1.2.3 From 88efa2baf15a5766c6b9cc29e60761b441cec170 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 6 Oct 2016 20:48:35 -0400 Subject: staging: iio: isl29018: document device tree bindings Fix the following warnings from checkpatch: WARNING: DT compatible string "isil,isl29018" appears un-documented -- check ./Documentation/devicetree/bindings/ WARNING: DT compatible string "isil,isl29023" appears un-documented -- check ./Documentation/devicetree/bindings/ WARNING: DT compatible string "isil,isl29035" appears un-documented -- check ./Documentation/devicetree/bindings/ Signed-off-by: Brian Masney Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/light/isl29018.txt | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/isl29018.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/iio/light/isl29018.txt b/Documentation/devicetree/bindings/iio/light/isl29018.txt new file mode 100644 index 000000000000..425ab459e209 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/light/isl29018.txt @@ -0,0 +1,28 @@ +* ISL 29018/29023/29035 I2C ALS, Proximity, and Infrared sensor + +Required properties: + + - compatible: Should be one of + "isil,isl29018" + "isil,isl29023" + "isil,isl29035" + - reg: the I2C address of the device + +Optional properties: + + - interrupt-parent: should be the phandle for the interrupt controller + - interrupts: the sole interrupt generated by the device + + Refer to interrupt-controller/interrupts.txt for generic interrupt client + node bindings. + + - vcc-supply: phandle to the regulator that provides power to the sensor. + +Example: + +isl29018@44 { + compatible = "isil,isl29018"; + reg = <0x44>; + interrupt-parent = <&gpio>; + interrupts = ; +}; -- cgit v1.2.3 From af06437dae78cc61d89f528fa4a121c27c50bc55 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Mon, 10 Oct 2016 03:20:01 -0400 Subject: staging: iio: isl29018: add ABI documentation for infrared suppression Add ABI documentation from the ISL29018 Data Sheet (FN6619.4, Oct 8, 2012) about the infrared suppression that can be controlled with the proximity_on_chip_ambient_infrared_suppression sysfs attribute. Signed-off-by: Brian Masney Signed-off-by: Jonathan Cameron --- .../ABI/testing/sysfs-bus-iio-light-isl29018 | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-isl29018 (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-isl29018 b/Documentation/ABI/testing/sysfs-bus-iio-light-isl29018 new file mode 100644 index 000000000000..f0ce0a0476ea --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-isl29018 @@ -0,0 +1,19 @@ +What: /sys/bus/iio/devices/iio:deviceX/proximity_on_chip_ambient_infrared_suppression +Date: January 2011 +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + From ISL29018 Data Sheet (FN6619.4, Oct 8, 2012) regarding the + infrared suppression: + + Scheme 0, makes full n (4, 8, 12, 16) bits (unsigned) proximity + detection. The range of Scheme 0 proximity count is from 0 to + 2^n. Logic 1 of this bit, Scheme 1, makes n-1 (3, 7, 11, 15) + bits (2's complementary) proximity_less_ambient detection. The + range of Scheme 1 proximity count is from -2^(n-1) to 2^(n-1). + The sign bit is extended for resolutions less than 16. While + Scheme 0 has wider dynamic range, Scheme 1 proximity detection + is less affected by the ambient IR noise variation. + + 0 Sensing IR from LED and ambient + 1 Sensing IR from LED with ambient IR rejection -- cgit v1.2.3 From 0b0feb34175d57a342846a13d2072446fd3bcdde Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 13 Oct 2016 22:06:05 +0200 Subject: Documentation: dt: iio: humidity: add hts221 sensor device binding Signed-off-by: Lorenzo Bianconi Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/humidity/hts221.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/humidity/hts221.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/iio/humidity/hts221.txt b/Documentation/devicetree/bindings/iio/humidity/hts221.txt new file mode 100644 index 000000000000..b20ab9c12080 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/humidity/hts221.txt @@ -0,0 +1,22 @@ +* HTS221 STM humidity + temperature sensor + +Required properties: +- compatible: should be "st,hts221" +- reg: i2c address of the sensor / spi cs line + +Optional properties: +- interrupt-parent: should be the phandle for the interrupt controller +- interrupts: interrupt mapping for IRQ. It should be configured with + flags IRQ_TYPE_LEVEL_HIGH or IRQ_TYPE_EDGE_RISING. + + Refer to interrupt-controller/interrupts.txt for generic interrupt + client node bindings. + +Example: + +hts221@5f { + compatible = "st,hts221"; + reg = <0x5f>; + interrupt-parent = <&gpio0>; + interrupts = <0 IRQ_TYPE_EDGE_RISING>; +}; -- cgit v1.2.3 From 8b0ee39f4de13f9d8b50846621e558b78b7defff Mon Sep 17 00:00:00 2001 From: Tomas Novotny Date: Tue, 18 Oct 2016 19:43:07 +0200 Subject: Documentation: dt: iio: add mcp4725/6 dac device binding Signed-off-by: Tomas Novotny Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/dac/mcp4725.txt | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/mcp4725.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/iio/dac/mcp4725.txt b/Documentation/devicetree/bindings/iio/dac/mcp4725.txt new file mode 100644 index 000000000000..1bc6c093fbfe --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/mcp4725.txt @@ -0,0 +1,35 @@ +Microchip mcp4725 and mcp4726 DAC device driver + +Required properties: + - compatible: Must be "microchip,mcp4725" or "microchip,mcp4726" + - reg: Should contain the DAC I2C address + - vdd-supply: Phandle to the Vdd power supply. This supply is used as a + voltage reference on mcp4725. It is used as a voltage reference on + mcp4726 if there is no vref-supply specified. + +Optional properties (valid only for mcp4726): + - vref-supply: Optional phandle to the Vref power supply. Vref pin is + used as a voltage reference when this supply is specified. + - microchip,vref-buffered: Boolean to enable buffering of the external + Vref pin. This boolean is not valid without the vref-supply. Quoting + the datasheet: This is offered in cases where the reference voltage + does not have the current capability not to drop its voltage when + connected to the internal resistor ladder circuit. + +Examples: + + /* simple mcp4725 */ + mcp4725@60 { + compatible = "microchip,mcp4725"; + reg = <0x60>; + vdd-supply = <&vdac_vdd>; + }; + + /* mcp4726 with the buffered external reference voltage */ + mcp4726@60 { + compatible = "microchip,mcp4726"; + reg = <0x60>; + vdd-supply = <&vdac_vdd>; + vref-supply = <&vdac_vref>; + microchip,vref-buffered; + }; -- cgit v1.2.3 From e0549df64ed888e4f908d81990c55edb6b2c2f7d Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 25 Oct 2016 16:15:53 +0200 Subject: iio: gyro: Add MPU-3050 device tree bindings This adds device tree bindings for the MPU-3050 gyroscope. Since it is the first set of bindings for a gyroscope, the folder for it is also created. Cc: devicetree@vger.kernel.org Cc: Peter Rosin Acked-by: Rob Herring Signed-off-by: Linus Walleij Signed-off-by: Jonathan Cameron --- .../bindings/iio/gyroscope/invensense,mpu3050.txt | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/gyroscope/invensense,mpu3050.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/iio/gyroscope/invensense,mpu3050.txt b/Documentation/devicetree/bindings/iio/gyroscope/invensense,mpu3050.txt new file mode 100644 index 000000000000..b0d3b59966bc --- /dev/null +++ b/Documentation/devicetree/bindings/iio/gyroscope/invensense,mpu3050.txt @@ -0,0 +1,46 @@ +Invensense MPU-3050 Gyroscope device tree bindings + +Required properties: + - compatible : should be "invensense,mpu3050" + - reg : the I2C address of the sensor + +Optional properties: + - interrupt-parent : should be the phandle for the interrupt controller + - interrupts : interrupt mapping for the trigger interrupt from the + internal oscillator. The following IRQ modes are supported: + IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_HIGH and + IRQ_TYPE_LEVEL_LOW. The driver should detect and configure the hardware + for the desired interrupt type. + - vdd-supply : supply regulator for the main power voltage. + - vlogic-supply : supply regulator for the signal voltage. + - mount-matrix : see iio/mount-matrix.txt + +Optional subnodes: + - The MPU-3050 will pass through and forward the I2C signals from the + incoming I2C bus, alternatively drive traffic to a slave device (usually + an accelerometer) on its own initiative. Therefore is supports a subnode + i2c gate node. For details see: i2c/i2c-gate.txt + +Example: + +mpu3050@68 { + compatible = "invensense,mpu3050"; + reg = <0x68>; + interrupt-parent = <&foo>; + interrupts = <12 IRQ_TYPE_EDGE_FALLING>; + vdd-supply = <&bar>; + vlogic-supply = <&baz>; + + /* External I2C interface */ + i2c-gate { + #address-cells = <1>; + #size-cells = <0>; + + fnord@18 { + compatible = "fnord"; + reg = <0x18>; + interrupt-parent = <&foo>; + interrupts = <13 IRQ_TYPE_EDGE_FALLING>; + }; + }; +}; -- cgit v1.2.3 From 974e6f02e27e1b46c6c5e600e70ced25079f73eb Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Mon, 1 Aug 2016 11:54:35 +0200 Subject: iio: cros_ec_sensors_core: Add common functions for the ChromeOS EC Sensor Hub. Add the core functions to be able to support the sensors attached behind the ChromeOS Embedded Controller and used by other IIO cros-ec sensor drivers. The cros_ec_sensor_core driver matches with current driver in ChromeOS 4.4 tree, so it includes all the fixes at the moment. The support for this driver was made by Gwendal Grignou. The original patch and all the fixes has been squashed and rebased on top of mainline. Signed-off-by: Gwendal Grignou Signed-off-by: Guenter Roeck [eballetbo: split, squash and rebase on top of mainline the patches found in ChromeOS tree] Signed-off-by: Enric Balletbo i Serra Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio-cros-ec | 18 + drivers/iio/common/Kconfig | 1 + drivers/iio/common/Makefile | 1 + drivers/iio/common/cros_ec_sensors/Kconfig | 14 + drivers/iio/common/cros_ec_sensors/Makefile | 5 + .../common/cros_ec_sensors/cros_ec_sensors_core.c | 450 +++++++++++++++++++++ .../common/cros_ec_sensors/cros_ec_sensors_core.h | 175 ++++++++ include/linux/mfd/cros_ec.h | 9 + include/linux/mfd/cros_ec_commands.h | 99 ++++- 9 files changed, 767 insertions(+), 5 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-cros-ec create mode 100644 drivers/iio/common/cros_ec_sensors/Kconfig create mode 100644 drivers/iio/common/cros_ec_sensors/Makefile create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c create mode 100644 drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.h (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-cros-ec b/Documentation/ABI/testing/sysfs-bus-iio-cros-ec new file mode 100644 index 000000000000..297b9720f024 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-cros-ec @@ -0,0 +1,18 @@ +What: /sys/bus/iio/devices/iio:deviceX/calibrate +Date: July 2015 +KernelVersion: 4.7 +Contact: linux-iio@vger.kernel.org +Description: + Writing '1' will perform a FOC (Fast Online Calibration). The + corresponding calibration offsets can be read from *_calibbias + entries. + +What: /sys/bus/iio/devices/iio:deviceX/location +Date: July 2015 +KernelVersion: 4.7 +Contact: linux-iio@vger.kernel.org +Description: + This attribute returns a string with the physical location where + the motion sensor is placed. For example, in a laptop a motion + sensor can be located on the base or on the lid. Current valid + values are 'base' and 'lid'. diff --git a/drivers/iio/common/Kconfig b/drivers/iio/common/Kconfig index 26a6026de614..e108996a9627 100644 --- a/drivers/iio/common/Kconfig +++ b/drivers/iio/common/Kconfig @@ -2,6 +2,7 @@ # IIO common modules # +source "drivers/iio/common/cros_ec_sensors/Kconfig" source "drivers/iio/common/hid-sensors/Kconfig" source "drivers/iio/common/ms_sensors/Kconfig" source "drivers/iio/common/ssp_sensors/Kconfig" diff --git a/drivers/iio/common/Makefile b/drivers/iio/common/Makefile index 585da6a1b188..6fa760e1bdd5 100644 --- a/drivers/iio/common/Makefile +++ b/drivers/iio/common/Makefile @@ -7,6 +7,7 @@ # # When adding new entries keep the list in alphabetical order +obj-y += cros_ec_sensors/ obj-y += hid-sensors/ obj-y += ms_sensors/ obj-y += ssp_sensors/ diff --git a/drivers/iio/common/cros_ec_sensors/Kconfig b/drivers/iio/common/cros_ec_sensors/Kconfig new file mode 100644 index 000000000000..24743be15a5b --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/Kconfig @@ -0,0 +1,14 @@ +# +# Chrome OS Embedded Controller managed sensors library +# +config IIO_CROS_EC_SENSORS_CORE + tristate "ChromeOS EC Sensors Core" + depends on SYSFS && MFD_CROS_EC + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Base module for the ChromeOS EC Sensors module. + Contains core functions used by other IIO CrosEC sensor + drivers. + Define common attributes and sysfs interrupt handler. + diff --git a/drivers/iio/common/cros_ec_sensors/Makefile b/drivers/iio/common/cros_ec_sensors/Makefile new file mode 100644 index 000000000000..95b690139bfd --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for sensors seen through the ChromeOS EC sensor hub. +# + +obj-$(CONFIG_IIO_CROS_EC_SENSORS_CORE) += cros_ec_sensors_core.o diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c new file mode 100644 index 000000000000..a3be7991355e --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c @@ -0,0 +1,450 @@ +/* + * cros_ec_sensors_core - Common function for Chrome OS EC sensor driver. + * + * Copyright (C) 2016 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cros_ec_sensors_core.h" + +static char *cros_ec_loc[] = { + [MOTIONSENSE_LOC_BASE] = "base", + [MOTIONSENSE_LOC_LID] = "lid", + [MOTIONSENSE_LOC_MAX] = "unknown", +}; + +int cros_ec_sensors_core_init(struct platform_device *pdev, + struct iio_dev *indio_dev, + bool physical_device) +{ + struct device *dev = &pdev->dev; + struct cros_ec_sensors_core_state *state = iio_priv(indio_dev); + struct cros_ec_dev *ec = dev_get_drvdata(pdev->dev.parent); + struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev); + + platform_set_drvdata(pdev, indio_dev); + + state->ec = ec->ec_dev; + state->msg = devm_kzalloc(&pdev->dev, + max((u16)sizeof(struct ec_params_motion_sense), + state->ec->max_response), GFP_KERNEL); + if (!state->msg) + return -ENOMEM; + + state->resp = (struct ec_response_motion_sense *)state->msg->data; + + mutex_init(&state->cmd_lock); + + /* Set up the host command structure. */ + state->msg->version = 2; + state->msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; + state->msg->outsize = sizeof(struct ec_params_motion_sense); + + indio_dev->dev.parent = &pdev->dev; + indio_dev->name = pdev->name; + + if (physical_device) { + indio_dev->modes = INDIO_DIRECT_MODE; + + state->param.cmd = MOTIONSENSE_CMD_INFO; + state->param.info.sensor_num = sensor_platform->sensor_num; + if (cros_ec_motion_send_host_cmd(state, 0)) { + dev_warn(dev, "Can not access sensor info\n"); + return -EIO; + } + state->type = state->resp->info.type; + state->loc = state->resp->info.location; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_init); + +int cros_ec_motion_send_host_cmd(struct cros_ec_sensors_core_state *state, + u16 opt_length) +{ + int ret; + + if (opt_length) + state->msg->insize = min(opt_length, state->ec->max_response); + else + state->msg->insize = state->ec->max_response; + + memcpy(state->msg->data, &state->param, sizeof(state->param)); + + ret = cros_ec_cmd_xfer_status(state->ec, state->msg); + if (ret < 0) + return -EIO; + + if (ret && + state->resp != (struct ec_response_motion_sense *)state->msg->data) + memcpy(state->resp, state->msg->data, ret); + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_motion_send_host_cmd); + +static ssize_t cros_ec_sensors_calibrate(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret, i; + bool calibrate; + + ret = strtobool(buf, &calibrate); + if (ret < 0) + return ret; + if (!calibrate) + return -EINVAL; + + mutex_lock(&st->cmd_lock); + st->param.cmd = MOTIONSENSE_CMD_PERFORM_CALIB; + ret = cros_ec_motion_send_host_cmd(st, 0); + if (ret != 0) { + dev_warn(&indio_dev->dev, "Unable to calibrate sensor\n"); + } else { + /* Save values */ + for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) + st->calib[i] = st->resp->perform_calib.offset[i]; + } + mutex_unlock(&st->cmd_lock); + + return ret ? ret : len; +} + +static ssize_t cros_ec_sensors_loc(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, + char *buf) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%s\n", cros_ec_loc[st->loc]); +} + +const struct iio_chan_spec_ext_info cros_ec_sensors_ext_info[] = { + { + .name = "calibrate", + .shared = IIO_SHARED_BY_ALL, + .write = cros_ec_sensors_calibrate + }, + { + .name = "location", + .shared = IIO_SHARED_BY_ALL, + .read = cros_ec_sensors_loc + }, + { }, +}; +EXPORT_SYMBOL_GPL(cros_ec_sensors_ext_info); + +/** + * cros_ec_sensors_idx_to_reg - convert index into offset in shared memory + * @st: pointer to state information for device + * @idx: sensor index (should be element of enum sensor_index) + * + * Return: address to read at + */ +static unsigned int cros_ec_sensors_idx_to_reg( + struct cros_ec_sensors_core_state *st, + unsigned int idx) +{ + /* + * When using LPC interface, only space for 2 Accel and one Gyro. + * First halfword of MOTIONSENSE_TYPE_ACCEL is used by angle. + */ + if (st->type == MOTIONSENSE_TYPE_ACCEL) + return EC_MEMMAP_ACC_DATA + sizeof(u16) * + (1 + idx + st->param.info.sensor_num * + CROS_EC_SENSOR_MAX_AXIS); + + return EC_MEMMAP_GYRO_DATA + sizeof(u16) * idx; +} + +static int cros_ec_sensors_cmd_read_u8(struct cros_ec_device *ec, + unsigned int offset, u8 *dest) +{ + return ec->cmd_readmem(ec, offset, 1, dest); +} + +static int cros_ec_sensors_cmd_read_u16(struct cros_ec_device *ec, + unsigned int offset, u16 *dest) +{ + __le16 tmp; + int ret = ec->cmd_readmem(ec, offset, 2, &tmp); + + if (ret >= 0) + *dest = le16_to_cpu(tmp); + + return ret; +} + +/** + * cros_ec_sensors_read_until_not_busy() - read until is not busy + * + * @st: pointer to state information for device + * + * Read from EC status byte until it reads not busy. + * Return: 8-bit status if ok, -errno on failure. + */ +static int cros_ec_sensors_read_until_not_busy( + struct cros_ec_sensors_core_state *st) +{ + struct cros_ec_device *ec = st->ec; + u8 status; + int ret, attempts = 0; + + ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, &status); + if (ret < 0) + return ret; + + while (status & EC_MEMMAP_ACC_STATUS_BUSY_BIT) { + /* Give up after enough attempts, return error. */ + if (attempts++ >= 50) + return -EIO; + + /* Small delay every so often. */ + if (attempts % 5 == 0) + msleep(25); + + ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, + &status); + if (ret < 0) + return ret; + } + + return status; +} + +/** + * read_ec_sensors_data_unsafe() - read acceleration data from EC shared memory + * @indio_dev: pointer to IIO device + * @scan_mask: bitmap of the sensor indices to scan + * @data: location to store data + * + * This is the unsafe function for reading the EC data. It does not guarantee + * that the EC will not modify the data as it is being read in. + * + * Return: 0 on success, -errno on failure. + */ +static int cros_ec_sensors_read_data_unsafe(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + struct cros_ec_device *ec = st->ec; + unsigned int i; + int ret; + + /* Read all sensors enabled in scan_mask. Each value is 2 bytes. */ + for_each_set_bit(i, &scan_mask, indio_dev->masklength) { + ret = cros_ec_sensors_cmd_read_u16(ec, + cros_ec_sensors_idx_to_reg(st, i), + data); + if (ret < 0) + return ret; + + data++; + } + + return 0; +} + +int cros_ec_sensors_read_lpc(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + struct cros_ec_device *ec = st->ec; + u8 samp_id = 0xff, status = 0; + int ret, attempts = 0; + + /* + * Continually read all data from EC until the status byte after + * all reads reflects that the EC is not busy and the sample id + * matches the sample id from before all reads. This guarantees + * that data read in was not modified by the EC while reading. + */ + while ((status & (EC_MEMMAP_ACC_STATUS_BUSY_BIT | + EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK)) != samp_id) { + /* If we have tried to read too many times, return error. */ + if (attempts++ >= 5) + return -EIO; + + /* Read status byte until EC is not busy. */ + status = cros_ec_sensors_read_until_not_busy(st); + if (status < 0) + return status; + + /* + * Store the current sample id so that we can compare to the + * sample id after reading the data. + */ + samp_id = status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK; + + /* Read all EC data, format it, and store it into data. */ + ret = cros_ec_sensors_read_data_unsafe(indio_dev, scan_mask, + data); + if (ret < 0) + return ret; + + /* Read status byte. */ + ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, + &status); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_read_lpc); + +int cros_ec_sensors_read_cmd(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data) +{ + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret; + unsigned int i; + + /* Read all sensor data through a command. */ + st->param.cmd = MOTIONSENSE_CMD_DATA; + ret = cros_ec_motion_send_host_cmd(st, sizeof(st->resp->data)); + if (ret != 0) { + dev_warn(&indio_dev->dev, "Unable to read sensor data\n"); + return ret; + } + + for_each_set_bit(i, &scan_mask, indio_dev->masklength) { + *data = st->resp->data.data[i]; + data++; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_read_cmd); + +irqreturn_t cros_ec_sensors_capture(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct cros_ec_sensors_core_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->cmd_lock); + + /* Clear capture data. */ + memset(st->samples, 0, indio_dev->scan_bytes); + + /* Read data based on which channels are enabled in scan mask. */ + ret = st->read_ec_sensors_data(indio_dev, + *(indio_dev->active_scan_mask), + (s16 *)st->samples); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, st->samples, + iio_get_time_ns(indio_dev)); + +done: + /* + * Tell the core we are done with this trigger and ready for the + * next one. + */ + iio_trigger_notify_done(indio_dev->trig); + + mutex_unlock(&st->cmd_lock); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_capture); + +int cros_ec_sensors_core_read(struct cros_ec_sensors_core_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret = IIO_VAL_INT; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + st->param.cmd = MOTIONSENSE_CMD_EC_RATE; + st->param.ec_rate.data = + EC_MOTION_SENSE_NO_VALUE; + + if (cros_ec_motion_send_host_cmd(st, 0)) + ret = -EIO; + else + *val = st->resp->ec_rate.ret; + break; + case IIO_CHAN_INFO_FREQUENCY: + st->param.cmd = MOTIONSENSE_CMD_SENSOR_ODR; + st->param.sensor_odr.data = + EC_MOTION_SENSE_NO_VALUE; + + if (cros_ec_motion_send_host_cmd(st, 0)) + ret = -EIO; + else + *val = st->resp->sensor_odr.ret; + break; + default: + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_read); + +int cros_ec_sensors_core_write(struct cros_ec_sensors_core_state *st, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_FREQUENCY: + st->param.cmd = MOTIONSENSE_CMD_SENSOR_ODR; + st->param.sensor_odr.data = val; + + /* Always roundup, so caller gets at least what it asks for. */ + st->param.sensor_odr.roundup = 1; + + if (cros_ec_motion_send_host_cmd(st, 0)) + ret = -EIO; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + st->param.cmd = MOTIONSENSE_CMD_EC_RATE; + st->param.ec_rate.data = val; + + if (cros_ec_motion_send_host_cmd(st, 0)) + ret = -EIO; + else + st->curr_sampl_freq = val; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} +EXPORT_SYMBOL_GPL(cros_ec_sensors_core_write); + +MODULE_DESCRIPTION("ChromeOS EC sensor hub core functions"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.h b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.h new file mode 100644 index 000000000000..8bc2ca3c2e2e --- /dev/null +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.h @@ -0,0 +1,175 @@ +/* + * ChromeOS EC sensor hub + * + * Copyright (C) 2016 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __CROS_EC_SENSORS_CORE_H +#define __CROS_EC_SENSORS_CORE_H + +#include + +enum { + CROS_EC_SENSOR_X, + CROS_EC_SENSOR_Y, + CROS_EC_SENSOR_Z, + CROS_EC_SENSOR_MAX_AXIS, +}; + +/* EC returns sensor values using signed 16 bit registers */ +#define CROS_EC_SENSOR_BITS 16 + +/* + * 4 16 bit channels are allowed. + * Good enough for current sensors, they use up to 3 16 bit vectors. + */ +#define CROS_EC_SAMPLE_SIZE (sizeof(s64) * 2) + +/* Minimum sampling period to use when device is suspending */ +#define CROS_EC_MIN_SUSPEND_SAMPLING_FREQUENCY 1000 /* 1 second */ + +/** + * struct cros_ec_sensors_core_state - state data for EC sensors IIO driver + * @ec: cros EC device structure + * @cmd_lock: lock used to prevent simultaneous access to the + * commands. + * @msg: cros EC command structure + * @param: motion sensor parameters structure + * @resp: motion sensor response structure + * @type: type of motion sensor + * @loc: location where the motion sensor is placed + * @calib: calibration parameters. Note that trigger + * captured data will always provide the calibrated + * data + * @samples: static array to hold data from a single capture. + * For each channel we need 2 bytes, except for + * the timestamp. The timestamp is always last and + * is always 8-byte aligned. + * @read_ec_sensors_data: function used for accessing sensors values + * @cuur_sampl_freq: current sampling period + */ +struct cros_ec_sensors_core_state { + struct cros_ec_device *ec; + struct mutex cmd_lock; + + struct cros_ec_command *msg; + struct ec_params_motion_sense param; + struct ec_response_motion_sense *resp; + + enum motionsensor_type type; + enum motionsensor_location loc; + + s16 calib[CROS_EC_SENSOR_MAX_AXIS]; + + u8 samples[CROS_EC_SAMPLE_SIZE]; + + int (*read_ec_sensors_data)(struct iio_dev *indio_dev, + unsigned long scan_mask, s16 *data); + + int curr_sampl_freq; +}; + +/** + * cros_ec_sensors_read_lpc() - retrieve data from EC shared memory + * @indio_dev: pointer to IIO device + * @scan_mask: bitmap of the sensor indices to scan + * @data: location to store data + * + * This is the safe function for reading the EC data. It guarantees that the + * data sampled was not modified by the EC while being read. + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_sensors_read_lpc(struct iio_dev *indio_dev, unsigned long scan_mask, + s16 *data); + +/** + * cros_ec_sensors_read_cmd() - retrieve data using the EC command protocol + * @indio_dev: pointer to IIO device + * @scan_mask: bitmap of the sensor indices to scan + * @data: location to store data + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_sensors_read_cmd(struct iio_dev *indio_dev, unsigned long scan_mask, + s16 *data); + +/** + * cros_ec_sensors_core_init() - basic initialization of the core structure + * @pdev: platform device created for the sensors + * @indio_dev: iio device structure of the device + * @physical_device: true if the device refers to a physical device + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_sensors_core_init(struct platform_device *pdev, + struct iio_dev *indio_dev, bool physical_device); + +/** + * cros_ec_sensors_capture() - the trigger handler function + * @irq: the interrupt number. + * @p: a pointer to the poll function. + * + * On a trigger event occurring, if the pollfunc is attached then this + * handler is called as a threaded interrupt (and hence may sleep). It + * is responsible for grabbing data from the device and pushing it into + * the associated buffer. + * + * Return: IRQ_HANDLED + */ +irqreturn_t cros_ec_sensors_capture(int irq, void *p); + +/** + * cros_ec_motion_send_host_cmd() - send motion sense host command + * @st: pointer to state information for device + * @opt_length: optional length to reduce the response size, useful on the data + * path. Otherwise, the maximal allowed response size is used + * + * When called, the sub-command is assumed to be set in param->cmd. + * + * Return: 0 on success, -errno on failure. + */ +int cros_ec_motion_send_host_cmd(struct cros_ec_sensors_core_state *st, + u16 opt_length); + +/** + * cros_ec_sensors_core_read() - function to request a value from the sensor + * @st: pointer to state information for device + * @chan: channel specification structure table + * @val: will contain one element making up the returned value + * @val2: will contain another element making up the returned value + * @mask: specifies which values to be requested + * + * Return: the type of value returned by the device + */ +int cros_ec_sensors_core_read(struct cros_ec_sensors_core_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask); + +/** + * cros_ec_sensors_core_write() - function to write a value to the sensor + * @st: pointer to state information for device + * @chan: channel specification structure table + * @val: first part of value to write + * @val2: second part of value to write + * @mask: specifies which values to write + * + * Return: the type of value returned by the device + */ +int cros_ec_sensors_core_write(struct cros_ec_sensors_core_state *st, + struct iio_chan_spec const *chan, + int val, int val2, long mask); + +/* List of extended channel specification for all sensors */ +extern const struct iio_chan_spec_ext_info cros_ec_sensors_ext_info[]; + +#endif /* __CROS_EC_SENSORS_CORE_H */ diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index 76f7ef4d3a0d..1f85b7aff097 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -148,6 +148,15 @@ struct cros_ec_device { int event_size; }; +/** + * struct cros_ec_sensor_platform - ChromeOS EC sensor platform information + * + * @sensor_num: Id of the sensor, as reported by the EC. + */ +struct cros_ec_sensor_platform { + u8 sensor_num; +}; + /* struct cros_ec_platform - ChromeOS EC platform information * * @ec_name: name of EC device (e.g. 'cros-ec', 'cros-pd', ...) diff --git a/include/linux/mfd/cros_ec_commands.h b/include/linux/mfd/cros_ec_commands.h index 76728ff37d01..8826e0f64b0e 100644 --- a/include/linux/mfd/cros_ec_commands.h +++ b/include/linux/mfd/cros_ec_commands.h @@ -1315,6 +1315,24 @@ enum motionsense_command { */ MOTIONSENSE_CMD_KB_WAKE_ANGLE = 5, + /* + * Returns a single sensor data. + */ + MOTIONSENSE_CMD_DATA = 6, + + /* + * Perform low level calibration.. On sensors that support it, ask to + * do offset calibration. + */ + MOTIONSENSE_CMD_PERFORM_CALIB = 10, + + /* + * Sensor Offset command is a setter/getter command for the offset used + * for calibration. The offsets can be calculated by the host, or via + * PERFORM_CALIB command. + */ + MOTIONSENSE_CMD_SENSOR_OFFSET = 11, + /* Number of motionsense sub-commands. */ MOTIONSENSE_NUM_CMDS }; @@ -1335,12 +1353,18 @@ enum motionsensor_id { enum motionsensor_type { MOTIONSENSE_TYPE_ACCEL = 0, MOTIONSENSE_TYPE_GYRO = 1, + MOTIONSENSE_TYPE_MAG = 2, + MOTIONSENSE_TYPE_PROX = 3, + MOTIONSENSE_TYPE_LIGHT = 4, + MOTIONSENSE_TYPE_ACTIVITY = 5, + MOTIONSENSE_TYPE_MAX }; /* List of motion sensor locations. */ enum motionsensor_location { MOTIONSENSE_LOC_BASE = 0, MOTIONSENSE_LOC_LID = 1, + MOTIONSENSE_LOC_MAX, }; /* List of motion sensor chips. */ @@ -1361,6 +1385,31 @@ enum motionsensor_chip { */ #define EC_MOTION_SENSE_NO_VALUE -1 +#define EC_MOTION_SENSE_INVALID_CALIB_TEMP 0x8000 + +/* Set Calibration information */ +#define MOTION_SENSE_SET_OFFSET 1 + +struct ec_response_motion_sensor_data { + /* Flags for each sensor. */ + uint8_t flags; + /* Sensor number the data comes from */ + uint8_t sensor_num; + /* Each sensor is up to 3-axis. */ + union { + int16_t data[3]; + struct { + uint16_t rsvd; + uint32_t timestamp; + } __packed; + struct { + uint8_t activity; /* motionsensor_activity */ + uint8_t state; + int16_t add_info[2]; + }; + }; +} __packed; + struct ec_params_motion_sense { uint8_t cmd; union { @@ -1378,9 +1427,37 @@ struct ec_params_motion_sense { int16_t data; } ec_rate, kb_wake_angle; + /* Used for MOTIONSENSE_CMD_SENSOR_OFFSET */ + struct { + uint8_t sensor_num; + + /* + * bit 0: If set (MOTION_SENSE_SET_OFFSET), set + * the calibration information in the EC. + * If unset, just retrieve calibration information. + */ + uint16_t flags; + + /* + * Temperature at calibration, in units of 0.01 C + * 0x8000: invalid / unknown. + * 0x0: 0C + * 0x7fff: +327.67C + */ + int16_t temp; + + /* + * Offset for calibration. + * Unit: + * Accelerometer: 1/1024 g + * Gyro: 1/1024 deg/s + * Compass: 1/16 uT + */ + int16_t offset[3]; + } __packed sensor_offset; + /* Used for MOTIONSENSE_CMD_INFO. */ struct { - /* Should be element of enum motionsensor_id. */ uint8_t sensor_num; } info; @@ -1410,11 +1487,14 @@ struct ec_response_motion_sense { /* Flags representing the motion sensor module. */ uint8_t module_flags; - /* Flags for each sensor in enum motionsensor_id. */ - uint8_t sensor_flags[EC_MOTION_SENSOR_COUNT]; + /* Number of sensors managed directly by the EC. */ + uint8_t sensor_count; - /* Array of all sensor data. Each sensor is 3-axis. */ - int16_t data[3*EC_MOTION_SENSOR_COUNT]; + /* + * Sensor data is truncated if response_max is too small + * for holding all the data. + */ + struct ec_response_motion_sensor_data sensor[0]; } dump; /* Used for MOTIONSENSE_CMD_INFO. */ @@ -1429,6 +1509,9 @@ struct ec_response_motion_sense { uint8_t chip; } info; + /* Used for MOTIONSENSE_CMD_DATA */ + struct ec_response_motion_sensor_data data; + /* * Used for MOTIONSENSE_CMD_EC_RATE, MOTIONSENSE_CMD_SENSOR_ODR, * MOTIONSENSE_CMD_SENSOR_RANGE, and @@ -1438,6 +1521,12 @@ struct ec_response_motion_sense { /* Current value of the parameter queried. */ int32_t ret; } ec_rate, sensor_odr, sensor_range, kb_wake_angle; + + /* Used for MOTIONSENSE_CMD_SENSOR_OFFSET */ + struct { + int16_t temp; + int16_t offset[3]; + } sensor_offset, perform_calib; }; } __packed; -- cgit v1.2.3 From b0d801985418e0890adf14398373b9c4470911d1 Mon Sep 17 00:00:00 2001 From: Paul Kocialkowski Date: Tue, 25 Oct 2016 21:20:10 +0200 Subject: iio: si7020: Add devicetree support and trivial bindings This adds devicetree support for the si7020 iio driver. Since it works well without requiring any additional property, its compatible string is added to the trivial i2c devices bindings list. Signed-off-by: Paul Kocialkowski Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/i2c/trivial-devices.txt | 1 + drivers/iio/humidity/si7020.c | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt index 6e4ba816094b..03349ad5abfa 100644 --- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt +++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt @@ -152,6 +152,7 @@ ricoh,rv5c387a I2C bus SERIAL INTERFACE REAL-TIME CLOCK IC samsung,24ad0xd1 S524AD0XF1 (128K/256K-bit Serial EEPROM for Low Power) sgx,vz89x SGX Sensortech VZ89X Sensors sii,s35390a 2-wire CMOS real-time clock +silabs,si7020 Relative Humidity and Temperature Sensors skyworks,sky81452 Skyworks SKY81452: Six-Channel White LED Driver with Touch Panel Bias Supply st,24c256 i2c serial eeprom (24cxx) st,m41t00 Serial real-time clock (RTC) diff --git a/drivers/iio/humidity/si7020.c b/drivers/iio/humidity/si7020.c index ffc2ccf6374e..345a7656c5ef 100644 --- a/drivers/iio/humidity/si7020.c +++ b/drivers/iio/humidity/si7020.c @@ -154,8 +154,17 @@ static const struct i2c_device_id si7020_id[] = { }; MODULE_DEVICE_TABLE(i2c, si7020_id); +static const struct of_device_id si7020_dt_ids[] = { + { .compatible = "silabs,si7020" }, + { } +}; +MODULE_DEVICE_TABLE(of, si7020_dt_ids); + static struct i2c_driver si7020_driver = { - .driver.name = "si7020", + .driver = { + .name = "si7020", + .of_match_table = of_match_ptr(si7020_dt_ids), + }, .probe = si7020_probe, .id_table = si7020_id, }; -- cgit v1.2.3 From 1fbf148106326a457779934d00f55fc5417e35d9 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Tue, 25 Oct 2016 23:09:04 +0200 Subject: Documentation: dt: iio: accel: add lng2dm sensor device binding Signed-off-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/st-sensors.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/iio/st-sensors.txt b/Documentation/devicetree/bindings/iio/st-sensors.txt index e41fe340162b..c040c9ad1889 100644 --- a/Documentation/devicetree/bindings/iio/st-sensors.txt +++ b/Documentation/devicetree/bindings/iio/st-sensors.txt @@ -42,6 +42,7 @@ Accelerometers: - st,lsm303agr-accel - st,lis2dh12-accel - st,h3lis331dl-accel +- st,lng2dm-accel Gyroscopes: - st,l3g4200d-gyro -- cgit v1.2.3 From a3b6b4b63beda16f3e91fdd942bbaaaf6b1f8b25 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Fri, 28 Oct 2016 06:00:12 -0400 Subject: staging: iio: tsl2583: add of_match table for device tree support Add device tree support for the tsl2583 IIO driver with no custom properties. Signed-off-by: Brian Masney Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/light/tsl2583.txt | 26 ++++++++++++++++++++++ drivers/staging/iio/light/tsl2583.c | 13 +++++++++++ 2 files changed, 39 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/tsl2583.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/iio/light/tsl2583.txt b/Documentation/devicetree/bindings/iio/light/tsl2583.txt new file mode 100644 index 000000000000..8e2066c83f70 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/light/tsl2583.txt @@ -0,0 +1,26 @@ +* TAOS TSL 2580/2581/2583 ALS sensor + +Required properties: + + - compatible: Should be one of + "amstaos,tsl2580" + "amstaos,tsl2581" + "amstaos,tsl2583" + - reg: the I2C address of the device + +Optional properties: + + - interrupt-parent: should be the phandle for the interrupt controller + - interrupts: the sole interrupt generated by the device + + Refer to interrupt-controller/interrupts.txt for generic interrupt client + node bindings. + + - vcc-supply: phandle to the regulator that provides power to the sensor. + +Example: + +tsl2581@29 { + compatible = "amstaos,tsl2581"; + reg = <0x29>; +}; diff --git a/drivers/staging/iio/light/tsl2583.c b/drivers/staging/iio/light/tsl2583.c index 08f1583ee34e..fd4b6efd3016 100644 --- a/drivers/staging/iio/light/tsl2583.c +++ b/drivers/staging/iio/light/tsl2583.c @@ -947,11 +947,24 @@ static struct i2c_device_id taos_idtable[] = { }; MODULE_DEVICE_TABLE(i2c, taos_idtable); +#ifdef CONFIG_OF +static const struct of_device_id taos2583_of_match[] = { + { .compatible = "amstaos,tsl2580", }, + { .compatible = "amstaos,tsl2581", }, + { .compatible = "amstaos,tsl2583", }, + { }, +}; +MODULE_DEVICE_TABLE(of, taos2583_of_match); +#else +#define taos2583_of_match NULL +#endif + /* Driver definition */ static struct i2c_driver taos_driver = { .driver = { .name = "tsl2583", .pm = TAOS_PM_OPS, + .of_match_table = taos2583_of_match, }, .id_table = taos_idtable, .probe = taos_probe, -- cgit v1.2.3 From 2704e30014dd2f4e27675abda414e91de5bef8a4 Mon Sep 17 00:00:00 2001 From: Peter Rosin Date: Tue, 8 Nov 2016 12:58:53 +0100 Subject: iio: mcp4531: provide range of available raw values Example: $ cat '/sys/bus/iio/devices/iio:device0/out_resistance_raw_available' [0 1 256] Meaning: min 0, step 1 and max 256. Signed-off-by: Peter Rosin Signed-off-by: Jonathan Cameron --- .../testing/sysfs-bus-iio-potentiometer-mcp4531 | 8 ++ MAINTAINERS | 1 + drivers/iio/potentiometer/mcp4531.c | 104 ++++++++++++--------- 3 files changed, 71 insertions(+), 42 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-potentiometer-mcp4531 (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-potentiometer-mcp4531 b/Documentation/ABI/testing/sysfs-bus-iio-potentiometer-mcp4531 new file mode 100644 index 000000000000..2a91fbe394fc --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-potentiometer-mcp4531 @@ -0,0 +1,8 @@ +What: /sys/bus/iio/devices/iio:deviceX/out_resistance_raw_available +Date: October 2016 +KernelVersion: 4.9 +Contact: Peter Rosin +Description: + The range of available values represented as the minimum value, + the step and the maximum value, all enclosed in square brackets. + Example: [0 1 256] diff --git a/MAINTAINERS b/MAINTAINERS index ec1ee3ef112e..1395246819d2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7709,6 +7709,7 @@ MCP4531 MICROCHIP DIGITAL POTENTIOMETER DRIVER M: Peter Rosin L: linux-iio@vger.kernel.org S: Maintained +F: Documentation/ABI/testing/sysfs-bus-iio-potentiometer-mcp4531 F: drivers/iio/potentiometer/mcp4531.c MEASUREMENT COMPUTING CIO-DAC IIO DRIVER diff --git a/drivers/iio/potentiometer/mcp4531.c b/drivers/iio/potentiometer/mcp4531.c index 13b6ae2fcf7b..0d1bcf89ae17 100644 --- a/drivers/iio/potentiometer/mcp4531.c +++ b/drivers/iio/potentiometer/mcp4531.c @@ -38,7 +38,7 @@ struct mcp4531_cfg { int wipers; - int max_pos; + int avail[3]; int kohms; }; @@ -78,38 +78,38 @@ enum mcp4531_type { }; static const struct mcp4531_cfg mcp4531_cfg[] = { - [MCP453x_502] = { .wipers = 1, .max_pos = 128, .kohms = 5, }, - [MCP453x_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, }, - [MCP453x_503] = { .wipers = 1, .max_pos = 128, .kohms = 50, }, - [MCP453x_104] = { .wipers = 1, .max_pos = 128, .kohms = 100, }, - [MCP454x_502] = { .wipers = 1, .max_pos = 128, .kohms = 5, }, - [MCP454x_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, }, - [MCP454x_503] = { .wipers = 1, .max_pos = 128, .kohms = 50, }, - [MCP454x_104] = { .wipers = 1, .max_pos = 128, .kohms = 100, }, - [MCP455x_502] = { .wipers = 1, .max_pos = 256, .kohms = 5, }, - [MCP455x_103] = { .wipers = 1, .max_pos = 256, .kohms = 10, }, - [MCP455x_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, }, - [MCP455x_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, }, - [MCP456x_502] = { .wipers = 1, .max_pos = 256, .kohms = 5, }, - [MCP456x_103] = { .wipers = 1, .max_pos = 256, .kohms = 10, }, - [MCP456x_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, }, - [MCP456x_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, }, - [MCP463x_502] = { .wipers = 2, .max_pos = 128, .kohms = 5, }, - [MCP463x_103] = { .wipers = 2, .max_pos = 128, .kohms = 10, }, - [MCP463x_503] = { .wipers = 2, .max_pos = 128, .kohms = 50, }, - [MCP463x_104] = { .wipers = 2, .max_pos = 128, .kohms = 100, }, - [MCP464x_502] = { .wipers = 2, .max_pos = 128, .kohms = 5, }, - [MCP464x_103] = { .wipers = 2, .max_pos = 128, .kohms = 10, }, - [MCP464x_503] = { .wipers = 2, .max_pos = 128, .kohms = 50, }, - [MCP464x_104] = { .wipers = 2, .max_pos = 128, .kohms = 100, }, - [MCP465x_502] = { .wipers = 2, .max_pos = 256, .kohms = 5, }, - [MCP465x_103] = { .wipers = 2, .max_pos = 256, .kohms = 10, }, - [MCP465x_503] = { .wipers = 2, .max_pos = 256, .kohms = 50, }, - [MCP465x_104] = { .wipers = 2, .max_pos = 256, .kohms = 100, }, - [MCP466x_502] = { .wipers = 2, .max_pos = 256, .kohms = 5, }, - [MCP466x_103] = { .wipers = 2, .max_pos = 256, .kohms = 10, }, - [MCP466x_503] = { .wipers = 2, .max_pos = 256, .kohms = 50, }, - [MCP466x_104] = { .wipers = 2, .max_pos = 256, .kohms = 100, }, + [MCP453x_502] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 5, }, + [MCP453x_103] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 10, }, + [MCP453x_503] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 50, }, + [MCP453x_104] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 100, }, + [MCP454x_502] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 5, }, + [MCP454x_103] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 10, }, + [MCP454x_503] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 50, }, + [MCP454x_104] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 100, }, + [MCP455x_502] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 5, }, + [MCP455x_103] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 10, }, + [MCP455x_503] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 50, }, + [MCP455x_104] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 100, }, + [MCP456x_502] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 5, }, + [MCP456x_103] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 10, }, + [MCP456x_503] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 50, }, + [MCP456x_104] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 100, }, + [MCP463x_502] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 5, }, + [MCP463x_103] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 10, }, + [MCP463x_503] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 50, }, + [MCP463x_104] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 100, }, + [MCP464x_502] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 5, }, + [MCP464x_103] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 10, }, + [MCP464x_503] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 50, }, + [MCP464x_104] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 100, }, + [MCP465x_502] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 5, }, + [MCP465x_103] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 10, }, + [MCP465x_503] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 50, }, + [MCP465x_104] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 100, }, + [MCP466x_502] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 5, }, + [MCP466x_103] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 10, }, + [MCP466x_503] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 50, }, + [MCP466x_104] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 100, }, }; #define MCP4531_WRITE (0 << 2) @@ -124,13 +124,14 @@ struct mcp4531_data { const struct mcp4531_cfg *cfg; }; -#define MCP4531_CHANNEL(ch) { \ - .type = IIO_RESISTANCE, \ - .indexed = 1, \ - .output = 1, \ - .channel = (ch), \ - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +#define MCP4531_CHANNEL(ch) { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_RAW), \ } static const struct iio_chan_spec mcp4531_channels[] = { @@ -156,13 +157,31 @@ static int mcp4531_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = 1000 * data->cfg->kohms; - *val2 = data->cfg->max_pos; + *val2 = data->cfg->avail[2]; return IIO_VAL_FRACTIONAL; } return -EINVAL; } +static int mcp4531_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct mcp4531_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *length = ARRAY_SIZE(data->cfg->avail); + *vals = data->cfg->avail; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + } + + return -EINVAL; +} + static int mcp4531_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) @@ -172,7 +191,7 @@ static int mcp4531_write_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: - if (val > data->cfg->max_pos || val < 0) + if (val > data->cfg->avail[2] || val < 0) return -EINVAL; break; default: @@ -186,6 +205,7 @@ static int mcp4531_write_raw(struct iio_dev *indio_dev, static const struct iio_info mcp4531_info = { .read_raw = mcp4531_read_raw, + .read_avail = mcp4531_read_avail, .write_raw = mcp4531_write_raw, .driver_module = THIS_MODULE, }; -- cgit v1.2.3 From ff6bd170c04628b19db87e03d654952f1e459fbc Mon Sep 17 00:00:00 2001 From: Peter Rosin Date: Tue, 8 Nov 2016 12:58:54 +0100 Subject: dt-bindings: add axentia to vendor-prefixes Acked-by: Rob Herring Signed-off-by: Peter Rosin Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/vendor-prefixes.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 8d04aa99187c..8e4253e20138 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -39,6 +39,7 @@ auo AU Optronics Corporation auvidea Auvidea GmbH avago Avago Technologies avic Shanghai AVIC Optoelectronics Co., Ltd. +axentia Axentia Technologies AB axis Axis Communications AB boe BOE Technology Group Co., Ltd. bosch Bosch Sensortec GmbH -- cgit v1.2.3 From ed13134ba8c021d484d712a54c285da312567b39 Mon Sep 17 00:00:00 2001 From: Peter Rosin Date: Tue, 8 Nov 2016 12:58:55 +0100 Subject: dt-bindings: iio: document dpot-dac bindings Acked-by: Rob Herring Signed-off-by: Peter Rosin Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/dac/dpot-dac.txt | 41 ++++++++++++++++++++++ MAINTAINERS | 6 ++++ 2 files changed, 47 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/dpot-dac.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/iio/dac/dpot-dac.txt b/Documentation/devicetree/bindings/iio/dac/dpot-dac.txt new file mode 100644 index 000000000000..fdf47a01bfef --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/dpot-dac.txt @@ -0,0 +1,41 @@ +Bindings for DAC emulation using a digital potentiometer + +It is assumed that the dpot is used as a voltage divider between the +current dpot wiper setting and the maximum resistance of the dpot. The +divided voltage is provided by a vref regulator. + + .------. + .-----------. | | + | vref |--' .---. + | regulator |--. | | + '-----------' | | d | + | | p | + | | o | wiper + | | t |<---------+ + | | | + | '---' dac output voltage + | | + '------+------------+ + +Required properties: +- compatible: Should be "dpot-dac" +- vref-supply: The regulator supplying the voltage divider. +- io-channels: Channel node of the dpot to be used for the voltage division. +- io-channel-names: Should be "dpot". + +Example: + + &i2c { + dpot: mcp4651-503@28 { + compatible = "microchip,mcp4651-503"; + reg = <0x28>; + #io-channel-cells = <1>; + }; + }; + + dac { + compatible = "dpot-dac"; + vref-supply = <®_3v3>; + io-channels = <&dpot 0>; + io-channel-names = "dpot"; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 1395246819d2..0de06732159d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6119,6 +6119,12 @@ L: linux-media@vger.kernel.org S: Maintained F: drivers/media/rc/iguanair.c +IIO DIGITAL POTENTIOMETER DAC +M: Peter Rosin +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/dac/dpot-dac.txt + IIO SUBSYSTEM AND DRIVERS M: Jonathan Cameron R: Hartmut Knaack -- cgit v1.2.3 From 7fde1484af21f9668e9575bd8a119ebc4fe6fe42 Mon Sep 17 00:00:00 2001 From: Peter Rosin Date: Tue, 8 Nov 2016 12:58:56 +0100 Subject: iio: dpot-dac: DAC driver based on a digital potentiometer It is assumed that the dpot is used as a voltage divider between the current dpot wiper setting and the maximum resistance of the dpot. The divided voltage is provided by a vref regulator. .------. .-----------. | | | vref |--' .---. | regulator |--. | | '-----------' | | d | | | p | | | o | wiper | | t |<---------+ | | | | '---' dac output voltage | | '------+------------+ Signed-off-by: Peter Rosin Signed-off-by: Jonathan Cameron --- .../ABI/testing/sysfs-bus-iio-dac-dpot-dac | 8 + MAINTAINERS | 2 + drivers/iio/dac/Kconfig | 10 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/dpot-dac.c | 266 +++++++++++++++++++++ 5 files changed, 287 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac create mode 100644 drivers/iio/dac/dpot-dac.c (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac b/Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac new file mode 100644 index 000000000000..580e93f373f6 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac @@ -0,0 +1,8 @@ +What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_raw_available +Date: October 2016 +KernelVersion: 4.9 +Contact: Peter Rosin +Description: + The range of available values represented as the minimum value, + the step and the maximum value, all enclosed in square brackets. + Example: [0 1 256] diff --git a/MAINTAINERS b/MAINTAINERS index 0de06732159d..e63719d53658 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6123,7 +6123,9 @@ IIO DIGITAL POTENTIOMETER DAC M: Peter Rosin L: linux-iio@vger.kernel.org S: Maintained +F: Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac F: Documentation/devicetree/bindings/iio/dac/dpot-dac.txt +F: drivers/iio/dac/dpot-dac.c IIO SUBSYSTEM AND DRIVERS M: Jonathan Cameron diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 120b24478469..d3084028905b 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -200,6 +200,16 @@ config AD8801 To compile this driver as a module choose M here: the module will be called ad8801. +config DPOT_DAC + tristate "DAC emulation using a DPOT" + depends on OF + help + Say yes here to build support for DAC emulation using a digital + potentiometer. + + To compile this driver as a module, choose M here: the module will be + called dpot-dac. + config LPC18XX_DAC tristate "NXP LPC18xx DAC driver" depends on ARCH_LPC18XX || COMPILE_TEST diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 27642bbf75f2..f01bf4a99867 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_AD5686) += ad5686.o obj-$(CONFIG_AD7303) += ad7303.o obj-$(CONFIG_AD8801) += ad8801.o obj-$(CONFIG_CIO_DAC) += cio-dac.o +obj-$(CONFIG_DPOT_DAC) += dpot-dac.o obj-$(CONFIG_LPC18XX_DAC) += lpc18xx_dac.o obj-$(CONFIG_M62332) += m62332.o obj-$(CONFIG_MAX517) += max517.o diff --git a/drivers/iio/dac/dpot-dac.c b/drivers/iio/dac/dpot-dac.c new file mode 100644 index 000000000000..960a2b430480 --- /dev/null +++ b/drivers/iio/dac/dpot-dac.c @@ -0,0 +1,266 @@ +/* + * IIO DAC emulation driver using a digital potentiometer + * + * Copyright (C) 2016 Axentia Technologies AB + * + * Author: Peter Rosin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * It is assumed that the dpot is used as a voltage divider between the + * current dpot wiper setting and the maximum resistance of the dpot. The + * divided voltage is provided by a vref regulator. + * + * .------. + * .-----------. | | + * | vref |--' .---. + * | regulator |--. | | + * '-----------' | | d | + * | | p | + * | | o | wiper + * | | t |<---------+ + * | | | + * | '---' dac output voltage + * | | + * '------+------------+ + */ + +#include +#include +#include +#include +#include +#include +#include + +struct dpot_dac { + struct regulator *vref; + struct iio_channel *dpot; + u32 max_ohms; +}; + +static const struct iio_chan_spec dpot_dac_iio_channel = { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), + .output = 1, + .indexed = 1, +}; + +static int dpot_dac_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dpot_dac *dac = iio_priv(indio_dev); + int ret; + unsigned long long tmp; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return iio_read_channel_raw(dac->dpot, val); + + case IIO_CHAN_INFO_SCALE: + ret = iio_read_channel_scale(dac->dpot, val, val2); + switch (ret) { + case IIO_VAL_FRACTIONAL_LOG2: + tmp = *val * 1000000000LL; + do_div(tmp, dac->max_ohms); + tmp *= regulator_get_voltage(dac->vref) / 1000; + do_div(tmp, 1000000000LL); + *val = tmp; + return ret; + case IIO_VAL_INT: + /* + * Convert integer scale to fractional scale by + * setting the denominator (val2) to one... + */ + *val2 = 1; + ret = IIO_VAL_FRACTIONAL; + /* ...and fall through. */ + case IIO_VAL_FRACTIONAL: + *val *= regulator_get_voltage(dac->vref) / 1000; + *val2 *= dac->max_ohms; + break; + } + + return ret; + } + + return -EINVAL; +} + +static int dpot_dac_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct dpot_dac *dac = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *type = IIO_VAL_INT; + return iio_read_avail_channel_raw(dac->dpot, vals, length); + } + + return -EINVAL; +} + +static int dpot_dac_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct dpot_dac *dac = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return iio_write_channel_raw(dac->dpot, val); + } + + return -EINVAL; +} + +static const struct iio_info dpot_dac_info = { + .read_raw = dpot_dac_read_raw, + .read_avail = dpot_dac_read_avail, + .write_raw = dpot_dac_write_raw, + .driver_module = THIS_MODULE, +}; + +static int dpot_dac_channel_max_ohms(struct iio_dev *indio_dev) +{ + struct device *dev = &indio_dev->dev; + struct dpot_dac *dac = iio_priv(indio_dev); + unsigned long long tmp; + int ret; + int val; + int val2; + int max; + + ret = iio_read_max_channel_raw(dac->dpot, &max); + if (ret < 0) { + dev_err(dev, "dpot does not indicate its raw maximum value\n"); + return ret; + } + + switch (iio_read_channel_scale(dac->dpot, &val, &val2)) { + case IIO_VAL_INT: + return max * val; + case IIO_VAL_FRACTIONAL: + tmp = (unsigned long long)max * val; + do_div(tmp, val2); + return tmp; + case IIO_VAL_FRACTIONAL_LOG2: + tmp = val * 1000000000LL * max >> val2; + do_div(tmp, 1000000000LL); + return tmp; + default: + dev_err(dev, "dpot has a scale that is too weird\n"); + } + + return -EINVAL; +} + +static int dpot_dac_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct dpot_dac *dac; + enum iio_chan_type type; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*dac)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + dac = iio_priv(indio_dev); + + indio_dev->name = dev_name(dev); + indio_dev->dev.parent = dev; + indio_dev->info = &dpot_dac_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &dpot_dac_iio_channel; + indio_dev->num_channels = 1; + + dac->vref = devm_regulator_get(dev, "vref"); + if (IS_ERR(dac->vref)) { + if (PTR_ERR(dac->vref) != -EPROBE_DEFER) + dev_err(&pdev->dev, "failed to get vref regulator\n"); + return PTR_ERR(dac->vref); + } + + dac->dpot = devm_iio_channel_get(dev, "dpot"); + if (IS_ERR(dac->dpot)) { + if (PTR_ERR(dac->dpot) != -EPROBE_DEFER) + dev_err(dev, "failed to get dpot input channel\n"); + return PTR_ERR(dac->dpot); + } + + ret = iio_get_channel_type(dac->dpot, &type); + if (ret < 0) + return ret; + + if (type != IIO_RESISTANCE) { + dev_err(dev, "dpot is of the wrong type\n"); + return -EINVAL; + } + + ret = dpot_dac_channel_max_ohms(indio_dev); + if (ret < 0) + return ret; + dac->max_ohms = ret; + + ret = regulator_enable(dac->vref); + if (ret) { + dev_err(dev, "failed to enable the vref regulator\n"); + return ret; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "failed to register iio device\n"); + goto disable_reg; + } + + return 0; + +disable_reg: + regulator_disable(dac->vref); + return ret; +} + +static int dpot_dac_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct dpot_dac *dac = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(dac->vref); + + return 0; +} + +static const struct of_device_id dpot_dac_match[] = { + { .compatible = "dpot-dac" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dpot_dac_match); + +static struct platform_driver dpot_dac_driver = { + .probe = dpot_dac_probe, + .remove = dpot_dac_remove, + .driver = { + .name = "iio-dpot-dac", + .of_match_table = dpot_dac_match, + }, +}; +module_platform_driver(dpot_dac_driver); + +MODULE_DESCRIPTION("DAC emulation driver using a digital potentiometer"); +MODULE_AUTHOR("Peter Rosin "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From e778aa142ab0666fa8af789a3bbabfb3334e6ff5 Mon Sep 17 00:00:00 2001 From: Peter Rosin Date: Tue, 8 Nov 2016 12:58:57 +0100 Subject: dt-bindings: iio: document envelope-detector bindings Acked-by: Rob Herring Signed-off-by: Peter Rosin Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/envelope-detector.txt | 54 ++++++++++++++++++++++ MAINTAINERS | 6 +++ 2 files changed, 60 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/envelope-detector.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/iio/adc/envelope-detector.txt b/Documentation/devicetree/bindings/iio/adc/envelope-detector.txt new file mode 100644 index 000000000000..27544bdd4478 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/envelope-detector.txt @@ -0,0 +1,54 @@ +Bindings for ADC envelope detector using a DAC and a comparator + +The DAC is used to find the peak level of an alternating voltage input +signal by a binary search using the output of a comparator wired to +an interrupt pin. Like so: + _ + | \ + input +------>-------|+ \ + | \ + .-------. | }---. + | | | / | + | dac|-->--|- / | + | | |_/ | + | | | + | | | + | irq|------<-------' + | | + '-------' + +Required properties: +- compatible: Should be "axentia,tse850-envelope-detector" +- io-channels: Channel node of the dac to be used for comparator input. +- io-channel-names: Should be "dac". +- interrupt specification for one client interrupt, + see ../../interrupt-controller/interrupts.txt for details. +- interrupt-names: Should be "comp". + +Example: + + &i2c { + dpot: mcp4651-104@28 { + compatible = "microchip,mcp4651-104"; + reg = <0x28>; + #io-channel-cells = <1>; + }; + }; + + dac: dac { + compatible = "dpot-dac"; + vref-supply = <®_3v3>; + io-channels = <&dpot 0>; + io-channel-names = "dpot"; + #io-channel-cells = <1>; + }; + + envelope-detector { + compatible = "axentia,tse850-envelope-detector"; + io-channels = <&dac 0>; + io-channel-names = "dac"; + + interrupt-parent = <&gpio>; + interrupts = <3 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "comp"; + }; diff --git a/MAINTAINERS b/MAINTAINERS index e63719d53658..76c3124fd62f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6127,6 +6127,12 @@ F: Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac F: Documentation/devicetree/bindings/iio/dac/dpot-dac.txt F: drivers/iio/dac/dpot-dac.c +IIO ENVELOPE DETECTOR +M: Peter Rosin +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/adc/envelope-detector.txt + IIO SUBSYSTEM AND DRIVERS M: Jonathan Cameron R: Hartmut Knaack -- cgit v1.2.3 From b475f80b354a1915fda1b34070d712b825b60543 Mon Sep 17 00:00:00 2001 From: Peter Rosin Date: Tue, 8 Nov 2016 12:58:58 +0100 Subject: iio: envelope-detector: ADC driver based on a DAC and a comparator The DAC is used to find the peak level of an alternating voltage input signal by a binary search using the output of a comparator wired to an interrupt pin. Like so: _ | \ input +------>-------|+ \ | \ .-------. | }---. | | | / | | dac|-->--|- / | | | |_/ | | | | | | | | irq|------<-------' | | '-------' Signed-off-by: Peter Rosin Acked-by: Thomas Gleixner Signed-off-by: Jonathan Cameron --- .../testing/sysfs-bus-iio-adc-envelope-detector | 36 ++ MAINTAINERS | 2 + drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/envelope-detector.c | 422 +++++++++++++++++++++ 5 files changed, 471 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector create mode 100644 drivers/iio/adc/envelope-detector.c (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector b/Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector new file mode 100644 index 000000000000..2071f9bcfaa5 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector @@ -0,0 +1,36 @@ +What: /sys/bus/iio/devices/iio:deviceX/in_altvoltageY_invert +Date: October 2016 +KernelVersion: 4.9 +Contact: Peter Rosin +Description: + The DAC is used to find the peak level of an alternating + voltage input signal by a binary search using the output + of a comparator wired to an interrupt pin. Like so: + _ + | \ + input +------>-------|+ \ + | \ + .-------. | }---. + | | | / | + | dac|-->--|- / | + | | |_/ | + | | | + | | | + | irq|------<-------' + | | + '-------' + The boolean invert attribute (0/1) should be set when the + input signal is centered around the maximum value of the + dac instead of zero. The envelope detector will search + from below in this case and will also invert the result. + The edge/level of the interrupt is also switched to its + opposite value. + +What: /sys/bus/iio/devices/iio:deviceX/in_altvoltageY_compare_interval +Date: October 2016 +KernelVersion: 4.9 +Contact: Peter Rosin +Description: + Number of milliseconds to wait for the comparator in each + step of the binary search for the input peak level. Needs + to relate to the frequency of the input signal. diff --git a/MAINTAINERS b/MAINTAINERS index 76c3124fd62f..0393f1f1bee7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6131,7 +6131,9 @@ IIO ENVELOPE DETECTOR M: Peter Rosin L: linux-iio@vger.kernel.org S: Maintained +F: Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector F: Documentation/devicetree/bindings/iio/adc/envelope-detector.txt +F: drivers/iio/adc/envelope-detector.c IIO SUBSYSTEM AND DRIVERS M: Jonathan Cameron diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 57ebb997072c..6bbee0b0dfff 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -207,6 +207,16 @@ config DA9150_GPADC To compile this driver as a module, choose M here: the module will be called berlin2-adc. +config ENVELOPE_DETECTOR + tristate "Envelope detector using a DAC and a comparator" + depends on OF + help + Say yes here to build support for an envelope detector using a DAC + and a comparator. + + To compile this driver as a module, choose M here: the module will be + called envelope-detector. + config EXYNOS_ADC tristate "Exynos ADC driver support" depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST) diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 96894b32300d..9391217648cb 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_BCM_IPROC_ADC) += bcm_iproc_adc.o obj-$(CONFIG_BERLIN2_ADC) += berlin2-adc.o obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o +obj-$(CONFIG_ENVELOPE_DETECTOR) += envelope-detector.o obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o obj-$(CONFIG_HI8435) += hi8435.o diff --git a/drivers/iio/adc/envelope-detector.c b/drivers/iio/adc/envelope-detector.c new file mode 100644 index 000000000000..fef15c0d7c9c --- /dev/null +++ b/drivers/iio/adc/envelope-detector.c @@ -0,0 +1,422 @@ +/* + * Driver for an envelope detector using a DAC and a comparator + * + * Copyright (C) 2016 Axentia Technologies AB + * + * Author: Peter Rosin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * The DAC is used to find the peak level of an alternating voltage input + * signal by a binary search using the output of a comparator wired to + * an interrupt pin. Like so: + * _ + * | \ + * input +------>-------|+ \ + * | \ + * .-------. | }---. + * | | | / | + * | dac|-->--|- / | + * | | |_/ | + * | | | + * | | | + * | irq|------<-------' + * | | + * '-------' + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct envelope { + spinlock_t comp_lock; /* protects comp */ + int comp; + + struct mutex read_lock; /* protects everything else */ + + int comp_irq; + u32 comp_irq_trigger; + u32 comp_irq_trigger_inv; + + struct iio_channel *dac; + struct delayed_work comp_timeout; + + unsigned int comp_interval; + bool invert; + u32 dac_max; + + int high; + int level; + int low; + + struct completion done; +}; + +/* + * The envelope_detector_comp_latch function works together with the compare + * interrupt service routine below (envelope_detector_comp_isr) as a latch + * (one-bit memory) for if the interrupt has triggered since last calling + * this function. + * The ..._comp_isr function disables the interrupt so that the cpu does not + * need to service a possible interrupt flood from the comparator when no-one + * cares anyway, and this ..._comp_latch function reenables them again if + * needed. + */ +static int envelope_detector_comp_latch(struct envelope *env) +{ + int comp; + + spin_lock_irq(&env->comp_lock); + comp = env->comp; + env->comp = 0; + spin_unlock_irq(&env->comp_lock); + + if (!comp) + return 0; + + /* + * The irq was disabled, and is reenabled just now. + * But there might have been a pending irq that + * happened while the irq was disabled that fires + * just as the irq is reenabled. That is not what + * is desired. + */ + enable_irq(env->comp_irq); + + /* So, synchronize this possibly pending irq... */ + synchronize_irq(env->comp_irq); + + /* ...and redo the whole dance. */ + spin_lock_irq(&env->comp_lock); + comp = env->comp; + env->comp = 0; + spin_unlock_irq(&env->comp_lock); + + if (comp) + enable_irq(env->comp_irq); + + return 1; +} + +static irqreturn_t envelope_detector_comp_isr(int irq, void *ctx) +{ + struct envelope *env = ctx; + + spin_lock(&env->comp_lock); + env->comp = 1; + disable_irq_nosync(env->comp_irq); + spin_unlock(&env->comp_lock); + + return IRQ_HANDLED; +} + +static void envelope_detector_setup_compare(struct envelope *env) +{ + int ret; + + /* + * Do a binary search for the peak input level, and stop + * when that level is "trapped" between two adjacent DAC + * values. + * When invert is active, use the midpoint floor so that + * env->level ends up as env->low when the termination + * criteria below is fulfilled, and use the midpoint + * ceiling when invert is not active so that env->level + * ends up as env->high in that case. + */ + env->level = (env->high + env->low + !env->invert) / 2; + + if (env->high == env->low + 1) { + complete(&env->done); + return; + } + + /* Set a "safe" DAC level (if there is such a thing)... */ + ret = iio_write_channel_raw(env->dac, env->invert ? 0 : env->dac_max); + if (ret < 0) + goto err; + + /* ...clear the comparison result... */ + envelope_detector_comp_latch(env); + + /* ...set the real DAC level... */ + ret = iio_write_channel_raw(env->dac, env->level); + if (ret < 0) + goto err; + + /* ...and wait for a bit to see if the latch catches anything. */ + schedule_delayed_work(&env->comp_timeout, + msecs_to_jiffies(env->comp_interval)); + return; + +err: + env->level = ret; + complete(&env->done); +} + +static void envelope_detector_timeout(struct work_struct *work) +{ + struct envelope *env = container_of(work, struct envelope, + comp_timeout.work); + + /* Adjust low/high depending on the latch content... */ + if (!envelope_detector_comp_latch(env) ^ !env->invert) + env->low = env->level; + else + env->high = env->level; + + /* ...and continue the search. */ + envelope_detector_setup_compare(env); +} + +static int envelope_detector_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct envelope *env = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * When invert is active, start with high=max+1 and low=0 + * since we will end up with the low value when the + * termination criteria is fulfilled (rounding down). And + * start with high=max and low=-1 when invert is not active + * since we will end up with the high value in that case. + * This ensures that the returned value in both cases are + * in the same range as the DAC and is a value that has not + * triggered the comparator. + */ + mutex_lock(&env->read_lock); + env->high = env->dac_max + env->invert; + env->low = -1 + env->invert; + envelope_detector_setup_compare(env); + wait_for_completion(&env->done); + if (env->level < 0) { + ret = env->level; + goto err_unlock; + } + *val = env->invert ? env->dac_max - env->level : env->level; + mutex_unlock(&env->read_lock); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + return iio_read_channel_scale(env->dac, val, val2); + } + + return -EINVAL; + +err_unlock: + mutex_unlock(&env->read_lock); + return ret; +} + +static ssize_t envelope_show_invert(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, char *buf) +{ + struct envelope *env = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", env->invert); +} + +static ssize_t envelope_store_invert(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, + const char *buf, size_t len) +{ + struct envelope *env = iio_priv(indio_dev); + unsigned long invert; + int ret; + u32 trigger; + + ret = kstrtoul(buf, 0, &invert); + if (ret < 0) + return ret; + if (invert > 1) + return -EINVAL; + + trigger = invert ? env->comp_irq_trigger_inv : env->comp_irq_trigger; + + mutex_lock(&env->read_lock); + if (invert != env->invert) + ret = irq_set_irq_type(env->comp_irq, trigger); + if (!ret) { + env->invert = invert; + ret = len; + } + mutex_unlock(&env->read_lock); + + return ret; +} + +static ssize_t envelope_show_comp_interval(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, + char *buf) +{ + struct envelope *env = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", env->comp_interval); +} + +static ssize_t envelope_store_comp_interval(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, + const char *buf, size_t len) +{ + struct envelope *env = iio_priv(indio_dev); + unsigned long interval; + int ret; + + ret = kstrtoul(buf, 0, &interval); + if (ret < 0) + return ret; + if (interval > 1000) + return -EINVAL; + + mutex_lock(&env->read_lock); + env->comp_interval = interval; + mutex_unlock(&env->read_lock); + + return len; +} + +static const struct iio_chan_spec_ext_info envelope_detector_ext_info[] = { + { .name = "invert", + .read = envelope_show_invert, + .write = envelope_store_invert, }, + { .name = "compare_interval", + .read = envelope_show_comp_interval, + .write = envelope_store_comp_interval, }, + { /* sentinel */ } +}; + +static const struct iio_chan_spec envelope_detector_iio_channel = { + .type = IIO_ALTVOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_SCALE), + .ext_info = envelope_detector_ext_info, + .indexed = 1, +}; + +static const struct iio_info envelope_detector_info = { + .read_raw = &envelope_detector_read_raw, + .driver_module = THIS_MODULE, +}; + +static int envelope_detector_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct envelope *env; + enum iio_chan_type type; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*env)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + env = iio_priv(indio_dev); + env->comp_interval = 50; /* some sensible default? */ + + spin_lock_init(&env->comp_lock); + mutex_init(&env->read_lock); + init_completion(&env->done); + INIT_DELAYED_WORK(&env->comp_timeout, envelope_detector_timeout); + + indio_dev->name = dev_name(dev); + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = dev->of_node; + indio_dev->info = &envelope_detector_info; + indio_dev->channels = &envelope_detector_iio_channel; + indio_dev->num_channels = 1; + + env->dac = devm_iio_channel_get(dev, "dac"); + if (IS_ERR(env->dac)) { + if (PTR_ERR(env->dac) != -EPROBE_DEFER) + dev_err(dev, "failed to get dac input channel\n"); + return PTR_ERR(env->dac); + } + + env->comp_irq = platform_get_irq_byname(pdev, "comp"); + if (env->comp_irq < 0) { + if (env->comp_irq != -EPROBE_DEFER) + dev_err(dev, "failed to get compare interrupt\n"); + return env->comp_irq; + } + + ret = devm_request_irq(dev, env->comp_irq, envelope_detector_comp_isr, + 0, "envelope-detector", env); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to request interrupt\n"); + return ret; + } + env->comp_irq_trigger = irq_get_trigger_type(env->comp_irq); + if (env->comp_irq_trigger & IRQF_TRIGGER_RISING) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_FALLING; + if (env->comp_irq_trigger & IRQF_TRIGGER_FALLING) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_RISING; + if (env->comp_irq_trigger & IRQF_TRIGGER_HIGH) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_LOW; + if (env->comp_irq_trigger & IRQF_TRIGGER_LOW) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_HIGH; + + ret = iio_get_channel_type(env->dac, &type); + if (ret < 0) + return ret; + + if (type != IIO_VOLTAGE) { + dev_err(dev, "dac is of the wrong type\n"); + return -EINVAL; + } + + ret = iio_read_max_channel_raw(env->dac, &env->dac_max); + if (ret < 0) { + dev_err(dev, "dac does not indicate its raw maximum value\n"); + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id envelope_detector_match[] = { + { .compatible = "axentia,tse850-envelope-detector", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, envelope_detector_match); + +static struct platform_driver envelope_detector_driver = { + .probe = envelope_detector_probe, + .driver = { + .name = "iio-envelope-detector", + .of_match_table = envelope_detector_match, + }, +}; +module_platform_driver(envelope_detector_driver); + +MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator"); +MODULE_AUTHOR("Peter Rosin "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From f44d5c8ac3993421370fc00951abd5864ca71689 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Sat, 12 Nov 2016 13:19:41 -0500 Subject: staging: iio: tsl2583: move out of staging Move tsl2580, tsl2581, tsl2583 driver out of staging into mainline. Signed-off-by: Brian Masney Signed-off-by: Jonathan Cameron --- .../ABI/testing/sysfs-bus-iio-light-tsl2583 | 20 + drivers/iio/light/Kconfig | 7 + drivers/iio/light/Makefile | 1 + drivers/iio/light/tsl2583.c | 913 +++++++++++++++++++++ .../light/sysfs-bus-iio-light-tsl2583 | 20 - drivers/staging/iio/light/Kconfig | 7 - drivers/staging/iio/light/Makefile | 1 - drivers/staging/iio/light/tsl2583.c | 913 --------------------- 8 files changed, 941 insertions(+), 941 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-tsl2583 create mode 100644 drivers/iio/light/tsl2583.c delete mode 100644 drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2583 delete mode 100644 drivers/staging/iio/light/tsl2583.c (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-tsl2583 b/Documentation/ABI/testing/sysfs-bus-iio-light-tsl2583 new file mode 100644 index 000000000000..a2e19964e87e --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-tsl2583 @@ -0,0 +1,20 @@ +What: /sys/bus/iio/devices/device[n]/in_illuminance_calibrate +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property causes an internal calibration of the als gain trim + value which is later used in calculating illuminance in lux. + +What: /sys/bus/iio/devices/device[n]/in_illuminance_lux_table +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property gets/sets the table of coefficients + used in calculating illuminance in lux. + +What: /sys/bus/iio/devices/device[n]/in_illuminance_input_target +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property is the known externally illuminance (in lux). + It is used in the process of calibrating the device accuracy. diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index d01172089828..298ea5081a96 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -338,6 +338,13 @@ config SENSORS_TSL2563 This driver can also be built as a module. If so, the module will be called tsl2563. +config TSL2583 + tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters" + depends on I2C + help + Provides support for the TAOS tsl2580, tsl2581 and tsl2583 devices. + Access ALS data via iio, sysfs. + config TSL4531 tristate "TAOS TSL4531 ambient light sensors" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 15f24c557f5f..4de520036e6e 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_SI1145) += si1145.o obj-$(CONFIG_STK3310) += stk3310.o obj-$(CONFIG_TCS3414) += tcs3414.o obj-$(CONFIG_TCS3472) += tcs3472.o +obj-$(CONFIG_TSL2583) += tsl2583.o obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c new file mode 100644 index 000000000000..0b87f6adbb79 --- /dev/null +++ b/drivers/iio/light/tsl2583.c @@ -0,0 +1,913 @@ +/* + * Device driver for monitoring ambient light intensity (lux) + * within the TAOS tsl258x family of devices (tsl2580, tsl2581, tsl2583). + * + * Copyright (c) 2011, TAOS Corporation. + * Copyright (c) 2016 Brian Masney + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Device Registers and Masks */ +#define TSL2583_CNTRL 0x00 +#define TSL2583_ALS_TIME 0X01 +#define TSL2583_INTERRUPT 0x02 +#define TSL2583_GAIN 0x07 +#define TSL2583_REVID 0x11 +#define TSL2583_CHIPID 0x12 +#define TSL2583_ALS_CHAN0LO 0x14 +#define TSL2583_ALS_CHAN0HI 0x15 +#define TSL2583_ALS_CHAN1LO 0x16 +#define TSL2583_ALS_CHAN1HI 0x17 +#define TSL2583_TMR_LO 0x18 +#define TSL2583_TMR_HI 0x19 + +/* tsl2583 cmd reg masks */ +#define TSL2583_CMD_REG 0x80 +#define TSL2583_CMD_SPL_FN 0x60 +#define TSL2583_CMD_ALS_INT_CLR 0x01 + +/* tsl2583 cntrl reg masks */ +#define TSL2583_CNTL_ADC_ENBL 0x02 +#define TSL2583_CNTL_PWR_OFF 0x00 +#define TSL2583_CNTL_PWR_ON 0x01 + +/* tsl2583 status reg masks */ +#define TSL2583_STA_ADC_VALID 0x01 +#define TSL2583_STA_ADC_INTR 0x10 + +/* Lux calculation constants */ +#define TSL2583_LUX_CALC_OVER_FLOW 65535 + +#define TSL2583_INTERRUPT_DISABLED 0x00 + +#define TSL2583_CHIP_ID 0x90 +#define TSL2583_CHIP_ID_MASK 0xf0 + +/* Per-device data */ +struct tsl2583_als_info { + u16 als_ch0; + u16 als_ch1; + u16 lux; +}; + +struct tsl2583_lux { + unsigned int ratio; + unsigned int ch0; + unsigned int ch1; +}; + +static const struct tsl2583_lux tsl2583_default_lux[] = { + { 9830, 8520, 15729 }, + { 12452, 10807, 23344 }, + { 14746, 6383, 11705 }, + { 17695, 4063, 6554 }, + { 0, 0, 0 } /* Termination segment */ +}; + +#define TSL2583_MAX_LUX_TABLE_ENTRIES 11 + +struct tsl2583_settings { + int als_time; + int als_gain; + int als_gain_trim; + int als_cal_target; + + /* + * This structure is intentionally large to accommodate updates via + * sysfs. Sized to 11 = max 10 segments + 1 termination segment. + * Assumption is that one and only one type of glass used. + */ + struct tsl2583_lux als_device_lux[TSL2583_MAX_LUX_TABLE_ENTRIES]; +}; + +struct tsl2583_chip { + struct mutex als_mutex; + struct i2c_client *client; + struct tsl2583_als_info als_cur_info; + struct tsl2583_settings als_settings; + int als_time_scale; + int als_saturation; + bool suspended; +}; + +struct gainadj { + s16 ch0; + s16 ch1; + s16 mean; +}; + +/* Index = (0 - 3) Used to validate the gain selection index */ +static const struct gainadj gainadj[] = { + { 1, 1, 1 }, + { 8, 8, 8 }, + { 16, 16, 16 }, + { 107, 115, 111 } +}; + +/* + * Provides initial operational parameter defaults. + * These defaults may be changed through the device's sysfs files. + */ +static void tsl2583_defaults(struct tsl2583_chip *chip) +{ + /* + * The integration time must be a multiple of 50ms and within the + * range [50, 600] ms. + */ + chip->als_settings.als_time = 100; + + /* + * This is an index into the gainadj table. Assume clear glass as the + * default. + */ + chip->als_settings.als_gain = 0; + + /* Default gain trim to account for aperture effects */ + chip->als_settings.als_gain_trim = 1000; + + /* Known external ALS reading used for calibration */ + chip->als_settings.als_cal_target = 130; + + /* Default lux table. */ + memcpy(chip->als_settings.als_device_lux, tsl2583_default_lux, + sizeof(tsl2583_default_lux)); +} + +/* + * Reads and calculates current lux value. + * The raw ch0 and ch1 values of the ambient light sensed in the last + * integration cycle are read from the device. + * Time scale factor array values are adjusted based on the integration time. + * The raw values are multiplied by a scale factor, and device gain is obtained + * using gain index. Limit checks are done next, then the ratio of a multiple + * of ch1 value, to the ch0 value, is calculated. The array als_device_lux[] + * declared above is then scanned to find the first ratio value that is just + * above the ratio we just calculated. The ch0 and ch1 multiplier constants in + * the array are then used along with the time scale factor array values, to + * calculate the lux. + */ +static int tsl2583_get_lux(struct iio_dev *indio_dev) +{ + u16 ch0, ch1; /* separated ch0/ch1 data from device */ + u32 lux; /* raw lux calculated from device data */ + u64 lux64; + u32 ratio; + u8 buf[5]; + struct tsl2583_lux *p; + struct tsl2583_chip *chip = iio_priv(indio_dev); + int i, ret; + + ret = i2c_smbus_read_byte_data(chip->client, TSL2583_CMD_REG); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to read CMD_REG register\n", + __func__); + goto done; + } + + /* is data new & valid */ + if (!(ret & TSL2583_STA_ADC_INTR)) { + dev_err(&chip->client->dev, "%s: data not valid; returning last value\n", + __func__); + ret = chip->als_cur_info.lux; /* return LAST VALUE */ + goto done; + } + + for (i = 0; i < 4; i++) { + int reg = TSL2583_CMD_REG | (TSL2583_ALS_CHAN0LO + i); + + ret = i2c_smbus_read_byte_data(chip->client, reg); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to read register %x\n", + __func__, reg); + goto done; + } + buf[i] = ret; + } + + /* + * Clear the pending interrupt status bit on the chip to allow the next + * integration cycle to start. This has to be done even though this + * driver currently does not support interrupts. + */ + ret = i2c_smbus_write_byte(chip->client, + (TSL2583_CMD_REG | TSL2583_CMD_SPL_FN | + TSL2583_CMD_ALS_INT_CLR)); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to clear the interrupt bit\n", + __func__); + goto done; /* have no data, so return failure */ + } + + /* extract ALS/lux data */ + ch0 = le16_to_cpup((const __le16 *)&buf[0]); + ch1 = le16_to_cpup((const __le16 *)&buf[2]); + + chip->als_cur_info.als_ch0 = ch0; + chip->als_cur_info.als_ch1 = ch1; + + if ((ch0 >= chip->als_saturation) || (ch1 >= chip->als_saturation)) + goto return_max; + + if (!ch0) { + /* + * The sensor appears to be in total darkness so set the + * calculated lux to 0 and return early to avoid a division by + * zero below when calculating the ratio. + */ + ret = 0; + chip->als_cur_info.lux = 0; + goto done; + } + + /* calculate ratio */ + ratio = (ch1 << 15) / ch0; + + /* convert to unscaled lux using the pointer to the table */ + for (p = (struct tsl2583_lux *)chip->als_settings.als_device_lux; + p->ratio != 0 && p->ratio < ratio; p++) + ; + + if (p->ratio == 0) { + lux = 0; + } else { + u32 ch0lux, ch1lux; + + ch0lux = ((ch0 * p->ch0) + + (gainadj[chip->als_settings.als_gain].ch0 >> 1)) + / gainadj[chip->als_settings.als_gain].ch0; + ch1lux = ((ch1 * p->ch1) + + (gainadj[chip->als_settings.als_gain].ch1 >> 1)) + / gainadj[chip->als_settings.als_gain].ch1; + + /* note: lux is 31 bit max at this point */ + if (ch1lux > ch0lux) { + dev_dbg(&chip->client->dev, "%s: No Data - Returning 0\n", + __func__); + ret = 0; + chip->als_cur_info.lux = 0; + goto done; + } + + lux = ch0lux - ch1lux; + } + + /* adjust for active time scale */ + if (chip->als_time_scale == 0) + lux = 0; + else + lux = (lux + (chip->als_time_scale >> 1)) / + chip->als_time_scale; + + /* + * Adjust for active gain scale. + * The tsl2583_default_lux tables above have a factor of 8192 built in, + * so we need to shift right. + * User-specified gain provides a multiplier. + * Apply user-specified gain before shifting right to retain precision. + * Use 64 bits to avoid overflow on multiplication. + * Then go back to 32 bits before division to avoid using div_u64(). + */ + lux64 = lux; + lux64 = lux64 * chip->als_settings.als_gain_trim; + lux64 >>= 13; + lux = lux64; + lux = (lux + 500) / 1000; + + if (lux > TSL2583_LUX_CALC_OVER_FLOW) { /* check for overflow */ +return_max: + lux = TSL2583_LUX_CALC_OVER_FLOW; + } + + /* Update the structure with the latest VALID lux. */ + chip->als_cur_info.lux = lux; + ret = lux; + +done: + return ret; +} + +/* + * Obtain single reading and calculate the als_gain_trim (later used + * to derive actual lux). + * Return updated gain_trim value. + */ +static int tsl2583_als_calibrate(struct iio_dev *indio_dev) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + unsigned int gain_trim_val; + int ret; + int lux_val; + + ret = i2c_smbus_read_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_CNTRL); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to read from the CNTRL register\n", + __func__); + return ret; + } + + if ((ret & (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON)) + != (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON)) { + dev_err(&chip->client->dev, + "%s: Device is not powered on and/or ADC is not enabled\n", + __func__); + return -EINVAL; + } else if ((ret & TSL2583_STA_ADC_VALID) != TSL2583_STA_ADC_VALID) { + dev_err(&chip->client->dev, + "%s: The two ADC channels have not completed an integration cycle\n", + __func__); + return -ENODATA; + } + + lux_val = tsl2583_get_lux(indio_dev); + if (lux_val < 0) { + dev_err(&chip->client->dev, "%s: failed to get lux\n", + __func__); + return lux_val; + } + + gain_trim_val = (unsigned int)(((chip->als_settings.als_cal_target) + * chip->als_settings.als_gain_trim) / lux_val); + if ((gain_trim_val < 250) || (gain_trim_val > 4000)) { + dev_err(&chip->client->dev, + "%s: trim_val of %d is not within the range [250, 4000]\n", + __func__, gain_trim_val); + return -ENODATA; + } + + chip->als_settings.als_gain_trim = (int)gain_trim_val; + + return 0; +} + +static int tsl2583_set_als_time(struct tsl2583_chip *chip) +{ + int als_count, als_time, ret; + u8 val; + + /* determine als integration register */ + als_count = (chip->als_settings.als_time * 100 + 135) / 270; + if (!als_count) + als_count = 1; /* ensure at least one cycle */ + + /* convert back to time (encompasses overrides) */ + als_time = (als_count * 27 + 5) / 10; + + val = 256 - als_count; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_ALS_TIME, + val); + if (ret < 0) { + dev_err(&chip->client->dev, "%s: failed to set the als time to %d\n", + __func__, val); + return ret; + } + + /* set chip struct re scaling and saturation */ + chip->als_saturation = als_count * 922; /* 90% of full scale */ + chip->als_time_scale = (als_time + 25) / 50; + + return ret; +} + +static int tsl2583_set_als_gain(struct tsl2583_chip *chip) +{ + int ret; + + /* Set the gain based on als_settings struct */ + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_GAIN, + chip->als_settings.als_gain); + if (ret < 0) + dev_err(&chip->client->dev, + "%s: failed to set the gain to %d\n", __func__, + chip->als_settings.als_gain); + + return ret; +} + +static int tsl2583_set_power_state(struct tsl2583_chip *chip, u8 state) +{ + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_CNTRL, state); + if (ret < 0) + dev_err(&chip->client->dev, + "%s: failed to set the power state to %d\n", __func__, + state); + + return ret; +} + +/* + * Turn the device on. + * Configuration must be set before calling this function. + */ +static int tsl2583_chip_init_and_power_on(struct iio_dev *indio_dev) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + /* Power on the device; ADC off. */ + ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_ON); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2583_CMD_REG | TSL2583_INTERRUPT, + TSL2583_INTERRUPT_DISABLED); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed to disable interrupts\n", __func__); + return ret; + } + + ret = tsl2583_set_als_time(chip); + if (ret < 0) + return ret; + + ret = tsl2583_set_als_gain(chip); + if (ret < 0) + return ret; + + usleep_range(3000, 3500); + + ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_ON | + TSL2583_CNTL_ADC_ENBL); + if (ret < 0) + return ret; + + chip->suspended = false; + + return ret; +} + +/* Sysfs Interface Functions */ + +static ssize_t in_illuminance_input_target_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->als_mutex); + ret = sprintf(buf, "%d\n", chip->als_settings.als_cal_target); + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static ssize_t in_illuminance_input_target_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int value; + + if (kstrtoint(buf, 0, &value) || !value) + return -EINVAL; + + mutex_lock(&chip->als_mutex); + chip->als_settings.als_cal_target = value; + mutex_unlock(&chip->als_mutex); + + return len; +} + +static ssize_t in_illuminance_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int value, ret; + + if (kstrtoint(buf, 0, &value) || value != 1) + return -EINVAL; + + mutex_lock(&chip->als_mutex); + + if (chip->suspended) { + ret = -EBUSY; + goto done; + } + + ret = tsl2583_als_calibrate(indio_dev); + if (ret < 0) + goto done; + + ret = len; +done: + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static ssize_t in_illuminance_lux_table_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + unsigned int i; + int offset = 0; + + for (i = 0; i < ARRAY_SIZE(chip->als_settings.als_device_lux); i++) { + offset += sprintf(buf + offset, "%u,%u,%u,", + chip->als_settings.als_device_lux[i].ratio, + chip->als_settings.als_device_lux[i].ch0, + chip->als_settings.als_device_lux[i].ch1); + if (chip->als_settings.als_device_lux[i].ratio == 0) { + /* + * We just printed the first "0" entry. + * Now get rid of the extra "," and break. + */ + offset--; + break; + } + } + + offset += sprintf(buf + offset, "\n"); + + return offset; +} + +static ssize_t in_illuminance_lux_table_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + const unsigned int max_ints = TSL2583_MAX_LUX_TABLE_ENTRIES * 3; + int value[TSL2583_MAX_LUX_TABLE_ENTRIES * 3]; + int ret = -EINVAL; + unsigned int n; + + mutex_lock(&chip->als_mutex); + + get_options(buf, ARRAY_SIZE(value), value); + + /* + * We now have an array of ints starting at value[1], and + * enumerated by value[0]. + * We expect each group of three ints is one table entry, + * and the last table entry is all 0. + */ + n = value[0]; + if ((n % 3) || n < 6 || n > max_ints) { + dev_err(dev, + "%s: The number of entries in the lux table must be a multiple of 3 and within the range [6, %d]\n", + __func__, max_ints); + goto done; + } + if ((value[n - 2] | value[n - 1] | value[n]) != 0) { + dev_err(dev, "%s: The last 3 entries in the lux table must be zeros.\n", + __func__); + goto done; + } + + memcpy(chip->als_settings.als_device_lux, &value[1], + value[0] * sizeof(value[1])); + + ret = len; + +done: + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static IIO_CONST_ATTR(in_illuminance_calibscale_available, "1 8 16 111"); +static IIO_CONST_ATTR(in_illuminance_integration_time_available, + "0.000050 0.000100 0.000150 0.000200 0.000250 0.000300 0.000350 0.000400 0.000450 0.000500 0.000550 0.000600 0.000650"); +static IIO_DEVICE_ATTR_RW(in_illuminance_input_target, 0); +static IIO_DEVICE_ATTR_WO(in_illuminance_calibrate, 0); +static IIO_DEVICE_ATTR_RW(in_illuminance_lux_table, 0); + +static struct attribute *sysfs_attrs_ctrl[] = { + &iio_const_attr_in_illuminance_calibscale_available.dev_attr.attr, + &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, + &iio_dev_attr_in_illuminance_input_target.dev_attr.attr, + &iio_dev_attr_in_illuminance_calibrate.dev_attr.attr, + &iio_dev_attr_in_illuminance_lux_table.dev_attr.attr, + NULL +}; + +static const struct attribute_group tsl2583_attribute_group = { + .attrs = sysfs_attrs_ctrl, +}; + +static const struct iio_chan_spec tsl2583_channels[] = { + { + .type = IIO_LIGHT, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + { + .type = IIO_LIGHT, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + }, +}; + +static int tsl2583_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&chip->als_mutex); + + if (chip->suspended) { + ret = -EBUSY; + goto read_done; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_LIGHT) { + ret = tsl2583_get_lux(indio_dev); + if (ret < 0) + goto read_done; + + /* + * From page 20 of the TSL2581, TSL2583 data + * sheet (TAOS134 − MARCH 2011): + * + * One of the photodiodes (channel 0) is + * sensitive to both visible and infrared light, + * while the second photodiode (channel 1) is + * sensitive primarily to infrared light. + */ + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + *val = chip->als_cur_info.als_ch0; + else + *val = chip->als_cur_info.als_ch1; + + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_PROCESSED: + if (chan->type == IIO_LIGHT) { + ret = tsl2583_get_lux(indio_dev); + if (ret < 0) + goto read_done; + + *val = ret; + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + if (chan->type == IIO_LIGHT) { + *val = chip->als_settings.als_gain_trim; + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + *val = gainadj[chip->als_settings.als_gain].mean; + ret = IIO_VAL_INT; + } + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT) { + *val = 0; + *val2 = chip->als_settings.als_time; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; + default: + break; + } + +read_done: + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static int tsl2583_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&chip->als_mutex); + + if (chip->suspended) { + ret = -EBUSY; + goto write_done; + } + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + if (chan->type == IIO_LIGHT) { + chip->als_settings.als_gain_trim = val; + ret = 0; + } + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(gainadj); i++) { + if (gainadj[i].mean == val) { + chip->als_settings.als_gain = i; + ret = tsl2583_set_als_gain(chip); + break; + } + } + } + break; + case IIO_CHAN_INFO_INT_TIME: + if (chan->type == IIO_LIGHT && !val && val2 >= 50 && + val2 <= 650 && !(val2 % 50)) { + chip->als_settings.als_time = val2; + ret = tsl2583_set_als_time(chip); + } + break; + default: + break; + } + +write_done: + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static const struct iio_info tsl2583_info = { + .attrs = &tsl2583_attribute_group, + .driver_module = THIS_MODULE, + .read_raw = tsl2583_read_raw, + .write_raw = tsl2583_write_raw, +}; + +static int tsl2583_probe(struct i2c_client *clientp, + const struct i2c_device_id *idp) +{ + int ret; + struct tsl2583_chip *chip; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(clientp->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&clientp->dev, "%s: i2c smbus byte data functionality is unsupported\n", + __func__); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->client = clientp; + i2c_set_clientdata(clientp, indio_dev); + + mutex_init(&chip->als_mutex); + chip->suspended = true; + + ret = i2c_smbus_read_byte_data(clientp, + TSL2583_CMD_REG | TSL2583_CHIPID); + if (ret < 0) { + dev_err(&clientp->dev, + "%s: failed to read the chip ID register\n", __func__); + return ret; + } + + if ((ret & TSL2583_CHIP_ID_MASK) != TSL2583_CHIP_ID) { + dev_err(&clientp->dev, "%s: received an unknown chip ID %x\n", + __func__, ret); + return -EINVAL; + } + + indio_dev->info = &tsl2583_info; + indio_dev->channels = tsl2583_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl2583_channels); + indio_dev->dev.parent = &clientp->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->client->name; + + ret = devm_iio_device_register(indio_dev->dev.parent, indio_dev); + if (ret) { + dev_err(&clientp->dev, "%s: iio registration failed\n", + __func__); + return ret; + } + + /* Load up the V2 defaults (these are hard coded defaults for now) */ + tsl2583_defaults(chip); + + /* Make sure the chip is on */ + ret = tsl2583_chip_init_and_power_on(indio_dev); + if (ret < 0) + return ret; + + dev_info(&clientp->dev, "Light sensor found.\n"); + + return 0; +} + +static int __maybe_unused tsl2583_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->als_mutex); + + ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_OFF); + chip->suspended = true; + + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static int __maybe_unused tsl2583_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->als_mutex); + + ret = tsl2583_chip_init_and_power_on(indio_dev); + + mutex_unlock(&chip->als_mutex); + + return ret; +} + +static SIMPLE_DEV_PM_OPS(tsl2583_pm_ops, tsl2583_suspend, tsl2583_resume); + +static struct i2c_device_id tsl2583_idtable[] = { + { "tsl2580", 0 }, + { "tsl2581", 1 }, + { "tsl2583", 2 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, tsl2583_idtable); + +static const struct of_device_id tsl2583_of_match[] = { + { .compatible = "amstaos,tsl2580", }, + { .compatible = "amstaos,tsl2581", }, + { .compatible = "amstaos,tsl2583", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tsl2583_of_match); + +/* Driver definition */ +static struct i2c_driver tsl2583_driver = { + .driver = { + .name = "tsl2583", + .pm = &tsl2583_pm_ops, + .of_match_table = tsl2583_of_match, + }, + .id_table = tsl2583_idtable, + .probe = tsl2583_probe, +}; +module_i2c_driver(tsl2583_driver); + +MODULE_AUTHOR("J. August Brenner "); +MODULE_AUTHOR("Brian Masney "); +MODULE_DESCRIPTION("TAOS tsl2583 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2583 b/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2583 deleted file mode 100644 index a2e19964e87e..000000000000 --- a/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2583 +++ /dev/null @@ -1,20 +0,0 @@ -What: /sys/bus/iio/devices/device[n]/in_illuminance_calibrate -KernelVersion: 2.6.37 -Contact: linux-iio@vger.kernel.org -Description: - This property causes an internal calibration of the als gain trim - value which is later used in calculating illuminance in lux. - -What: /sys/bus/iio/devices/device[n]/in_illuminance_lux_table -KernelVersion: 2.6.37 -Contact: linux-iio@vger.kernel.org -Description: - This property gets/sets the table of coefficients - used in calculating illuminance in lux. - -What: /sys/bus/iio/devices/device[n]/in_illuminance_input_target -KernelVersion: 2.6.37 -Contact: linux-iio@vger.kernel.org -Description: - This property is the known externally illuminance (in lux). - It is used in the process of calibrating the device accuracy. diff --git a/drivers/staging/iio/light/Kconfig b/drivers/staging/iio/light/Kconfig index dbf22d396ddf..4fbf6298c0f3 100644 --- a/drivers/staging/iio/light/Kconfig +++ b/drivers/staging/iio/light/Kconfig @@ -13,13 +13,6 @@ config SENSORS_ISL29028 Proximity value via iio. The ISL29028 provides the concurrent sensing of ambient light and proximity. -config TSL2583 - tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters" - depends on I2C - help - Provides support for the TAOS tsl2580, tsl2581 and tsl2583 devices. - Access ALS data via iio, sysfs. - config TSL2x7x tristate "TAOS TSL/TMD2x71 and TSL/TMD2x72 Family of light and proximity sensors" depends on I2C diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile index 6480856e1682..f8693e9fdc94 100644 --- a/drivers/staging/iio/light/Makefile +++ b/drivers/staging/iio/light/Makefile @@ -3,5 +3,4 @@ # obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o -obj-$(CONFIG_TSL2583) += tsl2583.o obj-$(CONFIG_TSL2x7x) += tsl2x7x_core.o diff --git a/drivers/staging/iio/light/tsl2583.c b/drivers/staging/iio/light/tsl2583.c deleted file mode 100644 index 0b87f6adbb79..000000000000 --- a/drivers/staging/iio/light/tsl2583.c +++ /dev/null @@ -1,913 +0,0 @@ -/* - * Device driver for monitoring ambient light intensity (lux) - * within the TAOS tsl258x family of devices (tsl2580, tsl2581, tsl2583). - * - * Copyright (c) 2011, TAOS Corporation. - * Copyright (c) 2016 Brian Masney - * - * 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Device Registers and Masks */ -#define TSL2583_CNTRL 0x00 -#define TSL2583_ALS_TIME 0X01 -#define TSL2583_INTERRUPT 0x02 -#define TSL2583_GAIN 0x07 -#define TSL2583_REVID 0x11 -#define TSL2583_CHIPID 0x12 -#define TSL2583_ALS_CHAN0LO 0x14 -#define TSL2583_ALS_CHAN0HI 0x15 -#define TSL2583_ALS_CHAN1LO 0x16 -#define TSL2583_ALS_CHAN1HI 0x17 -#define TSL2583_TMR_LO 0x18 -#define TSL2583_TMR_HI 0x19 - -/* tsl2583 cmd reg masks */ -#define TSL2583_CMD_REG 0x80 -#define TSL2583_CMD_SPL_FN 0x60 -#define TSL2583_CMD_ALS_INT_CLR 0x01 - -/* tsl2583 cntrl reg masks */ -#define TSL2583_CNTL_ADC_ENBL 0x02 -#define TSL2583_CNTL_PWR_OFF 0x00 -#define TSL2583_CNTL_PWR_ON 0x01 - -/* tsl2583 status reg masks */ -#define TSL2583_STA_ADC_VALID 0x01 -#define TSL2583_STA_ADC_INTR 0x10 - -/* Lux calculation constants */ -#define TSL2583_LUX_CALC_OVER_FLOW 65535 - -#define TSL2583_INTERRUPT_DISABLED 0x00 - -#define TSL2583_CHIP_ID 0x90 -#define TSL2583_CHIP_ID_MASK 0xf0 - -/* Per-device data */ -struct tsl2583_als_info { - u16 als_ch0; - u16 als_ch1; - u16 lux; -}; - -struct tsl2583_lux { - unsigned int ratio; - unsigned int ch0; - unsigned int ch1; -}; - -static const struct tsl2583_lux tsl2583_default_lux[] = { - { 9830, 8520, 15729 }, - { 12452, 10807, 23344 }, - { 14746, 6383, 11705 }, - { 17695, 4063, 6554 }, - { 0, 0, 0 } /* Termination segment */ -}; - -#define TSL2583_MAX_LUX_TABLE_ENTRIES 11 - -struct tsl2583_settings { - int als_time; - int als_gain; - int als_gain_trim; - int als_cal_target; - - /* - * This structure is intentionally large to accommodate updates via - * sysfs. Sized to 11 = max 10 segments + 1 termination segment. - * Assumption is that one and only one type of glass used. - */ - struct tsl2583_lux als_device_lux[TSL2583_MAX_LUX_TABLE_ENTRIES]; -}; - -struct tsl2583_chip { - struct mutex als_mutex; - struct i2c_client *client; - struct tsl2583_als_info als_cur_info; - struct tsl2583_settings als_settings; - int als_time_scale; - int als_saturation; - bool suspended; -}; - -struct gainadj { - s16 ch0; - s16 ch1; - s16 mean; -}; - -/* Index = (0 - 3) Used to validate the gain selection index */ -static const struct gainadj gainadj[] = { - { 1, 1, 1 }, - { 8, 8, 8 }, - { 16, 16, 16 }, - { 107, 115, 111 } -}; - -/* - * Provides initial operational parameter defaults. - * These defaults may be changed through the device's sysfs files. - */ -static void tsl2583_defaults(struct tsl2583_chip *chip) -{ - /* - * The integration time must be a multiple of 50ms and within the - * range [50, 600] ms. - */ - chip->als_settings.als_time = 100; - - /* - * This is an index into the gainadj table. Assume clear glass as the - * default. - */ - chip->als_settings.als_gain = 0; - - /* Default gain trim to account for aperture effects */ - chip->als_settings.als_gain_trim = 1000; - - /* Known external ALS reading used for calibration */ - chip->als_settings.als_cal_target = 130; - - /* Default lux table. */ - memcpy(chip->als_settings.als_device_lux, tsl2583_default_lux, - sizeof(tsl2583_default_lux)); -} - -/* - * Reads and calculates current lux value. - * The raw ch0 and ch1 values of the ambient light sensed in the last - * integration cycle are read from the device. - * Time scale factor array values are adjusted based on the integration time. - * The raw values are multiplied by a scale factor, and device gain is obtained - * using gain index. Limit checks are done next, then the ratio of a multiple - * of ch1 value, to the ch0 value, is calculated. The array als_device_lux[] - * declared above is then scanned to find the first ratio value that is just - * above the ratio we just calculated. The ch0 and ch1 multiplier constants in - * the array are then used along with the time scale factor array values, to - * calculate the lux. - */ -static int tsl2583_get_lux(struct iio_dev *indio_dev) -{ - u16 ch0, ch1; /* separated ch0/ch1 data from device */ - u32 lux; /* raw lux calculated from device data */ - u64 lux64; - u32 ratio; - u8 buf[5]; - struct tsl2583_lux *p; - struct tsl2583_chip *chip = iio_priv(indio_dev); - int i, ret; - - ret = i2c_smbus_read_byte_data(chip->client, TSL2583_CMD_REG); - if (ret < 0) { - dev_err(&chip->client->dev, "%s: failed to read CMD_REG register\n", - __func__); - goto done; - } - - /* is data new & valid */ - if (!(ret & TSL2583_STA_ADC_INTR)) { - dev_err(&chip->client->dev, "%s: data not valid; returning last value\n", - __func__); - ret = chip->als_cur_info.lux; /* return LAST VALUE */ - goto done; - } - - for (i = 0; i < 4; i++) { - int reg = TSL2583_CMD_REG | (TSL2583_ALS_CHAN0LO + i); - - ret = i2c_smbus_read_byte_data(chip->client, reg); - if (ret < 0) { - dev_err(&chip->client->dev, "%s: failed to read register %x\n", - __func__, reg); - goto done; - } - buf[i] = ret; - } - - /* - * Clear the pending interrupt status bit on the chip to allow the next - * integration cycle to start. This has to be done even though this - * driver currently does not support interrupts. - */ - ret = i2c_smbus_write_byte(chip->client, - (TSL2583_CMD_REG | TSL2583_CMD_SPL_FN | - TSL2583_CMD_ALS_INT_CLR)); - if (ret < 0) { - dev_err(&chip->client->dev, "%s: failed to clear the interrupt bit\n", - __func__); - goto done; /* have no data, so return failure */ - } - - /* extract ALS/lux data */ - ch0 = le16_to_cpup((const __le16 *)&buf[0]); - ch1 = le16_to_cpup((const __le16 *)&buf[2]); - - chip->als_cur_info.als_ch0 = ch0; - chip->als_cur_info.als_ch1 = ch1; - - if ((ch0 >= chip->als_saturation) || (ch1 >= chip->als_saturation)) - goto return_max; - - if (!ch0) { - /* - * The sensor appears to be in total darkness so set the - * calculated lux to 0 and return early to avoid a division by - * zero below when calculating the ratio. - */ - ret = 0; - chip->als_cur_info.lux = 0; - goto done; - } - - /* calculate ratio */ - ratio = (ch1 << 15) / ch0; - - /* convert to unscaled lux using the pointer to the table */ - for (p = (struct tsl2583_lux *)chip->als_settings.als_device_lux; - p->ratio != 0 && p->ratio < ratio; p++) - ; - - if (p->ratio == 0) { - lux = 0; - } else { - u32 ch0lux, ch1lux; - - ch0lux = ((ch0 * p->ch0) + - (gainadj[chip->als_settings.als_gain].ch0 >> 1)) - / gainadj[chip->als_settings.als_gain].ch0; - ch1lux = ((ch1 * p->ch1) + - (gainadj[chip->als_settings.als_gain].ch1 >> 1)) - / gainadj[chip->als_settings.als_gain].ch1; - - /* note: lux is 31 bit max at this point */ - if (ch1lux > ch0lux) { - dev_dbg(&chip->client->dev, "%s: No Data - Returning 0\n", - __func__); - ret = 0; - chip->als_cur_info.lux = 0; - goto done; - } - - lux = ch0lux - ch1lux; - } - - /* adjust for active time scale */ - if (chip->als_time_scale == 0) - lux = 0; - else - lux = (lux + (chip->als_time_scale >> 1)) / - chip->als_time_scale; - - /* - * Adjust for active gain scale. - * The tsl2583_default_lux tables above have a factor of 8192 built in, - * so we need to shift right. - * User-specified gain provides a multiplier. - * Apply user-specified gain before shifting right to retain precision. - * Use 64 bits to avoid overflow on multiplication. - * Then go back to 32 bits before division to avoid using div_u64(). - */ - lux64 = lux; - lux64 = lux64 * chip->als_settings.als_gain_trim; - lux64 >>= 13; - lux = lux64; - lux = (lux + 500) / 1000; - - if (lux > TSL2583_LUX_CALC_OVER_FLOW) { /* check for overflow */ -return_max: - lux = TSL2583_LUX_CALC_OVER_FLOW; - } - - /* Update the structure with the latest VALID lux. */ - chip->als_cur_info.lux = lux; - ret = lux; - -done: - return ret; -} - -/* - * Obtain single reading and calculate the als_gain_trim (later used - * to derive actual lux). - * Return updated gain_trim value. - */ -static int tsl2583_als_calibrate(struct iio_dev *indio_dev) -{ - struct tsl2583_chip *chip = iio_priv(indio_dev); - unsigned int gain_trim_val; - int ret; - int lux_val; - - ret = i2c_smbus_read_byte_data(chip->client, - TSL2583_CMD_REG | TSL2583_CNTRL); - if (ret < 0) { - dev_err(&chip->client->dev, - "%s: failed to read from the CNTRL register\n", - __func__); - return ret; - } - - if ((ret & (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON)) - != (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON)) { - dev_err(&chip->client->dev, - "%s: Device is not powered on and/or ADC is not enabled\n", - __func__); - return -EINVAL; - } else if ((ret & TSL2583_STA_ADC_VALID) != TSL2583_STA_ADC_VALID) { - dev_err(&chip->client->dev, - "%s: The two ADC channels have not completed an integration cycle\n", - __func__); - return -ENODATA; - } - - lux_val = tsl2583_get_lux(indio_dev); - if (lux_val < 0) { - dev_err(&chip->client->dev, "%s: failed to get lux\n", - __func__); - return lux_val; - } - - gain_trim_val = (unsigned int)(((chip->als_settings.als_cal_target) - * chip->als_settings.als_gain_trim) / lux_val); - if ((gain_trim_val < 250) || (gain_trim_val > 4000)) { - dev_err(&chip->client->dev, - "%s: trim_val of %d is not within the range [250, 4000]\n", - __func__, gain_trim_val); - return -ENODATA; - } - - chip->als_settings.als_gain_trim = (int)gain_trim_val; - - return 0; -} - -static int tsl2583_set_als_time(struct tsl2583_chip *chip) -{ - int als_count, als_time, ret; - u8 val; - - /* determine als integration register */ - als_count = (chip->als_settings.als_time * 100 + 135) / 270; - if (!als_count) - als_count = 1; /* ensure at least one cycle */ - - /* convert back to time (encompasses overrides) */ - als_time = (als_count * 27 + 5) / 10; - - val = 256 - als_count; - ret = i2c_smbus_write_byte_data(chip->client, - TSL2583_CMD_REG | TSL2583_ALS_TIME, - val); - if (ret < 0) { - dev_err(&chip->client->dev, "%s: failed to set the als time to %d\n", - __func__, val); - return ret; - } - - /* set chip struct re scaling and saturation */ - chip->als_saturation = als_count * 922; /* 90% of full scale */ - chip->als_time_scale = (als_time + 25) / 50; - - return ret; -} - -static int tsl2583_set_als_gain(struct tsl2583_chip *chip) -{ - int ret; - - /* Set the gain based on als_settings struct */ - ret = i2c_smbus_write_byte_data(chip->client, - TSL2583_CMD_REG | TSL2583_GAIN, - chip->als_settings.als_gain); - if (ret < 0) - dev_err(&chip->client->dev, - "%s: failed to set the gain to %d\n", __func__, - chip->als_settings.als_gain); - - return ret; -} - -static int tsl2583_set_power_state(struct tsl2583_chip *chip, u8 state) -{ - int ret; - - ret = i2c_smbus_write_byte_data(chip->client, - TSL2583_CMD_REG | TSL2583_CNTRL, state); - if (ret < 0) - dev_err(&chip->client->dev, - "%s: failed to set the power state to %d\n", __func__, - state); - - return ret; -} - -/* - * Turn the device on. - * Configuration must be set before calling this function. - */ -static int tsl2583_chip_init_and_power_on(struct iio_dev *indio_dev) -{ - struct tsl2583_chip *chip = iio_priv(indio_dev); - int ret; - - /* Power on the device; ADC off. */ - ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_ON); - if (ret < 0) - return ret; - - ret = i2c_smbus_write_byte_data(chip->client, - TSL2583_CMD_REG | TSL2583_INTERRUPT, - TSL2583_INTERRUPT_DISABLED); - if (ret < 0) { - dev_err(&chip->client->dev, - "%s: failed to disable interrupts\n", __func__); - return ret; - } - - ret = tsl2583_set_als_time(chip); - if (ret < 0) - return ret; - - ret = tsl2583_set_als_gain(chip); - if (ret < 0) - return ret; - - usleep_range(3000, 3500); - - ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_ON | - TSL2583_CNTL_ADC_ENBL); - if (ret < 0) - return ret; - - chip->suspended = false; - - return ret; -} - -/* Sysfs Interface Functions */ - -static ssize_t in_illuminance_input_target_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct iio_dev *indio_dev = dev_to_iio_dev(dev); - struct tsl2583_chip *chip = iio_priv(indio_dev); - int ret; - - mutex_lock(&chip->als_mutex); - ret = sprintf(buf, "%d\n", chip->als_settings.als_cal_target); - mutex_unlock(&chip->als_mutex); - - return ret; -} - -static ssize_t in_illuminance_input_target_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) -{ - struct iio_dev *indio_dev = dev_to_iio_dev(dev); - struct tsl2583_chip *chip = iio_priv(indio_dev); - int value; - - if (kstrtoint(buf, 0, &value) || !value) - return -EINVAL; - - mutex_lock(&chip->als_mutex); - chip->als_settings.als_cal_target = value; - mutex_unlock(&chip->als_mutex); - - return len; -} - -static ssize_t in_illuminance_calibrate_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) -{ - struct iio_dev *indio_dev = dev_to_iio_dev(dev); - struct tsl2583_chip *chip = iio_priv(indio_dev); - int value, ret; - - if (kstrtoint(buf, 0, &value) || value != 1) - return -EINVAL; - - mutex_lock(&chip->als_mutex); - - if (chip->suspended) { - ret = -EBUSY; - goto done; - } - - ret = tsl2583_als_calibrate(indio_dev); - if (ret < 0) - goto done; - - ret = len; -done: - mutex_unlock(&chip->als_mutex); - - return ret; -} - -static ssize_t in_illuminance_lux_table_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct iio_dev *indio_dev = dev_to_iio_dev(dev); - struct tsl2583_chip *chip = iio_priv(indio_dev); - unsigned int i; - int offset = 0; - - for (i = 0; i < ARRAY_SIZE(chip->als_settings.als_device_lux); i++) { - offset += sprintf(buf + offset, "%u,%u,%u,", - chip->als_settings.als_device_lux[i].ratio, - chip->als_settings.als_device_lux[i].ch0, - chip->als_settings.als_device_lux[i].ch1); - if (chip->als_settings.als_device_lux[i].ratio == 0) { - /* - * We just printed the first "0" entry. - * Now get rid of the extra "," and break. - */ - offset--; - break; - } - } - - offset += sprintf(buf + offset, "\n"); - - return offset; -} - -static ssize_t in_illuminance_lux_table_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) -{ - struct iio_dev *indio_dev = dev_to_iio_dev(dev); - struct tsl2583_chip *chip = iio_priv(indio_dev); - const unsigned int max_ints = TSL2583_MAX_LUX_TABLE_ENTRIES * 3; - int value[TSL2583_MAX_LUX_TABLE_ENTRIES * 3]; - int ret = -EINVAL; - unsigned int n; - - mutex_lock(&chip->als_mutex); - - get_options(buf, ARRAY_SIZE(value), value); - - /* - * We now have an array of ints starting at value[1], and - * enumerated by value[0]. - * We expect each group of three ints is one table entry, - * and the last table entry is all 0. - */ - n = value[0]; - if ((n % 3) || n < 6 || n > max_ints) { - dev_err(dev, - "%s: The number of entries in the lux table must be a multiple of 3 and within the range [6, %d]\n", - __func__, max_ints); - goto done; - } - if ((value[n - 2] | value[n - 1] | value[n]) != 0) { - dev_err(dev, "%s: The last 3 entries in the lux table must be zeros.\n", - __func__); - goto done; - } - - memcpy(chip->als_settings.als_device_lux, &value[1], - value[0] * sizeof(value[1])); - - ret = len; - -done: - mutex_unlock(&chip->als_mutex); - - return ret; -} - -static IIO_CONST_ATTR(in_illuminance_calibscale_available, "1 8 16 111"); -static IIO_CONST_ATTR(in_illuminance_integration_time_available, - "0.000050 0.000100 0.000150 0.000200 0.000250 0.000300 0.000350 0.000400 0.000450 0.000500 0.000550 0.000600 0.000650"); -static IIO_DEVICE_ATTR_RW(in_illuminance_input_target, 0); -static IIO_DEVICE_ATTR_WO(in_illuminance_calibrate, 0); -static IIO_DEVICE_ATTR_RW(in_illuminance_lux_table, 0); - -static struct attribute *sysfs_attrs_ctrl[] = { - &iio_const_attr_in_illuminance_calibscale_available.dev_attr.attr, - &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, - &iio_dev_attr_in_illuminance_input_target.dev_attr.attr, - &iio_dev_attr_in_illuminance_calibrate.dev_attr.attr, - &iio_dev_attr_in_illuminance_lux_table.dev_attr.attr, - NULL -}; - -static const struct attribute_group tsl2583_attribute_group = { - .attrs = sysfs_attrs_ctrl, -}; - -static const struct iio_chan_spec tsl2583_channels[] = { - { - .type = IIO_LIGHT, - .modified = 1, - .channel2 = IIO_MOD_LIGHT_IR, - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), - }, - { - .type = IIO_LIGHT, - .modified = 1, - .channel2 = IIO_MOD_LIGHT_BOTH, - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), - }, - { - .type = IIO_LIGHT, - .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | - BIT(IIO_CHAN_INFO_CALIBBIAS) | - BIT(IIO_CHAN_INFO_CALIBSCALE) | - BIT(IIO_CHAN_INFO_INT_TIME), - }, -}; - -static int tsl2583_read_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int *val, int *val2, long mask) -{ - struct tsl2583_chip *chip = iio_priv(indio_dev); - int ret = -EINVAL; - - mutex_lock(&chip->als_mutex); - - if (chip->suspended) { - ret = -EBUSY; - goto read_done; - } - - switch (mask) { - case IIO_CHAN_INFO_RAW: - if (chan->type == IIO_LIGHT) { - ret = tsl2583_get_lux(indio_dev); - if (ret < 0) - goto read_done; - - /* - * From page 20 of the TSL2581, TSL2583 data - * sheet (TAOS134 − MARCH 2011): - * - * One of the photodiodes (channel 0) is - * sensitive to both visible and infrared light, - * while the second photodiode (channel 1) is - * sensitive primarily to infrared light. - */ - if (chan->channel2 == IIO_MOD_LIGHT_BOTH) - *val = chip->als_cur_info.als_ch0; - else - *val = chip->als_cur_info.als_ch1; - - ret = IIO_VAL_INT; - } - break; - case IIO_CHAN_INFO_PROCESSED: - if (chan->type == IIO_LIGHT) { - ret = tsl2583_get_lux(indio_dev); - if (ret < 0) - goto read_done; - - *val = ret; - ret = IIO_VAL_INT; - } - break; - case IIO_CHAN_INFO_CALIBBIAS: - if (chan->type == IIO_LIGHT) { - *val = chip->als_settings.als_gain_trim; - ret = IIO_VAL_INT; - } - break; - case IIO_CHAN_INFO_CALIBSCALE: - if (chan->type == IIO_LIGHT) { - *val = gainadj[chip->als_settings.als_gain].mean; - ret = IIO_VAL_INT; - } - break; - case IIO_CHAN_INFO_INT_TIME: - if (chan->type == IIO_LIGHT) { - *val = 0; - *val2 = chip->als_settings.als_time; - ret = IIO_VAL_INT_PLUS_MICRO; - } - break; - default: - break; - } - -read_done: - mutex_unlock(&chip->als_mutex); - - return ret; -} - -static int tsl2583_write_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int val, int val2, long mask) -{ - struct tsl2583_chip *chip = iio_priv(indio_dev); - int ret = -EINVAL; - - mutex_lock(&chip->als_mutex); - - if (chip->suspended) { - ret = -EBUSY; - goto write_done; - } - - switch (mask) { - case IIO_CHAN_INFO_CALIBBIAS: - if (chan->type == IIO_LIGHT) { - chip->als_settings.als_gain_trim = val; - ret = 0; - } - break; - case IIO_CHAN_INFO_CALIBSCALE: - if (chan->type == IIO_LIGHT) { - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(gainadj); i++) { - if (gainadj[i].mean == val) { - chip->als_settings.als_gain = i; - ret = tsl2583_set_als_gain(chip); - break; - } - } - } - break; - case IIO_CHAN_INFO_INT_TIME: - if (chan->type == IIO_LIGHT && !val && val2 >= 50 && - val2 <= 650 && !(val2 % 50)) { - chip->als_settings.als_time = val2; - ret = tsl2583_set_als_time(chip); - } - break; - default: - break; - } - -write_done: - mutex_unlock(&chip->als_mutex); - - return ret; -} - -static const struct iio_info tsl2583_info = { - .attrs = &tsl2583_attribute_group, - .driver_module = THIS_MODULE, - .read_raw = tsl2583_read_raw, - .write_raw = tsl2583_write_raw, -}; - -static int tsl2583_probe(struct i2c_client *clientp, - const struct i2c_device_id *idp) -{ - int ret; - struct tsl2583_chip *chip; - struct iio_dev *indio_dev; - - if (!i2c_check_functionality(clientp->adapter, - I2C_FUNC_SMBUS_BYTE_DATA)) { - dev_err(&clientp->dev, "%s: i2c smbus byte data functionality is unsupported\n", - __func__); - return -EOPNOTSUPP; - } - - indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip)); - if (!indio_dev) - return -ENOMEM; - - chip = iio_priv(indio_dev); - chip->client = clientp; - i2c_set_clientdata(clientp, indio_dev); - - mutex_init(&chip->als_mutex); - chip->suspended = true; - - ret = i2c_smbus_read_byte_data(clientp, - TSL2583_CMD_REG | TSL2583_CHIPID); - if (ret < 0) { - dev_err(&clientp->dev, - "%s: failed to read the chip ID register\n", __func__); - return ret; - } - - if ((ret & TSL2583_CHIP_ID_MASK) != TSL2583_CHIP_ID) { - dev_err(&clientp->dev, "%s: received an unknown chip ID %x\n", - __func__, ret); - return -EINVAL; - } - - indio_dev->info = &tsl2583_info; - indio_dev->channels = tsl2583_channels; - indio_dev->num_channels = ARRAY_SIZE(tsl2583_channels); - indio_dev->dev.parent = &clientp->dev; - indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->name = chip->client->name; - - ret = devm_iio_device_register(indio_dev->dev.parent, indio_dev); - if (ret) { - dev_err(&clientp->dev, "%s: iio registration failed\n", - __func__); - return ret; - } - - /* Load up the V2 defaults (these are hard coded defaults for now) */ - tsl2583_defaults(chip); - - /* Make sure the chip is on */ - ret = tsl2583_chip_init_and_power_on(indio_dev); - if (ret < 0) - return ret; - - dev_info(&clientp->dev, "Light sensor found.\n"); - - return 0; -} - -static int __maybe_unused tsl2583_suspend(struct device *dev) -{ - struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); - struct tsl2583_chip *chip = iio_priv(indio_dev); - int ret; - - mutex_lock(&chip->als_mutex); - - ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_OFF); - chip->suspended = true; - - mutex_unlock(&chip->als_mutex); - - return ret; -} - -static int __maybe_unused tsl2583_resume(struct device *dev) -{ - struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); - struct tsl2583_chip *chip = iio_priv(indio_dev); - int ret; - - mutex_lock(&chip->als_mutex); - - ret = tsl2583_chip_init_and_power_on(indio_dev); - - mutex_unlock(&chip->als_mutex); - - return ret; -} - -static SIMPLE_DEV_PM_OPS(tsl2583_pm_ops, tsl2583_suspend, tsl2583_resume); - -static struct i2c_device_id tsl2583_idtable[] = { - { "tsl2580", 0 }, - { "tsl2581", 1 }, - { "tsl2583", 2 }, - {} -}; -MODULE_DEVICE_TABLE(i2c, tsl2583_idtable); - -static const struct of_device_id tsl2583_of_match[] = { - { .compatible = "amstaos,tsl2580", }, - { .compatible = "amstaos,tsl2581", }, - { .compatible = "amstaos,tsl2583", }, - { }, -}; -MODULE_DEVICE_TABLE(of, tsl2583_of_match); - -/* Driver definition */ -static struct i2c_driver tsl2583_driver = { - .driver = { - .name = "tsl2583", - .pm = &tsl2583_pm_ops, - .of_match_table = tsl2583_of_match, - }, - .id_table = tsl2583_idtable, - .probe = tsl2583_probe, -}; -module_i2c_driver(tsl2583_driver); - -MODULE_AUTHOR("J. August Brenner "); -MODULE_AUTHOR("Brian Masney "); -MODULE_DESCRIPTION("TAOS tsl2583 ambient light sensor driver"); -MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 841fcea454fe8cd9e0744721bf491700a912d87e Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier Date: Tue, 15 Nov 2016 16:30:56 +0100 Subject: Documentation: dt-bindings: Document STM32 ADC DT bindings This patch adds documentation of device tree bindings for the STM32 ADC. Signed-off-by: Fabrice Gasnier Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/st,stm32-adc.txt | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/st,stm32-adc.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/iio/adc/st,stm32-adc.txt b/Documentation/devicetree/bindings/iio/adc/st,stm32-adc.txt new file mode 100644 index 000000000000..49ed82e89870 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/st,stm32-adc.txt @@ -0,0 +1,83 @@ +STMicroelectronics STM32 ADC device driver + +STM32 ADC is a successive approximation analog-to-digital converter. +It has several multiplexed input channels. Conversions can be performed +in single, continuous, scan or discontinuous mode. Result of the ADC is +stored in a left-aligned or right-aligned 32-bit data register. +Conversions can be launched in software or using hardware triggers. + +The analog watchdog feature allows the application to detect if the input +voltage goes beyond the user-defined, higher or lower thresholds. + +Each STM32 ADC block can have up to 3 ADC instances. + +Each instance supports two contexts to manage conversions, each one has its +own configurable sequence and trigger: +- regular conversion can be done in sequence, running in background +- injected conversions have higher priority, and so have the ability to + interrupt regular conversion sequence (either triggered in SW or HW). + Regular sequence is resumed, in case it has been interrupted. + +Contents of a stm32 adc root node: +----------------------------------- +Required properties: +- compatible: Should be "st,stm32f4-adc-core". +- reg: Offset and length of the ADC block register set. +- interrupts: Must contain the interrupt for ADC block. +- clocks: Clock for the analog circuitry (common to all ADCs). +- clock-names: Must be "adc". +- interrupt-controller: Identifies the controller node as interrupt-parent +- vref-supply: Phandle to the vref input analog reference voltage. +- #interrupt-cells = <1>; +- #address-cells = <1>; +- #size-cells = <0>; + +Optional properties: +- A pinctrl state named "default" for each ADC channel may be defined to set + inX ADC pins in mode of operation for analog input on external pin. + +Contents of a stm32 adc child node: +----------------------------------- +An ADC block node should contain at least one subnode, representing an +ADC instance available on the machine. + +Required properties: +- compatible: Should be "st,stm32f4-adc". +- reg: Offset of ADC instance in ADC block (e.g. may be 0x0, 0x100, 0x200). +- clocks: Input clock private to this ADC instance. +- interrupt-parent: Phandle to the parent interrupt controller. +- interrupts: IRQ Line for the ADC (e.g. may be 0 for adc@0, 1 for adc@100 or + 2 for adc@200). +- st,adc-channels: List of single-ended channels muxed for this ADC. + It can have up to 16 channels, numbered from 0 to 15 (resp. for in0..in15). +- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers" in + Documentation/devicetree/bindings/iio/iio-bindings.txt + +Example: + adc: adc@40012000 { + compatible = "st,stm32f4-adc-core"; + reg = <0x40012000 0x400>; + interrupts = <18>; + clocks = <&rcc 0 168>; + clock-names = "adc"; + vref-supply = <®_vref>; + interrupt-controller; + pinctrl-names = "default"; + pinctrl-0 = <&adc3_in8_pin>; + + #interrupt-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "st,stm32f4-adc"; + #io-channel-cells = <1>; + reg = <0x0>; + clocks = <&rcc 0 168>; + interrupt-parent = <&adc>; + interrupts = <0>; + st,adc-channels = <8>; + }; + ... + other adc child nodes follow... + }; -- cgit v1.2.3 From b4904595e624cf0a62ab3bce7d08cdbe1c22443c Mon Sep 17 00:00:00 2001 From: Stuart Yoder Date: Wed, 7 Dec 2016 17:29:00 -0600 Subject: staging: fsl-mc: add sysfs ABI doc define the bind/unbind sysfs interfaces for the fsl-mc bus Signed-off-by: Stuart Yoder Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-fsl-mc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-fsl-mc (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-fsl-mc b/Documentation/ABI/testing/sysfs-bus-fsl-mc new file mode 100644 index 000000000000..80256b8b4f26 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-fsl-mc @@ -0,0 +1,21 @@ +What: /sys/bus/fsl-mc/drivers/.../bind +Date: December 2016 +Contact: stuart.yoder@nxp.com +Description: + Writing a device location to this file will cause + the driver to attempt to bind to the device found at + this location. The format for the location is Object.Id + and is the same as found in /sys/bus/fsl-mc/devices/. + For example: + # echo dpni.2 > /sys/bus/fsl-mc/drivers/fsl_dpaa2_eth/bind + +What: /sys/bus/fsl-mc/drivers/.../unbind +Date: December 2016 +Contact: stuart.yoder@nxp.com +Description: + Writing a device location to this file will cause the + driver to attempt to unbind from the device found at + this location. The format for the location is Object.Id + and is the same as found in /sys/bus/fsl-mc/devices/. + For example: + # echo dpni.2 > /sys/bus/fsl-mc/drivers/fsl_dpaa2_eth/unbind -- cgit v1.2.3