From 78f16dbda5eee2f9379a6313cd01792160e2ec70 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 27 Nov 2015 14:52:51 +0100 Subject: [PATCH 01/28] iio: adc: mcp3422: Add mcp3421 support The mcp3421 is the single channel variant of the mcp342x family. Support is straight forward, only the channels array has to be added for this chip. Signed-off-by: Sascha Hauer Signed-off-by: Jonathan Cameron --- drivers/iio/adc/mcp3422.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/iio/adc/mcp3422.c b/drivers/iio/adc/mcp3422.c index 3555122008b4..6eca7aea8a37 100644 --- a/drivers/iio/adc/mcp3422.c +++ b/drivers/iio/adc/mcp3422.c @@ -305,6 +305,10 @@ static const struct attribute_group mcp3422_attribute_group = { .attrs = mcp3422_attributes, }; +static const struct iio_chan_spec mcp3421_channels[] = { + MCP3422_CHAN(0), +}; + static const struct iio_chan_spec mcp3422_channels[] = { MCP3422_CHAN(0), MCP3422_CHAN(1), @@ -352,6 +356,10 @@ static int mcp3422_probe(struct i2c_client *client, indio_dev->info = &mcp3422_info; switch (adc->id) { + case 1: + indio_dev->channels = mcp3421_channels; + indio_dev->num_channels = ARRAY_SIZE(mcp3421_channels); + break; case 2: case 3: case 6: @@ -383,6 +391,7 @@ static int mcp3422_probe(struct i2c_client *client, } static const struct i2c_device_id mcp3422_id[] = { + { "mcp3421", 1 }, { "mcp3422", 2 }, { "mcp3423", 3 }, { "mcp3424", 4 }, From 930cc0f39b07a830e6939c5634f9a310f306c4b1 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 27 Nov 2015 14:52:52 +0100 Subject: [PATCH 02/28] dt-bindings: iio: adc: Update mcp342x binding for the mcp3421 The mcp3421 is the single channel variant of the mcp342x family and can be supported by the mcp342x driver. Signed-off-by: Sascha Hauer Cc: devicetree@vger.kernel.org Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/mcp3422.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/adc/mcp3422.txt b/Documentation/devicetree/bindings/iio/adc/mcp3422.txt index 333139cc0bfb..dcae4ccfcc52 100644 --- a/Documentation/devicetree/bindings/iio/adc/mcp3422.txt +++ b/Documentation/devicetree/bindings/iio/adc/mcp3422.txt @@ -1,7 +1,8 @@ -* Microchip mcp3422/3/4/6/7/8 chip family (ADC) +* Microchip mcp3421/2/3/4/6/7/8 chip family (ADC) Required properties: - compatible: Should be + "microchip,mcp3421" or "microchip,mcp3422" or "microchip,mcp3423" or "microchip,mcp3424" or From c3304c212326cfcabb1faf6a0035d0c631778d5b Mon Sep 17 00:00:00 2001 From: Adriana Reus Date: Tue, 24 Nov 2015 12:59:48 +0200 Subject: [PATCH 03/28] iio: light: us5182d: Add property for choosing default power mode This chip supports two power modes. 1. "one-shot" mode - the chip activates and executes one complete conversion loop and then shuts itself down. This is the default mode chosen for raw reads. 2. "continuous" mode - the chip takes continuous measurements. Continuous mode is more expensive power-wise but may be more reliable. Add a property so that if preferred, the default power mode for raw reads can be set to continuous. Separate one-shot enabling in a separate function that will be used depending on the chosen power mode. Also create a function for powering the chip on and off. Signed-off-by: Adriana Reus Signed-off-by: Jonathan Cameron --- drivers/iio/light/us5182d.c | 90 ++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 12 deletions(-) diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c index 49dab3cb3e23..855f183ba216 100644 --- a/drivers/iio/light/us5182d.c +++ b/drivers/iio/light/us5182d.c @@ -99,6 +99,11 @@ enum mode { US5182D_PX_ONLY }; +enum pmode { + US5182D_CONTINUOUS, + US5182D_ONESHOT +}; + struct us5182d_data { struct i2c_client *client; struct mutex lock; @@ -112,6 +117,9 @@ struct us5182d_data { u16 *us5182d_dark_ths; u8 opmode; + u8 power_mode; + + bool default_continuous; }; static IIO_CONST_ATTR(in_illuminance_scale_available, @@ -130,13 +138,11 @@ static const struct { u8 reg; u8 val; } us5182d_regvals[] = { - {US5182D_REG_CFG0, (US5182D_CFG0_SHUTDOWN_EN | - US5182D_CFG0_WORD_ENABLE)}, + {US5182D_REG_CFG0, US5182D_CFG0_WORD_ENABLE}, {US5182D_REG_CFG1, US5182D_CFG1_ALS_RES16}, {US5182D_REG_CFG2, (US5182D_CFG2_PX_RES16 | US5182D_CFG2_PXGAIN_DEFAULT)}, {US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100}, - {US5182D_REG_MODE_STORE, US5182D_STORE_MODE}, {US5182D_REG_CFG4, 0x00}, }; @@ -169,7 +175,7 @@ static int us5182d_get_als(struct us5182d_data *data) return result; } -static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) +static int us5182d_oneshot_en(struct us5182d_data *data) { int ret; @@ -183,6 +189,20 @@ static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) */ ret = ret | US5182D_CFG0_ONESHOT_EN; + return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); +} + +static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) +{ + int ret; + + if (mode == data->opmode) + return 0; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) + return ret; + /* update mode */ ret = ret & ~US5182D_OPMODE_MASK; ret = ret | (mode << US5182D_OPMODE_SHIFT); @@ -196,9 +216,6 @@ static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) if (ret < 0) return ret; - if (mode == data->opmode) - return 0; - ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_MODE_STORE, US5182D_STORE_MODE); if (ret < 0) @@ -210,6 +227,23 @@ static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) return 0; } +static int us5182d_shutdown_en(struct us5182d_data *data, u8 state) +{ + int ret; + + if (data->power_mode == US5182D_ONESHOT) + return 0; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) + return ret; + + ret = ret & ~US5182D_CFG0_SHUTDOWN_EN; + ret = ret | state; + + return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); +} + static int us5182d_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -222,6 +256,11 @@ static int us5182d_read_raw(struct iio_dev *indio_dev, switch (chan->type) { case IIO_LIGHT: mutex_lock(&data->lock); + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_oneshot_en(data); + if (ret < 0) + goto out_err; + } ret = us5182d_set_opmode(data, US5182D_OPMODE_ALS); if (ret < 0) goto out_err; @@ -234,6 +273,11 @@ static int us5182d_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_PROXIMITY: mutex_lock(&data->lock); + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_oneshot_en(data); + if (ret < 0) + goto out_err; + } ret = us5182d_set_opmode(data, US5182D_OPMODE_PX); if (ret < 0) goto out_err; @@ -368,6 +412,7 @@ static int us5182d_init(struct iio_dev *indio_dev) return ret; data->opmode = 0; + data->power_mode = US5182D_CONTINUOUS; for (i = 0; i < ARRAY_SIZE(us5182d_regvals); i++) { ret = i2c_smbus_write_byte_data(data->client, us5182d_regvals[i].reg, @@ -376,7 +421,15 @@ static int us5182d_init(struct iio_dev *indio_dev) return ret; } - return 0; + if (!data->default_continuous) { + ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + if (ret < 0) + return ret; + data->power_mode = US5182D_ONESHOT; + } + + + return ret; } static void us5182d_get_platform_data(struct iio_dev *indio_dev) @@ -399,6 +452,8 @@ static void us5182d_get_platform_data(struct iio_dev *indio_dev) "upisemi,lower-dark-gain", &data->lower_dark_gain)) data->lower_dark_gain = US5182D_REG_AUTO_LDARK_GAIN_DEFAULT; + data->default_continuous = device_property_read_bool(&data->client->dev, + "upisemi,continuous"); } static int us5182d_dark_gain_config(struct iio_dev *indio_dev) @@ -464,16 +519,27 @@ static int us5182d_probe(struct i2c_client *client, ret = us5182d_dark_gain_config(indio_dev); if (ret < 0) - return ret; + goto out_err; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto out_err; + + return 0; + +out_err: + us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + return ret; - return iio_device_register(indio_dev); } static int us5182d_remove(struct i2c_client *client) { + struct us5182d_data *data = iio_priv(i2c_get_clientdata(client)); + iio_device_unregister(i2c_get_clientdata(client)); - return i2c_smbus_write_byte_data(client, US5182D_REG_CFG0, - US5182D_CFG0_SHUTDOWN_EN); + + return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); } static const struct acpi_device_id us5182d_acpi_match[] = { From 023e30fb0d3a3b9d6b8dc9e47590aa544d58a22f Mon Sep 17 00:00:00 2001 From: Adriana Reus Date: Tue, 24 Nov 2015 12:59:49 +0200 Subject: [PATCH 04/28] Documentation: devicetree: Add property for controlling power saving mode for the us5182 als sensor Add a property to allow changing the default power-saving mode. By default, at read raw the chip will activate and provide one measurent, then it will shut itself down. However, the chip can also work in "continuous" mode which may be more reliable but is also more power consuming. Signed-off-by: Adriana Reus Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/light/us5182d.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/light/us5182d.txt b/Documentation/devicetree/bindings/iio/light/us5182d.txt index 6f0a530144fd..a61979997f37 100644 --- a/Documentation/devicetree/bindings/iio/light/us5182d.txt +++ b/Documentation/devicetree/bindings/iio/light/us5182d.txt @@ -7,13 +7,24 @@ Required properties: Optional properties: - upisemi,glass-coef: glass attenuation factor - compensation factor of resolution 1000 for material transmittance. + - upisemi,dark-ths: array of 8 elements containing 16-bit thresholds (adc counts) corresponding to every scale. + - upisemi,upper-dark-gain: 8-bit dark gain compensation factor(4 int and 4 fractional bits - Q4.4) applied when light > threshold + - upisemi,lower-dark-gain: 8-bit dark gain compensation factor(4 int and 4 fractional bits - Q4.4) applied when light < threshold +- upisemi,continuous: This chip has two power modes: one-shot (chip takes one + measurement and then shuts itself down) and continuous ( + chip takes continuous measurements). The one-shot mode is + more power-friendly but the continuous mode may be more + reliable. If this property is specified the continuous + mode will be used instead of the default one-shot one for + raw reads. + If the optional properties are not specified these factors will default to the values in the below example. The glass-coef defaults to no compensation for the covering material. From a22a3c5c40deb31f03c2810a46e669bedbf476c5 Mon Sep 17 00:00:00 2001 From: Adriana Reus Date: Tue, 24 Nov 2015 12:59:50 +0200 Subject: [PATCH 05/28] iio: light: us5182d: Add functions for selectively enabling als and proximity Keep track of the als and px enabled/disabled status in order to enable them selectively. Signed-off-by: Adriana Reus Signed-off-by: Jonathan Cameron --- drivers/iio/light/us5182d.c | 66 ++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c index 855f183ba216..e67956c1f296 100644 --- a/drivers/iio/light/us5182d.c +++ b/drivers/iio/light/us5182d.c @@ -119,6 +119,9 @@ struct us5182d_data { u8 opmode; u8 power_mode; + bool als_enabled; + bool px_enabled; + bool default_continuous; }; @@ -227,6 +230,50 @@ static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) return 0; } +static int us5182d_als_enable(struct us5182d_data *data) +{ + int ret; + u8 mode; + + if (data->power_mode == US5182D_ONESHOT) + return us5182d_set_opmode(data, US5182D_ALS_ONLY); + + if (data->als_enabled) + return 0; + + mode = data->px_enabled ? US5182D_ALS_PX : US5182D_ALS_ONLY; + + ret = us5182d_set_opmode(data, mode); + if (ret < 0) + return ret; + + data->als_enabled = true; + + return 0; +} + +static int us5182d_px_enable(struct us5182d_data *data) +{ + int ret; + u8 mode; + + if (data->power_mode == US5182D_ONESHOT) + return us5182d_set_opmode(data, US5182D_PX_ONLY); + + if (data->px_enabled) + return 0; + + mode = data->als_enabled ? US5182D_ALS_PX : US5182D_PX_ONLY; + + ret = us5182d_set_opmode(data, mode); + if (ret < 0) + return ret; + + data->px_enabled = true; + + return 0; +} + static int us5182d_shutdown_en(struct us5182d_data *data, u8 state) { int ret; @@ -241,7 +288,16 @@ static int us5182d_shutdown_en(struct us5182d_data *data, u8 state) ret = ret & ~US5182D_CFG0_SHUTDOWN_EN; ret = ret | state; - return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); + if (ret < 0) + return ret; + + if (state & US5182D_CFG0_SHUTDOWN_EN) { + data->als_enabled = false; + data->px_enabled = false; + } + + return ret; } static int us5182d_read_raw(struct iio_dev *indio_dev, @@ -261,7 +317,7 @@ static int us5182d_read_raw(struct iio_dev *indio_dev, if (ret < 0) goto out_err; } - ret = us5182d_set_opmode(data, US5182D_OPMODE_ALS); + ret = us5182d_als_enable(data); if (ret < 0) goto out_err; @@ -278,7 +334,7 @@ static int us5182d_read_raw(struct iio_dev *indio_dev, if (ret < 0) goto out_err; } - ret = us5182d_set_opmode(data, US5182D_OPMODE_PX); + ret = us5182d_px_enable(data); if (ret < 0) goto out_err; @@ -421,6 +477,9 @@ static int us5182d_init(struct iio_dev *indio_dev) return ret; } + data->als_enabled = true; + data->px_enabled = true; + if (!data->default_continuous) { ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); if (ret < 0) @@ -428,7 +487,6 @@ static int us5182d_init(struct iio_dev *indio_dev) data->power_mode = US5182D_ONESHOT; } - return ret; } From f0e5f57d3ac25aa2afb25dc94d2b42a8defa8a19 Mon Sep 17 00:00:00 2001 From: Adriana Reus Date: Tue, 24 Nov 2015 12:59:51 +0200 Subject: [PATCH 06/28] iio: light: us8152d: Add power management support Add power management for sleep as well as runtime pm. Signed-off-by: Adriana Reus Signed-off-by: Jonathan Cameron --- drivers/iio/light/us5182d.c | 95 ++++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 7 deletions(-) diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c index e67956c1f296..256c4bc12d21 100644 --- a/drivers/iio/light/us5182d.c +++ b/drivers/iio/light/us5182d.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #define US5182D_REG_CFG0 0x00 #define US5182D_CFG0_ONESHOT_EN BIT(6) @@ -81,6 +83,7 @@ #define US5182D_READ_BYTE 1 #define US5182D_READ_WORD 2 #define US5182D_OPSTORE_SLEEP_TIME 20 /* ms */ +#define US5182D_SLEEP_MS 3000 /* ms */ /* Available ranges: [12354, 7065, 3998, 2202, 1285, 498, 256, 138] lux */ static const int us5182d_scales[] = {188500, 107800, 61000, 33600, 19600, 7600, @@ -300,6 +303,26 @@ static int us5182d_shutdown_en(struct us5182d_data *data, u8 state) return ret; } + +static int us5182d_set_power_state(struct us5182d_data *data, bool on) +{ + int ret; + + if (data->power_mode == US5182D_ONESHOT) + return 0; + + if (on) { + ret = pm_runtime_get_sync(&data->client->dev); + if (ret < 0) + pm_runtime_put_noidle(&data->client->dev); + } else { + pm_runtime_mark_last_busy(&data->client->dev); + ret = pm_runtime_put_autosuspend(&data->client->dev); + } + + return ret; +} + static int us5182d_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -317,15 +340,20 @@ static int us5182d_read_raw(struct iio_dev *indio_dev, if (ret < 0) goto out_err; } - ret = us5182d_als_enable(data); + ret = us5182d_set_power_state(data, true); if (ret < 0) goto out_err; - + ret = us5182d_als_enable(data); + if (ret < 0) + goto out_poweroff; ret = us5182d_get_als(data); + if (ret < 0) + goto out_poweroff; + *val = ret; + ret = us5182d_set_power_state(data, false); if (ret < 0) goto out_err; mutex_unlock(&data->lock); - *val = ret; return IIO_VAL_INT; case IIO_PROXIMITY: mutex_lock(&data->lock); @@ -334,17 +362,22 @@ static int us5182d_read_raw(struct iio_dev *indio_dev, if (ret < 0) goto out_err; } - ret = us5182d_px_enable(data); + ret = us5182d_set_power_state(data, true); if (ret < 0) goto out_err; - + ret = us5182d_px_enable(data); + if (ret < 0) + goto out_poweroff; ret = i2c_smbus_read_word_data(data->client, US5182D_REG_PDL); + if (ret < 0) + goto out_poweroff; + *val = ret; + ret = us5182d_set_power_state(data, false); if (ret < 0) goto out_err; mutex_unlock(&data->lock); - *val = ret; - return IIO_VAL_INT; + return IIO_VAL_INT; default: return -EINVAL; } @@ -363,6 +396,9 @@ static int us5182d_read_raw(struct iio_dev *indio_dev, } return -EINVAL; + +out_poweroff: + us5182d_set_power_state(data, false); out_err: mutex_unlock(&data->lock); return ret; @@ -579,6 +615,17 @@ static int us5182d_probe(struct i2c_client *client, if (ret < 0) goto out_err; + if (data->default_continuous) { + pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_err; + } + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + US5182D_SLEEP_MS); + pm_runtime_use_autosuspend(&client->dev); + ret = iio_device_register(indio_dev); if (ret < 0) goto out_err; @@ -597,9 +644,42 @@ static int us5182d_remove(struct i2c_client *client) iio_device_unregister(i2c_get_clientdata(client)); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); } +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM) +static int us5182d_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct us5182d_data *data = iio_priv(indio_dev); + + if (data->power_mode == US5182D_CONTINUOUS) + return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + + return 0; +} + +static int us5182d_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct us5182d_data *data = iio_priv(indio_dev); + + if (data->power_mode == US5182D_CONTINUOUS) + return us5182d_shutdown_en(data, + ~US5182D_CFG0_SHUTDOWN_EN & 0xff); + + return 0; +} +#endif + +static const struct dev_pm_ops us5182d_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(us5182d_suspend, us5182d_resume) + SET_RUNTIME_PM_OPS(us5182d_suspend, us5182d_resume, NULL) +}; + static const struct acpi_device_id us5182d_acpi_match[] = { { "USD5182", 0}, {} @@ -617,6 +697,7 @@ MODULE_DEVICE_TABLE(i2c, us5182d_id); static struct i2c_driver us5182d_driver = { .driver = { .name = US5182D_DRV_NAME, + .pm = &us5182d_pm_ops, .acpi_match_table = ACPI_PTR(us5182d_acpi_match), }, .probe = us5182d_probe, From 22b19ab3e2d6da666be816e649af67bf721b8561 Mon Sep 17 00:00:00 2001 From: Nizam Haider Date: Mon, 23 Nov 2015 23:03:07 +0530 Subject: [PATCH 07/28] Staging: iio: adc: use dev_get_platdata() Use the wrapper function for retrieving the platform data instead of accessing dev->platform_data directly. Signed-off-by: Nizam Haider Signed-off-by: Jonathan Cameron --- drivers/staging/iio/adc/ad7192.c | 2 +- drivers/staging/iio/adc/ad7280a.c | 2 +- drivers/staging/iio/adc/ad7816.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/staging/iio/adc/ad7192.c b/drivers/staging/iio/adc/ad7192.c index bb40f3728742..92211039ffa9 100644 --- a/drivers/staging/iio/adc/ad7192.c +++ b/drivers/staging/iio/adc/ad7192.c @@ -609,7 +609,7 @@ static const struct iio_chan_spec ad7192_channels[] = { static int ad7192_probe(struct spi_device *spi) { - const struct ad7192_platform_data *pdata = spi->dev.platform_data; + const struct ad7192_platform_data *pdata = dev_get_platdata(&spi->dev); struct ad7192_state *st; struct iio_dev *indio_dev; int ret, voltage_uv = 0; diff --git a/drivers/staging/iio/adc/ad7280a.c b/drivers/staging/iio/adc/ad7280a.c index 35acb1a4669b..f45ebedb7a05 100644 --- a/drivers/staging/iio/adc/ad7280a.c +++ b/drivers/staging/iio/adc/ad7280a.c @@ -833,7 +833,7 @@ static const struct ad7280_platform_data ad7793_default_pdata = { static int ad7280_probe(struct spi_device *spi) { - const struct ad7280_platform_data *pdata = spi->dev.platform_data; + const struct ad7280_platform_data *pdata = dev_get_platdata(&spi->dev); struct ad7280_state *st; int ret; const unsigned short tACQ_ns[4] = {465, 1010, 1460, 1890}; diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c index c8e156646528..22260512cf01 100644 --- a/drivers/staging/iio/adc/ad7816.c +++ b/drivers/staging/iio/adc/ad7816.c @@ -345,7 +345,7 @@ static int ad7816_probe(struct spi_device *spi_dev) { struct ad7816_chip_info *chip; struct iio_dev *indio_dev; - unsigned short *pins = spi_dev->dev.platform_data; + unsigned short *pins = dev_get_platdata(&spi_dev->dev); int ret = 0; int i; From a260527f83dfebe7c49bd6582ed6aa1be29f355a Mon Sep 17 00:00:00 2001 From: Nizam Haider Date: Mon, 23 Nov 2015 23:18:50 +0530 Subject: [PATCH 08/28] Staging: iio: light: tsl2x7x_core: use dev_get_platdata() Use the wrapper function for retrieving the platform data instead of accessing dev->platform_data directly. Signed-off-by: Nizam Haider Signed-off-by: Jonathan Cameron --- drivers/staging/iio/light/tsl2x7x_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/iio/light/tsl2x7x_core.c b/drivers/staging/iio/light/tsl2x7x_core.c index 9dfd04855a1b..5b1c1650a0e4 100644 --- a/drivers/staging/iio/light/tsl2x7x_core.c +++ b/drivers/staging/iio/light/tsl2x7x_core.c @@ -1898,7 +1898,7 @@ static int tsl2x7x_probe(struct i2c_client *clientp, mutex_init(&chip->prox_mutex); chip->tsl2x7x_chip_status = TSL2X7X_CHIP_UNKNOWN; - chip->pdata = clientp->dev.platform_data; + chip->pdata = dev_get_platdata(&clientp->dev); chip->id = id->driver_data; chip->chip_info = &tsl2x7x_chip_info_tbl[device_channel_config[id->driver_data]]; From 7221819ac48195ea9f1e179c3f812939319d9e7b Mon Sep 17 00:00:00 2001 From: Nizam Haider Date: Mon, 23 Nov 2015 23:37:16 +0530 Subject: [PATCH 09/28] Staging: iio: frequency: use dev_get_platdata() Use the wrapper function for retrieving the platform data instead of accessing dev->platform_data directly. Signed-off-by: Nizam Haider Signed-off-by: Jonathan Cameron --- drivers/staging/iio/frequency/ad9832.c | 2 +- drivers/staging/iio/frequency/ad9834.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/staging/iio/frequency/ad9832.c b/drivers/staging/iio/frequency/ad9832.c index 2b65faa6296a..18b27a1984b2 100644 --- a/drivers/staging/iio/frequency/ad9832.c +++ b/drivers/staging/iio/frequency/ad9832.c @@ -201,7 +201,7 @@ static const struct iio_info ad9832_info = { static int ad9832_probe(struct spi_device *spi) { - struct ad9832_platform_data *pdata = spi->dev.platform_data; + struct ad9832_platform_data *pdata = dev_get_platdata(&spi->dev); struct iio_dev *indio_dev; struct ad9832_state *st; struct regulator *reg; diff --git a/drivers/staging/iio/frequency/ad9834.c b/drivers/staging/iio/frequency/ad9834.c index 6464f2cbe94b..6366216e4f37 100644 --- a/drivers/staging/iio/frequency/ad9834.c +++ b/drivers/staging/iio/frequency/ad9834.c @@ -318,7 +318,7 @@ static const struct iio_info ad9833_info = { static int ad9834_probe(struct spi_device *spi) { - struct ad9834_platform_data *pdata = spi->dev.platform_data; + struct ad9834_platform_data *pdata = dev_get_platdata(&spi->dev); struct ad9834_state *st; struct iio_dev *indio_dev; struct regulator *reg; From 8261d961d1f397925d7a470864c14663d01ed714 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Mon, 9 Nov 2015 09:13:59 +0200 Subject: [PATCH 10/28] iio: core: Introduce IIO configfs support This patch creates the IIO configfs root group. The group will appear under /iio/, usually /config/iio. We introduce configfs support in IIO in order to be able to easily create IIO objects from userspace. The first supported IIO objects are triggers introduced with next patches. Signed-off-by: Daniel Baluta Tested-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- drivers/iio/Kconfig | 8 +++++ drivers/iio/Makefile | 1 + drivers/iio/industrialio-configfs.c | 50 +++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 drivers/iio/industrialio-configfs.c diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 6b8c77c97d40..9509b8a4c551 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -22,6 +22,14 @@ if IIO_BUFFER source "drivers/iio/buffer/Kconfig" endif # IIO_BUFFER +config IIO_CONFIGFS + tristate "Enable IIO configuration via configfs" + select CONFIGFS_FS + help + This allows configuring various IIO bits through configfs + (e.g. software triggers). For more info see + Documentation/iio/iio_configfs.txt. + config IIO_TRIGGER bool "Enable triggered sampling support" help diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 6769f2f43e86..39d119f18f2e 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -7,6 +7,7 @@ industrialio-y := industrialio-core.o industrialio-event.o inkern.o industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o +obj-$(CONFIG_IIO_CONFIGFS) += industrialio-configfs.o obj-$(CONFIG_IIO_TRIGGERED_EVENT) += industrialio-triggered-event.o obj-y += accel/ diff --git a/drivers/iio/industrialio-configfs.c b/drivers/iio/industrialio-configfs.c new file mode 100644 index 000000000000..83563dd7fcf4 --- /dev/null +++ b/drivers/iio/industrialio-configfs.c @@ -0,0 +1,50 @@ +/* + * Industrial I/O configfs bits + * + * Copyright (c) 2015 Intel Corporation + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include + +static struct config_item_type iio_root_group_type = { + .ct_owner = THIS_MODULE, +}; + +struct configfs_subsystem iio_configfs_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "iio", + .ci_type = &iio_root_group_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(iio_configfs_subsys.su_mutex), +}; +EXPORT_SYMBOL(iio_configfs_subsys); + +static int __init iio_configfs_init(void) +{ + config_group_init(&iio_configfs_subsys.su_group); + + return configfs_register_subsystem(&iio_configfs_subsys); +} +module_init(iio_configfs_init); + +static void __exit iio_configfs_exit(void) +{ + configfs_unregister_subsystem(&iio_configfs_subsys); +} +module_exit(iio_configfs_exit); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Industrial I/O configfs support"); +MODULE_LICENSE("GPL v2"); From b662f809d41009749a9ee6f9a4db3d9af579e171 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Mon, 9 Nov 2015 09:14:00 +0200 Subject: [PATCH 11/28] iio: core: Introduce IIO software triggers A software trigger associates an IIO device trigger with a software interrupt source (e.g: timer, sysfs). This patch adds the generic infrastructure for handling software triggers. Software interrupts sources are kept in a iio_trigger_types_list and registered separately when the associated kernel module is loaded. Software triggers can be created directly from drivers or from user space via configfs interface. To sum up, this dynamically creates "triggers" group to be found under /config/iio/triggers and offers the possibility of dynamically creating trigger types groups. The first supported trigger type is "hrtimer" found under /config/iio/triggers/hrtimer. Signed-off-by: Daniel Baluta Signed-off-by: Jonathan Cameron --- drivers/iio/Kconfig | 8 ++ drivers/iio/Makefile | 1 + drivers/iio/industrialio-sw-trigger.c | 183 ++++++++++++++++++++++++++ include/linux/iio/sw_trigger.h | 71 ++++++++++ 4 files changed, 263 insertions(+) create mode 100644 drivers/iio/industrialio-sw-trigger.c create mode 100644 include/linux/iio/sw_trigger.h diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 9509b8a4c551..ac8715e56ba1 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -46,6 +46,14 @@ config IIO_CONSUMERS_PER_TRIGGER This value controls the maximum number of consumers that a given trigger may handle. Default is 2. +config IIO_SW_TRIGGER + tristate "Enable software triggers support" + select IIO_CONFIGFS + help + Provides IIO core support for software triggers. A software + trigger can be created via configfs or directly by a driver + using the API provided. + config IIO_TRIGGERED_EVENT tristate select IIO_TRIGGER diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 39d119f18f2e..f670b82298aa 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -8,6 +8,7 @@ industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o obj-$(CONFIG_IIO_CONFIGFS) += industrialio-configfs.o +obj-$(CONFIG_IIO_SW_TRIGGER) += industrialio-sw-trigger.o obj-$(CONFIG_IIO_TRIGGERED_EVENT) += industrialio-triggered-event.o obj-y += accel/ diff --git a/drivers/iio/industrialio-sw-trigger.c b/drivers/iio/industrialio-sw-trigger.c new file mode 100644 index 000000000000..4825cfd9c4ea --- /dev/null +++ b/drivers/iio/industrialio-sw-trigger.c @@ -0,0 +1,183 @@ +/* + * The Industrial I/O core, software trigger functions + * + * Copyright (c) 2015 Intel Corporation + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include + +static struct config_group *iio_triggers_group; +static struct config_item_type iio_trigger_type_group_type; + +static struct config_item_type iio_triggers_group_type = { + .ct_owner = THIS_MODULE, +}; + +static LIST_HEAD(iio_trigger_types_list); +static DEFINE_MUTEX(iio_trigger_types_lock); + +static +struct iio_sw_trigger_type *__iio_find_sw_trigger_type(const char *name, + unsigned len) +{ + struct iio_sw_trigger_type *t = NULL, *iter; + + list_for_each_entry(iter, &iio_trigger_types_list, list) + if (!strcmp(iter->name, name)) { + t = iter; + break; + } + + return t; +} + +int iio_register_sw_trigger_type(struct iio_sw_trigger_type *t) +{ + struct iio_sw_trigger_type *iter; + int ret = 0; + + mutex_lock(&iio_trigger_types_lock); + iter = __iio_find_sw_trigger_type(t->name, strlen(t->name)); + if (iter) + ret = -EBUSY; + else + list_add_tail(&t->list, &iio_trigger_types_list); + mutex_unlock(&iio_trigger_types_lock); + + if (ret) + return ret; + + t->group = configfs_register_default_group(iio_triggers_group, t->name, + &iio_trigger_type_group_type); + if (IS_ERR(t->group)) + ret = PTR_ERR(t->group); + + return ret; +} +EXPORT_SYMBOL(iio_register_sw_trigger_type); + +void iio_unregister_sw_trigger_type(struct iio_sw_trigger_type *t) +{ + struct iio_sw_trigger_type *iter; + + mutex_lock(&iio_trigger_types_lock); + iter = __iio_find_sw_trigger_type(t->name, strlen(t->name)); + if (iter) + list_del(&t->list); + mutex_unlock(&iio_trigger_types_lock); + + configfs_unregister_default_group(t->group); +} +EXPORT_SYMBOL(iio_unregister_sw_trigger_type); + +static +struct iio_sw_trigger_type *iio_get_sw_trigger_type(const char *name) +{ + struct iio_sw_trigger_type *t; + + mutex_lock(&iio_trigger_types_lock); + t = __iio_find_sw_trigger_type(name, strlen(name)); + if (t && !try_module_get(t->owner)) + t = NULL; + mutex_unlock(&iio_trigger_types_lock); + + return t; +} + +struct iio_sw_trigger *iio_sw_trigger_create(const char *type, const char *name) +{ + struct iio_sw_trigger *t; + struct iio_sw_trigger_type *tt; + + tt = iio_get_sw_trigger_type(type); + if (!tt) { + pr_err("Invalid trigger type: %s\n", type); + return ERR_PTR(-EINVAL); + } + t = tt->ops->probe(name); + if (IS_ERR(t)) + goto out_module_put; + + t->trigger_type = tt; + + return t; +out_module_put: + module_put(tt->owner); + return t; +} +EXPORT_SYMBOL(iio_sw_trigger_create); + +void iio_sw_trigger_destroy(struct iio_sw_trigger *t) +{ + struct iio_sw_trigger_type *tt = t->trigger_type; + + tt->ops->remove(t); + module_put(tt->owner); +} +EXPORT_SYMBOL(iio_sw_trigger_destroy); + +static struct config_group *trigger_make_group(struct config_group *group, + const char *name) +{ + struct iio_sw_trigger *t; + + t = iio_sw_trigger_create(group->cg_item.ci_name, name); + if (IS_ERR(t)) + return ERR_CAST(t); + + config_item_set_name(&t->group.cg_item, "%s", name); + + return &t->group; +} + +static void trigger_drop_group(struct config_group *group, + struct config_item *item) +{ + struct iio_sw_trigger *t = to_iio_sw_trigger(item); + + iio_sw_trigger_destroy(t); + config_item_put(item); +} + +static struct configfs_group_operations trigger_ops = { + .make_group = &trigger_make_group, + .drop_item = &trigger_drop_group, +}; + +static struct config_item_type iio_trigger_type_group_type = { + .ct_group_ops = &trigger_ops, + .ct_owner = THIS_MODULE, +}; + +static int __init iio_sw_trigger_init(void) +{ + iio_triggers_group = + configfs_register_default_group(&iio_configfs_subsys.su_group, + "triggers", + &iio_triggers_group_type); + if (IS_ERR(iio_triggers_group)) + return PTR_ERR(iio_triggers_group); + return 0; +} +module_init(iio_sw_trigger_init); + +static void __exit iio_sw_trigger_exit(void) +{ + configfs_unregister_default_group(iio_triggers_group); +} +module_exit(iio_sw_trigger_exit); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Industrial I/O software triggers support"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/iio/sw_trigger.h b/include/linux/iio/sw_trigger.h new file mode 100644 index 000000000000..c2f33b2b35a5 --- /dev/null +++ b/include/linux/iio/sw_trigger.h @@ -0,0 +1,71 @@ +/* + * Industrial I/O software trigger interface + * + * Copyright (c) 2015 Intel Corporation + * + * 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. + */ + +#ifndef __IIO_SW_TRIGGER +#define __IIO_SW_TRIGGER + +#include +#include +#include +#include + +#define module_iio_sw_trigger_driver(__iio_sw_trigger_type) \ + module_driver(__iio_sw_trigger_type, iio_register_sw_trigger_type, \ + iio_unregister_sw_trigger_type) + +extern struct configfs_subsystem iio_configfs_subsys; +struct iio_sw_trigger_ops; + +struct iio_sw_trigger_type { + const char *name; + struct module *owner; + const struct iio_sw_trigger_ops *ops; + struct list_head list; + struct config_group *group; +}; + +struct iio_sw_trigger { + struct iio_trigger *trigger; + struct iio_sw_trigger_type *trigger_type; + struct config_group group; +}; + +struct iio_sw_trigger_ops { + struct iio_sw_trigger* (*probe)(const char *); + int (*remove)(struct iio_sw_trigger *); +}; + +static inline +struct iio_sw_trigger *to_iio_sw_trigger(struct config_item *item) +{ + return container_of(to_config_group(item), struct iio_sw_trigger, + group); +} + +int iio_register_sw_trigger_type(struct iio_sw_trigger_type *tt); +void iio_unregister_sw_trigger_type(struct iio_sw_trigger_type *tt); + +struct iio_sw_trigger *iio_sw_trigger_create(const char *, const char *); +void iio_sw_trigger_destroy(struct iio_sw_trigger *); + +int iio_sw_trigger_type_configfs_register(struct iio_sw_trigger_type *tt); +void iio_sw_trigger_type_configfs_unregister(struct iio_sw_trigger_type *tt); + +static inline +void iio_swt_group_init_type_name(struct iio_sw_trigger *t, + const char *name, + struct config_item_type *type) +{ +#ifdef CONFIG_CONFIGFS_FS + config_group_init_type_name(&t->group, name, type); +#endif +} + +#endif /* __IIO_SW_TRIGGER */ From ac5006a2a558a2441a840c7be1e0e717839d5e07 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Mon, 9 Nov 2015 09:14:01 +0200 Subject: [PATCH 12/28] iio: trigger: Introduce IIO hrtimer based trigger This patch registers a new IIO software trigger interrupt source based on high resolution timers. Notice that if configfs is enabled we create sampling_frequency attribute allowing users to change hrtimer period (1/sampling_frequency). The IIO hrtimer trigger has a long history, this patch is based on an older version from Marten and Lars-Peter. Signed-off-by: Marten Svanfeldt Signed-off-by: Lars-Peter Clausen Signed-off-by: Daniel Baluta Signed-off-by: Jonathan Cameron --- drivers/iio/trigger/Kconfig | 10 ++ drivers/iio/trigger/Makefile | 2 + drivers/iio/trigger/iio-trig-hrtimer.c | 193 +++++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 drivers/iio/trigger/iio-trig-hrtimer.c diff --git a/drivers/iio/trigger/Kconfig b/drivers/iio/trigger/Kconfig index 79996123a71b..519e6772f6f5 100644 --- a/drivers/iio/trigger/Kconfig +++ b/drivers/iio/trigger/Kconfig @@ -5,6 +5,16 @@ menu "Triggers - standalone" +config IIO_HRTIMER_TRIGGER + tristate "High resolution timer trigger" + depends on IIO_SW_TRIGGER + help + Provides a frequency based IIO trigger using high resolution + timers as interrupt source. + + To compile this driver as a module, choose M here: the + module will be called iio-trig-hrtimer. + config IIO_INTERRUPT_TRIGGER tristate "Generic interrupt trigger" help diff --git a/drivers/iio/trigger/Makefile b/drivers/iio/trigger/Makefile index 0694daecaf22..fe06eb564367 100644 --- a/drivers/iio/trigger/Makefile +++ b/drivers/iio/trigger/Makefile @@ -3,5 +3,7 @@ # # When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_IIO_HRTIMER_TRIGGER) += iio-trig-hrtimer.o obj-$(CONFIG_IIO_INTERRUPT_TRIGGER) += iio-trig-interrupt.o obj-$(CONFIG_IIO_SYSFS_TRIGGER) += iio-trig-sysfs.o diff --git a/drivers/iio/trigger/iio-trig-hrtimer.c b/drivers/iio/trigger/iio-trig-hrtimer.c new file mode 100644 index 000000000000..5e6d451febeb --- /dev/null +++ b/drivers/iio/trigger/iio-trig-hrtimer.c @@ -0,0 +1,193 @@ +/** + * The industrial I/O periodic hrtimer trigger driver + * + * Copyright (C) Intuitive Aerial AB + * Written by Marten Svanfeldt, marten@intuitiveaerial.com + * Copyright (C) 2012, Analog Device Inc. + * Author: Lars-Peter Clausen + * Copyright (C) 2015, Intel Corporation + * + * 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. + * + */ +#include +#include +#include + +#include +#include +#include + +/* default sampling frequency - 100Hz */ +#define HRTIMER_DEFAULT_SAMPLING_FREQUENCY 100 + +struct iio_hrtimer_info { + struct iio_sw_trigger swt; + struct hrtimer timer; + unsigned long sampling_frequency; + ktime_t period; +}; + +static struct config_item_type iio_hrtimer_type = { + .ct_owner = THIS_MODULE, +}; + +static +ssize_t iio_hrtimer_show_sampling_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig); + + return snprintf(buf, PAGE_SIZE, "%lu\n", info->sampling_frequency); +} + +static +ssize_t iio_hrtimer_store_sampling_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + if (!val || val > NSEC_PER_SEC) + return -EINVAL; + + info->sampling_frequency = val; + info->period = ktime_set(0, NSEC_PER_SEC / val); + + return len; +} + +static DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR, + iio_hrtimer_show_sampling_frequency, + iio_hrtimer_store_sampling_frequency); + +static struct attribute *iio_hrtimer_attrs[] = { + &dev_attr_sampling_frequency.attr, + NULL +}; + +static const struct attribute_group iio_hrtimer_attr_group = { + .attrs = iio_hrtimer_attrs, +}; + +static const struct attribute_group *iio_hrtimer_attr_groups[] = { + &iio_hrtimer_attr_group, + NULL +}; + +static enum hrtimer_restart iio_hrtimer_trig_handler(struct hrtimer *timer) +{ + struct iio_hrtimer_info *info; + + info = container_of(timer, struct iio_hrtimer_info, timer); + + hrtimer_forward_now(timer, info->period); + iio_trigger_poll(info->swt.trigger); + + return HRTIMER_RESTART; +} + +static int iio_trig_hrtimer_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_hrtimer_info *trig_info; + + trig_info = iio_trigger_get_drvdata(trig); + + if (state) + hrtimer_start(&trig_info->timer, trig_info->period, + HRTIMER_MODE_REL); + else + hrtimer_cancel(&trig_info->timer); + + return 0; +} + +static const struct iio_trigger_ops iio_hrtimer_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = iio_trig_hrtimer_set_state, +}; + +static struct iio_sw_trigger *iio_trig_hrtimer_probe(const char *name) +{ + struct iio_hrtimer_info *trig_info; + int ret; + + trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); + if (!trig_info) + return ERR_PTR(-ENOMEM); + + trig_info->swt.trigger = iio_trigger_alloc("%s", name); + if (!trig_info->swt.trigger) { + ret = -ENOMEM; + goto err_free_trig_info; + } + + iio_trigger_set_drvdata(trig_info->swt.trigger, trig_info); + trig_info->swt.trigger->ops = &iio_hrtimer_trigger_ops; + trig_info->swt.trigger->dev.groups = iio_hrtimer_attr_groups; + + hrtimer_init(&trig_info->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + trig_info->timer.function = iio_hrtimer_trig_handler; + + trig_info->sampling_frequency = HRTIMER_DEFAULT_SAMPLING_FREQUENCY; + trig_info->period = ktime_set(0, NSEC_PER_SEC / + trig_info->sampling_frequency); + + ret = iio_trigger_register(trig_info->swt.trigger); + if (ret) + goto err_free_trigger; + + iio_swt_group_init_type_name(&trig_info->swt, name, &iio_hrtimer_type); + return &trig_info->swt; +err_free_trigger: + iio_trigger_free(trig_info->swt.trigger); +err_free_trig_info: + kfree(trig_info); + + return ERR_PTR(ret); +} + +static int iio_trig_hrtimer_remove(struct iio_sw_trigger *swt) +{ + struct iio_hrtimer_info *trig_info; + + trig_info = iio_trigger_get_drvdata(swt->trigger); + + iio_trigger_unregister(swt->trigger); + + /* cancel the timer after unreg to make sure no one rearms it */ + hrtimer_cancel(&trig_info->timer); + iio_trigger_free(swt->trigger); + kfree(trig_info); + + return 0; +} + +static const struct iio_sw_trigger_ops iio_trig_hrtimer_ops = { + .probe = iio_trig_hrtimer_probe, + .remove = iio_trig_hrtimer_remove, +}; + +static struct iio_sw_trigger_type iio_trig_hrtimer = { + .name = "hrtimer", + .owner = THIS_MODULE, + .ops = &iio_trig_hrtimer_ops, +}; + +module_iio_sw_trigger_driver(iio_trig_hrtimer); + +MODULE_AUTHOR("Marten Svanfeldt "); +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Periodic hrtimer trigger for the IIO subsystem"); +MODULE_LICENSE("GPL v2"); From 4c3e2a4036054deca4819758f59ff65f27938388 Mon Sep 17 00:00:00 2001 From: Daniel Baluta Date: Mon, 9 Nov 2015 09:14:02 +0200 Subject: [PATCH 13/28] iio: Documentation: Add IIO configfs documentation Signed-off-by: Daniel Baluta Acked-by: Crt Mori Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/configfs-iio | 21 ++++++ Documentation/iio/iio_configfs.txt | 93 ++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 Documentation/ABI/testing/configfs-iio create mode 100644 Documentation/iio/iio_configfs.txt diff --git a/Documentation/ABI/testing/configfs-iio b/Documentation/ABI/testing/configfs-iio new file mode 100644 index 000000000000..2483756fccf5 --- /dev/null +++ b/Documentation/ABI/testing/configfs-iio @@ -0,0 +1,21 @@ +What: /config/iio +Date: October 2015 +KernelVersion: 4.4 +Contact: linux-iio@vger.kernel.org +Description: + This represents Industrial IO configuration entry point + directory. It contains sub-groups corresponding to IIO + objects. + +What: /config/iio/triggers +Date: October 2015 +KernelVersion: 4.4 +Description: + Industrial IO software triggers directory. + +What: /config/iio/triggers/hrtimers +Date: October 2015 +KernelVersion: 4.4 +Description: + High resolution timers directory. Creating a directory here + will result in creating a hrtimer trigger in the IIO subsystem. diff --git a/Documentation/iio/iio_configfs.txt b/Documentation/iio/iio_configfs.txt new file mode 100644 index 000000000000..f0add35cd52e --- /dev/null +++ b/Documentation/iio/iio_configfs.txt @@ -0,0 +1,93 @@ +Industrial IIO configfs support + +1. Overview + +Configfs is a filesystem-based manager of kernel objects. IIO uses some +objects that could be easily configured using configfs (e.g.: devices, +triggers). + +See Documentation/filesystems/configfs/configfs.txt for more information +about how configfs works. + +2. Usage + +In order to use configfs support in IIO we need to select it at compile +time via CONFIG_IIO_CONFIGFS config option. + +Then, mount the configfs filesystem (usually under /config directory): + +$ mkdir /config +$ mount -t configfs none /config + +At this point, all default IIO groups will be created and can be accessed +under /config/iio. Next chapters will describe available IIO configuration +objects. + +3. Software triggers + +One of the IIO default configfs groups is the "triggers" group. It is +automagically accessible when the configfs is mounted and can be found +under /config/iio/triggers. + +IIO software triggers implementation offers support for creating multiple +trigger types. A new trigger type is usually implemented as a separate +kernel module following the interface in include/linux/iio/sw_trigger.h: + +/* + * drivers/iio/trigger/iio-trig-sample.c + * sample kernel module implementing a new trigger type + */ +#include + + +static struct iio_sw_trigger *iio_trig_sample_probe(const char *name) +{ + /* + * This allocates and registers an IIO trigger plus other + * trigger type specific initialization. + */ +} + +static int iio_trig_hrtimer_remove(struct iio_sw_trigger *swt) +{ + /* + * This undoes the actions in iio_trig_sample_probe + */ +} + +static const struct iio_sw_trigger_ops iio_trig_sample_ops = { + .probe = iio_trig_sample_probe, + .remove = iio_trig_sample_remove, +}; + +static struct iio_sw_trigger_type iio_trig_sample = { + .name = "trig-sample", + .owner = THIS_MODULE, + .ops = &iio_trig_sample_ops, +}; + +module_iio_sw_trigger_driver(iio_trig_sample); + +Each trigger type has its own directory under /config/iio/triggers. Loading +iio-trig-sample module will create 'trig-sample' trigger type directory +/config/iio/triggers/trig-sample. + +We support the following interrupt sources (trigger types): + * hrtimer, uses high resolution timers as interrupt source + +3.1 Hrtimer triggers creation and destruction + +Loading iio-trig-hrtimer module will register hrtimer trigger types allowing +users to create hrtimer triggers under /config/iio/triggers/hrtimer. + +e.g: + +$ mkdir /config/triggers/hrtimer/instance1 +$ rmdir /config/triggers/hrtimer/instance1 + +Each trigger can have one or more attributes specific to the trigger type. + +3.2 "hrtimer" trigger types attributes + +"hrtimer" trigger type doesn't have any configurable attribute from /config dir. +It does introduce the sampling_frequency attribute to trigger directory. From 93e87d73cc46685902bffb0928c2514eaf209b44 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Tue, 1 Dec 2015 21:47:20 -0800 Subject: [PATCH 14/28] iio: chemical: vz89x: rework i2c transfer reading Add an optimized i2c transfer reading function, and fallback to racey smbus transfers if client->adapter doesn't support this. Signed-off-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- drivers/iio/chemical/vz89x.c | 70 +++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/drivers/iio/chemical/vz89x.c b/drivers/iio/chemical/vz89x.c index 11e59a5a5112..b8b804923230 100644 --- a/drivers/iio/chemical/vz89x.c +++ b/drivers/iio/chemical/vz89x.c @@ -34,8 +34,9 @@ struct vz89x_data { struct i2c_client *client; struct mutex lock; - unsigned long last_update; + int (*xfer)(struct vz89x_data *data, u8 cmd); + unsigned long last_update; u8 buffer[VZ89X_REG_MEASUREMENT_SIZE]; }; @@ -100,27 +101,60 @@ static int vz89x_measurement_is_valid(struct vz89x_data *data) return !!(data->buffer[VZ89X_REG_MEASUREMENT_SIZE - 1] > 0); } +static int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd) +{ + struct i2c_client *client = data->client; + struct i2c_msg msg[2]; + int ret; + u8 buf[3] = { cmd, 0, 0}; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].len = 3; + msg[0].buf = (char *) &buf; + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].len = VZ89X_REG_MEASUREMENT_SIZE; + msg[1].buf = (char *) &data->buffer; + + ret = i2c_transfer(client->adapter, msg, 2); + + return (ret == 2) ? 0 : ret; +} + +static int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd) +{ + struct i2c_client *client = data->client; + int ret; + int i; + + ret = i2c_smbus_write_word_data(client, cmd, 0); + if (ret < 0) + return ret; + + for (i = 0; i < VZ89X_REG_MEASUREMENT_SIZE; i++) { + ret = i2c_smbus_read_byte(client); + if (ret < 0) + return ret; + data->buffer[i] = ret; + } + + return 0; +} + static int vz89x_get_measurement(struct vz89x_data *data) { int ret; - int i; /* sensor can only be polled once a second max per datasheet */ if (!time_after(jiffies, data->last_update + HZ)) return 0; - ret = i2c_smbus_write_word_data(data->client, - VZ89X_REG_MEASUREMENT, 0); + ret = data->xfer(data, VZ89X_REG_MEASUREMENT); if (ret < 0) return ret; - for (i = 0; i < VZ89X_REG_MEASUREMENT_SIZE; i++) { - ret = i2c_smbus_read_byte(data->client); - if (ret < 0) - return ret; - data->buffer[i] = ret; - } - ret = vz89x_measurement_is_valid(data); if (ret) return -EAGAIN; @@ -204,15 +238,19 @@ static int vz89x_probe(struct i2c_client *client, struct iio_dev *indio_dev; struct vz89x_data *data; - if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA | - I2C_FUNC_SMBUS_BYTE)) - return -ENODEV; - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; - data = iio_priv(indio_dev); + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + data->xfer = vz89x_i2c_xfer; + else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE)) + data->xfer = vz89x_smbus_xfer; + else + return -ENOTSUPP; + i2c_set_clientdata(client, indio_dev); data->client = client; data->last_update = jiffies - HZ; From 8d6c16dd7213fa43702416e3dd1059e9e36bc758 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Sat, 5 Dec 2015 16:23:26 +0000 Subject: [PATCH 15/28] iio:configfs: Introduce iio/configfs.h to provide a location for the configfs_subsystem This exported element needs to be accesible to all drivers using configfs within IIO. Previously it was in the sw_trig.h file which only convered one such usecase. This also fixes a sparse warning as it is now in a header that makes sense to include from industrialio-configfs.c Signed-off-by: Jonathan Cameron < jic23@kernel.org> --- drivers/iio/industrialio-configfs.c | 1 + drivers/iio/industrialio-sw-trigger.c | 1 + include/linux/iio/configfs.h | 15 +++++++++++++++ include/linux/iio/sw_trigger.h | 1 - 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 include/linux/iio/configfs.h diff --git a/drivers/iio/industrialio-configfs.c b/drivers/iio/industrialio-configfs.c index 83563dd7fcf4..45ce2bc47180 100644 --- a/drivers/iio/industrialio-configfs.c +++ b/drivers/iio/industrialio-configfs.c @@ -15,6 +15,7 @@ #include #include +#include static struct config_item_type iio_root_group_type = { .ct_owner = THIS_MODULE, diff --git a/drivers/iio/industrialio-sw-trigger.c b/drivers/iio/industrialio-sw-trigger.c index 4825cfd9c4ea..311f9fe5aa34 100644 --- a/drivers/iio/industrialio-sw-trigger.c +++ b/drivers/iio/industrialio-sw-trigger.c @@ -15,6 +15,7 @@ #include #include +#include #include static struct config_group *iio_triggers_group; diff --git a/include/linux/iio/configfs.h b/include/linux/iio/configfs.h new file mode 100644 index 000000000000..93befd67c15c --- /dev/null +++ b/include/linux/iio/configfs.h @@ -0,0 +1,15 @@ +/* + * Industrial I/O configfs support + * + * Copyright (c) 2015 Intel Corporation + * + * 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. + */ +#ifndef __IIO_CONFIGFS +#define __IIO_CONFIGFS + +extern struct configfs_subsystem iio_configfs_subsys; + +#endif /* __IIO_CONFIGFS */ diff --git a/include/linux/iio/sw_trigger.h b/include/linux/iio/sw_trigger.h index c2f33b2b35a5..5198f8ed08a4 100644 --- a/include/linux/iio/sw_trigger.h +++ b/include/linux/iio/sw_trigger.h @@ -20,7 +20,6 @@ module_driver(__iio_sw_trigger_type, iio_register_sw_trigger_type, \ iio_unregister_sw_trigger_type) -extern struct configfs_subsystem iio_configfs_subsys; struct iio_sw_trigger_ops; struct iio_sw_trigger_type { From 366e65633cf4f117609965cd6e189f2cd11533d2 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Tue, 1 Dec 2015 21:05:22 -0800 Subject: [PATCH 16/28] iio: proximity: lidar: optimize i2c transactions Optimize device tranactions using i2c transfers versus multiple possibly racey i2c_smbus_* function calls, and only one transaction for distance measurement. Falls back to smbus method if i2c functionality isn't available. Signed-off-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- .../iio/proximity/pulsedlight-lidar-lite-v2.c | 97 ++++++++++++++----- 1 file changed, 71 insertions(+), 26 deletions(-) diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c index be8ccef735f8..e7ea44d61942 100644 --- a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c +++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c @@ -36,8 +36,10 @@ #define LIDAR_REG_STATUS_INVALID BIT(3) #define LIDAR_REG_STATUS_READY BIT(0) -#define LIDAR_REG_DATA_HBYTE 0x0f -#define LIDAR_REG_DATA_LBYTE 0x10 +#define LIDAR_REG_DATA_HBYTE 0x0f +#define LIDAR_REG_DATA_LBYTE 0x10 +#define LIDAR_REG_DATA_WORD_READ BIT(7) + #define LIDAR_REG_PWR_CONTROL 0x65 #define LIDAR_DRV_NAME "lidar" @@ -46,6 +48,9 @@ struct lidar_data { struct iio_dev *indio_dev; struct i2c_client *client; + int (*xfer)(struct lidar_data *data, u8 reg, u8 *val, int len); + int i2c_enabled; + u16 buffer[8]; /* 2 byte distance + 8 byte timestamp */ }; @@ -64,7 +69,28 @@ static const struct iio_chan_spec lidar_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(1), }; -static int lidar_read_byte(struct lidar_data *data, int reg) +static int lidar_i2c_xfer(struct lidar_data *data, u8 reg, u8 *val, int len) +{ + struct i2c_client *client = data->client; + struct i2c_msg msg[2]; + int ret; + + msg[0].addr = client->addr; + msg[0].flags = client->flags | I2C_M_STOP; + msg[0].len = 1; + msg[0].buf = (char *) ® + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].len = len; + msg[1].buf = (char *) val; + + ret = i2c_transfer(client->adapter, msg, 2); + + return (ret == 2) ? 0 : ret; +} + +static int lidar_smbus_xfer(struct lidar_data *data, u8 reg, u8 *val, int len) { struct i2c_client *client = data->client; int ret; @@ -74,17 +100,35 @@ static int lidar_read_byte(struct lidar_data *data, int reg) * so in turn i2c_smbus_read_byte_data cannot be used */ - ret = i2c_smbus_write_byte(client, reg); - if (ret < 0) { - dev_err(&client->dev, "cannot write addr value"); - return ret; + while (len--) { + ret = i2c_smbus_write_byte(client, reg++); + if (ret < 0) { + dev_err(&client->dev, "cannot write addr value"); + return ret; + } + + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(&client->dev, "cannot read data value"); + return ret; + } + + *(val++) = ret; } - ret = i2c_smbus_read_byte(client); - if (ret < 0) - dev_err(&client->dev, "cannot read data value"); + return 0; +} - return ret; +static int lidar_read_byte(struct lidar_data *data, u8 reg) +{ + int ret; + u8 val; + + ret = data->xfer(data, reg, &val, 1); + if (ret < 0) + return ret; + + return val; } static inline int lidar_write_control(struct lidar_data *data, int val) @@ -100,22 +144,14 @@ static inline int lidar_write_power(struct lidar_data *data, int val) static int lidar_read_measurement(struct lidar_data *data, u16 *reg) { - int ret; - int val; + int ret = data->xfer(data, LIDAR_REG_DATA_HBYTE | + (data->i2c_enabled ? LIDAR_REG_DATA_WORD_READ : 0), + (u8 *) reg, 2); - ret = lidar_read_byte(data, LIDAR_REG_DATA_HBYTE); - if (ret < 0) - return ret; - val = ret << 8; + if (!ret) + *reg = be16_to_cpu(*reg); - ret = lidar_read_byte(data, LIDAR_REG_DATA_LBYTE); - if (ret < 0) - return ret; - - val |= ret; - *reg = val; - - return 0; + return ret; } static int lidar_get_measurement(struct lidar_data *data, u16 *reg) @@ -233,6 +269,16 @@ static int lidar_probe(struct i2c_client *client, indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; + data = iio_priv(indio_dev); + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + data->xfer = lidar_i2c_xfer; + data->i2c_enabled = 1; + } else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE)) + data->xfer = lidar_smbus_xfer; + else + return -ENOTSUPP; indio_dev->info = &lidar_info; indio_dev->name = LIDAR_DRV_NAME; @@ -240,7 +286,6 @@ static int lidar_probe(struct i2c_client *client, indio_dev->num_channels = ARRAY_SIZE(lidar_channels); indio_dev->modes = INDIO_DIRECT_MODE; - data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); data->client = client; From ec0096f853fef46000c80bb3fd035d8376d957ac Mon Sep 17 00:00:00 2001 From: Robert Kmiec Date: Fri, 4 Dec 2015 00:54:48 +0100 Subject: [PATCH 17/28] iio: st_accel_core: Remove unneeded define Definition of ST_SENSORS_WAI_ADDRESS was introduced within a very first commit of this driver, but it was never used. This address is already defined as ST_SENSORS_DEFAULT_WAI_ADDRESS in include/linux/iio/common/st_sensors.h To avoid duplication of the same constant in two different places called almost exactly the same, the one which was never used should be removed. Signed-off-by: Robert Kmiec Signed-off-by: Jonathan Cameron --- drivers/iio/common/st_sensors/st_sensors_core.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c index 25258e2c1a82..8447c31e27f2 100644 --- a/drivers/iio/common/st_sensors/st_sensors_core.c +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -18,9 +18,6 @@ #include #include - -#define ST_SENSORS_WAI_ADDRESS 0x0f - static inline u32 st_sensors_get_unaligned_le24(const u8 *p) { return (s32)((p[0] | p[1] << 8 | p[2] << 16) << 8) >> 8; From 9ab655a32e008bfe906b0bf8fb907b412f7c2e87 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 4 Dec 2015 00:28:17 +0100 Subject: [PATCH 18/28] staging: iio: select IRQ_WORK for IIO_DUMMY_EVGEN The iio dummy code was recently changed to use irq_work_queue, but that code is compiled into the kernel only if IRQ_WORK is set, so we can get a link error here: drivers/built-in.o: In function `iio_evgen_poke': (.text+0x208a04): undefined reference to `irq_work_queue' This changes the Kconfig file to match what other drivers do. Signed-off-by: Arnd Bergmann Fixes: fd2bb310ca3d ("Staging: iio: Move evgen interrupt generation to irq_work") Acked-by: Daniel Baluta Signed-off-by: Jonathan Cameron --- drivers/iio/dummy/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/dummy/Kconfig b/drivers/iio/dummy/Kconfig index e8676aa97d62..71805ced1aae 100644 --- a/drivers/iio/dummy/Kconfig +++ b/drivers/iio/dummy/Kconfig @@ -5,7 +5,8 @@ menu "IIO dummy driver" depends on IIO config IIO_DUMMY_EVGEN - tristate + select IRQ_WORK + tristate config IIO_SIMPLE_DUMMY tristate "An example driver with no hardware requirements" From b41fa86b67bd338d4ffa0b69f0fb1c3013a489e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=A4bler?= Date: Wed, 9 Dec 2015 10:24:04 +0100 Subject: [PATCH 19/28] iio:adc128s052: add support for adc124s021 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Stäbler Reviewed-by: Martin Kepplinger Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/ti-adc128s052.txt | 4 ++-- drivers/iio/adc/Kconfig | 6 +++--- drivers/iio/adc/ti-adc128s052.c | 13 ++++++++++++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/adc/ti-adc128s052.txt b/Documentation/devicetree/bindings/iio/adc/ti-adc128s052.txt index 15ca6b47958e..daa2b2c29428 100644 --- a/Documentation/devicetree/bindings/iio/adc/ti-adc128s052.txt +++ b/Documentation/devicetree/bindings/iio/adc/ti-adc128s052.txt @@ -1,7 +1,7 @@ -* Texas Instruments' ADC128S052 and ADC122S021 ADC chip +* Texas Instruments' ADC128S052, ADC122S021 and ADC124S021 ADC chip Required properties: - - compatible: Should be "ti,adc128s052" or "ti,adc122s021" + - compatible: Should be "ti,adc128s052", "ti,adc122s021" or "ti,adc124s021" - reg: spi chip select number for the device - vref-supply: The regulator supply for ADC reference voltage diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 9162dfefff30..57e3ca0b7ff4 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -332,11 +332,11 @@ config TI_ADC081C called ti-adc081c. config TI_ADC128S052 - tristate "Texas Instruments ADC128S052/ADC122S021" + tristate "Texas Instruments ADC128S052/ADC122S021/ADC124S021" depends on SPI help - If you say yes here you get support for Texas Instruments ADC128S052 - and ADC122S021 chips. + If you say yes here you get support for Texas Instruments ADC128S052, + ADC122S021 and ADC124S021 chips. This driver can also be built as a module. If so, the module will be called ti-adc128s052. diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c index ff6f7f63c8d9..bc58867d6e8d 100644 --- a/drivers/iio/adc/ti-adc128s052.c +++ b/drivers/iio/adc/ti-adc128s052.c @@ -1,10 +1,11 @@ /* * Copyright (C) 2014 Angelo Compagnucci * - * Driver for Texas Instruments' ADC128S052 and ADC122S021 ADC chip. + * Driver for Texas Instruments' ADC128S052, ADC122S021 and ADC124S021 ADC chip. * Datasheets can be found here: * http://www.ti.com/lit/ds/symlink/adc128s052.pdf * http://www.ti.com/lit/ds/symlink/adc122s021.pdf + * http://www.ti.com/lit/ds/symlink/adc124s021.pdf * * 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 @@ -114,9 +115,17 @@ static const struct iio_chan_spec adc122s021_channels[] = { ADC128_VOLTAGE_CHANNEL(1), }; +static const struct iio_chan_spec adc124s021_channels[] = { + ADC128_VOLTAGE_CHANNEL(0), + ADC128_VOLTAGE_CHANNEL(1), + ADC128_VOLTAGE_CHANNEL(2), + ADC128_VOLTAGE_CHANNEL(3), +}; + static const struct adc128_configuration adc128_config[] = { { adc128s052_channels, ARRAY_SIZE(adc128s052_channels) }, { adc122s021_channels, ARRAY_SIZE(adc122s021_channels) }, + { adc124s021_channels, ARRAY_SIZE(adc124s021_channels) }, }; static const struct iio_info adc128_info = { @@ -177,6 +186,7 @@ static int adc128_remove(struct spi_device *spi) static const struct of_device_id adc128_of_match[] = { { .compatible = "ti,adc128s052", }, { .compatible = "ti,adc122s021", }, + { .compatible = "ti,adc124s021", }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, adc128_of_match); @@ -184,6 +194,7 @@ MODULE_DEVICE_TABLE(of, adc128_of_match); static const struct spi_device_id adc128_id[] = { { "adc128s052", 0}, /* index into adc128_config */ { "adc122s021", 1}, + { "adc124s021", 2}, { } }; MODULE_DEVICE_TABLE(spi, adc128_id); From 4d33615df58bf308626489cbfb8acbc8bbd45658 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Wed, 9 Dec 2015 22:04:49 -0800 Subject: [PATCH 20/28] iio: light: add MAX30100 oximeter driver support MAX30100 is an heart rate and pulse oximeter sensor that works using two LEDS of different wavelengths, and detecting the light reflected back. This patchset adds support for both IR and RED LED channels which can be processed in userspace to determine heart rate and blood oxygen levels. Signed-off-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- .../bindings/iio/health/max30100.txt | 21 + drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/health/Kconfig | 21 + drivers/iio/health/Makefile | 7 + drivers/iio/health/max30100.c | 453 ++++++++++++++++++ 6 files changed, 504 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/health/max30100.txt create mode 100644 drivers/iio/health/Kconfig create mode 100644 drivers/iio/health/Makefile create mode 100644 drivers/iio/health/max30100.c diff --git a/Documentation/devicetree/bindings/iio/health/max30100.txt b/Documentation/devicetree/bindings/iio/health/max30100.txt new file mode 100644 index 000000000000..f6fbac66ad06 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/health/max30100.txt @@ -0,0 +1,21 @@ +Maxim MAX30100 heart rate and pulse oximeter sensor + +* https://datasheets.maximintegrated.com/en/ds/MAX30100.pdf + +Required properties: + - compatible: must be "maxim,max30100" + - reg: the I2C address of the sensor + - 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. + +Example: + +max30100@057 { + compatible = "maxim,max30100"; + reg = <57>; + interrupt-parent = <&gpio1>; + interrupts = <16 2>; +}; diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index ac8715e56ba1..505e921f0b19 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -69,6 +69,7 @@ source "drivers/iio/dac/Kconfig" source "drivers/iio/dummy/Kconfig" source "drivers/iio/frequency/Kconfig" source "drivers/iio/gyro/Kconfig" +source "drivers/iio/health/Kconfig" source "drivers/iio/humidity/Kconfig" source "drivers/iio/imu/Kconfig" source "drivers/iio/light/Kconfig" diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index f670b82298aa..20f649073462 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -21,6 +21,7 @@ obj-y += dac/ obj-y += dummy/ obj-y += gyro/ obj-y += frequency/ +obj-y += health/ obj-y += humidity/ obj-y += imu/ obj-y += light/ diff --git a/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig new file mode 100644 index 000000000000..a647679da805 --- /dev/null +++ b/drivers/iio/health/Kconfig @@ -0,0 +1,21 @@ +# +# Health sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Health sensors" + +config MAX30100 + tristate "MAX30100 heart rate and pulse oximeter sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say Y here to build I2C interface support for the Maxim + MAX30100 heart rate, and pulse oximeter sensor. + + To compile this driver as a module, choose M here: the + module will be called max30100. + +endmenu diff --git a/drivers/iio/health/Makefile b/drivers/iio/health/Makefile new file mode 100644 index 000000000000..7c475d7faad8 --- /dev/null +++ b/drivers/iio/health/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for IIO Health sensors +# + +# When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_MAX30100) += max30100.o diff --git a/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c new file mode 100644 index 000000000000..9d1c81f91dd7 --- /dev/null +++ b/drivers/iio/health/max30100.c @@ -0,0 +1,453 @@ +/* + * max30100.c - Support for MAX30100 heart rate and pulse oximeter sensor + * + * Copyright (C) 2015 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: allow LED current and pulse length controls via device tree properties + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX30100_REGMAP_NAME "max30100_regmap" +#define MAX30100_DRV_NAME "max30100" + +#define MAX30100_REG_INT_STATUS 0x00 +#define MAX30100_REG_INT_STATUS_PWR_RDY BIT(0) +#define MAX30100_REG_INT_STATUS_SPO2_RDY BIT(4) +#define MAX30100_REG_INT_STATUS_HR_RDY BIT(5) +#define MAX30100_REG_INT_STATUS_FIFO_RDY BIT(7) + +#define MAX30100_REG_INT_ENABLE 0x01 +#define MAX30100_REG_INT_ENABLE_SPO2_EN BIT(0) +#define MAX30100_REG_INT_ENABLE_HR_EN BIT(1) +#define MAX30100_REG_INT_ENABLE_FIFO_EN BIT(3) +#define MAX30100_REG_INT_ENABLE_MASK 0xf0 +#define MAX30100_REG_INT_ENABLE_MASK_SHIFT 4 + +#define MAX30100_REG_FIFO_WR_PTR 0x02 +#define MAX30100_REG_FIFO_OVR_CTR 0x03 +#define MAX30100_REG_FIFO_RD_PTR 0x04 +#define MAX30100_REG_FIFO_DATA 0x05 +#define MAX30100_REG_FIFO_DATA_ENTRY_COUNT 16 +#define MAX30100_REG_FIFO_DATA_ENTRY_LEN 4 + +#define MAX30100_REG_MODE_CONFIG 0x06 +#define MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN BIT(0) +#define MAX30100_REG_MODE_CONFIG_MODE_HR_EN BIT(1) +#define MAX30100_REG_MODE_CONFIG_MODE_MASK 0x03 +#define MAX30100_REG_MODE_CONFIG_TEMP_EN BIT(3) +#define MAX30100_REG_MODE_CONFIG_PWR BIT(7) + +#define MAX30100_REG_SPO2_CONFIG 0x07 +#define MAX30100_REG_SPO2_CONFIG_100HZ BIT(2) +#define MAX30100_REG_SPO2_CONFIG_HI_RES_EN BIT(6) +#define MAX30100_REG_SPO2_CONFIG_1600US 0x3 + +#define MAX30100_REG_LED_CONFIG 0x09 +#define MAX30100_REG_LED_CONFIG_RED_LED_SHIFT 4 + +#define MAX30100_REG_LED_CONFIG_24MA 0x07 +#define MAX30100_REG_LED_CONFIG_50MA 0x0f + +#define MAX30100_REG_TEMP_INTEGER 0x16 +#define MAX30100_REG_TEMP_FRACTION 0x17 + +struct max30100_data { + struct i2c_client *client; + struct iio_dev *indio_dev; + struct mutex lock; + struct regmap *regmap; + + __be16 buffer[2]; /* 2 16-bit channels */ +}; + +static bool max30100_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX30100_REG_INT_STATUS: + case MAX30100_REG_MODE_CONFIG: + case MAX30100_REG_FIFO_WR_PTR: + case MAX30100_REG_FIFO_OVR_CTR: + case MAX30100_REG_FIFO_RD_PTR: + case MAX30100_REG_FIFO_DATA: + case MAX30100_REG_TEMP_INTEGER: + case MAX30100_REG_TEMP_FRACTION: + return true; + default: + return false; + } +} + +static const struct regmap_config max30100_regmap_config = { + .name = MAX30100_REGMAP_NAME, + + .reg_bits = 8, + .val_bits = 8, + + .max_register = MAX30100_REG_TEMP_FRACTION, + .cache_type = REGCACHE_FLAT, + + .volatile_reg = max30100_is_volatile_reg, +}; + +static const unsigned long max30100_scan_masks[] = {0x3, 0}; + +static const struct iio_chan_spec max30100_channels[] = { + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_IR, + .modified = 1, + + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_RED, + .modified = 1, + + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1, + }, +}; + +static int max30100_set_powermode(struct max30100_data *data, bool state) +{ + return regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG, + MAX30100_REG_MODE_CONFIG_PWR, + state ? 0 : MAX30100_REG_MODE_CONFIG_PWR); +} + +static int max30100_clear_fifo(struct max30100_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, MAX30100_REG_FIFO_WR_PTR, 0); + if (ret) + return ret; + + ret = regmap_write(data->regmap, MAX30100_REG_FIFO_OVR_CTR, 0); + if (ret) + return ret; + + return regmap_write(data->regmap, MAX30100_REG_FIFO_RD_PTR, 0); +} + +static int max30100_buffer_postenable(struct iio_dev *indio_dev) +{ + struct max30100_data *data = iio_priv(indio_dev); + int ret; + + ret = max30100_set_powermode(data, true); + if (ret) + return ret; + + return max30100_clear_fifo(data); +} + +static int max30100_buffer_predisable(struct iio_dev *indio_dev) +{ + struct max30100_data *data = iio_priv(indio_dev); + + return max30100_set_powermode(data, false); +} + +static const struct iio_buffer_setup_ops max30100_buffer_setup_ops = { + .postenable = max30100_buffer_postenable, + .predisable = max30100_buffer_predisable, +}; + +static inline int max30100_fifo_count(struct max30100_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, MAX30100_REG_INT_STATUS, &val); + if (ret) + return ret; + + /* FIFO is almost full */ + if (val & MAX30100_REG_INT_STATUS_FIFO_RDY) + return MAX30100_REG_FIFO_DATA_ENTRY_COUNT - 1; + + return 0; +} + +static int max30100_read_measurement(struct max30100_data *data) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(data->client, + MAX30100_REG_FIFO_DATA, + MAX30100_REG_FIFO_DATA_ENTRY_LEN, + (u8 *) &data->buffer); + + return (ret == MAX30100_REG_FIFO_DATA_ENTRY_LEN) ? 0 : ret; +} + +static irqreturn_t max30100_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct max30100_data *data = iio_priv(indio_dev); + int ret, cnt = 0; + + mutex_lock(&data->lock); + + while (cnt-- || (cnt = max30100_fifo_count(data) > 0)) { + ret = max30100_read_measurement(data); + if (ret) + break; + + iio_push_to_buffers(data->indio_dev, data->buffer); + } + + mutex_unlock(&data->lock); + + return IRQ_HANDLED; +} + +static int max30100_chip_init(struct max30100_data *data) +{ + int ret; + + /* RED IR LED = 24mA, IR LED = 50mA */ + ret = regmap_write(data->regmap, MAX30100_REG_LED_CONFIG, + (MAX30100_REG_LED_CONFIG_24MA << + MAX30100_REG_LED_CONFIG_RED_LED_SHIFT) | + MAX30100_REG_LED_CONFIG_50MA); + if (ret) + return ret; + + /* enable hi-res SPO2 readings at 100Hz */ + ret = regmap_write(data->regmap, MAX30100_REG_SPO2_CONFIG, + MAX30100_REG_SPO2_CONFIG_HI_RES_EN | + MAX30100_REG_SPO2_CONFIG_100HZ); + if (ret) + return ret; + + /* enable SPO2 mode */ + ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG, + MAX30100_REG_MODE_CONFIG_MODE_MASK, + MAX30100_REG_MODE_CONFIG_MODE_HR_EN | + MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN); + if (ret) + return ret; + + /* enable FIFO interrupt */ + return regmap_update_bits(data->regmap, MAX30100_REG_INT_ENABLE, + MAX30100_REG_INT_ENABLE_MASK, + MAX30100_REG_INT_ENABLE_FIFO_EN + << MAX30100_REG_INT_ENABLE_MASK_SHIFT); +} + +static int max30100_read_temp(struct max30100_data *data, int *val) +{ + int ret; + unsigned int reg; + + ret = regmap_read(data->regmap, MAX30100_REG_TEMP_INTEGER, ®); + if (ret < 0) + return ret; + *val = reg << 4; + + ret = regmap_read(data->regmap, MAX30100_REG_TEMP_FRACTION, ®); + if (ret < 0) + return ret; + + *val |= reg & 0xf; + *val = sign_extend32(*val, 11); + + return 0; +} + +static int max30100_get_temp(struct max30100_data *data, int *val) +{ + int ret; + + /* start acquisition */ + ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG, + MAX30100_REG_MODE_CONFIG_TEMP_EN, + MAX30100_REG_MODE_CONFIG_TEMP_EN); + if (ret) + return ret; + + usleep_range(35000, 50000); + + return max30100_read_temp(data, val); +} + +static int max30100_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max30100_data *data = iio_priv(indio_dev); + int ret = -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Temperature reading can only be acquired while engine + * is running + */ + mutex_lock(&indio_dev->mlock); + + if (!iio_buffer_enabled(indio_dev)) + ret = -EAGAIN; + else { + ret = max30100_get_temp(data, val); + if (!ret) + ret = IIO_VAL_INT; + + } + + mutex_unlock(&indio_dev->mlock); + break; + case IIO_CHAN_INFO_SCALE: + *val = 1; /* 0.0625 */ + *val2 = 16; + ret = IIO_VAL_FRACTIONAL; + break; + } + + return ret; +} + +static const struct iio_info max30100_info = { + .driver_module = THIS_MODULE, + .read_raw = max30100_read_raw, +}; + +static int max30100_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max30100_data *data; + struct iio_buffer *buffer; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + buffer = devm_iio_kfifo_allocate(&client->dev); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + indio_dev->name = MAX30100_DRV_NAME; + indio_dev->channels = max30100_channels; + indio_dev->info = &max30100_info; + indio_dev->num_channels = ARRAY_SIZE(max30100_channels); + indio_dev->available_scan_masks = max30100_scan_masks; + indio_dev->modes = (INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE); + indio_dev->setup_ops = &max30100_buffer_setup_ops; + + data = iio_priv(indio_dev); + data->indio_dev = indio_dev; + data->client = client; + + mutex_init(&data->lock); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &max30100_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap initialization failed.\n"); + return PTR_ERR(data->regmap); + } + max30100_set_powermode(data, false); + + ret = max30100_chip_init(data); + if (ret) + return ret; + + if (client->irq <= 0) { + dev_err(&client->dev, "no valid irq defined\n"); + return -EINVAL; + } + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, max30100_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max30100_irq", indio_dev); + if (ret) { + dev_err(&client->dev, "request irq (%d) failed\n", client->irq); + return ret; + } + + return iio_device_register(indio_dev); +} + +static int max30100_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct max30100_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + max30100_set_powermode(data, false); + + return 0; +} + +static const struct i2c_device_id max30100_id[] = { + { "max30100", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, max30100_id); + +static const struct of_device_id max30100_dt_ids[] = { + { .compatible = "maxim,max30100" }, + { } +}; +MODULE_DEVICE_TABLE(of, max30100_dt_ids); + +static struct i2c_driver max30100_driver = { + .driver = { + .name = MAX30100_DRV_NAME, + .of_match_table = of_match_ptr(max30100_dt_ids), + }, + .probe = max30100_probe, + .remove = max30100_remove, + .id_table = max30100_id, +}; +module_i2c_driver(max30100_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("MAX30100 heart rate and pulse oximeter sensor"); +MODULE_LICENSE("GPL"); From 466df4d0c1a5edee243698bdcad1ec4f3a1799b1 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Sat, 5 Dec 2015 22:58:22 -0800 Subject: [PATCH 21/28] iio: chemical: add AMS iAQ-core support Add support for AMS iAQ-core continuous and pulsed VOC sensors. Signed-off-by: Matt Ranostay Signed-off-by: Jonathan Cameron --- .../bindings/i2c/trivial-devices.txt | 1 + drivers/iio/chemical/Kconfig | 8 + drivers/iio/chemical/Makefile | 1 + drivers/iio/chemical/ams-iaq-core.c | 200 ++++++++++++++++++ 4 files changed, 210 insertions(+) create mode 100644 drivers/iio/chemical/ams-iaq-core.c diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt index c50cf13c852e..f6fec952d683 100644 --- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt +++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt @@ -20,6 +20,7 @@ adi,adt7476 +/-1C TDM Extended Temp Range I.C adi,adt7490 +/-1C TDM Extended Temp Range I.C adi,adxl345 Three-Axis Digital Accelerometer adi,adxl346 Three-Axis Digital Accelerometer (backward-compatibility value "adi,adxl345" must be listed too) +ams,iaq-core AMS iAQ-Core VOC Sensor at,24c08 i2c serial eeprom (24cxx) atmel,24c00 i2c serial eeprom (24cxx) atmel,24c01 i2c serial eeprom (24cxx) diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig index 3061b7299f0f..f16de61be46d 100644 --- a/drivers/iio/chemical/Kconfig +++ b/drivers/iio/chemical/Kconfig @@ -4,6 +4,14 @@ menu "Chemical Sensors" +config IAQCORE + tristate "AMS iAQ-Core VOC sensors" + depends on I2C + help + Say Y here to build I2C interface support for the AMS + iAQ-Core Continuous/Pulsed VOC (Volatile Organic Compounds) + sensors + config VZ89X tristate "SGX Sensortech MiCS VZ89X VOC sensor" depends on I2C diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile index 7292f2ded587..167861fadfab 100644 --- a/drivers/iio/chemical/Makefile +++ b/drivers/iio/chemical/Makefile @@ -3,4 +3,5 @@ # # When adding new entries keep the list in alphabetical order +obj-$(CONFIG_IAQCORE) += ams-iaq-core.o obj-$(CONFIG_VZ89X) += vz89x.o diff --git a/drivers/iio/chemical/ams-iaq-core.c b/drivers/iio/chemical/ams-iaq-core.c new file mode 100644 index 000000000000..41a8e6f2e31d --- /dev/null +++ b/drivers/iio/chemical/ams-iaq-core.c @@ -0,0 +1,200 @@ +/* + * ams-iaq-core.c - Support for AMS iAQ-Core VOC sensors + * + * Copyright (C) 2015 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. + * + */ + +#include +#include +#include +#include +#include + +#define AMS_IAQCORE_DATA_SIZE 9 + +#define AMS_IAQCORE_VOC_CO2_IDX 0 +#define AMS_IAQCORE_VOC_RESISTANCE_IDX 1 +#define AMS_IAQCORE_VOC_TVOC_IDX 2 + +struct ams_iaqcore_reading { + __be16 co2_ppm; + u8 status; + __be32 resistance; + __be16 voc_ppb; +} __attribute__((__packed__)); + +struct ams_iaqcore_data { + struct i2c_client *client; + struct mutex lock; + unsigned long last_update; + + struct ams_iaqcore_reading buffer; +}; + +static const struct iio_chan_spec ams_iaqcore_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_CO2_IDX, + }, + { + .type = IIO_RESISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_RESISTANCE_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_TVOC_IDX, + }, +}; + +static int ams_iaqcore_read_measurement(struct ams_iaqcore_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + struct i2c_msg msg = { + .addr = client->addr, + .flags = client->flags | I2C_M_RD, + .len = AMS_IAQCORE_DATA_SIZE, + .buf = (char *) &data->buffer, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + + return (ret == AMS_IAQCORE_DATA_SIZE) ? 0 : ret; +} + +static int ams_iaqcore_get_measurement(struct ams_iaqcore_data *data) +{ + int ret; + + /* sensor can only be polled once a second max per datasheet */ + if (!time_after(jiffies, data->last_update + HZ)) + return 0; + + ret = ams_iaqcore_read_measurement(data); + if (ret < 0) + return ret; + + data->last_update = jiffies; + + return 0; +} + +static int ams_iaqcore_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ams_iaqcore_data *data = iio_priv(indio_dev); + int ret; + + if (mask != IIO_CHAN_INFO_PROCESSED) + return -EINVAL; + + mutex_lock(&data->lock); + ret = ams_iaqcore_get_measurement(data); + + if (ret) + goto err_out; + + switch (chan->address) { + case AMS_IAQCORE_VOC_CO2_IDX: + *val = 0; + *val2 = be16_to_cpu(data->buffer.co2_ppm); + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case AMS_IAQCORE_VOC_RESISTANCE_IDX: + *val = be32_to_cpu(data->buffer.resistance); + ret = IIO_VAL_INT; + break; + case AMS_IAQCORE_VOC_TVOC_IDX: + *val = 0; + *val2 = be16_to_cpu(data->buffer.voc_ppb); + ret = IIO_VAL_INT_PLUS_NANO; + break; + default: + ret = -EINVAL; + } + +err_out: + mutex_unlock(&data->lock); + + return ret; +} + +static const struct iio_info ams_iaqcore_info = { + .read_raw = ams_iaqcore_read_raw, + .driver_module = THIS_MODULE, +}; + +static int ams_iaqcore_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct ams_iaqcore_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + /* so initial reading will complete */ + data->last_update = jiffies - HZ; + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ams_iaqcore_info, + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = ams_iaqcore_channels; + indio_dev->num_channels = ARRAY_SIZE(ams_iaqcore_channels); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id ams_iaqcore_id[] = { + { "ams-iaq-core", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ams_iaqcore_id); + +static const struct of_device_id ams_iaqcore_dt_ids[] = { + { .compatible = "ams,iaq-core" }, + { } +}; +MODULE_DEVICE_TABLE(of, ams_iaqcore_dt_ids); + +static struct i2c_driver ams_iaqcore_driver = { + .driver = { + .name = "ams-iaq-core", + .of_match_table = of_match_ptr(ams_iaqcore_dt_ids), + }, + .probe = ams_iaqcore_probe, + .id_table = ams_iaqcore_id, +}; +module_i2c_driver(ams_iaqcore_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors"); +MODULE_LICENSE("GPL v2"); From c43a102e67db99c8bfe6e8a9280cec13ff53b789 Mon Sep 17 00:00:00 2001 From: Marc Titinger Date: Mon, 7 Dec 2015 10:09:34 +0100 Subject: [PATCH 22/28] iio: ina2xx: add support for TI INA2xx Power Monitors in SOFTWARE buffer mode, a kthread will capture the active scan_elements into a kfifo, then compute the remaining time until the next capture tick and do an active wait (udelay). This will produce a stream of up to fours channels plus a 64bits timestamps (ns). Tested with ina226, on BeagleBoneBlack. Datasheet: http://www.ti.com/lit/gpn/ina226 Signed-off-by: Marc Titinger Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ina2xx-adc.c | 668 +++++++++++++++++++++++++++++++++++ 3 files changed, 679 insertions(+) create mode 100644 drivers/iio/adc/ina2xx-adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 57e3ca0b7ff4..cb68bd9d2052 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -194,6 +194,16 @@ config HI8435 This driver can also be built as a module. If so, the module will be called hi8435. +config INA2XX_ADC + tristate "Texas Instruments INA2xx Power Monitors IIO driver" + depends on I2C && !SENSORS_INA2XX + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for TI INA2xx family of Power Monitors. + This driver is mutually exclusive with the HWMON version. + config LP8788_ADC tristate "LP8788 ADC driver" depends on MFD_LP8788 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 91a65bf11c58..c62c5fa91c82 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o obj-$(CONFIG_HI8435) += hi8435.o +obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o obj-$(CONFIG_MAX1027) += max1027.o obj-$(CONFIG_MAX1363) += max1363.o diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c new file mode 100644 index 000000000000..707bd69a7353 --- /dev/null +++ b/drivers/iio/adc/ina2xx-adc.c @@ -0,0 +1,668 @@ +/* + * INA2XX Current and Power Monitors + * + * Copyright 2015 Baylibre SAS. + * + * 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. + * + * Based on linux/drivers/iio/adc/ad7291.c + * Copyright 2010-2011 Analog Devices Inc. + * + * Based on linux/drivers/hwmon/ina2xx.c + * Copyright 2012 Lothar Felten + * + * Licensed under the GPL-2 or later. + * + * IIO driver for INA219-220-226-230-231 + * + * Configurable 7-bit I2C slave address from 0x40 to 0x4F + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* INA2XX registers definition */ +#define INA2XX_CONFIG 0x00 +#define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */ +#define INA2XX_BUS_VOLTAGE 0x02 /* readonly */ +#define INA2XX_POWER 0x03 /* readonly */ +#define INA2XX_CURRENT 0x04 /* readonly */ +#define INA2XX_CALIBRATION 0x05 + +#define INA226_ALERT_MASK 0x06 +#define INA266_CVRF BIT(3) + +#define INA2XX_MAX_REGISTERS 8 + +/* settings - depend on use case */ +#define INA219_CONFIG_DEFAULT 0x399F /* PGA=8 */ +#define INA226_CONFIG_DEFAULT 0x4327 +#define INA226_DEFAULT_AVG 4 +#define INA226_DEFAULT_IT 1110 + +#define INA2XX_RSHUNT_DEFAULT 10000 + +/* + * bit mask for reading the averaging setting in the configuration register + * FIXME: use regmap_fields. + */ +#define INA2XX_MODE_MASK GENMASK(3, 0) + +#define INA226_AVG_MASK GENMASK(11, 9) +#define INA226_SHIFT_AVG(val) ((val) << 9) + +/* Integration time for VBus */ +#define INA226_ITB_MASK GENMASK(8, 6) +#define INA226_SHIFT_ITB(val) ((val) << 6) + +/* Integration time for VShunt */ +#define INA226_ITS_MASK GENMASK(5, 3) +#define INA226_SHIFT_ITS(val) ((val) << 3) + +/* Cosmetic macro giving the sampling period for a full P=UxI cycle */ +#define SAMPLING_PERIOD(c) ((c->int_time_vbus + c->int_time_vshunt) \ + * c->avg) + +static bool ina2xx_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return (reg == INA2XX_CONFIG) || (reg > INA2XX_CURRENT); +} + +static bool ina2xx_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (reg != INA2XX_CONFIG); +} + +static inline bool is_signed_reg(unsigned int reg) +{ + return (reg == INA2XX_SHUNT_VOLTAGE) || (reg == INA2XX_CURRENT); +} + +static const struct regmap_config ina2xx_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = INA2XX_MAX_REGISTERS, + .writeable_reg = ina2xx_is_writeable_reg, + .volatile_reg = ina2xx_is_volatile_reg, +}; + +enum ina2xx_ids { ina219, ina226 }; + +struct ina2xx_config { + u16 config_default; + int calibration_factor; + int shunt_div; + int bus_voltage_shift; + int bus_voltage_lsb; /* uV */ + int power_lsb; /* uW */ +}; + +struct ina2xx_chip_info { + struct regmap *regmap; + struct task_struct *task; + const struct ina2xx_config *config; + struct mutex state_lock; + long rshunt; + int avg; + s64 prev_ns; /* track buffer capture time, check for underruns*/ + int int_time_vbus; /* Bus voltage integration time uS */ + int int_time_vshunt; /* Shunt voltage integration time uS */ +}; + +static const struct ina2xx_config ina2xx_config[] = { + [ina219] = { + .config_default = INA219_CONFIG_DEFAULT, + .calibration_factor = 40960000, + .shunt_div = 100, + .bus_voltage_shift = 3, + .bus_voltage_lsb = 4000, + .power_lsb = 20000, + }, + [ina226] = { + .config_default = INA226_CONFIG_DEFAULT, + .calibration_factor = 5120000, + .shunt_div = 400, + .bus_voltage_shift = 0, + .bus_voltage_lsb = 1250, + .power_lsb = 25000, + }, +}; + +static int ina2xx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int regval; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(chip->regmap, chan->address, ®val); + if (ret < 0) + return ret; + + if (is_signed_reg(chan->address)) + *val = (s16) regval; + else + *val = regval; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = chip->avg; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + if (chan->address == INA2XX_SHUNT_VOLTAGE) + *val2 = chip->int_time_vshunt; + else + *val2 = chip->int_time_vbus; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * Sample freq is read only, it is a consequence of + * 1/AVG*(CT_bus+CT_shunt). + */ + *val = DIV_ROUND_CLOSEST(1000000, SAMPLING_PERIOD(chip)); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case INA2XX_SHUNT_VOLTAGE: + /* processed (mV) = raw*1000/shunt_div */ + *val2 = chip->config->shunt_div; + *val = 1000; + return IIO_VAL_FRACTIONAL; + + case INA2XX_BUS_VOLTAGE: + /* processed (mV) = raw*lsb (uV) / (1000 << shift) */ + *val = chip->config->bus_voltage_lsb; + *val2 = 1000 << chip->config->bus_voltage_shift; + return IIO_VAL_FRACTIONAL; + + case INA2XX_POWER: + /* processed (mW) = raw*lsb (uW) / 1000 */ + *val = chip->config->power_lsb; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + + case INA2XX_CURRENT: + /* processed (mA) = raw (mA) */ + *val = 1; + return IIO_VAL_INT; + } + } + + return -EINVAL; +} + +/* + * Available averaging rates for ina226. The indices correspond with + * the bit values expected by the chip (according to the ina226 datasheet, + * table 3 AVG bit settings, found at + * http://www.ti.com/lit/ds/symlink/ina226.pdf. + */ +static const int ina226_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024 }; + +static int ina226_set_average(struct ina2xx_chip_info *chip, unsigned int val, + unsigned int *config) +{ + int bits; + + if (val > 1024 || val < 1) + return -EINVAL; + + bits = find_closest(val, ina226_avg_tab, + ARRAY_SIZE(ina226_avg_tab)); + + chip->avg = ina226_avg_tab[bits]; + + *config &= ~INA226_AVG_MASK; + *config |= INA226_SHIFT_AVG(bits) & INA226_AVG_MASK; + + return 0; +} + +/* Conversion times in uS */ +static const int ina226_conv_time_tab[] = { 140, 204, 332, 588, 1100, + 2116, 4156, 8244 }; + +static int ina226_set_int_time_vbus(struct ina2xx_chip_info *chip, + unsigned int val_us, unsigned int *config) +{ + int bits; + + if (val_us > 8244 || val_us < 140) + return -EINVAL; + + bits = find_closest(val_us, ina226_conv_time_tab, + ARRAY_SIZE(ina226_conv_time_tab)); + + chip->int_time_vbus = ina226_conv_time_tab[bits]; + + *config &= ~INA226_ITB_MASK; + *config |= INA226_SHIFT_ITB(bits) & INA226_ITB_MASK; + + return 0; +} + +static int ina226_set_int_time_vshunt(struct ina2xx_chip_info *chip, + unsigned int val_us, unsigned int *config) +{ + int bits; + + if (val_us > 8244 || val_us < 140) + return -EINVAL; + + bits = find_closest(val_us, ina226_conv_time_tab, + ARRAY_SIZE(ina226_conv_time_tab)); + + chip->int_time_vshunt = ina226_conv_time_tab[bits]; + + *config &= ~INA226_ITS_MASK; + *config |= INA226_SHIFT_ITS(bits) & INA226_ITS_MASK; + + return 0; +} + +static int ina2xx_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + int ret; + unsigned int config, tmp; + + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + mutex_lock(&chip->state_lock); + + ret = regmap_read(chip->regmap, INA2XX_CONFIG, &config); + if (ret < 0) + goto _err; + + tmp = config; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = ina226_set_average(chip, val, &tmp); + break; + + case IIO_CHAN_INFO_INT_TIME: + if (chan->address == INA2XX_SHUNT_VOLTAGE) + ret = ina226_set_int_time_vshunt(chip, val2, &tmp); + else + ret = ina226_set_int_time_vbus(chip, val2, &tmp); + break; + default: + ret = -EINVAL; + } + + if (!ret && (tmp != config)) + ret = regmap_write(chip->regmap, INA2XX_CONFIG, tmp); +_err: + mutex_unlock(&chip->state_lock); + + return ret; +} + + +#define INA2XX_CHAN(_type, _index, _address) { \ + .type = (_type), \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + } \ +} + +/* + * Sampling Freq is a consequence of the integration times of + * the Voltage channels. + */ +#define INA2XX_CHAN_VOLTAGE(_index, _address) { \ + .type = IIO_VOLTAGE, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + } \ +} + +static const struct iio_chan_spec ina2xx_channels[] = { + INA2XX_CHAN_VOLTAGE(0, INA2XX_SHUNT_VOLTAGE), + INA2XX_CHAN_VOLTAGE(1, INA2XX_BUS_VOLTAGE), + INA2XX_CHAN(IIO_CURRENT, 2, INA2XX_CURRENT), + INA2XX_CHAN(IIO_POWER, 3, INA2XX_POWER), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int ina2xx_work_buffer(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned short data[8]; + int bit, ret, i = 0; + unsigned long buffer_us, elapsed_us; + s64 time_a, time_b; + unsigned int alert; + + time_a = iio_get_time_ns(); + + /* + * Because the timer thread and the chip conversion clock + * are asynchronous, the period difference will eventually + * result in reading V[k-1] again, or skip V[k] at time Tk. + * In order to resync the timer with the conversion process + * we check the ConVersionReadyFlag. + * On hardware that supports using the ALERT pin to toggle a + * GPIO a triggered buffer could be used instead. + * For now, we pay for that extra read of the ALERT register + */ + do { + ret = regmap_read(chip->regmap, INA226_ALERT_MASK, + &alert); + if (ret < 0) + return ret; + + alert &= INA266_CVRF; + trace_printk("Conversion ready: %d\n", !!alert); + + } while (!alert); + + /* + * Single register reads: bulk_read will not work with ina226 + * as there is no auto-increment of the address register for + * data length longer than 16bits. + */ + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + unsigned int val; + + ret = regmap_read(chip->regmap, + INA2XX_SHUNT_VOLTAGE + bit, &val); + if (ret < 0) + return ret; + + data[i++] = val; + } + + time_b = iio_get_time_ns(); + + iio_push_to_buffers_with_timestamp(indio_dev, + (unsigned int *)data, time_a); + + buffer_us = (unsigned long)(time_b - time_a) / 1000; + elapsed_us = (unsigned long)(time_a - chip->prev_ns) / 1000; + + trace_printk("uS: elapsed: %lu, buf: %lu\n", elapsed_us, buffer_us); + + chip->prev_ns = time_a; + + return buffer_us; +}; + +static int ina2xx_capture_thread(void *data) +{ + struct iio_dev *indio_dev = (struct iio_dev *)data; + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int sampling_us = SAMPLING_PERIOD(chip); + int buffer_us; + + /* + * Poll a bit faster than the chip internal Fs, in case + * we wish to sync with the conversion ready flag. + */ + sampling_us -= 200; + + do { + buffer_us = ina2xx_work_buffer(indio_dev); + if (buffer_us < 0) + return buffer_us; + + if (sampling_us > buffer_us) + udelay(sampling_us - buffer_us); + + } while (!kthread_should_stop()); + + return 0; +} + +static int ina2xx_buffer_enable(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int sampling_us = SAMPLING_PERIOD(chip); + + trace_printk("Enabling buffer w/ scan_mask %02x, freq = %d, avg =%u\n", + (unsigned int)(*indio_dev->active_scan_mask), + 1000000/sampling_us, chip->avg); + + trace_printk("Expected work period: %u us\n", sampling_us); + + chip->prev_ns = iio_get_time_ns(); + + chip->task = kthread_run(ina2xx_capture_thread, (void *)indio_dev, + "ina2xx-%uus", sampling_us); + + return PTR_ERR_OR_ZERO(chip->task); +} + +static int ina2xx_buffer_disable(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + + if (chip->task) { + kthread_stop(chip->task); + chip->task = NULL; + } + + return 0; +} + +static const struct iio_buffer_setup_ops ina2xx_setup_ops = { + .postenable = &ina2xx_buffer_enable, + .predisable = &ina2xx_buffer_disable, +}; + +static int ina2xx_debug_reg(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, unsigned *readval) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + + if (!readval) + return regmap_write(chip->regmap, reg, writeval); + + return regmap_read(chip->regmap, reg, readval); +} + +/* Possible integration times for vshunt and vbus */ +static IIO_CONST_ATTR_INT_TIME_AVAIL \ + ("0.000140 0.000204 0.000332 0.000588 0.001100 0.002116 0.004156 0.008244"); + +static struct attribute *ina2xx_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ina2xx_attribute_group = { + .attrs = ina2xx_attributes, +}; + +static const struct iio_info ina2xx_info = { + .debugfs_reg_access = &ina2xx_debug_reg, + .read_raw = &ina2xx_read_raw, + .write_raw = &ina2xx_write_raw, + .attrs = &ina2xx_attribute_group, + .driver_module = THIS_MODULE, +}; + +/* Initialize the configuration and calibration registers. */ +static int ina2xx_init(struct ina2xx_chip_info *chip, unsigned int config) +{ + u16 regval; + int ret = regmap_write(chip->regmap, INA2XX_CONFIG, config); + + if (ret < 0) + return ret; + /* + * Set current LSB to 1mA, shunt is in uOhms + * (equation 13 in datasheet). We hardcode a Current_LSB + * of 1.0 x10-6. The only remaining parameter is RShunt. + * There is no need to expose the CALIBRATION register + * to the user for now. + */ + regval = DIV_ROUND_CLOSEST(chip->config->calibration_factor, + chip->rshunt); + + return regmap_write(chip->regmap, INA2XX_CALIBRATION, regval); +} + +static int ina2xx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ina2xx_chip_info *chip; + struct iio_dev *indio_dev; + struct iio_buffer *buffer; + int ret; + unsigned int val; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + chip->config = &ina2xx_config[id->driver_data]; + + if (of_property_read_u32(client->dev.of_node, + "shunt-resistor", &val) < 0) { + struct ina2xx_platform_data *pdata = + dev_get_platdata(&client->dev); + + if (pdata) + val = pdata->shunt_uohms; + else + val = INA2XX_RSHUNT_DEFAULT; + } + + if (val <= 0 || val > chip->config->calibration_factor) + return -ENODEV; + + chip->rshunt = val; + + mutex_init(&chip->state_lock); + + /* This is only used for device removal purposes. */ + i2c_set_clientdata(client, indio_dev); + + indio_dev->name = id->name; + indio_dev->channels = ina2xx_channels; + indio_dev->num_channels = ARRAY_SIZE(ina2xx_channels); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ina2xx_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + + chip->regmap = devm_regmap_init_i2c(client, &ina2xx_regmap_config); + if (IS_ERR(chip->regmap)) { + dev_err(&client->dev, "failed to allocate register map\n"); + return PTR_ERR(chip->regmap); + } + + /* Patch the current config register with default. */ + val = chip->config->config_default; + + if (id->driver_data == ina226) { + ina226_set_average(chip, INA226_DEFAULT_AVG, &val); + ina226_set_int_time_vbus(chip, INA226_DEFAULT_IT, &val); + ina226_set_int_time_vshunt(chip, INA226_DEFAULT_IT, &val); + } + + ret = ina2xx_init(chip, val); + if (ret < 0) { + dev_err(&client->dev, "error configuring the device: %d\n", + ret); + return -ENODEV; + } + + buffer = devm_iio_kfifo_allocate(&indio_dev->dev); + if (!buffer) + return -ENOMEM; + + indio_dev->setup_ops = &ina2xx_setup_ops; + + iio_device_attach_buffer(indio_dev, buffer); + + return iio_device_register(indio_dev); +} + + +static int ina2xx_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + /* Powerdown */ + return regmap_update_bits(chip->regmap, INA2XX_CONFIG, + INA2XX_MODE_MASK, 0); +} + + +static const struct i2c_device_id ina2xx_id[] = { + {"ina219", ina219}, + {"ina220", ina219}, + {"ina226", ina226}, + {"ina230", ina226}, + {"ina231", ina226}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ina2xx_id); + +static struct i2c_driver ina2xx_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = ina2xx_probe, + .remove = ina2xx_remove, + .id_table = ina2xx_id, +}; + +module_i2c_driver(ina2xx_driver); + +MODULE_AUTHOR("Marc Titinger "); +MODULE_DESCRIPTION("Texas Instruments INA2XX ADC driver"); +MODULE_LICENSE("GPL v2"); From f9993c0771ce24063fe62bf73ac57bcfc9ad81de Mon Sep 17 00:00:00 2001 From: Marc Titinger Date: Mon, 7 Dec 2015 10:09:35 +0100 Subject: [PATCH 23/28] iio: ina2xx: provide a sysfs parameter to allow async readout of the ADCs This can lead to repeated or skipped samples depending on the clock beat between the capture thread and the chip sampling clock, but will also spare reading/waiting for the Capture Ready Flag and improve the available i2c bandwidth for reading measurements. Output of iio_info: ...snip... 4 device-specific attributes found: attr 0: in_oversampling_ratio value: 4 attr 1: in_allow_async_readout value: 0 attr 2: integration_time_available value: 140 204 332 588 1100 2116... attr 3: in_sampling_frequency value: 114 Signed-off-by: Marc Titinger Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ina2xx-adc.c | 54 ++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c index 707bd69a7353..ff31f5b49a41 100644 --- a/drivers/iio/adc/ina2xx-adc.c +++ b/drivers/iio/adc/ina2xx-adc.c @@ -116,6 +116,7 @@ struct ina2xx_chip_info { s64 prev_ns; /* track buffer capture time, check for underruns*/ int int_time_vbus; /* Bus voltage integration time uS */ int int_time_vshunt; /* Shunt voltage integration time uS */ + bool allow_async_readout; }; static const struct ina2xx_config ina2xx_config[] = { @@ -322,6 +323,33 @@ _err: } +static ssize_t ina2xx_allow_async_readout_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%d\n", chip->allow_async_readout); +} + +static ssize_t ina2xx_allow_async_readout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + bool val; + int ret; + + ret = strtobool((const char *) buf, &val); + if (ret) + return ret; + + chip->allow_async_readout = val; + + return len; +} + + #define INA2XX_CHAN(_type, _index, _address) { \ .type = (_type), \ .address = (_address), \ @@ -390,16 +418,17 @@ static int ina2xx_work_buffer(struct iio_dev *indio_dev) * GPIO a triggered buffer could be used instead. * For now, we pay for that extra read of the ALERT register */ - do { - ret = regmap_read(chip->regmap, INA226_ALERT_MASK, - &alert); - if (ret < 0) - return ret; + if (!chip->allow_async_readout) + do { + ret = regmap_read(chip->regmap, INA226_ALERT_MASK, + &alert); + if (ret < 0) + return ret; - alert &= INA266_CVRF; - trace_printk("Conversion ready: %d\n", !!alert); + alert &= INA266_CVRF; + trace_printk("Conversion ready: %d\n", !!alert); - } while (!alert); + } while (!alert); /* * Single register reads: bulk_read will not work with ina226 @@ -444,7 +473,8 @@ static int ina2xx_capture_thread(void *data) * Poll a bit faster than the chip internal Fs, in case * we wish to sync with the conversion ready flag. */ - sampling_us -= 200; + if (!chip->allow_async_readout) + sampling_us -= 200; do { buffer_us = ina2xx_work_buffer(indio_dev); @@ -469,6 +499,7 @@ static int ina2xx_buffer_enable(struct iio_dev *indio_dev) 1000000/sampling_us, chip->avg); trace_printk("Expected work period: %u us\n", sampling_us); + trace_printk("Async readout mode: %d\n", chip->allow_async_readout); chip->prev_ns = iio_get_time_ns(); @@ -510,7 +541,12 @@ static int ina2xx_debug_reg(struct iio_dev *indio_dev, static IIO_CONST_ATTR_INT_TIME_AVAIL \ ("0.000140 0.000204 0.000332 0.000588 0.001100 0.002116 0.004156 0.008244"); +static IIO_DEVICE_ATTR(in_allow_async_readout, S_IRUGO | S_IWUSR, + ina2xx_allow_async_readout_show, + ina2xx_allow_async_readout_store, 0); + static struct attribute *ina2xx_attributes[] = { + &iio_dev_attr_in_allow_async_readout.dev_attr.attr, &iio_const_attr_integration_time_available.dev_attr.attr, NULL, }; From b17dc40155a973d0da68b54184dd9c237426c426 Mon Sep 17 00:00:00 2001 From: Marc Titinger Date: Fri, 11 Dec 2015 17:49:15 +0100 Subject: [PATCH 24/28] iio: ina2xx: re-instate a sysfs show/store for the shunt resistor value Different probe modules use different resistor values. The front-end application may read a probe ID (from eeprom) and set the shunt value accordingly. Signed-off-by: Marc Titinger Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ina2xx-adc.c | 52 +++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c index ff31f5b49a41..dd4ff08d7b62 100644 --- a/drivers/iio/adc/ina2xx-adc.c +++ b/drivers/iio/adc/ina2xx-adc.c @@ -111,7 +111,7 @@ struct ina2xx_chip_info { struct task_struct *task; const struct ina2xx_config *config; struct mutex state_lock; - long rshunt; + unsigned int shunt_resistor; int avg; s64 prev_ns; /* track buffer capture time, check for underruns*/ int int_time_vbus; /* Bus voltage integration time uS */ @@ -349,6 +349,42 @@ static ssize_t ina2xx_allow_async_readout_store(struct device *dev, return len; } +static int set_shunt_resistor(struct ina2xx_chip_info *chip, unsigned int val) +{ + if (val <= 0 || val > chip->config->calibration_factor) + return -EINVAL; + + chip->shunt_resistor = val; + return 0; +} + +static ssize_t ina2xx_shunt_resistor_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%d\n", chip->shunt_resistor); +} + +static ssize_t ina2xx_shunt_resistor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + unsigned long val; + int ret; + + ret = kstrtoul((const char *) buf, 10, &val); + if (ret) + return ret; + + ret = set_shunt_resistor(chip, val); + if (ret) + return ret; + + return len; +} #define INA2XX_CHAN(_type, _index, _address) { \ .type = (_type), \ @@ -545,9 +581,14 @@ static IIO_DEVICE_ATTR(in_allow_async_readout, S_IRUGO | S_IWUSR, ina2xx_allow_async_readout_show, ina2xx_allow_async_readout_store, 0); +static IIO_DEVICE_ATTR(in_shunt_resistor, S_IRUGO | S_IWUSR, + ina2xx_shunt_resistor_show, + ina2xx_shunt_resistor_store, 0); + static struct attribute *ina2xx_attributes[] = { &iio_dev_attr_in_allow_async_readout.dev_attr.attr, &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_dev_attr_in_shunt_resistor.dev_attr.attr, NULL, }; @@ -579,7 +620,7 @@ static int ina2xx_init(struct ina2xx_chip_info *chip, unsigned int config) * to the user for now. */ regval = DIV_ROUND_CLOSEST(chip->config->calibration_factor, - chip->rshunt); + chip->shunt_resistor); return regmap_write(chip->regmap, INA2XX_CALIBRATION, regval); } @@ -612,10 +653,9 @@ static int ina2xx_probe(struct i2c_client *client, val = INA2XX_RSHUNT_DEFAULT; } - if (val <= 0 || val > chip->config->calibration_factor) - return -ENODEV; - - chip->rshunt = val; + ret = set_shunt_resistor(chip, val); + if (ret) + return ret; mutex_init(&chip->state_lock); From 46294cd948d530d4feccfd3afd6635e16a55dcb1 Mon Sep 17 00:00:00 2001 From: Marc Titinger Date: Fri, 11 Dec 2015 17:49:16 +0100 Subject: [PATCH 25/28] iio: ina2xx: give the capture kthread a more useful name string. PID PPID USER STAT VSZ %VSZ %CPU COMMAND 144 2 root DW 0 0% 33% [ina226:1-8800us] 141 2 root DW 0 0% 25% [ina226:0-8800us] 40 2 root SW 0 0% 15% [irq/156-4802a00] 147 2 root DW 0 0% 7% [ina226:2-8800us] 145 1 root S 1236 0% 6% dd if /dev/iio:device1 of /dev/null 148 1 root S 1236 0% 4% dd if /dev/iio:device2 of /dev/null 149 137 root R 1244 0% 3% top -d 1 142 1 root S 1236 0% 2% dd if /dev/iio:device0 of /dev/null Signed-off-by: Marc Titinger Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ina2xx-adc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c index dd4ff08d7b62..4c18c4cc8939 100644 --- a/drivers/iio/adc/ina2xx-adc.c +++ b/drivers/iio/adc/ina2xx-adc.c @@ -540,7 +540,8 @@ static int ina2xx_buffer_enable(struct iio_dev *indio_dev) chip->prev_ns = iio_get_time_ns(); chip->task = kthread_run(ina2xx_capture_thread, (void *)indio_dev, - "ina2xx-%uus", sampling_us); + "%s:%d-%uus", indio_dev->name, indio_dev->id, + sampling_us); return PTR_ERR_OR_ZERO(chip->task); } From 16846ebeffe4e74a16f25237003eab6d0535d8dd Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Tue, 8 Dec 2015 18:26:20 +0800 Subject: [PATCH 26/28] iio: adc: add IMX7D ADC driver support Freescale i.MX7D soc contains a new ADC IP. This patch add this ADC driver support, and the driver only support ADC software trigger. Signed-off-by: Haibo Chen Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 9 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/imx7d_adc.c | 609 ++++++++++++++++++++++++++++++++++++ 3 files changed, 619 insertions(+) create mode 100644 drivers/iio/adc/imx7d_adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index cb68bd9d2052..605ff42c4631 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -204,6 +204,15 @@ config INA2XX_ADC Say yes here to build support for TI INA2xx family of Power Monitors. This driver is mutually exclusive with the HWMON version. +config IMX7D_ADC + tristate "IMX7D ADC driver" + depends on ARCH_MXC || COMPILE_TEST + help + Say yes here to build support for IMX7D ADC. + + This driver can also be built as a module. If so, the module will be + called imx7d_adc. + config LP8788_ADC tristate "LP8788 ADC driver" depends on MFD_LP8788 diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c62c5fa91c82..6435780e9b71 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o obj-$(CONFIG_HI8435) += hi8435.o +obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o obj-$(CONFIG_MAX1027) += max1027.o diff --git a/drivers/iio/adc/imx7d_adc.c b/drivers/iio/adc/imx7d_adc.c new file mode 100644 index 000000000000..e2241ee94783 --- /dev/null +++ b/drivers/iio/adc/imx7d_adc.c @@ -0,0 +1,609 @@ +/* + * Freescale i.MX7D ADC driver + * + * Copyright (C) 2015 Freescale Semiconductor, Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* ADC register */ +#define IMX7D_REG_ADC_CH_A_CFG1 0x00 +#define IMX7D_REG_ADC_CH_A_CFG2 0x10 +#define IMX7D_REG_ADC_CH_B_CFG1 0x20 +#define IMX7D_REG_ADC_CH_B_CFG2 0x30 +#define IMX7D_REG_ADC_CH_C_CFG1 0x40 +#define IMX7D_REG_ADC_CH_C_CFG2 0x50 +#define IMX7D_REG_ADC_CH_D_CFG1 0x60 +#define IMX7D_REG_ADC_CH_D_CFG2 0x70 +#define IMX7D_REG_ADC_CH_SW_CFG 0x80 +#define IMX7D_REG_ADC_TIMER_UNIT 0x90 +#define IMX7D_REG_ADC_DMA_FIFO 0xa0 +#define IMX7D_REG_ADC_FIFO_STATUS 0xb0 +#define IMX7D_REG_ADC_INT_SIG_EN 0xc0 +#define IMX7D_REG_ADC_INT_EN 0xd0 +#define IMX7D_REG_ADC_INT_STATUS 0xe0 +#define IMX7D_REG_ADC_CHA_B_CNV_RSLT 0xf0 +#define IMX7D_REG_ADC_CHC_D_CNV_RSLT 0x100 +#define IMX7D_REG_ADC_CH_SW_CNV_RSLT 0x110 +#define IMX7D_REG_ADC_DMA_FIFO_DAT 0x120 +#define IMX7D_REG_ADC_ADC_CFG 0x130 + +#define IMX7D_REG_ADC_CHANNEL_CFG2_BASE 0x10 +#define IMX7D_EACH_CHANNEL_REG_OFFSET 0x20 + +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN (0x1 << 31) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE BIT(30) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN BIT(29) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(x) ((x) << 24) + +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4 (0x0 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8 (0x1 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16 (0x2 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32 (0x3 << 12) + +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4 (0x0 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8 (0x1 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16 (0x2 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32 (0x3 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64 (0x4 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128 (0x5 << 29) + +#define IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN BIT(31) +#define IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN BIT(1) +#define IMX7D_REG_ADC_ADC_CFG_ADC_EN BIT(0) + +#define IMX7D_REG_ADC_INT_CHA_COV_INT_EN BIT(8) +#define IMX7D_REG_ADC_INT_CHB_COV_INT_EN BIT(9) +#define IMX7D_REG_ADC_INT_CHC_COV_INT_EN BIT(10) +#define IMX7D_REG_ADC_INT_CHD_COV_INT_EN BIT(11) +#define IMX7D_REG_ADC_INT_CHANNEL_INT_EN \ + (IMX7D_REG_ADC_INT_CHA_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHB_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHC_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHD_COV_INT_EN) +#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS 0xf00 +#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT 0xf0000 + +#define IMX7D_ADC_TIMEOUT msecs_to_jiffies(100) + +enum imx7d_adc_clk_pre_div { + IMX7D_ADC_ANALOG_CLK_PRE_DIV_4, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_8, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_16, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_32, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_64, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_128, +}; + +enum imx7d_adc_average_num { + IMX7D_ADC_AVERAGE_NUM_4, + IMX7D_ADC_AVERAGE_NUM_8, + IMX7D_ADC_AVERAGE_NUM_16, + IMX7D_ADC_AVERAGE_NUM_32, +}; + +struct imx7d_adc_feature { + enum imx7d_adc_clk_pre_div clk_pre_div; + enum imx7d_adc_average_num avg_num; + + u32 core_time_unit; /* impact the sample rate */ + + bool average_en; +}; + +struct imx7d_adc { + struct device *dev; + void __iomem *regs; + struct clk *clk; + + u32 vref_uv; + u32 value; + u32 channel; + u32 pre_div_num; + + struct regulator *vref; + struct imx7d_adc_feature adc_feature; + + struct completion completion; +}; + +struct imx7d_adc_analogue_core_clk { + u32 pre_div; + u32 reg_config; +}; + +#define IMX7D_ADC_ANALOGUE_CLK_CONFIG(_pre_div, _reg_conf) { \ + .pre_div = (_pre_div), \ + .reg_config = (_reg_conf), \ +} + +static const struct imx7d_adc_analogue_core_clk imx7d_adc_analogue_clk[] = { + IMX7D_ADC_ANALOGUE_CLK_CONFIG(4, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(8, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(16, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(32, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(64, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(128, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128), +}; + +#define IMX7D_ADC_CHAN(_idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec imx7d_adc_iio_channels[] = { + IMX7D_ADC_CHAN(0), + IMX7D_ADC_CHAN(1), + IMX7D_ADC_CHAN(2), + IMX7D_ADC_CHAN(3), + IMX7D_ADC_CHAN(4), + IMX7D_ADC_CHAN(5), + IMX7D_ADC_CHAN(6), + IMX7D_ADC_CHAN(7), + IMX7D_ADC_CHAN(8), + IMX7D_ADC_CHAN(9), + IMX7D_ADC_CHAN(10), + IMX7D_ADC_CHAN(11), + IMX7D_ADC_CHAN(12), + IMX7D_ADC_CHAN(13), + IMX7D_ADC_CHAN(14), + IMX7D_ADC_CHAN(15), +}; + +static const u32 imx7d_adc_average_num[] = { + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32, +}; + +static void imx7d_adc_feature_config(struct imx7d_adc *info) +{ + info->adc_feature.clk_pre_div = IMX7D_ADC_ANALOG_CLK_PRE_DIV_4; + info->adc_feature.avg_num = IMX7D_ADC_AVERAGE_NUM_32; + info->adc_feature.core_time_unit = 1; + info->adc_feature.average_en = true; +} + +static void imx7d_adc_sample_rate_set(struct imx7d_adc *info) +{ + struct imx7d_adc_feature *adc_feature = &info->adc_feature; + struct imx7d_adc_analogue_core_clk adc_analogure_clk; + u32 i; + u32 tmp_cfg1; + u32 sample_rate = 0; + + /* + * Before sample set, disable channel A,B,C,D. Here we + * clear the bit 31 of register REG_ADC_CH_A\B\C\D_CFG1. + */ + for (i = 0; i < 4; i++) { + tmp_cfg1 = + readl(info->regs + i * IMX7D_EACH_CHANNEL_REG_OFFSET); + tmp_cfg1 &= ~IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN; + writel(tmp_cfg1, + info->regs + i * IMX7D_EACH_CHANNEL_REG_OFFSET); + } + + adc_analogure_clk = imx7d_adc_analogue_clk[adc_feature->clk_pre_div]; + sample_rate |= adc_analogure_clk.reg_config; + info->pre_div_num = adc_analogure_clk.pre_div; + + sample_rate |= adc_feature->core_time_unit; + writel(sample_rate, info->regs + IMX7D_REG_ADC_TIMER_UNIT); +} + +static void imx7d_adc_hw_init(struct imx7d_adc *info) +{ + u32 cfg; + + /* power up and enable adc analogue core */ + cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG); + cfg &= ~(IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN | + IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN); + cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_EN; + writel(cfg, info->regs + IMX7D_REG_ADC_ADC_CFG); + + /* enable channel A,B,C,D interrupt */ + writel(IMX7D_REG_ADC_INT_CHANNEL_INT_EN, + info->regs + IMX7D_REG_ADC_INT_SIG_EN); + writel(IMX7D_REG_ADC_INT_CHANNEL_INT_EN, + info->regs + IMX7D_REG_ADC_INT_EN); + + imx7d_adc_sample_rate_set(info); +} + +static void imx7d_adc_channel_set(struct imx7d_adc *info) +{ + u32 cfg1 = 0; + u32 cfg2; + u32 channel; + + channel = info->channel; + + /* the channel choose single conversion, and enable average mode */ + cfg1 |= (IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN | + IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE); + if (info->adc_feature.average_en) + cfg1 |= IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN; + + /* + * physical channel 0 chose logical channel A + * physical channel 1 chose logical channel B + * physical channel 2 chose logical channel C + * physical channel 3 chose logical channel D + */ + cfg1 |= IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(channel); + + /* + * read register REG_ADC_CH_A\B\C\D_CFG2, according to the + * channel chosen + */ + cfg2 = readl(info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel + + IMX7D_REG_ADC_CHANNEL_CFG2_BASE); + + cfg2 |= imx7d_adc_average_num[info->adc_feature.avg_num]; + + /* + * write the register REG_ADC_CH_A\B\C\D_CFG2, according to + * the channel chosen + */ + writel(cfg2, info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel + + IMX7D_REG_ADC_CHANNEL_CFG2_BASE); + writel(cfg1, info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel); +} + +static u32 imx7d_adc_get_sample_rate(struct imx7d_adc *info) +{ + /* input clock is always 24MHz */ + u32 input_clk = 24000000; + u32 analogue_core_clk; + u32 core_time_unit = info->adc_feature.core_time_unit; + u32 tmp; + + analogue_core_clk = input_clk / info->pre_div_num; + tmp = (core_time_unit + 1) * 6; + + return analogue_core_clk / tmp; +} + +static int imx7d_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct imx7d_adc *info = iio_priv(indio_dev); + + u32 channel; + long ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + reinit_completion(&info->completion); + + channel = chan->channel & 0x03; + info->channel = channel; + imx7d_adc_channel_set(info); + + ret = wait_for_completion_interruptible_timeout + (&info->completion, IMX7D_ADC_TIMEOUT); + if (ret == 0) { + mutex_unlock(&indio_dev->mlock); + return -ETIMEDOUT; + } + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + *val = info->value; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + info->vref_uv = regulator_get_voltage(info->vref); + *val = info->vref_uv / 1000; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = imx7d_adc_get_sample_rate(info); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int imx7d_adc_read_data(struct imx7d_adc *info) +{ + u32 channel; + u32 value; + + channel = info->channel & 0x03; + + /* + * channel A and B conversion result share one register, + * bit[27~16] is the channel B conversion result, + * bit[11~0] is the channel A conversion result. + * channel C and D is the same. + */ + if (channel < 2) + value = readl(info->regs + IMX7D_REG_ADC_CHA_B_CNV_RSLT); + else + value = readl(info->regs + IMX7D_REG_ADC_CHC_D_CNV_RSLT); + if (channel & 0x1) /* channel B or D */ + value = (value >> 16) & 0xFFF; + else /* channel A or C */ + value &= 0xFFF; + + return value; +} + +static irqreturn_t imx7d_adc_isr(int irq, void *dev_id) +{ + struct imx7d_adc *info = (struct imx7d_adc *)dev_id; + int status; + + status = readl(info->regs + IMX7D_REG_ADC_INT_STATUS); + if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS) { + info->value = imx7d_adc_read_data(info); + complete(&info->completion); + + /* + * The register IMX7D_REG_ADC_INT_STATUS can't clear + * itself after read operation, need software to write + * 0 to the related bit. Here we clear the channel A/B/C/D + * conversion finished flag. + */ + status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS; + writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS); + } + + /* + * If the channel A/B/C/D conversion timeout, report it and clear these + * timeout flags. + */ + if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT) { + pr_err("%s: ADC got conversion time out interrupt: 0x%08x\n", + dev_name(info->dev), status); + status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT; + writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS); + } + + return IRQ_HANDLED; +} + +static int imx7d_adc_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct imx7d_adc *info = iio_priv(indio_dev); + + if (!readval || reg % 4 || reg > IMX7D_REG_ADC_ADC_CFG) + return -EINVAL; + + *readval = readl(info->regs + reg); + + return 0; +} + +static const struct iio_info imx7d_adc_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = &imx7d_adc_read_raw, + .debugfs_reg_access = &imx7d_adc_reg_access, +}; + +static const struct of_device_id imx7d_adc_match[] = { + { .compatible = "fsl,imx7d-adc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx7d_adc_match); + +static void imx7d_adc_power_down(struct imx7d_adc *info) +{ + u32 adc_cfg; + + adc_cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG); + adc_cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN | + IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN; + adc_cfg &= ~IMX7D_REG_ADC_ADC_CFG_ADC_EN; + writel(adc_cfg, info->regs + IMX7D_REG_ADC_ADC_CFG); +} + +static int imx7d_adc_probe(struct platform_device *pdev) +{ + struct imx7d_adc *info; + struct iio_dev *indio_dev; + struct resource *mem; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!indio_dev) { + dev_err(&pdev->dev, "Failed allocating iio device\n"); + return -ENOMEM; + } + + info = iio_priv(indio_dev); + info->dev = &pdev->dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(info->regs)) { + ret = PTR_ERR(info->regs); + dev_err(&pdev->dev, + "Failed to remap adc memory, err = %d\n", ret); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "No irq resource?\n"); + return irq; + } + + info->clk = devm_clk_get(&pdev->dev, "adc"); + if (IS_ERR(info->clk)) { + ret = PTR_ERR(info->clk); + dev_err(&pdev->dev, "Failed getting clock, err = %d\n", ret); + return ret; + } + + info->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(info->vref)) { + ret = PTR_ERR(info->vref); + dev_err(&pdev->dev, + "Failed getting reference voltage, err = %d\n", ret); + return ret; + } + + ret = regulator_enable(info->vref); + if (ret) { + dev_err(&pdev->dev, + "Can't enable adc reference top voltage, err = %d\n", + ret); + return ret; + } + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&info->completion); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &imx7d_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = imx7d_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(imx7d_adc_iio_channels); + + ret = clk_prepare_enable(info->clk); + if (ret) { + dev_err(&pdev->dev, + "Could not prepare or enable the clock.\n"); + goto error_adc_clk_enable; + } + + ret = devm_request_irq(info->dev, irq, + imx7d_adc_isr, 0, + dev_name(&pdev->dev), info); + if (ret < 0) { + dev_err(&pdev->dev, "Failed requesting irq, irq = %d\n", irq); + goto error_iio_device_register; + } + + imx7d_adc_feature_config(info); + imx7d_adc_hw_init(info); + + ret = iio_device_register(indio_dev); + if (ret) { + imx7d_adc_power_down(info); + dev_err(&pdev->dev, "Couldn't register the device.\n"); + goto error_iio_device_register; + } + + return 0; + +error_iio_device_register: + clk_disable_unprepare(info->clk); +error_adc_clk_enable: + regulator_disable(info->vref); + + return ret; +} + +static int imx7d_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct imx7d_adc *info = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + imx7d_adc_power_down(info); + + clk_disable_unprepare(info->clk); + regulator_disable(info->vref); + + return 0; +} + +static int __maybe_unused imx7d_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx7d_adc *info = iio_priv(indio_dev); + + imx7d_adc_power_down(info); + + clk_disable_unprepare(info->clk); + regulator_disable(info->vref); + + return 0; +} + +static int __maybe_unused imx7d_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx7d_adc *info = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(info->vref); + if (ret) { + dev_err(info->dev, + "Can't enable adc reference top voltage, err = %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(info->clk); + if (ret) { + dev_err(info->dev, + "Could not prepare or enable clock.\n"); + regulator_disable(info->vref); + return ret; + } + + imx7d_adc_hw_init(info); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(imx7d_adc_pm_ops, imx7d_adc_suspend, imx7d_adc_resume); + +static struct platform_driver imx7d_adc_driver = { + .probe = imx7d_adc_probe, + .remove = imx7d_adc_remove, + .driver = { + .name = "imx7d_adc", + .of_match_table = imx7d_adc_match, + .pm = &imx7d_adc_pm_ops, + }, +}; + +module_platform_driver(imx7d_adc_driver); + +MODULE_AUTHOR("Haibo Chen "); +MODULE_DESCRIPTION("Freeacale IMX7D ADC driver"); +MODULE_LICENSE("GPL v2"); From 50672011d676aea5e521bc77dfce0c9eea040782 Mon Sep 17 00:00:00 2001 From: Haibo Chen Date: Tue, 8 Dec 2015 18:26:21 +0800 Subject: [PATCH 27/28] Documentation: add the binding file for Freescale imx7d ADC driver The patch adds the binding file for Freescale imx7d ADC driver. Signed-off-by: Haibo Chen Acked-by: Rob Herring Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/imx7d-adc.txt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/imx7d-adc.txt diff --git a/Documentation/devicetree/bindings/iio/adc/imx7d-adc.txt b/Documentation/devicetree/bindings/iio/adc/imx7d-adc.txt new file mode 100644 index 000000000000..5c184b940669 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/imx7d-adc.txt @@ -0,0 +1,22 @@ +Freescale imx7d ADC bindings + +The devicetree bindings are for the ADC driver written for +imx7d SoC. + +Required properties: +- compatible: Should be "fsl,imx7d-adc" +- reg: Offset and length of the register set for the ADC device +- interrupts: The interrupt number for the ADC device +- clocks: The root clock of the ADC controller +- clock-names: Must contain "adc", matching entry in the clocks property +- vref-supply: The regulator supply ADC reference voltage + +Example: +adc1: adc@30610000 { + compatible = "fsl,imx7d-adc"; + reg = <0x30610000 0x10000>; + interrupts = ; + clocks = <&clks IMX7D_ADC_ROOT_CLK>; + clock-names = "adc"; + vref-supply = <®_vcc_3v3_mcu>; +}; From c34c18195d30aa3b95f5ae1b4349875c45fdb8e4 Mon Sep 17 00:00:00 2001 From: Anshul Garg Date: Tue, 8 Dec 2015 09:45:56 -0800 Subject: [PATCH 28/28] iio/inkern.c Use list_for_each_entry_safe Use list_for_each_entry_safe instead of list_for_each_safe and list_entry call. Signed-off-by: Anshul Garg Signed-off-by: Jonathan Cameron --- drivers/iio/inkern.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c index c8bad3cf891d..80fbbfd76faf 100644 --- a/drivers/iio/inkern.c +++ b/drivers/iio/inkern.c @@ -61,12 +61,10 @@ EXPORT_SYMBOL_GPL(iio_map_array_register); int iio_map_array_unregister(struct iio_dev *indio_dev) { int ret = -ENODEV; - struct iio_map_internal *mapi; - struct list_head *pos, *tmp; + struct iio_map_internal *mapi, *next; mutex_lock(&iio_map_list_lock); - list_for_each_safe(pos, tmp, &iio_map_list) { - mapi = list_entry(pos, struct iio_map_internal, l); + list_for_each_entry_safe(mapi, next, &iio_map_list, l) { if (indio_dev == mapi->indio_dev) { list_del(&mapi->l); kfree(mapi);