diff options
Diffstat (limited to 'drivers/iio')
90 files changed, 8267 insertions, 571 deletions
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index c68bdb649005..ef8401ac1141 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -120,6 +120,8 @@ config HID_SENSOR_ACCEL_3D config IIO_ST_ACCEL_3AXIS tristate "STMicroelectronics accelerometers 3-Axis Driver" depends on (I2C || SPI_MASTER) && SYSFS + depends on !SENSORS_LIS3_I2C + depends on !SENSORS_LIS3_SPI select IIO_ST_SENSORS_CORE select IIO_ST_ACCEL_I2C_3AXIS if (I2C) select IIO_ST_ACCEL_SPI_3AXIS if (SPI_MASTER) diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c index 59b380dbf27f..6b5d3be283c4 100644 --- a/drivers/iio/accel/bmc150-accel-core.c +++ b/drivers/iio/accel/bmc150-accel-core.c @@ -1638,7 +1638,8 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, if (block_supported) { indio_dev->modes |= INDIO_BUFFER_SOFTWARE; indio_dev->info = &bmc150_accel_info_fifo; - indio_dev->buffer->attrs = bmc150_accel_fifo_attributes; + iio_buffer_set_attrs(indio_dev->buffer, + bmc150_accel_fifo_attributes); } } diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c index ab1e238d5c75..ca5759c0c318 100644 --- a/drivers/iio/accel/hid-sensor-accel-3d.c +++ b/drivers/iio/accel/hid-sensor-accel-3d.c @@ -42,11 +42,13 @@ struct accel_3d_state { struct hid_sensor_hub_callbacks callbacks; struct hid_sensor_common common_attributes; struct hid_sensor_hub_attribute_info accel[ACCEL_3D_CHANNEL_MAX]; - u32 accel_val[ACCEL_3D_CHANNEL_MAX]; + /* Reserve for 3 channels + padding + timestamp */ + u32 accel_val[ACCEL_3D_CHANNEL_MAX + 3]; int scale_pre_decml; int scale_post_decml; int scale_precision; int value_offset; + int64_t timestamp; }; static const u32 accel_3d_addresses[ACCEL_3D_CHANNEL_MAX] = { @@ -87,6 +89,42 @@ static const struct iio_chan_spec accel_3d_channels[] = { BIT(IIO_CHAN_INFO_SAMP_FREQ) | BIT(IIO_CHAN_INFO_HYSTERESIS), .scan_index = CHANNEL_SCAN_INDEX_Z, + }, + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +/* Channel definitions */ +static const struct iio_chan_spec gravity_channels[] = { + { + .type = IIO_GRAVITY, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_X, + }, { + .type = IIO_GRAVITY, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_Y, + }, { + .type = IIO_GRAVITY, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_Z, } }; @@ -111,6 +149,8 @@ static int accel_3d_read_raw(struct iio_dev *indio_dev, int report_id = -1; u32 address; int ret_type; + struct hid_sensor_hub_device *hsdev = + accel_state->common_attributes.hsdev; *val = 0; *val2 = 0; @@ -122,8 +162,7 @@ static int accel_3d_read_raw(struct iio_dev *indio_dev, if (report_id >= 0) *val = sensor_hub_input_attr_get_raw_value( accel_state->common_attributes.hsdev, - HID_USAGE_SENSOR_ACCEL_3D, address, - report_id, + hsdev->usage, address, report_id, SENSOR_HUB_SYNC); else { *val = 0; @@ -192,11 +231,11 @@ static const struct iio_info accel_3d_info = { }; /* Function to push data to buffer */ -static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data, - int len) +static void hid_sensor_push_data(struct iio_dev *indio_dev, void *data, + int len, int64_t timestamp) { dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); - iio_push_to_buffers(indio_dev, data); + iio_push_to_buffers_with_timestamp(indio_dev, data, timestamp); } /* Callback handler to send event after all samples are received and captured */ @@ -208,10 +247,17 @@ static int accel_3d_proc_event(struct hid_sensor_hub_device *hsdev, struct accel_3d_state *accel_state = iio_priv(indio_dev); dev_dbg(&indio_dev->dev, "accel_3d_proc_event\n"); - if (atomic_read(&accel_state->common_attributes.data_ready)) + if (atomic_read(&accel_state->common_attributes.data_ready)) { + if (!accel_state->timestamp) + accel_state->timestamp = iio_get_time_ns(indio_dev); + hid_sensor_push_data(indio_dev, - accel_state->accel_val, - sizeof(accel_state->accel_val)); + accel_state->accel_val, + sizeof(accel_state->accel_val), + accel_state->timestamp); + + accel_state->timestamp = 0; + } return 0; } @@ -236,6 +282,12 @@ static int accel_3d_capture_sample(struct hid_sensor_hub_device *hsdev, *(u32 *)raw_data; ret = 0; break; + case HID_USAGE_SENSOR_TIME_TIMESTAMP: + accel_state->timestamp = + hid_sensor_convert_timestamp( + &accel_state->common_attributes, + *(int64_t *)raw_data); + break; default: break; } @@ -272,7 +324,7 @@ static int accel_3d_parse_report(struct platform_device *pdev, st->accel[2].index, st->accel[2].report_id); st->scale_precision = hid_sensor_format_scale( - HID_USAGE_SENSOR_ACCEL_3D, + hsdev->usage, &st->accel[CHANNEL_SCAN_INDEX_X], &st->scale_pre_decml, &st->scale_post_decml); @@ -295,9 +347,12 @@ static int accel_3d_parse_report(struct platform_device *pdev, static int hid_accel_3d_probe(struct platform_device *pdev) { int ret = 0; - static const char *name = "accel_3d"; + static const char *name; struct iio_dev *indio_dev; struct accel_3d_state *accel_state; + const struct iio_chan_spec *channel_spec; + int channel_size; + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; indio_dev = devm_iio_device_alloc(&pdev->dev, @@ -311,24 +366,30 @@ static int hid_accel_3d_probe(struct platform_device *pdev) accel_state->common_attributes.hsdev = hsdev; accel_state->common_attributes.pdev = pdev; - ret = hid_sensor_parse_common_attributes(hsdev, - HID_USAGE_SENSOR_ACCEL_3D, + if (hsdev->usage == HID_USAGE_SENSOR_ACCEL_3D) { + name = "accel_3d"; + channel_spec = accel_3d_channels; + channel_size = sizeof(accel_3d_channels); + } else { + name = "gravity"; + channel_spec = gravity_channels; + channel_size = sizeof(gravity_channels); + } + ret = hid_sensor_parse_common_attributes(hsdev, hsdev->usage, &accel_state->common_attributes); if (ret) { dev_err(&pdev->dev, "failed to setup common attributes\n"); return ret; } + indio_dev->channels = kmemdup(channel_spec, channel_size, GFP_KERNEL); - indio_dev->channels = kmemdup(accel_3d_channels, - sizeof(accel_3d_channels), GFP_KERNEL); if (!indio_dev->channels) { dev_err(&pdev->dev, "failed to duplicate channels\n"); return -ENOMEM; } - ret = accel_3d_parse_report(pdev, hsdev, - (struct iio_chan_spec *)indio_dev->channels, - HID_USAGE_SENSOR_ACCEL_3D, accel_state); + (struct iio_chan_spec *)indio_dev->channels, + hsdev->usage, accel_state); if (ret) { dev_err(&pdev->dev, "failed to setup attributes\n"); goto error_free_dev_mem; @@ -363,7 +424,7 @@ static int hid_accel_3d_probe(struct platform_device *pdev) accel_state->callbacks.send_event = accel_3d_proc_event; accel_state->callbacks.capture_sample = accel_3d_capture_sample; accel_state->callbacks.pdev = pdev; - ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_ACCEL_3D, + ret = sensor_hub_register_callback(hsdev, hsdev->usage, &accel_state->callbacks); if (ret < 0) { dev_err(&pdev->dev, "callback reg failed\n"); @@ -390,7 +451,7 @@ static int hid_accel_3d_remove(struct platform_device *pdev) struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct accel_3d_state *accel_state = iio_priv(indio_dev); - sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ACCEL_3D); + sensor_hub_remove_callback(hsdev, hsdev->usage); iio_device_unregister(indio_dev); hid_sensor_remove_trigger(&accel_state->common_attributes); iio_triggered_buffer_cleanup(indio_dev); @@ -404,6 +465,9 @@ static const struct platform_device_id hid_accel_3d_ids[] = { /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ .name = "HID-SENSOR-200073", }, + { /* gravity sensor */ + .name = "HID-SENSOR-20007b", + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, hid_accel_3d_ids); diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index f418c588af6a..eb6e3dc789b2 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -248,7 +248,7 @@ static int mma8452_get_int_plus_micros_index(const int (*vals)[2], int n, return -EINVAL; } -static int mma8452_get_odr_index(struct mma8452_data *data) +static unsigned int mma8452_get_odr_index(struct mma8452_data *data) { return (data->ctrl_reg1 & MMA8452_CTRL_DR_MASK) >> MMA8452_CTRL_DR_SHIFT; @@ -260,7 +260,7 @@ static const int mma8452_samp_freq[8][2] = { }; /* Datasheet table: step time "Relationship with the ODR" (sample frequency) */ -static const int mma8452_transient_time_step_us[4][8] = { +static const unsigned int mma8452_transient_time_step_us[4][8] = { { 1250, 2500, 5000, 10000, 20000, 20000, 20000, 20000 }, /* normal */ { 1250, 2500, 5000, 10000, 20000, 80000, 80000, 80000 }, /* l p l n */ { 1250, 2500, 2500, 2500, 2500, 2500, 2500, 2500 }, /* high res*/ diff --git a/drivers/iio/accel/ssp_accel_sensor.c b/drivers/iio/accel/ssp_accel_sensor.c index 31db00970fa0..dd6ece8f9239 100644 --- a/drivers/iio/accel/ssp_accel_sensor.c +++ b/drivers/iio/accel/ssp_accel_sensor.c @@ -15,6 +15,7 @@ #include <linux/iio/common/ssp_sensors.h> #include <linux/iio/iio.h> +#include <linux/iio/buffer.h> #include <linux/iio/kfifo_buf.h> #include <linux/module.h> #include <linux/platform_device.h> @@ -135,7 +136,7 @@ static int ssp_accel_probe(struct platform_device *pdev) platform_set_drvdata(pdev, indio_dev); - ret = iio_device_register(indio_dev); + ret = devm_iio_device_register(&pdev->dev, indio_dev); if (ret < 0) return ret; @@ -145,21 +146,11 @@ static int ssp_accel_probe(struct platform_device *pdev) return 0; } -static int ssp_accel_remove(struct platform_device *pdev) -{ - struct iio_dev *indio_dev = platform_get_drvdata(pdev); - - iio_device_unregister(indio_dev); - - return 0; -} - static struct platform_driver ssp_accel_driver = { .driver = { .name = SSP_ACCEL_NAME, }, .probe = ssp_accel_probe, - .remove = ssp_accel_remove, }; module_platform_driver(ssp_accel_driver); diff --git a/drivers/iio/accel/st_accel.h b/drivers/iio/accel/st_accel.h index 7c231687109a..3ad44ce7ae82 100644 --- a/drivers/iio/accel/st_accel.h +++ b/drivers/iio/accel/st_accel.h @@ -14,6 +14,24 @@ #include <linux/types.h> #include <linux/iio/common/st_sensors.h> +enum st_accel_type { + LSM303DLH, + LSM303DLHC, + LIS3DH, + LSM330D, + LSM330DL, + LSM330DLC, + LIS331DLH, + LSM303DL, + LSM303DLM, + LSM330, + LSM303AGR, + LIS2DH12, + LIS3L02DQ, + LNG2DM, + ST_ACCEL_MAX, +}; + #define H3LIS331DL_DRIVER_NAME "h3lis331dl_accel" #define LIS3LV02DL_ACCEL_DEV_NAME "lis3lv02dl_accel" #define LSM303DLHC_ACCEL_DEV_NAME "lsm303dlhc_accel" diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c index c0f8867aa1ea..543f0ad7fd7e 100644 --- a/drivers/iio/accel/st_accel_i2c.c +++ b/drivers/iio/accel/st_accel_i2c.c @@ -11,6 +11,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/acpi.h> #include <linux/i2c.h> #include <linux/iio/iio.h> @@ -21,6 +22,11 @@ #ifdef CONFIG_OF static const struct of_device_id st_accel_of_match[] = { { + /* An older compatible */ + .compatible = "st,lis3lv02d", + .data = LIS3LV02DL_ACCEL_DEV_NAME, + }, + { .compatible = "st,lis3lv02dl-accel", .data = LIS3LV02DL_ACCEL_DEV_NAME, }, @@ -95,25 +101,67 @@ MODULE_DEVICE_TABLE(of, st_accel_of_match); #define st_accel_of_match NULL #endif +#ifdef CONFIG_ACPI +static const struct acpi_device_id st_accel_acpi_match[] = { + {"SMO8A90", LNG2DM}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, st_accel_acpi_match); +#else +#define st_accel_acpi_match NULL +#endif + +static const struct i2c_device_id st_accel_id_table[] = { + { LSM303DLH_ACCEL_DEV_NAME, LSM303DLH }, + { LSM303DLHC_ACCEL_DEV_NAME, LSM303DLHC }, + { LIS3DH_ACCEL_DEV_NAME, LIS3DH }, + { LSM330D_ACCEL_DEV_NAME, LSM330D }, + { LSM330DL_ACCEL_DEV_NAME, LSM330DL }, + { LSM330DLC_ACCEL_DEV_NAME, LSM330DLC }, + { LIS331DLH_ACCEL_DEV_NAME, LIS331DLH }, + { LSM303DL_ACCEL_DEV_NAME, LSM303DL }, + { LSM303DLM_ACCEL_DEV_NAME, LSM303DLM }, + { LSM330_ACCEL_DEV_NAME, LSM330 }, + { LSM303AGR_ACCEL_DEV_NAME, LSM303AGR }, + { LIS2DH12_ACCEL_DEV_NAME, LIS2DH12 }, + { LIS3L02DQ_ACCEL_DEV_NAME, LIS3L02DQ }, + { LNG2DM_ACCEL_DEV_NAME, LNG2DM }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, st_accel_id_table); + static int st_accel_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct iio_dev *indio_dev; struct st_sensor_data *adata; - int err; + int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adata)); if (!indio_dev) return -ENOMEM; adata = iio_priv(indio_dev); - st_sensors_of_i2c_probe(client, st_accel_of_match); + + if (client->dev.of_node) { + st_sensors_of_i2c_probe(client, st_accel_of_match); + } else if (ACPI_HANDLE(&client->dev)) { + ret = st_sensors_match_acpi_device(&client->dev); + if ((ret < 0) || (ret >= ST_ACCEL_MAX)) + return -ENODEV; + + strncpy(client->name, st_accel_id_table[ret].name, + sizeof(client->name)); + client->name[sizeof(client->name) - 1] = '\0'; + } else if (!id) + return -ENODEV; + st_sensors_i2c_configure(indio_dev, client, adata); - err = st_accel_common_probe(indio_dev); - if (err < 0) - return err; + ret = st_accel_common_probe(indio_dev); + if (ret < 0) + return ret; return 0; } @@ -125,29 +173,11 @@ static int st_accel_i2c_remove(struct i2c_client *client) return 0; } -static const struct i2c_device_id st_accel_id_table[] = { - { LSM303DLH_ACCEL_DEV_NAME }, - { LSM303DLHC_ACCEL_DEV_NAME }, - { LIS3DH_ACCEL_DEV_NAME }, - { LSM330D_ACCEL_DEV_NAME }, - { LSM330DL_ACCEL_DEV_NAME }, - { LSM330DLC_ACCEL_DEV_NAME }, - { LIS331DLH_ACCEL_DEV_NAME }, - { LSM303DL_ACCEL_DEV_NAME }, - { LSM303DLM_ACCEL_DEV_NAME }, - { LSM330_ACCEL_DEV_NAME }, - { LSM303AGR_ACCEL_DEV_NAME }, - { LIS2DH12_ACCEL_DEV_NAME }, - { LIS3L02DQ_ACCEL_DEV_NAME }, - { LNG2DM_ACCEL_DEV_NAME }, - {}, -}; -MODULE_DEVICE_TABLE(i2c, st_accel_id_table); - static struct i2c_driver st_accel_driver = { .driver = { .name = "st-accel-i2c", .of_match_table = of_match_ptr(st_accel_of_match), + .acpi_match_table = ACPI_PTR(st_accel_acpi_match), }, .probe = st_accel_i2c_probe, .remove = st_accel_i2c_remove, diff --git a/drivers/iio/accel/st_accel_spi.c b/drivers/iio/accel/st_accel_spi.c index c25ac50d4600..29a15f27a51b 100644 --- a/drivers/iio/accel/st_accel_spi.c +++ b/drivers/iio/accel/st_accel_spi.c @@ -65,9 +65,18 @@ static const struct spi_device_id st_accel_id_table[] = { }; MODULE_DEVICE_TABLE(spi, st_accel_id_table); +#ifdef CONFIG_OF +static const struct of_device_id lis302dl_spi_dt_ids[] = { + { .compatible = "st,lis302dl-spi" }, + {} +}; +MODULE_DEVICE_TABLE(of, lis302dl_spi_dt_ids); +#endif + static struct spi_driver st_accel_driver = { .driver = { .name = "st-accel-spi", + .of_match_table = of_match_ptr(lis302dl_spi_dt_ids), }, .probe = st_accel_spi_probe, .remove = st_accel_spi_remove, diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 9c8b558ba19e..dedae7adbce9 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -247,6 +247,25 @@ config HI8435 This driver can also be built as a module. If so, the module will be called hi8435. +config HX711 + tristate "AVIA HX711 ADC for weight cells" + depends on GPIOLIB + help + If you say yes here you get support for AVIA HX711 ADC which is used + for weigh cells + + This driver uses two GPIOs, one acts as the clock and controls the + channel selection and gain, the other one is used for the measurement + data + + Currently the raw value is read from the chip and delivered. + To get an actual weight one needs to subtract the + zero offset and multiply by a scale factor. + This should be done in userspace. + + This driver can also be built as a module. If so, the module will be + called hx711. + config INA2XX_ADC tristate "Texas Instruments INA2xx Power Monitors IIO driver" depends on I2C && !SENSORS_INA2XX @@ -307,6 +326,15 @@ config MAX1027 To compile this driver as a module, choose M here: the module will be called max1027. +config MAX11100 + tristate "Maxim max11100 ADC driver" + depends on SPI_MASTER + help + Say yes here to build support for Maxim max11100 SPI ADC + + To compile this driver as a module, choose M here: the module will be + called max11100. + config MAX1363 tristate "Maxim max1363 ADC driver" depends on I2C @@ -371,6 +399,18 @@ config MEN_Z188_ADC This driver can also be built as a module. If so, the module will be called men_z188_adc. +config MESON_SARADC + tristate "Amlogic Meson SAR ADC driver" + default ARCH_MESON + depends on OF && COMMON_CLK && (ARCH_MESON || COMPILE_TEST) + select REGMAP_MMIO + help + Say yes here to build support for the SAR ADC found in Amlogic Meson + SoCs. + + To compile this driver as a module, choose M here: the + module will be called meson_saradc. + config MXS_LRADC tristate "Freescale i.MX23/i.MX28 LRADC" depends on (ARCH_MXS || COMPILE_TEST) && HAS_IOMEM @@ -430,6 +470,19 @@ config QCOM_SPMI_VADC To compile this driver as a module, choose M here: the module will be called qcom-spmi-vadc. +config RCAR_GYRO_ADC + tristate "Renesas R-Car GyroADC driver" + depends on ARCH_RCAR_GEN2 || (ARM && COMPILE_TEST) + help + Say yes here to build support for the GyroADC found in Renesas + R-Car Gen2 SoCs. This block is a simple SPI offload engine for + reading data out of attached compatible ADCs in a round-robin + fashion. Up to 4 or 8 ADC channels are supported by this block, + depending on which ADCs are attached. + + To compile this driver as a module, choose M here: the + module will be called rcar-gyroadc. + config ROCKCHIP_SARADC tristate "Rockchip SARADC driver" depends on ARCH_ROCKCHIP || (ARM && COMPILE_TEST) @@ -444,8 +497,13 @@ config ROCKCHIP_SARADC config STM32_ADC_CORE tristate "STMicroelectronics STM32 adc core" depends on ARCH_STM32 || COMPILE_TEST + depends on HAS_DMA depends on OF depends on REGULATOR + select IIO_BUFFER + select MFD_STM32_TIMERS + select IIO_STM32_TIMER_TRIGGER + select IIO_TRIGGERED_BUFFER help Select this option to enable the core driver for STMicroelectronics STM32 analog-to-digital converter (ADC). @@ -549,6 +607,19 @@ config TI_ADS1015 This driver can also be built as a module. If so, the module will be called ti-ads1015. +config TI_ADS7950 + tristate "Texas Instruments ADS7950 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Texas Instruments ADS7950, ADS7951, + ADS7952, ADS7953, ADS7954, ADS7955, ADS7956, ADS7957, ADS7958, ADS7959. + ADS7960, ADS7961. + + To compile this driver as a module, choose M here: the + module will be called ti-ads7950. + config TI_ADS8688 tristate "Texas Instruments ADS8688" depends on SPI && OF @@ -571,6 +642,18 @@ config TI_AM335X_ADC To compile this driver as a module, choose M here: the module will be called ti_am335x_adc. +config TI_TLC4541 + tristate "Texas Instruments TLC4541 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Texas Instruments TLC4541 / TLC3541 + ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-tlc4541. + config TWL4030_MADC tristate "TWL4030 MADC (Monitoring A/D Converter)" depends on TWL4030_CORE diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index d36c4be8d1fc..d0012620cd1c 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -25,22 +25,26 @@ obj-$(CONFIG_ENVELOPE_DETECTOR) += envelope-detector.o obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o obj-$(CONFIG_HI8435) += hi8435.o +obj-$(CONFIG_HX711) += hx711.o obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o obj-$(CONFIG_LTC2485) += ltc2485.o obj-$(CONFIG_MAX1027) += max1027.o +obj-$(CONFIG_MAX11100) += max11100.o obj-$(CONFIG_MAX1363) += max1363.o obj-$(CONFIG_MCP320X) += mcp320x.o obj-$(CONFIG_MCP3422) += mcp3422.o obj-$(CONFIG_MEDIATEK_MT6577_AUXADC) += mt6577_auxadc.o obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o +obj-$(CONFIG_MESON_SARADC) += meson_saradc.o obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o +obj-$(CONFIG_RCAR_GYRO_ADC) += rcar-gyroadc.o obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_STX104) += stx104.o obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o @@ -51,8 +55,10 @@ obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o +obj-$(CONFIG_TI_ADS7950) += ti-ads7950.o obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o +obj-$(CONFIG_TI_TLC4541) += ti-tlc4541.o obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o obj-$(CONFIG_VF610_ADC) += vf610_adc.o diff --git a/drivers/iio/adc/axp288_adc.c b/drivers/iio/adc/axp288_adc.c index 7fd24949c0c1..64799ad7ebad 100644 --- a/drivers/iio/adc/axp288_adc.c +++ b/drivers/iio/adc/axp288_adc.c @@ -28,8 +28,6 @@ #include <linux/iio/driver.h> #define AXP288_ADC_EN_MASK 0xF1 -#define AXP288_ADC_TS_PIN_GPADC 0xF2 -#define AXP288_ADC_TS_PIN_ON 0xF3 enum axp288_adc_id { AXP288_ADC_TS, @@ -123,16 +121,6 @@ static int axp288_adc_read_channel(int *val, unsigned long address, return IIO_VAL_INT; } -static int axp288_adc_set_ts(struct regmap *regmap, unsigned int mode, - unsigned long address) -{ - /* channels other than GPADC do not need to switch TS pin */ - if (address != AXP288_GP_ADC_H) - return 0; - - return regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, mode); -} - static int axp288_adc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -143,16 +131,7 @@ static int axp288_adc_read_raw(struct iio_dev *indio_dev, mutex_lock(&indio_dev->mlock); switch (mask) { case IIO_CHAN_INFO_RAW: - if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_GPADC, - chan->address)) { - dev_err(&indio_dev->dev, "GPADC mode\n"); - ret = -EINVAL; - break; - } ret = axp288_adc_read_channel(val, chan->address, info->regmap); - if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_ON, - chan->address)) - dev_err(&indio_dev->dev, "TS pin restore\n"); break; default: ret = -EINVAL; @@ -162,15 +141,6 @@ static int axp288_adc_read_raw(struct iio_dev *indio_dev, return ret; } -static int axp288_adc_set_state(struct regmap *regmap) -{ - /* ADC should be always enabled for internal FG to function */ - if (regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, AXP288_ADC_TS_PIN_ON)) - return -EIO; - - return regmap_write(regmap, AXP20X_ADC_EN1, AXP288_ADC_EN_MASK); -} - static const struct iio_info axp288_adc_iio_info = { .read_raw = &axp288_adc_read_raw, .driver_module = THIS_MODULE, @@ -199,7 +169,7 @@ static int axp288_adc_probe(struct platform_device *pdev) * Set ADC to enabled state at all time, including system suspend. * otherwise internal fuel gauge functionality may be affected. */ - ret = axp288_adc_set_state(axp20x->regmap); + ret = regmap_write(info->regmap, AXP20X_ADC_EN1, AXP288_ADC_EN_MASK); if (ret) { dev_err(&pdev->dev, "unable to enable ADC device\n"); return ret; diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index c15756d7bf7f..ad1775b5f83c 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -632,7 +632,7 @@ static irqreturn_t exynos_ts_isr(int irq, void *dev_id) input_report_key(info->input, BTN_TOUCH, 1); input_sync(info->input); - msleep(1); + usleep_range(1000, 1100); }; writel(0, ADC_V1_CLRINTPNDNUP(info->regs)); diff --git a/drivers/iio/adc/fsl-imx25-gcq.c b/drivers/iio/adc/fsl-imx25-gcq.c index 72b32c1ab257..ea264fa9e567 100644 --- a/drivers/iio/adc/fsl-imx25-gcq.c +++ b/drivers/iio/adc/fsl-imx25-gcq.c @@ -401,6 +401,7 @@ static const struct of_device_id mx25_gcq_ids[] = { { .compatible = "fsl,imx25-gcq", }, { /* Sentinel */ } }; +MODULE_DEVICE_TABLE(of, mx25_gcq_ids); static struct platform_driver mx25_gcq_driver = { .driver = { diff --git a/drivers/iio/adc/hx711.c b/drivers/iio/adc/hx711.c new file mode 100644 index 000000000000..139639f73769 --- /dev/null +++ b/drivers/iio/adc/hx711.c @@ -0,0 +1,532 @@ +/* + * HX711: analog to digital converter for weight sensor module + * + * Copyright (c) 2016 Andreas Klinger <ak@it-klinger.de> + * + * 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 <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> + +/* gain to pulse and scale conversion */ +#define HX711_GAIN_MAX 3 + +struct hx711_gain_to_scale { + int gain; + int gain_pulse; + int scale; + int channel; +}; + +/* + * .scale depends on AVDD which in turn is known as soon as the regulator + * is available + * therefore we set .scale in hx711_probe() + * + * channel A in documentation is channel 0 in source code + * channel B in documentation is channel 1 in source code + */ +static struct hx711_gain_to_scale hx711_gain_to_scale[HX711_GAIN_MAX] = { + { 128, 1, 0, 0 }, + { 32, 2, 0, 1 }, + { 64, 3, 0, 0 } +}; + +static int hx711_get_gain_to_pulse(int gain) +{ + int i; + + for (i = 0; i < HX711_GAIN_MAX; i++) + if (hx711_gain_to_scale[i].gain == gain) + return hx711_gain_to_scale[i].gain_pulse; + return 1; +} + +static int hx711_get_gain_to_scale(int gain) +{ + int i; + + for (i = 0; i < HX711_GAIN_MAX; i++) + if (hx711_gain_to_scale[i].gain == gain) + return hx711_gain_to_scale[i].scale; + return 0; +} + +static int hx711_get_scale_to_gain(int scale) +{ + int i; + + for (i = 0; i < HX711_GAIN_MAX; i++) + if (hx711_gain_to_scale[i].scale == scale) + return hx711_gain_to_scale[i].gain; + return -EINVAL; +} + +struct hx711_data { + struct device *dev; + struct gpio_desc *gpiod_pd_sck; + struct gpio_desc *gpiod_dout; + struct regulator *reg_avdd; + int gain_set; /* gain set on device */ + int gain_chan_a; /* gain for channel A */ + struct mutex lock; +}; + +static int hx711_cycle(struct hx711_data *hx711_data) +{ + int val; + + /* + * if preempted for more then 60us while PD_SCK is high: + * hx711 is going in reset + * ==> measuring is false + */ + preempt_disable(); + gpiod_set_value(hx711_data->gpiod_pd_sck, 1); + val = gpiod_get_value(hx711_data->gpiod_dout); + /* + * here we are not waiting for 0.2 us as suggested by the datasheet, + * because the oscilloscope showed in a test scenario + * at least 1.15 us for PD_SCK high (T3 in datasheet) + * and 0.56 us for PD_SCK low on TI Sitara with 800 MHz + */ + gpiod_set_value(hx711_data->gpiod_pd_sck, 0); + preempt_enable(); + + return val; +} + +static int hx711_read(struct hx711_data *hx711_data) +{ + int i, ret; + int value = 0; + int val = gpiod_get_value(hx711_data->gpiod_dout); + + /* we double check if it's really down */ + if (val) + return -EIO; + + for (i = 0; i < 24; i++) { + value <<= 1; + ret = hx711_cycle(hx711_data); + if (ret) + value++; + } + + value ^= 0x800000; + + for (i = 0; i < hx711_get_gain_to_pulse(hx711_data->gain_set); i++) + hx711_cycle(hx711_data); + + return value; +} + +static int hx711_wait_for_ready(struct hx711_data *hx711_data) +{ + int i, val; + + /* + * a maximum reset cycle time of 56 ms was measured. + * we round it up to 100 ms + */ + for (i = 0; i < 100; i++) { + val = gpiod_get_value(hx711_data->gpiod_dout); + if (!val) + break; + /* sleep at least 1 ms */ + msleep(1); + } + if (val) + return -EIO; + + return 0; +} + +static int hx711_reset(struct hx711_data *hx711_data) +{ + int ret; + int val = gpiod_get_value(hx711_data->gpiod_dout); + + if (val) { + /* + * an examination with the oszilloscope indicated + * that the first value read after the reset is not stable + * if we reset too short; + * the shorter the reset cycle + * the less reliable the first value after reset is; + * there were no problems encountered with a value + * of 10 ms or higher + */ + gpiod_set_value(hx711_data->gpiod_pd_sck, 1); + msleep(10); + gpiod_set_value(hx711_data->gpiod_pd_sck, 0); + + ret = hx711_wait_for_ready(hx711_data); + if (ret) + return ret; + /* + * after a reset the gain is 128 so we do a dummy read + * to set the gain for the next read + */ + ret = hx711_read(hx711_data); + if (ret < 0) + return ret; + + /* + * after a dummy read we need to wait vor readiness + * for not mixing gain pulses with the clock + */ + ret = hx711_wait_for_ready(hx711_data); + if (ret) + return ret; + } + + return val; +} + +static int hx711_set_gain_for_channel(struct hx711_data *hx711_data, int chan) +{ + int ret; + + if (chan == 0) { + if (hx711_data->gain_set == 32) { + hx711_data->gain_set = hx711_data->gain_chan_a; + + ret = hx711_read(hx711_data); + if (ret < 0) + return ret; + + ret = hx711_wait_for_ready(hx711_data); + if (ret) + return ret; + } + } else { + if (hx711_data->gain_set != 32) { + hx711_data->gain_set = 32; + + ret = hx711_read(hx711_data); + if (ret < 0) + return ret; + + ret = hx711_wait_for_ready(hx711_data); + if (ret) + return ret; + } + } + + return 0; +} + +static int hx711_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct hx711_data *hx711_data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&hx711_data->lock); + + /* + * hx711_reset() must be called from here + * because it could be calling hx711_read() by itself + */ + if (hx711_reset(hx711_data)) { + mutex_unlock(&hx711_data->lock); + dev_err(hx711_data->dev, "reset failed!"); + return -EIO; + } + + ret = hx711_set_gain_for_channel(hx711_data, chan->channel); + if (ret < 0) { + mutex_unlock(&hx711_data->lock); + return ret; + } + + *val = hx711_read(hx711_data); + + mutex_unlock(&hx711_data->lock); + + if (*val < 0) + return *val; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + mutex_lock(&hx711_data->lock); + + *val2 = hx711_get_gain_to_scale(hx711_data->gain_set); + + mutex_unlock(&hx711_data->lock); + + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int hx711_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct hx711_data *hx711_data = iio_priv(indio_dev); + int ret; + int gain; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + /* + * a scale greater than 1 mV per LSB is not possible + * with the HX711, therefore val must be 0 + */ + if (val != 0) + return -EINVAL; + + mutex_lock(&hx711_data->lock); + + gain = hx711_get_scale_to_gain(val2); + if (gain < 0) { + mutex_unlock(&hx711_data->lock); + return gain; + } + + if (gain != hx711_data->gain_set) { + hx711_data->gain_set = gain; + if (gain != 32) + hx711_data->gain_chan_a = gain; + + ret = hx711_read(hx711_data); + if (ret < 0) { + mutex_unlock(&hx711_data->lock); + return ret; + } + } + + mutex_unlock(&hx711_data->lock); + return 0; + default: + return -EINVAL; + } + + return 0; +} + +static int hx711_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static ssize_t hx711_scale_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *iio_attr = to_iio_dev_attr(attr); + int channel = iio_attr->address; + int i, len = 0; + + for (i = 0; i < HX711_GAIN_MAX; i++) + if (hx711_gain_to_scale[i].channel == channel) + len += sprintf(buf + len, "0.%09d ", + hx711_gain_to_scale[i].scale); + + len += sprintf(buf + len, "\n"); + + return len; +} + +static IIO_DEVICE_ATTR(in_voltage0_scale_available, S_IRUGO, + hx711_scale_available_show, NULL, 0); + +static IIO_DEVICE_ATTR(in_voltage1_scale_available, S_IRUGO, + hx711_scale_available_show, NULL, 1); + +static struct attribute *hx711_attributes[] = { + &iio_dev_attr_in_voltage0_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage1_scale_available.dev_attr.attr, + NULL, +}; + +static struct attribute_group hx711_attribute_group = { + .attrs = hx711_attributes, +}; + +static const struct iio_info hx711_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = hx711_read_raw, + .write_raw = hx711_write_raw, + .write_raw_get_fmt = hx711_write_raw_get_fmt, + .attrs = &hx711_attribute_group, +}; + +static const struct iio_chan_spec hx711_chan_spec[] = { + { + .type = IIO_VOLTAGE, + .channel = 0, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_VOLTAGE, + .channel = 1, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int hx711_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hx711_data *hx711_data; + struct iio_dev *indio_dev; + int ret; + int i; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct hx711_data)); + if (!indio_dev) { + dev_err(dev, "failed to allocate IIO device\n"); + return -ENOMEM; + } + + hx711_data = iio_priv(indio_dev); + hx711_data->dev = dev; + + mutex_init(&hx711_data->lock); + + /* + * PD_SCK stands for power down and serial clock input of HX711 + * in the driver it is an output + */ + hx711_data->gpiod_pd_sck = devm_gpiod_get(dev, "sck", GPIOD_OUT_LOW); + if (IS_ERR(hx711_data->gpiod_pd_sck)) { + dev_err(dev, "failed to get sck-gpiod: err=%ld\n", + PTR_ERR(hx711_data->gpiod_pd_sck)); + return PTR_ERR(hx711_data->gpiod_pd_sck); + } + + /* + * DOUT stands for serial data output of HX711 + * for the driver it is an input + */ + hx711_data->gpiod_dout = devm_gpiod_get(dev, "dout", GPIOD_IN); + if (IS_ERR(hx711_data->gpiod_dout)) { + dev_err(dev, "failed to get dout-gpiod: err=%ld\n", + PTR_ERR(hx711_data->gpiod_dout)); + return PTR_ERR(hx711_data->gpiod_dout); + } + + hx711_data->reg_avdd = devm_regulator_get(dev, "avdd"); + if (IS_ERR(hx711_data->reg_avdd)) + return PTR_ERR(hx711_data->reg_avdd); + + ret = regulator_enable(hx711_data->reg_avdd); + if (ret < 0) + return ret; + + /* + * with + * full scale differential input range: AVDD / GAIN + * full scale output data: 2^24 + * we can say: + * AVDD / GAIN = 2^24 + * therefore: + * 1 LSB = AVDD / GAIN / 2^24 + * AVDD is in uV, but we need 10^-9 mV + * approximately to fit into a 32 bit number: + * 1 LSB = (AVDD * 100) / GAIN / 1678 [10^-9 mV] + */ + ret = regulator_get_voltage(hx711_data->reg_avdd); + if (ret < 0) { + regulator_disable(hx711_data->reg_avdd); + return ret; + } + /* we need 10^-9 mV */ + ret *= 100; + + for (i = 0; i < HX711_GAIN_MAX; i++) + hx711_gain_to_scale[i].scale = + ret / hx711_gain_to_scale[i].gain / 1678; + + hx711_data->gain_set = 128; + hx711_data->gain_chan_a = 128; + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = "hx711"; + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &hx711_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = hx711_chan_spec; + indio_dev->num_channels = ARRAY_SIZE(hx711_chan_spec); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "Couldn't register the device\n"); + regulator_disable(hx711_data->reg_avdd); + } + + return ret; +} + +static int hx711_remove(struct platform_device *pdev) +{ + struct hx711_data *hx711_data; + struct iio_dev *indio_dev; + + indio_dev = platform_get_drvdata(pdev); + hx711_data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + regulator_disable(hx711_data->reg_avdd); + + return 0; +} + +static const struct of_device_id of_hx711_match[] = { + { .compatible = "avia,hx711", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_hx711_match); + +static struct platform_driver hx711_driver = { + .probe = hx711_probe, + .remove = hx711_remove, + .driver = { + .name = "hx711-gpio", + .of_match_table = of_hx711_match, + }, +}; + +module_platform_driver(hx711_driver); + +MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>"); +MODULE_DESCRIPTION("HX711 bitbanging driver - ADC for weight cells"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:hx711-gpio"); + diff --git a/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c index 59b7d76e1ad2..3263231276ca 100644 --- a/drivers/iio/adc/ina2xx-adc.c +++ b/drivers/iio/adc/ina2xx-adc.c @@ -22,6 +22,8 @@ #include <linux/delay.h> #include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> #include <linux/iio/kfifo_buf.h> #include <linux/iio/sysfs.h> #include <linux/kthread.h> diff --git a/drivers/iio/adc/max11100.c b/drivers/iio/adc/max11100.c new file mode 100644 index 000000000000..a088cf99bfe1 --- /dev/null +++ b/drivers/iio/adc/max11100.c @@ -0,0 +1,181 @@ +/* + * iio/adc/max11100.c + * Maxim max11100 ADC Driver with IIO interface + * + * Copyright (C) 2016-17 Renesas Electronics Corporation + * Copyright (C) 2016-17 Jacopo Mondi + * + * 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 <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <linux/iio/iio.h> +#include <linux/iio/driver.h> + +/* + * LSB is the ADC single digital step + * 1 LSB = (vref_mv / 2 ^ 16) + * + * LSB is used to calculate analog voltage value + * from the number of ADC steps count + * + * Ain = (count * LSB) + */ +#define MAX11100_LSB_DIV (1 << 16) + +struct max11100_state { + struct regulator *vref_reg; + struct spi_device *spi; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + u8 buffer[3] ____cacheline_aligned; +}; + +static struct iio_chan_spec max11100_channels[] = { + { /* [0] */ + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int max11100_read_single(struct iio_dev *indio_dev, int *val) +{ + int ret; + struct max11100_state *state = iio_priv(indio_dev); + + ret = spi_read(state->spi, state->buffer, sizeof(state->buffer)); + if (ret) { + dev_err(&indio_dev->dev, "SPI transfer failed\n"); + return ret; + } + + /* the first 8 bits sent out from ADC must be 0s */ + if (state->buffer[0]) { + dev_err(&indio_dev->dev, "Invalid value: buffer[0] != 0\n"); + return -EINVAL; + } + + *val = (state->buffer[1] << 8) | state->buffer[2]; + + return 0; +} + +static int max11100_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + int ret, vref_uv; + struct max11100_state *state = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + ret = max11100_read_single(indio_dev, val); + if (ret) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + vref_uv = regulator_get_voltage(state->vref_reg); + if (vref_uv < 0) + /* dummy regulator "get_voltage" returns -EINVAL */ + return -EINVAL; + + *val = vref_uv / 1000; + *val2 = MAX11100_LSB_DIV; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static const struct iio_info max11100_info = { + .driver_module = THIS_MODULE, + .read_raw = max11100_read_raw, +}; + +static int max11100_probe(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev; + struct max11100_state *state; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + spi_set_drvdata(spi, indio_dev); + + state = iio_priv(indio_dev); + state->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->dev.of_node = spi->dev.of_node; + indio_dev->name = "max11100"; + indio_dev->info = &max11100_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = max11100_channels, + indio_dev->num_channels = ARRAY_SIZE(max11100_channels), + + state->vref_reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(state->vref_reg)) + return PTR_ERR(state->vref_reg); + + ret = regulator_enable(state->vref_reg); + if (ret) + return ret; + + ret = iio_device_register(indio_dev); + if (ret) + goto disable_regulator; + + return 0; + +disable_regulator: + regulator_disable(state->vref_reg); + + return ret; +} + +static int max11100_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct max11100_state *state = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(state->vref_reg); + + return 0; +} + +static const struct of_device_id max11100_ids[] = { + {.compatible = "maxim,max11100"}, + { }, +}; +MODULE_DEVICE_TABLE(of, max11100_ids); + +static struct spi_driver max11100_driver = { + .driver = { + .name = "max11100", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(max11100_ids), + }, + .probe = max11100_probe, + .remove = max11100_remove, +}; + +module_spi_driver(max11100_driver); + +MODULE_AUTHOR("Jacopo Mondi <jacopo@jmondi.org>"); +MODULE_DESCRIPTION("Maxim max11100 ADC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/max1363.c b/drivers/iio/adc/max1363.c index 841a13c9b6ea..c6c12feb4a08 100644 --- a/drivers/iio/adc/max1363.c +++ b/drivers/iio/adc/max1363.c @@ -1567,6 +1567,7 @@ static const struct of_device_id max1363_of_match[] = { MAX1363_COMPATIBLE("maxim,max11647", max11647), { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, max1363_of_match); #endif static int max1363_probe(struct i2c_client *client, diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c new file mode 100644 index 000000000000..89def6034f40 --- /dev/null +++ b/drivers/iio/adc/meson_saradc.c @@ -0,0 +1,922 @@ +/* + * Amlogic Meson Successive Approximation Register (SAR) A/D Converter + * + * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/iio/iio.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#define MESON_SAR_ADC_REG0 0x00 + #define MESON_SAR_ADC_REG0_PANEL_DETECT BIT(31) + #define MESON_SAR_ADC_REG0_BUSY_MASK GENMASK(30, 28) + #define MESON_SAR_ADC_REG0_DELTA_BUSY BIT(30) + #define MESON_SAR_ADC_REG0_AVG_BUSY BIT(29) + #define MESON_SAR_ADC_REG0_SAMPLE_BUSY BIT(28) + #define MESON_SAR_ADC_REG0_FIFO_FULL BIT(27) + #define MESON_SAR_ADC_REG0_FIFO_EMPTY BIT(26) + #define MESON_SAR_ADC_REG0_FIFO_COUNT_MASK GENMASK(25, 21) + #define MESON_SAR_ADC_REG0_ADC_BIAS_CTRL_MASK GENMASK(20, 19) + #define MESON_SAR_ADC_REG0_CURR_CHAN_ID_MASK GENMASK(18, 16) + #define MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL BIT(15) + #define MESON_SAR_ADC_REG0_SAMPLING_STOP BIT(14) + #define MESON_SAR_ADC_REG0_CHAN_DELTA_EN_MASK GENMASK(13, 12) + #define MESON_SAR_ADC_REG0_DETECT_IRQ_POL BIT(10) + #define MESON_SAR_ADC_REG0_DETECT_IRQ_EN BIT(9) + #define MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK GENMASK(8, 4) + #define MESON_SAR_ADC_REG0_FIFO_IRQ_EN BIT(3) + #define MESON_SAR_ADC_REG0_SAMPLING_START BIT(2) + #define MESON_SAR_ADC_REG0_CONTINUOUS_EN BIT(1) + #define MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE BIT(0) + +#define MESON_SAR_ADC_CHAN_LIST 0x04 + #define MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK GENMASK(26, 24) + #define MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(_chan) \ + (GENMASK(2, 0) << ((_chan) * 3)) + +#define MESON_SAR_ADC_AVG_CNTL 0x08 + #define MESON_SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(_chan) \ + (16 + ((_chan) * 2)) + #define MESON_SAR_ADC_AVG_CNTL_AVG_MODE_MASK(_chan) \ + (GENMASK(17, 16) << ((_chan) * 2)) + #define MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(_chan) \ + (0 + ((_chan) * 2)) + #define MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(_chan) \ + (GENMASK(1, 0) << ((_chan) * 2)) + +#define MESON_SAR_ADC_REG3 0x0c + #define MESON_SAR_ADC_REG3_CNTL_USE_SC_DLY BIT(31) + #define MESON_SAR_ADC_REG3_CLK_EN BIT(30) + #define MESON_SAR_ADC_REG3_BL30_INITIALIZED BIT(28) + #define MESON_SAR_ADC_REG3_CTRL_CONT_RING_COUNTER_EN BIT(27) + #define MESON_SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE BIT(26) + #define MESON_SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK GENMASK(25, 23) + #define MESON_SAR_ADC_REG3_DETECT_EN BIT(22) + #define MESON_SAR_ADC_REG3_ADC_EN BIT(21) + #define MESON_SAR_ADC_REG3_PANEL_DETECT_COUNT_MASK GENMASK(20, 18) + #define MESON_SAR_ADC_REG3_PANEL_DETECT_FILTER_TB_MASK GENMASK(17, 16) + #define MESON_SAR_ADC_REG3_ADC_CLK_DIV_SHIFT 10 + #define MESON_SAR_ADC_REG3_ADC_CLK_DIV_WIDTH 5 + #define MESON_SAR_ADC_REG3_BLOCK_DLY_SEL_MASK GENMASK(9, 8) + #define MESON_SAR_ADC_REG3_BLOCK_DLY_MASK GENMASK(7, 0) + +#define MESON_SAR_ADC_DELAY 0x10 + #define MESON_SAR_ADC_DELAY_INPUT_DLY_SEL_MASK GENMASK(25, 24) + #define MESON_SAR_ADC_DELAY_BL30_BUSY BIT(15) + #define MESON_SAR_ADC_DELAY_KERNEL_BUSY BIT(14) + #define MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK GENMASK(23, 16) + #define MESON_SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK GENMASK(9, 8) + #define MESON_SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK GENMASK(7, 0) + +#define MESON_SAR_ADC_LAST_RD 0x14 + #define MESON_SAR_ADC_LAST_RD_LAST_CHANNEL1_MASK GENMASK(23, 16) + #define MESON_SAR_ADC_LAST_RD_LAST_CHANNEL0_MASK GENMASK(9, 0) + +#define MESON_SAR_ADC_FIFO_RD 0x18 + #define MESON_SAR_ADC_FIFO_RD_CHAN_ID_MASK GENMASK(14, 12) + #define MESON_SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK GENMASK(11, 0) + +#define MESON_SAR_ADC_AUX_SW 0x1c + #define MESON_SAR_ADC_AUX_SW_MUX_SEL_CHAN_MASK(_chan) \ + (GENMASK(10, 8) << (((_chan) - 2) * 2)) + #define MESON_SAR_ADC_AUX_SW_VREF_P_MUX BIT(6) + #define MESON_SAR_ADC_AUX_SW_VREF_N_MUX BIT(5) + #define MESON_SAR_ADC_AUX_SW_MODE_SEL BIT(4) + #define MESON_SAR_ADC_AUX_SW_YP_DRIVE_SW BIT(3) + #define MESON_SAR_ADC_AUX_SW_XP_DRIVE_SW BIT(2) + #define MESON_SAR_ADC_AUX_SW_YM_DRIVE_SW BIT(1) + #define MESON_SAR_ADC_AUX_SW_XM_DRIVE_SW BIT(0) + +#define MESON_SAR_ADC_CHAN_10_SW 0x20 + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_MUX_SEL_MASK GENMASK(25, 23) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_VREF_P_MUX BIT(22) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_VREF_N_MUX BIT(21) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_MODE_SEL BIT(20) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_YP_DRIVE_SW BIT(19) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_XP_DRIVE_SW BIT(18) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_YM_DRIVE_SW BIT(17) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN1_XM_DRIVE_SW BIT(16) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_MUX_SEL_MASK GENMASK(9, 7) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_VREF_P_MUX BIT(6) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_VREF_N_MUX BIT(5) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_MODE_SEL BIT(4) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_YP_DRIVE_SW BIT(3) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_XP_DRIVE_SW BIT(2) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_YM_DRIVE_SW BIT(1) + #define MESON_SAR_ADC_CHAN_10_SW_CHAN0_XM_DRIVE_SW BIT(0) + +#define MESON_SAR_ADC_DETECT_IDLE_SW 0x24 + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_SW_EN BIT(26) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MUX_MASK GENMASK(25, 23) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_VREF_P_MUX BIT(22) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_VREF_N_MUX BIT(21) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_SEL BIT(20) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_YP_DRIVE_SW BIT(19) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_XP_DRIVE_SW BIT(18) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_YM_DRIVE_SW BIT(17) + #define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_XM_DRIVE_SW BIT(16) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK GENMASK(9, 7) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_VREF_P_MUX BIT(6) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_VREF_N_MUX BIT(5) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_SEL BIT(4) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_YP_DRIVE_SW BIT(3) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_XP_DRIVE_SW BIT(2) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_YM_DRIVE_SW BIT(1) + #define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_XM_DRIVE_SW BIT(0) + +#define MESON_SAR_ADC_DELTA_10 0x28 + #define MESON_SAR_ADC_DELTA_10_TEMP_SEL BIT(27) + #define MESON_SAR_ADC_DELTA_10_TS_REVE1 BIT(26) + #define MESON_SAR_ADC_DELTA_10_CHAN1_DELTA_VALUE_MASK GENMASK(25, 16) + #define MESON_SAR_ADC_DELTA_10_TS_REVE0 BIT(15) + #define MESON_SAR_ADC_DELTA_10_TS_C_SHIFT 11 + #define MESON_SAR_ADC_DELTA_10_TS_C_MASK GENMASK(14, 11) + #define MESON_SAR_ADC_DELTA_10_TS_VBG_EN BIT(10) + #define MESON_SAR_ADC_DELTA_10_CHAN0_DELTA_VALUE_MASK GENMASK(9, 0) + +/* + * NOTE: registers from here are undocumented (the vendor Linux kernel driver + * and u-boot source served as reference). These only seem to be relevant on + * GXBB and newer. + */ +#define MESON_SAR_ADC_REG11 0x2c + #define MESON_SAR_ADC_REG11_BANDGAP_EN BIT(13) + +#define MESON_SAR_ADC_REG13 0x34 + #define MESON_SAR_ADC_REG13_12BIT_CALIBRATION_MASK GENMASK(13, 8) + +#define MESON_SAR_ADC_MAX_FIFO_SIZE 32 + +#define MESON_SAR_ADC_CHAN(_chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_AVERAGE_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "SAR_ADC_CH"#_chan, \ +} + +/* + * TODO: the hardware supports IIO_TEMP for channel 6 as well which is + * currently not supported by this driver. + */ +static const struct iio_chan_spec meson_sar_adc_iio_channels[] = { + MESON_SAR_ADC_CHAN(0), + MESON_SAR_ADC_CHAN(1), + MESON_SAR_ADC_CHAN(2), + MESON_SAR_ADC_CHAN(3), + MESON_SAR_ADC_CHAN(4), + MESON_SAR_ADC_CHAN(5), + MESON_SAR_ADC_CHAN(6), + MESON_SAR_ADC_CHAN(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +enum meson_sar_adc_avg_mode { + NO_AVERAGING = 0x0, + MEAN_AVERAGING = 0x1, + MEDIAN_AVERAGING = 0x2, +}; + +enum meson_sar_adc_num_samples { + ONE_SAMPLE = 0x0, + TWO_SAMPLES = 0x1, + FOUR_SAMPLES = 0x2, + EIGHT_SAMPLES = 0x3, +}; + +enum meson_sar_adc_chan7_mux_sel { + CHAN7_MUX_VSS = 0x0, + CHAN7_MUX_VDD_DIV4 = 0x1, + CHAN7_MUX_VDD_DIV2 = 0x2, + CHAN7_MUX_VDD_MUL3_DIV4 = 0x3, + CHAN7_MUX_VDD = 0x4, + CHAN7_MUX_CH7_INPUT = 0x7, +}; + +struct meson_sar_adc_data { + unsigned int resolution; + const char *name; +}; + +struct meson_sar_adc_priv { + struct regmap *regmap; + struct regulator *vref; + const struct meson_sar_adc_data *data; + struct clk *clkin; + struct clk *core_clk; + struct clk *sana_clk; + struct clk *adc_sel_clk; + struct clk *adc_clk; + struct clk_gate clk_gate; + struct clk *adc_div_clk; + struct clk_divider clk_div; +}; + +static const struct regmap_config meson_sar_adc_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = MESON_SAR_ADC_REG13, +}; + +static unsigned int meson_sar_adc_get_fifo_count(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + u32 regval; + + regmap_read(priv->regmap, MESON_SAR_ADC_REG0, ®val); + + return FIELD_GET(MESON_SAR_ADC_REG0_FIFO_COUNT_MASK, regval); +} + +static int meson_sar_adc_wait_busy_clear(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int regval, timeout = 10000; + + /* + * NOTE: we need a small delay before reading the status, otherwise + * the sample engine may not have started internally (which would + * seem to us that sampling is already finished). + */ + do { + udelay(1); + regmap_read(priv->regmap, MESON_SAR_ADC_REG0, ®val); + } while (FIELD_GET(MESON_SAR_ADC_REG0_BUSY_MASK, regval) && timeout--); + + if (timeout < 0) + return -ETIMEDOUT; + + return 0; +} + +static int meson_sar_adc_read_raw_sample(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int ret, regval, fifo_chan, fifo_val, sum = 0, count = 0; + + ret = meson_sar_adc_wait_busy_clear(indio_dev); + if (ret) + return ret; + + while (meson_sar_adc_get_fifo_count(indio_dev) > 0 && + count < MESON_SAR_ADC_MAX_FIFO_SIZE) { + regmap_read(priv->regmap, MESON_SAR_ADC_FIFO_RD, ®val); + + fifo_chan = FIELD_GET(MESON_SAR_ADC_FIFO_RD_CHAN_ID_MASK, + regval); + if (fifo_chan != chan->channel) + continue; + + fifo_val = FIELD_GET(MESON_SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK, + regval); + fifo_val &= (BIT(priv->data->resolution) - 1); + + sum += fifo_val; + count++; + } + + if (!count) + return -ENOENT; + + *val = sum / count; + + return 0; +} + +static void meson_sar_adc_set_averaging(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum meson_sar_adc_avg_mode mode, + enum meson_sar_adc_num_samples samples) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int val, channel = chan->channel; + + val = samples << MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(channel); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_AVG_CNTL, + MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(channel), + val); + + val = mode << MESON_SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(channel); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_AVG_CNTL, + MESON_SAR_ADC_AVG_CNTL_AVG_MODE_MASK(channel), val); +} + +static void meson_sar_adc_enable_channel(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + u32 regval; + + /* + * the SAR ADC engine allows sampling multiple channels at the same + * time. to keep it simple we're only working with one *internal* + * channel, which starts counting at index 0 (which means: count = 1). + */ + regval = FIELD_PREP(MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, 0); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_LIST, + MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, regval); + + /* map channel index 0 to the channel which we want to read */ + regval = FIELD_PREP(MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(0), + chan->channel); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_LIST, + MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(0), regval); + + regval = FIELD_PREP(MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MUX_MASK, + chan->channel); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DETECT_IDLE_SW, + MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MUX_MASK, + regval); + + regval = FIELD_PREP(MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK, + chan->channel); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DETECT_IDLE_SW, + MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK, + regval); + + if (chan->channel == 6) + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10, + MESON_SAR_ADC_DELTA_10_TEMP_SEL, 0); +} + +static void meson_sar_adc_set_chan7_mux(struct iio_dev *indio_dev, + enum meson_sar_adc_chan7_mux_sel sel) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + u32 regval; + + regval = FIELD_PREP(MESON_SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, sel); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, regval); + + usleep_range(10, 20); +} + +static void meson_sar_adc_start_sample_engine(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE, + MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE); + + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_SAMPLING_START, + MESON_SAR_ADC_REG0_SAMPLING_START); +} + +static void meson_sar_adc_stop_sample_engine(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_SAMPLING_STOP, + MESON_SAR_ADC_REG0_SAMPLING_STOP); + + /* wait until all modules are stopped */ + meson_sar_adc_wait_busy_clear(indio_dev); + + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE, 0); +} + +static int meson_sar_adc_lock(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int val, timeout = 10000; + + mutex_lock(&indio_dev->mlock); + + /* prevent BL30 from using the SAR ADC while we are using it */ + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_KERNEL_BUSY, + MESON_SAR_ADC_DELAY_KERNEL_BUSY); + + /* wait until BL30 releases it's lock (so we can use the SAR ADC) */ + do { + udelay(1); + regmap_read(priv->regmap, MESON_SAR_ADC_DELAY, &val); + } while (val & MESON_SAR_ADC_DELAY_BL30_BUSY && timeout--); + + if (timeout < 0) + return -ETIMEDOUT; + + return 0; +} + +static void meson_sar_adc_unlock(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + + /* allow BL30 to use the SAR ADC again */ + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_KERNEL_BUSY, 0); + + mutex_unlock(&indio_dev->mlock); +} + +static void meson_sar_adc_clear_fifo(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int count; + + for (count = 0; count < MESON_SAR_ADC_MAX_FIFO_SIZE; count++) { + if (!meson_sar_adc_get_fifo_count(indio_dev)) + break; + + regmap_read(priv->regmap, MESON_SAR_ADC_FIFO_RD, 0); + } +} + +static int meson_sar_adc_get_sample(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum meson_sar_adc_avg_mode avg_mode, + enum meson_sar_adc_num_samples avg_samples, + int *val) +{ + int ret; + + ret = meson_sar_adc_lock(indio_dev); + if (ret) + return ret; + + /* clear the FIFO to make sure we're not reading old values */ + meson_sar_adc_clear_fifo(indio_dev); + + meson_sar_adc_set_averaging(indio_dev, chan, avg_mode, avg_samples); + + meson_sar_adc_enable_channel(indio_dev, chan); + + meson_sar_adc_start_sample_engine(indio_dev); + ret = meson_sar_adc_read_raw_sample(indio_dev, chan, val); + meson_sar_adc_stop_sample_engine(indio_dev); + + meson_sar_adc_unlock(indio_dev); + + if (ret) { + dev_warn(indio_dev->dev.parent, + "failed to read sample for channel %d: %d\n", + chan->channel, ret); + return ret; + } + + return IIO_VAL_INT; +} + +static int meson_sar_adc_iio_info_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return meson_sar_adc_get_sample(indio_dev, chan, NO_AVERAGING, + ONE_SAMPLE, val); + break; + + case IIO_CHAN_INFO_AVERAGE_RAW: + return meson_sar_adc_get_sample(indio_dev, chan, + MEAN_AVERAGING, EIGHT_SAMPLES, + val); + break; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(priv->vref); + if (ret < 0) { + dev_err(indio_dev->dev.parent, + "failed to get vref voltage: %d\n", ret); + return ret; + } + + *val = ret / 1000; + *val2 = priv->data->resolution; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static int meson_sar_adc_clk_init(struct iio_dev *indio_dev, + void __iomem *base) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + struct clk_init_data init; + const char *clk_parents[1]; + + init.name = devm_kasprintf(&indio_dev->dev, GFP_KERNEL, "%s#adc_div", + of_node_full_name(indio_dev->dev.of_node)); + init.flags = 0; + init.ops = &clk_divider_ops; + clk_parents[0] = __clk_get_name(priv->clkin); + init.parent_names = clk_parents; + init.num_parents = 1; + + priv->clk_div.reg = base + MESON_SAR_ADC_REG3; + priv->clk_div.shift = MESON_SAR_ADC_REG3_ADC_CLK_DIV_SHIFT; + priv->clk_div.width = MESON_SAR_ADC_REG3_ADC_CLK_DIV_WIDTH; + priv->clk_div.hw.init = &init; + priv->clk_div.flags = 0; + + priv->adc_div_clk = devm_clk_register(&indio_dev->dev, + &priv->clk_div.hw); + if (WARN_ON(IS_ERR(priv->adc_div_clk))) + return PTR_ERR(priv->adc_div_clk); + + init.name = devm_kasprintf(&indio_dev->dev, GFP_KERNEL, "%s#adc_en", + of_node_full_name(indio_dev->dev.of_node)); + init.flags = CLK_SET_RATE_PARENT; + init.ops = &clk_gate_ops; + clk_parents[0] = __clk_get_name(priv->adc_div_clk); + init.parent_names = clk_parents; + init.num_parents = 1; + + priv->clk_gate.reg = base + MESON_SAR_ADC_REG3; + priv->clk_gate.bit_idx = fls(MESON_SAR_ADC_REG3_CLK_EN); + priv->clk_gate.hw.init = &init; + + priv->adc_clk = devm_clk_register(&indio_dev->dev, &priv->clk_gate.hw); + if (WARN_ON(IS_ERR(priv->adc_clk))) + return PTR_ERR(priv->adc_clk); + + return 0; +} + +static int meson_sar_adc_init(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int regval, ret; + + /* + * make sure we start at CH7 input since the other muxes are only used + * for internal calibration. + */ + meson_sar_adc_set_chan7_mux(indio_dev, CHAN7_MUX_CH7_INPUT); + + /* + * leave sampling delay and the input clocks as configured by BL30 to + * make sure BL30 gets the values it expects when reading the + * temperature sensor. + */ + regmap_read(priv->regmap, MESON_SAR_ADC_REG3, ®val); + if (regval & MESON_SAR_ADC_REG3_BL30_INITIALIZED) + return 0; + + meson_sar_adc_stop_sample_engine(indio_dev); + + /* update the channel 6 MUX to select the temperature sensor */ + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0, + MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL, + MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL); + + /* disable all channels by default */ + regmap_write(priv->regmap, MESON_SAR_ADC_CHAN_LIST, 0x0); + + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE, 0); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_CNTL_USE_SC_DLY, + MESON_SAR_ADC_REG3_CNTL_USE_SC_DLY); + + /* delay between two samples = (10+1) * 1uS */ + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK, + FIELD_PREP(MESON_SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK, + 10)); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK, + FIELD_PREP(MESON_SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK, + 0)); + + /* delay between two samples = (10+1) * 1uS */ + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK, + FIELD_PREP(MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK, + 10)); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY, + MESON_SAR_ADC_DELAY_INPUT_DLY_SEL_MASK, + FIELD_PREP(MESON_SAR_ADC_DELAY_INPUT_DLY_SEL_MASK, + 1)); + + ret = clk_set_parent(priv->adc_sel_clk, priv->clkin); + if (ret) { + dev_err(indio_dev->dev.parent, + "failed to set adc parent to clkin\n"); + return ret; + } + + ret = clk_set_rate(priv->adc_clk, 1200000); + if (ret) { + dev_err(indio_dev->dev.parent, + "failed to set adc clock rate\n"); + return ret; + } + + return 0; +} + +static int meson_sar_adc_hw_enable(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int ret; + + ret = meson_sar_adc_lock(indio_dev); + if (ret) + goto err_lock; + + ret = regulator_enable(priv->vref); + if (ret < 0) { + dev_err(indio_dev->dev.parent, + "failed to enable vref regulator\n"); + goto err_vref; + } + + ret = clk_prepare_enable(priv->core_clk); + if (ret) { + dev_err(indio_dev->dev.parent, "failed to enable core clk\n"); + goto err_core_clk; + } + + ret = clk_prepare_enable(priv->sana_clk); + if (ret) { + dev_err(indio_dev->dev.parent, "failed to enable sana clk\n"); + goto err_sana_clk; + } + + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11, + MESON_SAR_ADC_REG11_BANDGAP_EN, + MESON_SAR_ADC_REG11_BANDGAP_EN); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_ADC_EN, + MESON_SAR_ADC_REG3_ADC_EN); + + udelay(5); + + ret = clk_prepare_enable(priv->adc_clk); + if (ret) { + dev_err(indio_dev->dev.parent, "failed to enable adc clk\n"); + goto err_adc_clk; + } + + meson_sar_adc_unlock(indio_dev); + + return 0; + +err_adc_clk: + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_ADC_EN, 0); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11, + MESON_SAR_ADC_REG11_BANDGAP_EN, 0); + clk_disable_unprepare(priv->sana_clk); +err_sana_clk: + clk_disable_unprepare(priv->core_clk); +err_core_clk: + regulator_disable(priv->vref); +err_vref: + meson_sar_adc_unlock(indio_dev); +err_lock: + return ret; +} + +static int meson_sar_adc_hw_disable(struct iio_dev *indio_dev) +{ + struct meson_sar_adc_priv *priv = iio_priv(indio_dev); + int ret; + + ret = meson_sar_adc_lock(indio_dev); + if (ret) + return ret; + + clk_disable_unprepare(priv->adc_clk); + + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3, + MESON_SAR_ADC_REG3_ADC_EN, 0); + regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11, + MESON_SAR_ADC_REG11_BANDGAP_EN, 0); + + clk_disable_unprepare(priv->sana_clk); + clk_disable_unprepare(priv->core_clk); + + regulator_disable(priv->vref); + + meson_sar_adc_unlock(indio_dev); + + return 0; +} + +static const struct iio_info meson_sar_adc_iio_info = { + .read_raw = meson_sar_adc_iio_info_read_raw, + .driver_module = THIS_MODULE, +}; + +struct meson_sar_adc_data meson_sar_adc_gxbb_data = { + .resolution = 10, + .name = "meson-gxbb-saradc", +}; + +struct meson_sar_adc_data meson_sar_adc_gxl_data = { + .resolution = 12, + .name = "meson-gxl-saradc", +}; + +struct meson_sar_adc_data meson_sar_adc_gxm_data = { + .resolution = 12, + .name = "meson-gxm-saradc", +}; + +static const struct of_device_id meson_sar_adc_of_match[] = { + { + .compatible = "amlogic,meson-gxbb-saradc", + .data = &meson_sar_adc_gxbb_data, + }, { + .compatible = "amlogic,meson-gxl-saradc", + .data = &meson_sar_adc_gxl_data, + }, { + .compatible = "amlogic,meson-gxm-saradc", + .data = &meson_sar_adc_gxm_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, meson_sar_adc_of_match); + +static int meson_sar_adc_probe(struct platform_device *pdev) +{ + struct meson_sar_adc_priv *priv; + struct iio_dev *indio_dev; + struct resource *res; + void __iomem *base; + const struct of_device_id *match; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv)); + if (!indio_dev) { + dev_err(&pdev->dev, "failed allocating iio device\n"); + return -ENOMEM; + } + + priv = iio_priv(indio_dev); + + match = of_match_device(meson_sar_adc_of_match, &pdev->dev); + priv->data = match->data; + + indio_dev->name = priv->data->name; + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &meson_sar_adc_iio_info; + + indio_dev->channels = meson_sar_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(meson_sar_adc_iio_channels); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &meson_sar_adc_regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->clkin = devm_clk_get(&pdev->dev, "clkin"); + if (IS_ERR(priv->clkin)) { + dev_err(&pdev->dev, "failed to get clkin\n"); + return PTR_ERR(priv->clkin); + } + + priv->core_clk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(priv->core_clk)) { + dev_err(&pdev->dev, "failed to get core clk\n"); + return PTR_ERR(priv->core_clk); + } + + priv->sana_clk = devm_clk_get(&pdev->dev, "sana"); + if (IS_ERR(priv->sana_clk)) { + if (PTR_ERR(priv->sana_clk) == -ENOENT) { + priv->sana_clk = NULL; + } else { + dev_err(&pdev->dev, "failed to get sana clk\n"); + return PTR_ERR(priv->sana_clk); + } + } + + priv->adc_clk = devm_clk_get(&pdev->dev, "adc_clk"); + if (IS_ERR(priv->adc_clk)) { + if (PTR_ERR(priv->adc_clk) == -ENOENT) { + priv->adc_clk = NULL; + } else { + dev_err(&pdev->dev, "failed to get adc clk\n"); + return PTR_ERR(priv->adc_clk); + } + } + + priv->adc_sel_clk = devm_clk_get(&pdev->dev, "adc_sel"); + if (IS_ERR(priv->adc_sel_clk)) { + if (PTR_ERR(priv->adc_sel_clk) == -ENOENT) { + priv->adc_sel_clk = NULL; + } else { + dev_err(&pdev->dev, "failed to get adc_sel clk\n"); + return PTR_ERR(priv->adc_sel_clk); + } + } + + /* on pre-GXBB SoCs the SAR ADC itself provides the ADC clock: */ + if (!priv->adc_clk) { + ret = meson_sar_adc_clk_init(indio_dev, base); + if (ret) + return ret; + } + + priv->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(priv->vref)) { + dev_err(&pdev->dev, "failed to get vref regulator\n"); + return PTR_ERR(priv->vref); + } + + ret = meson_sar_adc_init(indio_dev); + if (ret) + goto err; + + ret = meson_sar_adc_hw_enable(indio_dev); + if (ret) + goto err; + + platform_set_drvdata(pdev, indio_dev); + + ret = iio_device_register(indio_dev); + if (ret) + goto err_hw; + + return 0; + +err_hw: + meson_sar_adc_hw_disable(indio_dev); +err: + return ret; +} + +static int meson_sar_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + + return meson_sar_adc_hw_disable(indio_dev); +} + +static int __maybe_unused meson_sar_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return meson_sar_adc_hw_disable(indio_dev); +} + +static int __maybe_unused meson_sar_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return meson_sar_adc_hw_enable(indio_dev); +} + +static SIMPLE_DEV_PM_OPS(meson_sar_adc_pm_ops, + meson_sar_adc_suspend, meson_sar_adc_resume); + +static struct platform_driver meson_sar_adc_driver = { + .probe = meson_sar_adc_probe, + .remove = meson_sar_adc_remove, + .driver = { + .name = "meson-saradc", + .of_match_table = meson_sar_adc_of_match, + .pm = &meson_sar_adc_pm_ops, + }, +}; + +module_platform_driver(meson_sar_adc_driver); + +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); +MODULE_DESCRIPTION("Amlogic Meson SAR ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/qcom-spmi-vadc.c b/drivers/iio/adc/qcom-spmi-vadc.c index c2babe50a0d8..0a19761d656c 100644 --- a/drivers/iio/adc/qcom-spmi-vadc.c +++ b/drivers/iio/adc/qcom-spmi-vadc.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -84,7 +84,7 @@ #define VADC_MAX_ADC_CODE 0xa800 #define VADC_ABSOLUTE_RANGE_UV 625000 -#define VADC_RATIOMETRIC_RANGE_UV 1800000 +#define VADC_RATIOMETRIC_RANGE 1800 #define VADC_DEF_PRESCALING 0 /* 1:1 */ #define VADC_DEF_DECIMATION 0 /* 512 */ @@ -100,9 +100,23 @@ #define KELVINMIL_CELSIUSMIL 273150 +#define PMI_CHG_SCALE_1 -138890 +#define PMI_CHG_SCALE_2 391750000000LL + #define VADC_CHAN_MIN VADC_USBIN #define VADC_CHAN_MAX VADC_LR_MUX3_BUF_PU1_PU2_XO_THERM +/** + * struct vadc_map_pt - Map the graph representation for ADC channel + * @x: Represent the ADC digitized code. + * @y: Represent the physical data which can be temperature, voltage, + * resistance. + */ +struct vadc_map_pt { + s32 x; + s32 y; +}; + /* * VADC_CALIB_ABSOLUTE: uses the 625mV and 1.25V as reference channels. * VADC_CALIB_RATIOMETRIC: uses the reference voltage (1.8V) and GND for @@ -148,6 +162,9 @@ struct vadc_prescale_ratio { * start of conversion. * @avg_samples: ability to provide single result from the ADC * that is an average of multiple measurements. + * @scale_fn: Represents the scaling function to convert voltage + * physical units desired by the client for the channel. + * Referenced from enum vadc_scale_fn_type. */ struct vadc_channel_prop { unsigned int channel; @@ -156,6 +173,7 @@ struct vadc_channel_prop { unsigned int prescale; unsigned int hw_settle_time; unsigned int avg_samples; + unsigned int scale_fn; }; /** @@ -186,6 +204,35 @@ struct vadc_priv { struct mutex lock; }; +/** + * struct vadc_scale_fn - Scaling function prototype + * @scale: Function pointer to one of the scaling functions + * which takes the adc properties, channel properties, + * and returns the physical result. + */ +struct vadc_scale_fn { + int (*scale)(struct vadc_priv *, const struct vadc_channel_prop *, + u16, int *); +}; + +/** + * enum vadc_scale_fn_type - Scaling function to convert ADC code to + * physical scaled units for the channel. + * SCALE_DEFAULT: Default scaling to convert raw adc code to voltage (uV). + * SCALE_THERM_100K_PULLUP: Returns temperature in millidegC. + * Uses a mapping table with 100K pullup. + * SCALE_PMIC_THERM: Returns result in milli degree's Centigrade. + * SCALE_XOTHERM: Returns XO thermistor voltage in millidegC. + * SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp + */ +enum vadc_scale_fn_type { + SCALE_DEFAULT = 0, + SCALE_THERM_100K_PULLUP, + SCALE_PMIC_THERM, + SCALE_XOTHERM, + SCALE_PMI_CHG_TEMP, +}; + static const struct vadc_prescale_ratio vadc_prescale_ratios[] = { {.num = 1, .den = 1}, {.num = 1, .den = 3}, @@ -197,6 +244,44 @@ static const struct vadc_prescale_ratio vadc_prescale_ratios[] = { {.num = 1, .den = 10} }; +/* Voltage to temperature */ +static const struct vadc_map_pt adcmap_100k_104ef_104fb[] = { + {1758, -40}, + {1742, -35}, + {1719, -30}, + {1691, -25}, + {1654, -20}, + {1608, -15}, + {1551, -10}, + {1483, -5}, + {1404, 0}, + {1315, 5}, + {1218, 10}, + {1114, 15}, + {1007, 20}, + {900, 25}, + {795, 30}, + {696, 35}, + {605, 40}, + {522, 45}, + {448, 50}, + {383, 55}, + {327, 60}, + {278, 65}, + {237, 70}, + {202, 75}, + {172, 80}, + {146, 85}, + {125, 90}, + {107, 95}, + {92, 100}, + {79, 105}, + {68, 110}, + {59, 115}, + {51, 120}, + {44, 125} +}; + static int vadc_read(struct vadc_priv *vadc, u16 offset, u8 *data) { return regmap_bulk_read(vadc->regmap, vadc->base + offset, data, 1); @@ -418,7 +503,7 @@ static int vadc_measure_ref_points(struct vadc_priv *vadc) u16 read_1, read_2; int ret; - vadc->graph[VADC_CALIB_RATIOMETRIC].dx = VADC_RATIOMETRIC_RANGE_UV; + vadc->graph[VADC_CALIB_RATIOMETRIC].dx = VADC_RATIOMETRIC_RANGE; vadc->graph[VADC_CALIB_ABSOLUTE].dx = VADC_ABSOLUTE_RANGE_UV; prop = vadc_get_channel(vadc, VADC_REF_1250MV); @@ -468,27 +553,148 @@ err: return ret; } -static s32 vadc_calibrate(struct vadc_priv *vadc, - const struct vadc_channel_prop *prop, u16 adc_code) +static int vadc_map_voltage_temp(const struct vadc_map_pt *pts, + u32 tablesize, s32 input, s64 *output) +{ + bool descending = 1; + u32 i = 0; + + if (!pts) + return -EINVAL; + + /* Check if table is descending or ascending */ + if (tablesize > 1) { + if (pts[0].x < pts[1].x) + descending = 0; + } + + while (i < tablesize) { + if ((descending) && (pts[i].x < input)) { + /* table entry is less than measured*/ + /* value and table is descending, stop */ + break; + } else if ((!descending) && + (pts[i].x > input)) { + /* table entry is greater than measured*/ + /*value and table is ascending, stop */ + break; + } + i++; + } + + if (i == 0) { + *output = pts[0].y; + } else if (i == tablesize) { + *output = pts[tablesize - 1].y; + } else { + /* result is between search_index and search_index-1 */ + /* interpolate linearly */ + *output = (((s32)((pts[i].y - pts[i - 1].y) * + (input - pts[i - 1].x)) / + (pts[i].x - pts[i - 1].x)) + + pts[i - 1].y); + } + + return 0; +} + +static void vadc_scale_calib(struct vadc_priv *vadc, u16 adc_code, + const struct vadc_channel_prop *prop, + s64 *scale_voltage) +{ + *scale_voltage = (adc_code - + vadc->graph[prop->calibration].gnd); + *scale_voltage *= vadc->graph[prop->calibration].dx; + *scale_voltage = div64_s64(*scale_voltage, + vadc->graph[prop->calibration].dy); + if (prop->calibration == VADC_CALIB_ABSOLUTE) + *scale_voltage += + vadc->graph[prop->calibration].dx; + + if (*scale_voltage < 0) + *scale_voltage = 0; +} + +static int vadc_scale_volt(struct vadc_priv *vadc, + const struct vadc_channel_prop *prop, u16 adc_code, + int *result_uv) { const struct vadc_prescale_ratio *prescale; - s64 voltage; + s64 voltage = 0, result = 0; - voltage = adc_code - vadc->graph[prop->calibration].gnd; - voltage *= vadc->graph[prop->calibration].dx; - voltage = div64_s64(voltage, vadc->graph[prop->calibration].dy); + vadc_scale_calib(vadc, adc_code, prop, &voltage); + + prescale = &vadc_prescale_ratios[prop->prescale]; + voltage = voltage * prescale->den; + result = div64_s64(voltage, prescale->num); + *result_uv = result; + + return 0; +} + +static int vadc_scale_therm(struct vadc_priv *vadc, + const struct vadc_channel_prop *prop, u16 adc_code, + int *result_mdec) +{ + s64 voltage = 0, result = 0; + + vadc_scale_calib(vadc, adc_code, prop, &voltage); if (prop->calibration == VADC_CALIB_ABSOLUTE) - voltage += vadc->graph[prop->calibration].dx; + voltage = div64_s64(voltage, 1000); + + vadc_map_voltage_temp(adcmap_100k_104ef_104fb, + ARRAY_SIZE(adcmap_100k_104ef_104fb), + voltage, &result); + result *= 1000; + *result_mdec = result; - if (voltage < 0) + return 0; +} + +static int vadc_scale_die_temp(struct vadc_priv *vadc, + const struct vadc_channel_prop *prop, + u16 adc_code, int *result_mdec) +{ + const struct vadc_prescale_ratio *prescale; + s64 voltage = 0; + u64 temp; /* Temporary variable for do_div */ + + vadc_scale_calib(vadc, adc_code, prop, &voltage); + + if (voltage > 0) { + prescale = &vadc_prescale_ratios[prop->prescale]; + temp = voltage * prescale->den; + do_div(temp, prescale->num * 2); + voltage = temp; + } else { voltage = 0; + } - prescale = &vadc_prescale_ratios[prop->prescale]; + voltage -= KELVINMIL_CELSIUSMIL; + *result_mdec = voltage; + + return 0; +} + +static int vadc_scale_chg_temp(struct vadc_priv *vadc, + const struct vadc_channel_prop *prop, + u16 adc_code, int *result_mdec) +{ + const struct vadc_prescale_ratio *prescale; + s64 voltage = 0, result = 0; + vadc_scale_calib(vadc, adc_code, prop, &voltage); + + prescale = &vadc_prescale_ratios[prop->prescale]; voltage = voltage * prescale->den; + voltage = div64_s64(voltage, prescale->num); + voltage = ((PMI_CHG_SCALE_1) * (voltage * 2)); + voltage = (voltage + PMI_CHG_SCALE_2); + result = div64_s64(voltage, 1000000); + *result_mdec = result; - return div64_s64(voltage, prescale->num); + return 0; } static int vadc_decimation_from_dt(u32 value) @@ -536,6 +742,14 @@ static int vadc_avg_samples_from_dt(u32 value) return __ffs64(value); } +static struct vadc_scale_fn scale_fn[] = { + [SCALE_DEFAULT] = {vadc_scale_volt}, + [SCALE_THERM_100K_PULLUP] = {vadc_scale_therm}, + [SCALE_PMIC_THERM] = {vadc_scale_die_temp}, + [SCALE_XOTHERM] = {vadc_scale_therm}, + [SCALE_PMI_CHG_TEMP] = {vadc_scale_chg_temp}, +}; + static int vadc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -552,11 +766,8 @@ static int vadc_read_raw(struct iio_dev *indio_dev, if (ret) break; - *val = vadc_calibrate(vadc, prop, adc_code); + scale_fn[prop->scale_fn].scale(vadc, prop, adc_code, val); - /* 2mV/K, return milli Celsius */ - *val /= 2; - *val -= KELVINMIL_CELSIUSMIL; return IIO_VAL_INT; case IIO_CHAN_INFO_RAW: prop = &vadc->chan_props[chan->address]; @@ -564,12 +775,8 @@ static int vadc_read_raw(struct iio_dev *indio_dev, if (ret) break; - *val = vadc_calibrate(vadc, prop, adc_code); + *val = (int)adc_code; return IIO_VAL_INT; - case IIO_CHAN_INFO_SCALE: - *val = 0; - *val2 = 1000; - return IIO_VAL_INT_PLUS_MICRO; default: ret = -EINVAL; break; @@ -602,22 +809,39 @@ struct vadc_channels { unsigned int prescale_index; enum iio_chan_type type; long info_mask; + unsigned int scale_fn; }; -#define VADC_CHAN(_dname, _type, _mask, _pre) \ +#define VADC_CHAN(_dname, _type, _mask, _pre, _scale) \ [VADC_##_dname] = { \ .datasheet_name = __stringify(_dname), \ .prescale_index = _pre, \ .type = _type, \ - .info_mask = _mask \ + .info_mask = _mask, \ + .scale_fn = _scale \ }, \ -#define VADC_CHAN_TEMP(_dname, _pre) \ - VADC_CHAN(_dname, IIO_TEMP, BIT(IIO_CHAN_INFO_PROCESSED), _pre) \ +#define VADC_NO_CHAN(_dname, _type, _mask, _pre) \ + [VADC_##_dname] = { \ + .datasheet_name = __stringify(_dname), \ + .prescale_index = _pre, \ + .type = _type, \ + .info_mask = _mask \ + }, -#define VADC_CHAN_VOLT(_dname, _pre) \ +#define VADC_CHAN_TEMP(_dname, _pre, _scale) \ + VADC_CHAN(_dname, IIO_TEMP, \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), \ + _pre, _scale) \ + +#define VADC_CHAN_VOLT(_dname, _pre, _scale) \ VADC_CHAN(_dname, IIO_VOLTAGE, \ - BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\ + _pre, _scale) \ + +#define VADC_CHAN_NO_SCALE(_dname, _pre) \ + VADC_NO_CHAN(_dname, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_RAW), \ _pre) \ /* @@ -626,106 +850,106 @@ struct vadc_channels { * gaps in the array should be treated as reserved channels. */ static const struct vadc_channels vadc_chans[] = { - VADC_CHAN_VOLT(USBIN, 4) - VADC_CHAN_VOLT(DCIN, 4) - VADC_CHAN_VOLT(VCHG_SNS, 3) - VADC_CHAN_VOLT(SPARE1_03, 1) - VADC_CHAN_VOLT(USB_ID_MV, 1) - VADC_CHAN_VOLT(VCOIN, 1) - VADC_CHAN_VOLT(VBAT_SNS, 1) - VADC_CHAN_VOLT(VSYS, 1) - VADC_CHAN_TEMP(DIE_TEMP, 0) - VADC_CHAN_VOLT(REF_625MV, 0) - VADC_CHAN_VOLT(REF_1250MV, 0) - VADC_CHAN_VOLT(CHG_TEMP, 0) - VADC_CHAN_VOLT(SPARE1, 0) - VADC_CHAN_VOLT(SPARE2, 0) - VADC_CHAN_VOLT(GND_REF, 0) - VADC_CHAN_VOLT(VDD_VADC, 0) - - VADC_CHAN_VOLT(P_MUX1_1_1, 0) - VADC_CHAN_VOLT(P_MUX2_1_1, 0) - VADC_CHAN_VOLT(P_MUX3_1_1, 0) - VADC_CHAN_VOLT(P_MUX4_1_1, 0) - VADC_CHAN_VOLT(P_MUX5_1_1, 0) - VADC_CHAN_VOLT(P_MUX6_1_1, 0) - VADC_CHAN_VOLT(P_MUX7_1_1, 0) - VADC_CHAN_VOLT(P_MUX8_1_1, 0) - VADC_CHAN_VOLT(P_MUX9_1_1, 0) - VADC_CHAN_VOLT(P_MUX10_1_1, 0) - VADC_CHAN_VOLT(P_MUX11_1_1, 0) - VADC_CHAN_VOLT(P_MUX12_1_1, 0) - VADC_CHAN_VOLT(P_MUX13_1_1, 0) - VADC_CHAN_VOLT(P_MUX14_1_1, 0) - VADC_CHAN_VOLT(P_MUX15_1_1, 0) - VADC_CHAN_VOLT(P_MUX16_1_1, 0) - - VADC_CHAN_VOLT(P_MUX1_1_3, 1) - VADC_CHAN_VOLT(P_MUX2_1_3, 1) - VADC_CHAN_VOLT(P_MUX3_1_3, 1) - VADC_CHAN_VOLT(P_MUX4_1_3, 1) - VADC_CHAN_VOLT(P_MUX5_1_3, 1) - VADC_CHAN_VOLT(P_MUX6_1_3, 1) - VADC_CHAN_VOLT(P_MUX7_1_3, 1) - VADC_CHAN_VOLT(P_MUX8_1_3, 1) - VADC_CHAN_VOLT(P_MUX9_1_3, 1) - VADC_CHAN_VOLT(P_MUX10_1_3, 1) - VADC_CHAN_VOLT(P_MUX11_1_3, 1) - VADC_CHAN_VOLT(P_MUX12_1_3, 1) - VADC_CHAN_VOLT(P_MUX13_1_3, 1) - VADC_CHAN_VOLT(P_MUX14_1_3, 1) - VADC_CHAN_VOLT(P_MUX15_1_3, 1) - VADC_CHAN_VOLT(P_MUX16_1_3, 1) - - VADC_CHAN_VOLT(LR_MUX1_BAT_THERM, 0) - VADC_CHAN_VOLT(LR_MUX2_BAT_ID, 0) - VADC_CHAN_VOLT(LR_MUX3_XO_THERM, 0) - VADC_CHAN_VOLT(LR_MUX4_AMUX_THM1, 0) - VADC_CHAN_VOLT(LR_MUX5_AMUX_THM2, 0) - VADC_CHAN_VOLT(LR_MUX6_AMUX_THM3, 0) - VADC_CHAN_VOLT(LR_MUX7_HW_ID, 0) - VADC_CHAN_VOLT(LR_MUX8_AMUX_THM4, 0) - VADC_CHAN_VOLT(LR_MUX9_AMUX_THM5, 0) - VADC_CHAN_VOLT(LR_MUX10_USB_ID, 0) - VADC_CHAN_VOLT(AMUX_PU1, 0) - VADC_CHAN_VOLT(AMUX_PU2, 0) - VADC_CHAN_VOLT(LR_MUX3_BUF_XO_THERM, 0) - - VADC_CHAN_VOLT(LR_MUX1_PU1_BAT_THERM, 0) - VADC_CHAN_VOLT(LR_MUX2_PU1_BAT_ID, 0) - VADC_CHAN_VOLT(LR_MUX3_PU1_XO_THERM, 0) - VADC_CHAN_VOLT(LR_MUX4_PU1_AMUX_THM1, 0) - VADC_CHAN_VOLT(LR_MUX5_PU1_AMUX_THM2, 0) - VADC_CHAN_VOLT(LR_MUX6_PU1_AMUX_THM3, 0) - VADC_CHAN_VOLT(LR_MUX7_PU1_AMUX_HW_ID, 0) - VADC_CHAN_VOLT(LR_MUX8_PU1_AMUX_THM4, 0) - VADC_CHAN_VOLT(LR_MUX9_PU1_AMUX_THM5, 0) - VADC_CHAN_VOLT(LR_MUX10_PU1_AMUX_USB_ID, 0) - VADC_CHAN_VOLT(LR_MUX3_BUF_PU1_XO_THERM, 0) - - VADC_CHAN_VOLT(LR_MUX1_PU2_BAT_THERM, 0) - VADC_CHAN_VOLT(LR_MUX2_PU2_BAT_ID, 0) - VADC_CHAN_VOLT(LR_MUX3_PU2_XO_THERM, 0) - VADC_CHAN_VOLT(LR_MUX4_PU2_AMUX_THM1, 0) - VADC_CHAN_VOLT(LR_MUX5_PU2_AMUX_THM2, 0) - VADC_CHAN_VOLT(LR_MUX6_PU2_AMUX_THM3, 0) - VADC_CHAN_VOLT(LR_MUX7_PU2_AMUX_HW_ID, 0) - VADC_CHAN_VOLT(LR_MUX8_PU2_AMUX_THM4, 0) - VADC_CHAN_VOLT(LR_MUX9_PU2_AMUX_THM5, 0) - VADC_CHAN_VOLT(LR_MUX10_PU2_AMUX_USB_ID, 0) - VADC_CHAN_VOLT(LR_MUX3_BUF_PU2_XO_THERM, 0) - - VADC_CHAN_VOLT(LR_MUX1_PU1_PU2_BAT_THERM, 0) - VADC_CHAN_VOLT(LR_MUX2_PU1_PU2_BAT_ID, 0) - VADC_CHAN_VOLT(LR_MUX3_PU1_PU2_XO_THERM, 0) - VADC_CHAN_VOLT(LR_MUX4_PU1_PU2_AMUX_THM1, 0) - VADC_CHAN_VOLT(LR_MUX5_PU1_PU2_AMUX_THM2, 0) - VADC_CHAN_VOLT(LR_MUX6_PU1_PU2_AMUX_THM3, 0) - VADC_CHAN_VOLT(LR_MUX7_PU1_PU2_AMUX_HW_ID, 0) - VADC_CHAN_VOLT(LR_MUX8_PU1_PU2_AMUX_THM4, 0) - VADC_CHAN_VOLT(LR_MUX9_PU1_PU2_AMUX_THM5, 0) - VADC_CHAN_VOLT(LR_MUX10_PU1_PU2_AMUX_USB_ID, 0) - VADC_CHAN_VOLT(LR_MUX3_BUF_PU1_PU2_XO_THERM, 0) + VADC_CHAN_VOLT(USBIN, 4, SCALE_DEFAULT) + VADC_CHAN_VOLT(DCIN, 4, SCALE_DEFAULT) + VADC_CHAN_NO_SCALE(VCHG_SNS, 3) + VADC_CHAN_NO_SCALE(SPARE1_03, 1) + VADC_CHAN_NO_SCALE(USB_ID_MV, 1) + VADC_CHAN_VOLT(VCOIN, 1, SCALE_DEFAULT) + VADC_CHAN_NO_SCALE(VBAT_SNS, 1) + VADC_CHAN_VOLT(VSYS, 1, SCALE_DEFAULT) + VADC_CHAN_TEMP(DIE_TEMP, 0, SCALE_PMIC_THERM) + VADC_CHAN_VOLT(REF_625MV, 0, SCALE_DEFAULT) + VADC_CHAN_VOLT(REF_1250MV, 0, SCALE_DEFAULT) + VADC_CHAN_NO_SCALE(CHG_TEMP, 0) + VADC_CHAN_NO_SCALE(SPARE1, 0) + VADC_CHAN_TEMP(SPARE2, 0, SCALE_PMI_CHG_TEMP) + VADC_CHAN_VOLT(GND_REF, 0, SCALE_DEFAULT) + VADC_CHAN_VOLT(VDD_VADC, 0, SCALE_DEFAULT) + + VADC_CHAN_NO_SCALE(P_MUX1_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX2_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX3_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX4_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX5_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX6_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX7_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX8_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX9_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX10_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX11_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX12_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX13_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX14_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX15_1_1, 0) + VADC_CHAN_NO_SCALE(P_MUX16_1_1, 0) + + VADC_CHAN_NO_SCALE(P_MUX1_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX2_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX3_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX4_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX5_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX6_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX7_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX8_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX9_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX10_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX11_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX12_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX13_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX14_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX15_1_3, 1) + VADC_CHAN_NO_SCALE(P_MUX16_1_3, 1) + + VADC_CHAN_NO_SCALE(LR_MUX1_BAT_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX2_BAT_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_XO_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX4_AMUX_THM1, 0) + VADC_CHAN_NO_SCALE(LR_MUX5_AMUX_THM2, 0) + VADC_CHAN_NO_SCALE(LR_MUX6_AMUX_THM3, 0) + VADC_CHAN_NO_SCALE(LR_MUX7_HW_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX8_AMUX_THM4, 0) + VADC_CHAN_NO_SCALE(LR_MUX9_AMUX_THM5, 0) + VADC_CHAN_NO_SCALE(LR_MUX10_USB_ID, 0) + VADC_CHAN_NO_SCALE(AMUX_PU1, 0) + VADC_CHAN_NO_SCALE(AMUX_PU2, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_BUF_XO_THERM, 0) + + VADC_CHAN_NO_SCALE(LR_MUX1_PU1_BAT_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX2_PU1_BAT_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_PU1_XO_THERM, 0) + VADC_CHAN_TEMP(LR_MUX4_PU1_AMUX_THM1, 0, SCALE_THERM_100K_PULLUP) + VADC_CHAN_TEMP(LR_MUX5_PU1_AMUX_THM2, 0, SCALE_THERM_100K_PULLUP) + VADC_CHAN_TEMP(LR_MUX6_PU1_AMUX_THM3, 0, SCALE_THERM_100K_PULLUP) + VADC_CHAN_NO_SCALE(LR_MUX7_PU1_AMUX_HW_ID, 0) + VADC_CHAN_TEMP(LR_MUX8_PU1_AMUX_THM4, 0, SCALE_THERM_100K_PULLUP) + VADC_CHAN_TEMP(LR_MUX9_PU1_AMUX_THM5, 0, SCALE_THERM_100K_PULLUP) + VADC_CHAN_NO_SCALE(LR_MUX10_PU1_AMUX_USB_ID, 0) + VADC_CHAN_TEMP(LR_MUX3_BUF_PU1_XO_THERM, 0, SCALE_XOTHERM) + + VADC_CHAN_NO_SCALE(LR_MUX1_PU2_BAT_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX2_PU2_BAT_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_PU2_XO_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX4_PU2_AMUX_THM1, 0) + VADC_CHAN_NO_SCALE(LR_MUX5_PU2_AMUX_THM2, 0) + VADC_CHAN_NO_SCALE(LR_MUX6_PU2_AMUX_THM3, 0) + VADC_CHAN_NO_SCALE(LR_MUX7_PU2_AMUX_HW_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX8_PU2_AMUX_THM4, 0) + VADC_CHAN_NO_SCALE(LR_MUX9_PU2_AMUX_THM5, 0) + VADC_CHAN_NO_SCALE(LR_MUX10_PU2_AMUX_USB_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_BUF_PU2_XO_THERM, 0) + + VADC_CHAN_NO_SCALE(LR_MUX1_PU1_PU2_BAT_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX2_PU1_PU2_BAT_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_PU1_PU2_XO_THERM, 0) + VADC_CHAN_NO_SCALE(LR_MUX4_PU1_PU2_AMUX_THM1, 0) + VADC_CHAN_NO_SCALE(LR_MUX5_PU1_PU2_AMUX_THM2, 0) + VADC_CHAN_NO_SCALE(LR_MUX6_PU1_PU2_AMUX_THM3, 0) + VADC_CHAN_NO_SCALE(LR_MUX7_PU1_PU2_AMUX_HW_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX8_PU1_PU2_AMUX_THM4, 0) + VADC_CHAN_NO_SCALE(LR_MUX9_PU1_PU2_AMUX_THM5, 0) + VADC_CHAN_NO_SCALE(LR_MUX10_PU1_PU2_AMUX_USB_ID, 0) + VADC_CHAN_NO_SCALE(LR_MUX3_BUF_PU1_PU2_XO_THERM, 0) }; static int vadc_get_dt_channel_data(struct device *dev, @@ -844,6 +1068,7 @@ static int vadc_get_dt_data(struct vadc_priv *vadc, struct device_node *node) return ret; } + prop.scale_fn = vadc_chans[prop.channel].scale_fn; vadc->chan_props[index] = prop; vadc_chan = &vadc_chans[prop.channel]; diff --git a/drivers/iio/adc/rcar-gyroadc.c b/drivers/iio/adc/rcar-gyroadc.c new file mode 100644 index 000000000000..0c44f72c32a8 --- /dev/null +++ b/drivers/iio/adc/rcar-gyroadc.c @@ -0,0 +1,631 @@ +/* + * Renesas R-Car GyroADC driver + * + * Copyright 2016 Marek Vasut <marek.vasut@gmail.com> + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/regulator/consumer.h> +#include <linux/of_platform.h> +#include <linux/err.h> +#include <linux/pm_runtime.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> + +#define DRIVER_NAME "rcar-gyroadc" + +/* GyroADC registers. */ +#define RCAR_GYROADC_MODE_SELECT 0x00 +#define RCAR_GYROADC_MODE_SELECT_1_MB88101A 0x0 +#define RCAR_GYROADC_MODE_SELECT_2_ADCS7476 0x1 +#define RCAR_GYROADC_MODE_SELECT_3_MAX1162 0x3 + +#define RCAR_GYROADC_START_STOP 0x04 +#define RCAR_GYROADC_START_STOP_START BIT(0) + +#define RCAR_GYROADC_CLOCK_LENGTH 0x08 +#define RCAR_GYROADC_1_25MS_LENGTH 0x0c + +#define RCAR_GYROADC_REALTIME_DATA(ch) (0x10 + ((ch) * 4)) +#define RCAR_GYROADC_100MS_ADDED_DATA(ch) (0x30 + ((ch) * 4)) +#define RCAR_GYROADC_10MS_AVG_DATA(ch) (0x50 + ((ch) * 4)) + +#define RCAR_GYROADC_FIFO_STATUS 0x70 +#define RCAR_GYROADC_FIFO_STATUS_EMPTY(ch) BIT(0 + (4 * (ch))) +#define RCAR_GYROADC_FIFO_STATUS_FULL(ch) BIT(1 + (4 * (ch))) +#define RCAR_GYROADC_FIFO_STATUS_ERROR(ch) BIT(2 + (4 * (ch))) + +#define RCAR_GYROADC_INTR 0x74 +#define RCAR_GYROADC_INTR_INT BIT(0) + +#define RCAR_GYROADC_INTENR 0x78 +#define RCAR_GYROADC_INTENR_INTEN BIT(0) + +#define RCAR_GYROADC_SAMPLE_RATE 800 /* Hz */ + +#define RCAR_GYROADC_RUNTIME_PM_DELAY_MS 2000 + +enum rcar_gyroadc_model { + RCAR_GYROADC_MODEL_DEFAULT, + RCAR_GYROADC_MODEL_R8A7792, +}; + +struct rcar_gyroadc { + struct device *dev; + void __iomem *regs; + struct clk *iclk; + struct regulator *vref[8]; + unsigned int num_channels; + enum rcar_gyroadc_model model; + unsigned int mode; + unsigned int sample_width; +}; + +static void rcar_gyroadc_hw_init(struct rcar_gyroadc *priv) +{ + const unsigned long clk_mhz = clk_get_rate(priv->iclk) / 1000000; + const unsigned long clk_mul = + (priv->mode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) ? 10 : 5; + unsigned long clk_len = clk_mhz * clk_mul; + + /* + * According to the R-Car Gen2 datasheet Rev. 1.01, Sept 08 2014, + * page 77-7, clock length must be even number. If it's odd number, + * add one. + */ + if (clk_len & 1) + clk_len++; + + /* Stop the GyroADC. */ + writel(0, priv->regs + RCAR_GYROADC_START_STOP); + + /* Disable IRQ on V2H. */ + if (priv->model == RCAR_GYROADC_MODEL_R8A7792) + writel(0, priv->regs + RCAR_GYROADC_INTENR); + + /* Set mode and timing. */ + writel(priv->mode, priv->regs + RCAR_GYROADC_MODE_SELECT); + writel(clk_len, priv->regs + RCAR_GYROADC_CLOCK_LENGTH); + writel(clk_mhz * 1250, priv->regs + RCAR_GYROADC_1_25MS_LENGTH); +} + +static void rcar_gyroadc_hw_start(struct rcar_gyroadc *priv) +{ + /* Start sampling. */ + writel(RCAR_GYROADC_START_STOP_START, + priv->regs + RCAR_GYROADC_START_STOP); + + /* + * Wait for the first conversion to complete. This is longer than + * the 1.25 mS in the datasheet because 1.25 mS is not enough for + * the hardware to deliver the first sample and the hardware does + * then return zeroes instead of valid data. + */ + mdelay(3); +} + +static void rcar_gyroadc_hw_stop(struct rcar_gyroadc *priv) +{ + /* Stop the GyroADC. */ + writel(0, priv->regs + RCAR_GYROADC_START_STOP); +} + +#define RCAR_GYROADC_CHAN(_idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec rcar_gyroadc_iio_channels_1[] = { + RCAR_GYROADC_CHAN(0), + RCAR_GYROADC_CHAN(1), + RCAR_GYROADC_CHAN(2), + RCAR_GYROADC_CHAN(3), +}; + +static const struct iio_chan_spec rcar_gyroadc_iio_channels_2[] = { + RCAR_GYROADC_CHAN(0), + RCAR_GYROADC_CHAN(1), + RCAR_GYROADC_CHAN(2), + RCAR_GYROADC_CHAN(3), + RCAR_GYROADC_CHAN(4), + RCAR_GYROADC_CHAN(5), + RCAR_GYROADC_CHAN(6), + RCAR_GYROADC_CHAN(7), +}; + +static const struct iio_chan_spec rcar_gyroadc_iio_channels_3[] = { + RCAR_GYROADC_CHAN(0), + RCAR_GYROADC_CHAN(1), + RCAR_GYROADC_CHAN(2), + RCAR_GYROADC_CHAN(3), + RCAR_GYROADC_CHAN(4), + RCAR_GYROADC_CHAN(5), + RCAR_GYROADC_CHAN(6), + RCAR_GYROADC_CHAN(7), +}; + +static int rcar_gyroadc_set_power(struct rcar_gyroadc *priv, bool on) +{ + struct device *dev = priv->dev; + int ret; + + if (on) { + ret = pm_runtime_get_sync(dev); + if (ret < 0) + pm_runtime_put_noidle(dev); + } else { + pm_runtime_mark_last_busy(dev); + ret = pm_runtime_put_autosuspend(dev); + } + + return ret; +} + +static int rcar_gyroadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct rcar_gyroadc *priv = iio_priv(indio_dev); + struct regulator *consumer; + unsigned int datareg = RCAR_GYROADC_REALTIME_DATA(chan->channel); + unsigned int vref; + int ret; + + /* + * MB88101 is special in that it has only single regulator for + * all four channels. + */ + if (priv->mode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) + consumer = priv->vref[0]; + else + consumer = priv->vref[chan->channel]; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_VOLTAGE) + return -EINVAL; + + /* Channel not connected. */ + if (!consumer) + return -EINVAL; + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = rcar_gyroadc_set_power(priv, true); + if (ret < 0) { + iio_device_release_direct_mode(indio_dev); + return ret; + } + + *val = readl(priv->regs + datareg); + *val &= BIT(priv->sample_width) - 1; + + ret = rcar_gyroadc_set_power(priv, false); + iio_device_release_direct_mode(indio_dev); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* Channel not connected. */ + if (!consumer) + return -EINVAL; + + vref = regulator_get_voltage(consumer); + *val = vref / 1000; + *val2 = 1 << priv->sample_width; + + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = RCAR_GYROADC_SAMPLE_RATE; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int rcar_gyroadc_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct rcar_gyroadc *priv = iio_priv(indio_dev); + unsigned int maxreg = RCAR_GYROADC_FIFO_STATUS; + + if (readval == NULL) + return -EINVAL; + + if (reg % 4) + return -EINVAL; + + /* Handle the V2H case with extra interrupt block. */ + if (priv->model == RCAR_GYROADC_MODEL_R8A7792) + maxreg = RCAR_GYROADC_INTENR; + + if (reg > maxreg) + return -EINVAL; + + *readval = readl(priv->regs + reg); + + return 0; +} + +static const struct iio_info rcar_gyroadc_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = rcar_gyroadc_read_raw, + .debugfs_reg_access = rcar_gyroadc_reg_access, +}; + +static const struct of_device_id rcar_gyroadc_match[] = { + { + /* R-Car compatible GyroADC */ + .compatible = "renesas,rcar-gyroadc", + .data = (void *)RCAR_GYROADC_MODEL_DEFAULT, + }, { + /* R-Car V2H specialty with interrupt registers. */ + .compatible = "renesas,r8a7792-gyroadc", + .data = (void *)RCAR_GYROADC_MODEL_R8A7792, + }, { + /* sentinel */ + } +}; + +MODULE_DEVICE_TABLE(of, rcar_gyroadc_match); + +static const struct of_device_id rcar_gyroadc_child_match[] = { + /* Mode 1 ADCs */ + { + .compatible = "fujitsu,mb88101a", + .data = (void *)RCAR_GYROADC_MODE_SELECT_1_MB88101A, + }, + /* Mode 2 ADCs */ + { + .compatible = "ti,adcs7476", + .data = (void *)RCAR_GYROADC_MODE_SELECT_2_ADCS7476, + }, { + .compatible = "ti,adc121", + .data = (void *)RCAR_GYROADC_MODE_SELECT_2_ADCS7476, + }, { + .compatible = "adi,ad7476", + .data = (void *)RCAR_GYROADC_MODE_SELECT_2_ADCS7476, + }, + /* Mode 3 ADCs */ + { + .compatible = "maxim,max1162", + .data = (void *)RCAR_GYROADC_MODE_SELECT_3_MAX1162, + }, { + .compatible = "maxim,max11100", + .data = (void *)RCAR_GYROADC_MODE_SELECT_3_MAX1162, + }, + { /* sentinel */ } +}; + +static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev) +{ + const struct of_device_id *of_id; + const struct iio_chan_spec *channels; + struct rcar_gyroadc *priv = iio_priv(indio_dev); + struct device *dev = priv->dev; + struct device_node *np = dev->of_node; + struct device_node *child; + struct regulator *vref; + unsigned int reg; + unsigned int adcmode, childmode; + unsigned int sample_width; + unsigned int num_channels; + int ret, first = 1; + + for_each_child_of_node(np, child) { + of_id = of_match_node(rcar_gyroadc_child_match, child); + if (!of_id) { + dev_err(dev, "Ignoring unsupported ADC \"%s\".", + child->name); + continue; + } + + childmode = (unsigned int)of_id->data; + switch (childmode) { + case RCAR_GYROADC_MODE_SELECT_1_MB88101A: + sample_width = 12; + channels = rcar_gyroadc_iio_channels_1; + num_channels = ARRAY_SIZE(rcar_gyroadc_iio_channels_1); + break; + case RCAR_GYROADC_MODE_SELECT_2_ADCS7476: + sample_width = 15; + channels = rcar_gyroadc_iio_channels_2; + num_channels = ARRAY_SIZE(rcar_gyroadc_iio_channels_2); + break; + case RCAR_GYROADC_MODE_SELECT_3_MAX1162: + sample_width = 16; + channels = rcar_gyroadc_iio_channels_3; + num_channels = ARRAY_SIZE(rcar_gyroadc_iio_channels_3); + break; + } + + /* + * MB88101 is special in that it's only a single chip taking + * up all the CHS lines. Thus, the DT binding is also special + * and has no reg property. If we run into such ADC, handle + * it here. + */ + if (childmode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) { + reg = 0; + } else { + ret = of_property_read_u32(child, "reg", ®); + if (ret) { + dev_err(dev, + "Failed to get child reg property of ADC \"%s\".\n", + child->name); + return ret; + } + + /* Channel number is too high. */ + if (reg >= num_channels) { + dev_err(dev, + "Only %i channels supported with %s, but reg = <%i>.\n", + num_channels, child->name, reg); + return ret; + } + } + + /* Child node selected different mode than the rest. */ + if (!first && (adcmode != childmode)) { + dev_err(dev, + "Channel %i uses different ADC mode than the rest.\n", + reg); + return ret; + } + + /* Channel is valid, grab the regulator. */ + dev->of_node = child; + vref = devm_regulator_get(dev, "vref"); + dev->of_node = np; + if (IS_ERR(vref)) { + dev_dbg(dev, "Channel %i 'vref' supply not connected.\n", + reg); + return PTR_ERR(vref); + } + + priv->vref[reg] = vref; + + if (!first) + continue; + + /* First child node which passed sanity tests. */ + adcmode = childmode; + first = 0; + + priv->num_channels = num_channels; + priv->mode = childmode; + priv->sample_width = sample_width; + + indio_dev->channels = channels; + indio_dev->num_channels = num_channels; + + /* + * MB88101 is special and we only have one such device + * attached to the GyroADC at a time, so if we found it, + * we can stop parsing here. + */ + if (childmode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) + break; + } + + if (first) { + dev_err(dev, "No valid ADC channels found, aborting.\n"); + return -EINVAL; + } + + return 0; +} + +static void rcar_gyroadc_deinit_supplies(struct iio_dev *indio_dev) +{ + struct rcar_gyroadc *priv = iio_priv(indio_dev); + unsigned int i; + + for (i = 0; i < priv->num_channels; i++) { + if (!priv->vref[i]) + continue; + + regulator_disable(priv->vref[i]); + } +} + +static int rcar_gyroadc_init_supplies(struct iio_dev *indio_dev) +{ + struct rcar_gyroadc *priv = iio_priv(indio_dev); + struct device *dev = priv->dev; + unsigned int i; + int ret; + + for (i = 0; i < priv->num_channels; i++) { + if (!priv->vref[i]) + continue; + + ret = regulator_enable(priv->vref[i]); + if (ret) { + dev_err(dev, "Failed to enable regulator %i (ret=%i)\n", + i, ret); + goto err; + } + } + + return 0; + +err: + rcar_gyroadc_deinit_supplies(indio_dev); + return ret; +} + +static int rcar_gyroadc_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(rcar_gyroadc_match, &pdev->dev); + struct device *dev = &pdev->dev; + struct rcar_gyroadc *priv; + struct iio_dev *indio_dev; + struct resource *mem; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) { + dev_err(dev, "Failed to allocate IIO device.\n"); + return -ENOMEM; + } + + priv = iio_priv(indio_dev); + priv->dev = dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(priv->regs)) + return PTR_ERR(priv->regs); + + priv->iclk = devm_clk_get(dev, "if"); + if (IS_ERR(priv->iclk)) { + ret = PTR_ERR(priv->iclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get IF clock (ret=%i)\n", ret); + return ret; + } + + ret = rcar_gyroadc_parse_subdevs(indio_dev); + if (ret) + return ret; + + ret = rcar_gyroadc_init_supplies(indio_dev); + if (ret) + return ret; + + priv->model = (enum rcar_gyroadc_model)of_id->data; + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = DRIVER_NAME; + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &rcar_gyroadc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = clk_prepare_enable(priv->iclk); + if (ret) { + dev_err(dev, "Could not prepare or enable the IF clock.\n"); + goto err_clk_if_enable; + } + + pm_runtime_set_autosuspend_delay(dev, RCAR_GYROADC_RUNTIME_PM_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + pm_runtime_get_sync(dev); + rcar_gyroadc_hw_init(priv); + rcar_gyroadc_hw_start(priv); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "Couldn't register IIO device.\n"); + goto err_iio_device_register; + } + + pm_runtime_put_sync(dev); + + return 0; + +err_iio_device_register: + rcar_gyroadc_hw_stop(priv); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + clk_disable_unprepare(priv->iclk); +err_clk_if_enable: + rcar_gyroadc_deinit_supplies(indio_dev); + + return ret; +} + +static int rcar_gyroadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct rcar_gyroadc *priv = iio_priv(indio_dev); + struct device *dev = priv->dev; + + iio_device_unregister(indio_dev); + pm_runtime_get_sync(dev); + rcar_gyroadc_hw_stop(priv); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + clk_disable_unprepare(priv->iclk); + rcar_gyroadc_deinit_supplies(indio_dev); + + return 0; +} + +#if defined(CONFIG_PM) +static int rcar_gyroadc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rcar_gyroadc *priv = iio_priv(indio_dev); + + rcar_gyroadc_hw_stop(priv); + + return 0; +} + +static int rcar_gyroadc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rcar_gyroadc *priv = iio_priv(indio_dev); + + rcar_gyroadc_hw_start(priv); + + return 0; +} +#endif + +static const struct dev_pm_ops rcar_gyroadc_pm_ops = { + SET_RUNTIME_PM_OPS(rcar_gyroadc_suspend, rcar_gyroadc_resume, NULL) +}; + +static struct platform_driver rcar_gyroadc_driver = { + .probe = rcar_gyroadc_probe, + .remove = rcar_gyroadc_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = rcar_gyroadc_match, + .pm = &rcar_gyroadc_pm_ops, + }, +}; + +module_platform_driver(rcar_gyroadc_driver); + +MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); +MODULE_DESCRIPTION("Renesas R-Car GyroADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/stm32-adc-core.c b/drivers/iio/adc/stm32-adc-core.c index 4214b0cd6b1b..22b7c9321e78 100644 --- a/drivers/iio/adc/stm32-adc-core.c +++ b/drivers/iio/adc/stm32-adc-core.c @@ -201,6 +201,7 @@ static int stm32_adc_probe(struct platform_device *pdev) priv->common.base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(priv->common.base)) return PTR_ERR(priv->common.base); + priv->common.phys_base = res->start; priv->vref = devm_regulator_get(&pdev->dev, "vref"); if (IS_ERR(priv->vref)) { diff --git a/drivers/iio/adc/stm32-adc-core.h b/drivers/iio/adc/stm32-adc-core.h index 081fa5f55015..2ec7abbfbcaa 100644 --- a/drivers/iio/adc/stm32-adc-core.h +++ b/drivers/iio/adc/stm32-adc-core.h @@ -42,10 +42,12 @@ /** * struct stm32_adc_common - stm32 ADC driver common data (for all instances) * @base: control registers base cpu addr + * @phys_base: control registers base physical addr * @vref_mv: vref voltage (mv) */ struct stm32_adc_common { void __iomem *base; + phys_addr_t phys_base; int vref_mv; }; diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index 5715e79f4935..9b49a6addc2a 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -21,7 +21,14 @@ #include <linux/clk.h> #include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> #include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/timer/stm32-timer-trigger.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/module.h> @@ -58,21 +65,71 @@ /* STM32F4_ADC_CR2 - bit fields */ #define STM32F4_SWSTART BIT(30) +#define STM32F4_EXTEN_SHIFT 28 #define STM32F4_EXTEN_MASK GENMASK(29, 28) +#define STM32F4_EXTSEL_SHIFT 24 +#define STM32F4_EXTSEL_MASK GENMASK(27, 24) #define STM32F4_EOCS BIT(10) +#define STM32F4_DDS BIT(9) +#define STM32F4_DMA BIT(8) #define STM32F4_ADON BIT(0) -/* STM32F4_ADC_SQR1 - bit fields */ -#define STM32F4_L_SHIFT 20 -#define STM32F4_L_MASK GENMASK(23, 20) - -/* STM32F4_ADC_SQR3 - bit fields */ -#define STM32F4_SQ1_SHIFT 0 -#define STM32F4_SQ1_MASK GENMASK(4, 0) - +#define STM32_ADC_MAX_SQ 16 /* SQ1..SQ16 */ #define STM32_ADC_TIMEOUT_US 100000 #define STM32_ADC_TIMEOUT (msecs_to_jiffies(STM32_ADC_TIMEOUT_US / 1000)) +#define STM32_DMA_BUFFER_SIZE PAGE_SIZE + +/* External trigger enable */ +enum stm32_adc_exten { + STM32_EXTEN_SWTRIG, + STM32_EXTEN_HWTRIG_RISING_EDGE, + STM32_EXTEN_HWTRIG_FALLING_EDGE, + STM32_EXTEN_HWTRIG_BOTH_EDGES, +}; + +/* extsel - trigger mux selection value */ +enum stm32_adc_extsel { + STM32_EXT0, + STM32_EXT1, + STM32_EXT2, + STM32_EXT3, + STM32_EXT4, + STM32_EXT5, + STM32_EXT6, + STM32_EXT7, + STM32_EXT8, + STM32_EXT9, + STM32_EXT10, + STM32_EXT11, + STM32_EXT12, + STM32_EXT13, + STM32_EXT14, + STM32_EXT15, +}; + +/** + * struct stm32_adc_trig_info - ADC trigger info + * @name: name of the trigger, corresponding to its source + * @extsel: trigger selection + */ +struct stm32_adc_trig_info { + const char *name; + enum stm32_adc_extsel extsel; +}; + +/** + * stm32_adc_regs - stm32 ADC misc registers & bitfield desc + * @reg: register offset + * @mask: bitfield mask + * @shift: left shift + */ +struct stm32_adc_regs { + int reg; + int mask; + int shift; +}; + /** * struct stm32_adc - private data of each ADC IIO instance * @common: reference to ADC block common data @@ -82,15 +139,29 @@ * @clk: clock for this adc instance * @irq: interrupt for this adc instance * @lock: spinlock + * @bufi: data buffer index + * @num_conv: expected number of scan conversions + * @trigger_polarity: external trigger polarity (e.g. exten) + * @dma_chan: dma channel + * @rx_buf: dma rx buffer cpu address + * @rx_dma_buf: dma rx buffer bus address + * @rx_buf_sz: dma rx buffer size */ struct stm32_adc { struct stm32_adc_common *common; u32 offset; struct completion completion; - u16 *buffer; + u16 buffer[STM32_ADC_MAX_SQ]; struct clk *clk; int irq; spinlock_t lock; /* interrupt lock */ + unsigned int bufi; + unsigned int num_conv; + u32 trigger_polarity; + struct dma_chan *dma_chan; + u8 *rx_buf; + dma_addr_t rx_dma_buf; + unsigned int rx_buf_sz; }; /** @@ -126,6 +197,53 @@ static const struct stm32_adc_chan_spec stm32f4_adc123_channels[] = { }; /** + * stm32f4_sq - describe regular sequence registers + * - L: sequence len (register & bit field) + * - SQ1..SQ16: sequence entries (register & bit field) + */ +static const struct stm32_adc_regs stm32f4_sq[STM32_ADC_MAX_SQ + 1] = { + /* L: len bit field description to be kept as first element */ + { STM32F4_ADC_SQR1, GENMASK(23, 20), 20 }, + /* SQ1..SQ16 registers & bit fields (reg, mask, shift) */ + { STM32F4_ADC_SQR3, GENMASK(4, 0), 0 }, + { STM32F4_ADC_SQR3, GENMASK(9, 5), 5 }, + { STM32F4_ADC_SQR3, GENMASK(14, 10), 10 }, + { STM32F4_ADC_SQR3, GENMASK(19, 15), 15 }, + { STM32F4_ADC_SQR3, GENMASK(24, 20), 20 }, + { STM32F4_ADC_SQR3, GENMASK(29, 25), 25 }, + { STM32F4_ADC_SQR2, GENMASK(4, 0), 0 }, + { STM32F4_ADC_SQR2, GENMASK(9, 5), 5 }, + { STM32F4_ADC_SQR2, GENMASK(14, 10), 10 }, + { STM32F4_ADC_SQR2, GENMASK(19, 15), 15 }, + { STM32F4_ADC_SQR2, GENMASK(24, 20), 20 }, + { STM32F4_ADC_SQR2, GENMASK(29, 25), 25 }, + { STM32F4_ADC_SQR1, GENMASK(4, 0), 0 }, + { STM32F4_ADC_SQR1, GENMASK(9, 5), 5 }, + { STM32F4_ADC_SQR1, GENMASK(14, 10), 10 }, + { STM32F4_ADC_SQR1, GENMASK(19, 15), 15 }, +}; + +/* STM32F4 external trigger sources for all instances */ +static struct stm32_adc_trig_info stm32f4_adc_trigs[] = { + { TIM1_CH1, STM32_EXT0 }, + { TIM1_CH2, STM32_EXT1 }, + { TIM1_CH3, STM32_EXT2 }, + { TIM2_CH2, STM32_EXT3 }, + { TIM2_CH3, STM32_EXT4 }, + { TIM2_CH4, STM32_EXT5 }, + { TIM2_TRGO, STM32_EXT6 }, + { TIM3_CH1, STM32_EXT7 }, + { TIM3_TRGO, STM32_EXT8 }, + { TIM4_CH4, STM32_EXT9 }, + { TIM5_CH1, STM32_EXT10 }, + { TIM5_CH2, STM32_EXT11 }, + { TIM5_CH3, STM32_EXT12 }, + { TIM8_CH1, STM32_EXT13 }, + { TIM8_TRGO, STM32_EXT14 }, + {}, /* sentinel */ +}; + +/** * STM32 ADC registers access routines * @adc: stm32 adc instance * @reg: reg offset in adc instance @@ -187,10 +305,21 @@ static void stm32_adc_conv_irq_disable(struct stm32_adc *adc) /** * stm32_adc_start_conv() - Start conversions for regular channels. * @adc: stm32 adc instance + * @dma: use dma to transfer conversion result + * + * Start conversions for regular channels. + * Also take care of normal or DMA mode. Circular DMA may be used for regular + * conversions, in IIO buffer modes. Otherwise, use ADC interrupt with direct + * DR read instead (e.g. read_raw, or triggered buffer mode without DMA). */ -static void stm32_adc_start_conv(struct stm32_adc *adc) +static void stm32_adc_start_conv(struct stm32_adc *adc, bool dma) { stm32_adc_set_bits(adc, STM32F4_ADC_CR1, STM32F4_SCAN); + + if (dma) + stm32_adc_set_bits(adc, STM32F4_ADC_CR2, + STM32F4_DMA | STM32F4_DDS); + stm32_adc_set_bits(adc, STM32F4_ADC_CR2, STM32F4_EOCS | STM32F4_ADON); /* Wait for Power-up time (tSTAB from datasheet) */ @@ -207,10 +336,153 @@ static void stm32_adc_stop_conv(struct stm32_adc *adc) stm32_adc_clr_bits(adc, STM32F4_ADC_SR, STM32F4_STRT); stm32_adc_clr_bits(adc, STM32F4_ADC_CR1, STM32F4_SCAN); - stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_ADON); + stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, + STM32F4_ADON | STM32F4_DMA | STM32F4_DDS); +} + +/** + * stm32_adc_conf_scan_seq() - Build regular channels scan sequence + * @indio_dev: IIO device + * @scan_mask: channels to be converted + * + * Conversion sequence : + * Configure ADC scan sequence based on selected channels in scan_mask. + * Add channels to SQR registers, from scan_mask LSB to MSB, then + * program sequence len. + */ +static int stm32_adc_conf_scan_seq(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + const struct iio_chan_spec *chan; + u32 val, bit; + int i = 0; + + for_each_set_bit(bit, scan_mask, indio_dev->masklength) { + chan = indio_dev->channels + bit; + /* + * Assign one channel per SQ entry in regular + * sequence, starting with SQ1. + */ + i++; + if (i > STM32_ADC_MAX_SQ) + return -EINVAL; + + dev_dbg(&indio_dev->dev, "%s chan %d to SQ%d\n", + __func__, chan->channel, i); + + val = stm32_adc_readl(adc, stm32f4_sq[i].reg); + val &= ~stm32f4_sq[i].mask; + val |= chan->channel << stm32f4_sq[i].shift; + stm32_adc_writel(adc, stm32f4_sq[i].reg, val); + } + + if (!i) + return -EINVAL; + + /* Sequence len */ + val = stm32_adc_readl(adc, stm32f4_sq[0].reg); + val &= ~stm32f4_sq[0].mask; + val |= ((i - 1) << stm32f4_sq[0].shift); + stm32_adc_writel(adc, stm32f4_sq[0].reg, val); + + return 0; +} + +/** + * stm32_adc_get_trig_extsel() - Get external trigger selection + * @trig: trigger + * + * Returns trigger extsel value, if trig matches, -EINVAL otherwise. + */ +static int stm32_adc_get_trig_extsel(struct iio_trigger *trig) +{ + int i; + + /* lookup triggers registered by stm32 timer trigger driver */ + for (i = 0; stm32f4_adc_trigs[i].name; i++) { + /** + * Checking both stm32 timer trigger type and trig name + * should be safe against arbitrary trigger names. + */ + if (is_stm32_timer_trigger(trig) && + !strcmp(stm32f4_adc_trigs[i].name, trig->name)) { + return stm32f4_adc_trigs[i].extsel; + } + } + + return -EINVAL; } /** + * stm32_adc_set_trig() - Set a regular trigger + * @indio_dev: IIO device + * @trig: IIO trigger + * + * Set trigger source/polarity (e.g. SW, or HW with polarity) : + * - if HW trigger disabled (e.g. trig == NULL, conversion launched by sw) + * - if HW trigger enabled, set source & polarity + */ +static int stm32_adc_set_trig(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + u32 val, extsel = 0, exten = STM32_EXTEN_SWTRIG; + unsigned long flags; + int ret; + + if (trig) { + ret = stm32_adc_get_trig_extsel(trig); + if (ret < 0) + return ret; + + /* set trigger source and polarity (default to rising edge) */ + extsel = ret; + exten = adc->trigger_polarity + STM32_EXTEN_HWTRIG_RISING_EDGE; + } + + spin_lock_irqsave(&adc->lock, flags); + val = stm32_adc_readl(adc, STM32F4_ADC_CR2); + val &= ~(STM32F4_EXTEN_MASK | STM32F4_EXTSEL_MASK); + val |= exten << STM32F4_EXTEN_SHIFT; + val |= extsel << STM32F4_EXTSEL_SHIFT; + stm32_adc_writel(adc, STM32F4_ADC_CR2, val); + spin_unlock_irqrestore(&adc->lock, flags); + + return 0; +} + +static int stm32_adc_set_trig_pol(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int type) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + + adc->trigger_polarity = type; + + return 0; +} + +static int stm32_adc_get_trig_pol(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + + return adc->trigger_polarity; +} + +static const char * const stm32_trig_pol_items[] = { + "rising-edge", "falling-edge", "both-edges", +}; + +static const struct iio_enum stm32_adc_trig_pol = { + .items = stm32_trig_pol_items, + .num_items = ARRAY_SIZE(stm32_trig_pol_items), + .get = stm32_adc_get_trig_pol, + .set = stm32_adc_set_trig_pol, +}; + +/** * stm32_adc_single_conv() - Performs a single conversion * @indio_dev: IIO device * @chan: IIO channel @@ -228,28 +500,27 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev, struct stm32_adc *adc = iio_priv(indio_dev); long timeout; u32 val; - u16 result; int ret; reinit_completion(&adc->completion); - adc->buffer = &result; + adc->bufi = 0; - /* Program chan number in regular sequence */ - val = stm32_adc_readl(adc, STM32F4_ADC_SQR3); - val &= ~STM32F4_SQ1_MASK; - val |= chan->channel << STM32F4_SQ1_SHIFT; - stm32_adc_writel(adc, STM32F4_ADC_SQR3, val); + /* Program chan number in regular sequence (SQ1) */ + val = stm32_adc_readl(adc, stm32f4_sq[1].reg); + val &= ~stm32f4_sq[1].mask; + val |= chan->channel << stm32f4_sq[1].shift; + stm32_adc_writel(adc, stm32f4_sq[1].reg, val); /* Set regular sequence len (0 for 1 conversion) */ - stm32_adc_clr_bits(adc, STM32F4_ADC_SQR1, STM32F4_L_MASK); + stm32_adc_clr_bits(adc, stm32f4_sq[0].reg, stm32f4_sq[0].mask); /* Trigger detection disabled (conversion can be launched in SW) */ stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_EXTEN_MASK); stm32_adc_conv_irq_enable(adc); - stm32_adc_start_conv(adc); + stm32_adc_start_conv(adc, false); timeout = wait_for_completion_interruptible_timeout( &adc->completion, STM32_ADC_TIMEOUT); @@ -258,7 +529,7 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev, } else if (timeout < 0) { ret = timeout; } else { - *res = result; + *res = adc->buffer[0]; ret = IIO_VAL_INT; } @@ -301,17 +572,73 @@ static int stm32_adc_read_raw(struct iio_dev *indio_dev, static irqreturn_t stm32_adc_isr(int irq, void *data) { struct stm32_adc *adc = data; + struct iio_dev *indio_dev = iio_priv_to_dev(adc); u32 status = stm32_adc_readl(adc, STM32F4_ADC_SR); if (status & STM32F4_EOC) { - *adc->buffer = stm32_adc_readw(adc, STM32F4_ADC_DR); - complete(&adc->completion); + /* Reading DR also clears EOC status flag */ + adc->buffer[adc->bufi] = stm32_adc_readw(adc, STM32F4_ADC_DR); + if (iio_buffer_enabled(indio_dev)) { + adc->bufi++; + if (adc->bufi >= adc->num_conv) { + stm32_adc_conv_irq_disable(adc); + iio_trigger_poll(indio_dev->trig); + } + } else { + complete(&adc->completion); + } return IRQ_HANDLED; } return IRQ_NONE; } +/** + * stm32_adc_validate_trigger() - validate trigger for stm32 adc + * @indio_dev: IIO device + * @trig: new trigger + * + * Returns: 0 if trig matches one of the triggers registered by stm32 adc + * driver, -EINVAL otherwise. + */ +static int stm32_adc_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + return stm32_adc_get_trig_extsel(trig) < 0 ? -EINVAL : 0; +} + +static int stm32_adc_set_watermark(struct iio_dev *indio_dev, unsigned int val) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + unsigned int watermark = STM32_DMA_BUFFER_SIZE / 2; + + /* + * dma cyclic transfers are used, buffer is split into two periods. + * There should be : + * - always one buffer (period) dma is working on + * - one buffer (period) driver can push with iio_trigger_poll(). + */ + watermark = min(watermark, val * (unsigned)(sizeof(u16))); + adc->rx_buf_sz = watermark * 2; + + return 0; +} + +static int stm32_adc_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int ret; + + adc->num_conv = bitmap_weight(scan_mask, indio_dev->masklength); + + ret = stm32_adc_conf_scan_seq(indio_dev, scan_mask); + if (ret) + return ret; + + return 0; +} + static int stm32_adc_of_xlate(struct iio_dev *indio_dev, const struct of_phandle_args *iiospec) { @@ -350,11 +677,199 @@ static int stm32_adc_debugfs_reg_access(struct iio_dev *indio_dev, static const struct iio_info stm32_adc_iio_info = { .read_raw = stm32_adc_read_raw, + .validate_trigger = stm32_adc_validate_trigger, + .hwfifo_set_watermark = stm32_adc_set_watermark, + .update_scan_mode = stm32_adc_update_scan_mode, .debugfs_reg_access = stm32_adc_debugfs_reg_access, .of_xlate = stm32_adc_of_xlate, .driver_module = THIS_MODULE, }; +static unsigned int stm32_adc_dma_residue(struct stm32_adc *adc) +{ + struct dma_tx_state state; + enum dma_status status; + + status = dmaengine_tx_status(adc->dma_chan, + adc->dma_chan->cookie, + &state); + if (status == DMA_IN_PROGRESS) { + /* Residue is size in bytes from end of buffer */ + unsigned int i = adc->rx_buf_sz - state.residue; + unsigned int size; + + /* Return available bytes */ + if (i >= adc->bufi) + size = i - adc->bufi; + else + size = adc->rx_buf_sz + i - adc->bufi; + + return size; + } + + return 0; +} + +static void stm32_adc_dma_buffer_done(void *data) +{ + struct iio_dev *indio_dev = data; + + iio_trigger_poll_chained(indio_dev->trig); +} + +static int stm32_adc_dma_start(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + int ret; + + if (!adc->dma_chan) + return 0; + + dev_dbg(&indio_dev->dev, "%s size=%d watermark=%d\n", __func__, + adc->rx_buf_sz, adc->rx_buf_sz / 2); + + /* Prepare a DMA cyclic transaction */ + desc = dmaengine_prep_dma_cyclic(adc->dma_chan, + adc->rx_dma_buf, + adc->rx_buf_sz, adc->rx_buf_sz / 2, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc) + return -EBUSY; + + desc->callback = stm32_adc_dma_buffer_done; + desc->callback_param = indio_dev; + + cookie = dmaengine_submit(desc); + ret = dma_submit_error(cookie); + if (ret) { + dmaengine_terminate_all(adc->dma_chan); + return ret; + } + + /* Issue pending DMA requests */ + dma_async_issue_pending(adc->dma_chan); + + return 0; +} + +static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int ret; + + ret = stm32_adc_set_trig(indio_dev, indio_dev->trig); + if (ret) { + dev_err(&indio_dev->dev, "Can't set trigger\n"); + return ret; + } + + ret = stm32_adc_dma_start(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "Can't start dma\n"); + goto err_clr_trig; + } + + ret = iio_triggered_buffer_postenable(indio_dev); + if (ret < 0) + goto err_stop_dma; + + /* Reset adc buffer index */ + adc->bufi = 0; + + if (!adc->dma_chan) + stm32_adc_conv_irq_enable(adc); + + stm32_adc_start_conv(adc, !!adc->dma_chan); + + return 0; + +err_stop_dma: + if (adc->dma_chan) + dmaengine_terminate_all(adc->dma_chan); +err_clr_trig: + stm32_adc_set_trig(indio_dev, NULL); + + return ret; +} + +static int stm32_adc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + int ret; + + stm32_adc_stop_conv(adc); + if (!adc->dma_chan) + stm32_adc_conv_irq_disable(adc); + + ret = iio_triggered_buffer_predisable(indio_dev); + if (ret < 0) + dev_err(&indio_dev->dev, "predisable failed\n"); + + if (adc->dma_chan) + dmaengine_terminate_all(adc->dma_chan); + + if (stm32_adc_set_trig(indio_dev, NULL)) + dev_err(&indio_dev->dev, "Can't clear trigger\n"); + + return ret; +} + +static const struct iio_buffer_setup_ops stm32_adc_buffer_setup_ops = { + .postenable = &stm32_adc_buffer_postenable, + .predisable = &stm32_adc_buffer_predisable, +}; + +static irqreturn_t stm32_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct stm32_adc *adc = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "%s bufi=%d\n", __func__, adc->bufi); + + if (!adc->dma_chan) { + /* reset buffer index */ + adc->bufi = 0; + iio_push_to_buffers_with_timestamp(indio_dev, adc->buffer, + pf->timestamp); + } else { + int residue = stm32_adc_dma_residue(adc); + + while (residue >= indio_dev->scan_bytes) { + u16 *buffer = (u16 *)&adc->rx_buf[adc->bufi]; + + iio_push_to_buffers_with_timestamp(indio_dev, buffer, + pf->timestamp); + residue -= indio_dev->scan_bytes; + adc->bufi += indio_dev->scan_bytes; + if (adc->bufi >= adc->rx_buf_sz) + adc->bufi = 0; + } + } + + iio_trigger_notify_done(indio_dev->trig); + + /* re-enable eoc irq */ + if (!adc->dma_chan) + stm32_adc_conv_irq_enable(adc); + + return IRQ_HANDLED; +} + +static const struct iio_chan_spec_ext_info stm32_adc_ext_info[] = { + IIO_ENUM("trigger_polarity", IIO_SHARED_BY_ALL, &stm32_adc_trig_pol), + { + .name = "trigger_polarity_available", + .shared = IIO_SHARED_BY_ALL, + .read = iio_enum_available_read, + .private = (uintptr_t)&stm32_adc_trig_pol, + }, + {}, +}; + static void stm32_adc_chan_init_one(struct iio_dev *indio_dev, struct iio_chan_spec *chan, const struct stm32_adc_chan_spec *channel, @@ -370,6 +885,7 @@ static void stm32_adc_chan_init_one(struct iio_dev *indio_dev, chan->scan_type.sign = 'u'; chan->scan_type.realbits = 12; chan->scan_type.storagebits = 16; + chan->ext_info = stm32_adc_ext_info; } static int stm32_adc_chan_of_init(struct iio_dev *indio_dev) @@ -410,6 +926,45 @@ static int stm32_adc_chan_of_init(struct iio_dev *indio_dev) return 0; } +static int stm32_adc_dma_request(struct iio_dev *indio_dev) +{ + struct stm32_adc *adc = iio_priv(indio_dev); + struct dma_slave_config config; + int ret; + + adc->dma_chan = dma_request_slave_channel(&indio_dev->dev, "rx"); + if (!adc->dma_chan) + return 0; + + adc->rx_buf = dma_alloc_coherent(adc->dma_chan->device->dev, + STM32_DMA_BUFFER_SIZE, + &adc->rx_dma_buf, GFP_KERNEL); + if (!adc->rx_buf) { + ret = -ENOMEM; + goto err_release; + } + + /* Configure DMA channel to read data register */ + memset(&config, 0, sizeof(config)); + config.src_addr = (dma_addr_t)adc->common->phys_base; + config.src_addr += adc->offset + STM32F4_ADC_DR; + config.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + ret = dmaengine_slave_config(adc->dma_chan, &config); + if (ret) + goto err_free; + + return 0; + +err_free: + dma_free_coherent(adc->dma_chan->device->dev, STM32_DMA_BUFFER_SIZE, + adc->rx_buf, adc->rx_dma_buf); +err_release: + dma_release_channel(adc->dma_chan); + + return ret; +} + static int stm32_adc_probe(struct platform_device *pdev) { struct iio_dev *indio_dev; @@ -471,14 +1026,37 @@ static int stm32_adc_probe(struct platform_device *pdev) if (ret < 0) goto err_clk_disable; + ret = stm32_adc_dma_request(indio_dev); + if (ret < 0) + goto err_clk_disable; + + ret = iio_triggered_buffer_setup(indio_dev, + &iio_pollfunc_store_time, + &stm32_adc_trigger_handler, + &stm32_adc_buffer_setup_ops); + if (ret) { + dev_err(&pdev->dev, "buffer setup failed\n"); + goto err_dma_disable; + } + ret = iio_device_register(indio_dev); if (ret) { dev_err(&pdev->dev, "iio dev register failed\n"); - goto err_clk_disable; + goto err_buffer_cleanup; } return 0; +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + +err_dma_disable: + if (adc->dma_chan) { + dma_free_coherent(adc->dma_chan->device->dev, + STM32_DMA_BUFFER_SIZE, + adc->rx_buf, adc->rx_dma_buf); + dma_release_channel(adc->dma_chan); + } err_clk_disable: clk_disable_unprepare(adc->clk); @@ -491,6 +1069,13 @@ static int stm32_adc_remove(struct platform_device *pdev) struct iio_dev *indio_dev = iio_priv_to_dev(adc); iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (adc->dma_chan) { + dma_free_coherent(adc->dma_chan->device->dev, + STM32_DMA_BUFFER_SIZE, + adc->rx_buf, adc->rx_dma_buf); + dma_release_channel(adc->dma_chan); + } clk_disable_unprepare(adc->clk); return 0; diff --git a/drivers/iio/adc/stx104.c b/drivers/iio/adc/stx104.c index 7e3645749eaf..be2de48844bc 100644 --- a/drivers/iio/adc/stx104.c +++ b/drivers/iio/adc/stx104.c @@ -76,16 +76,6 @@ struct stx104_gpio { unsigned int out_state; }; -/** - * struct stx104_dev - STX104 device private data structure - * @indio_dev: IIO device - * @chip: instance of the gpio_chip - */ -struct stx104_dev { - struct iio_dev *indio_dev; - struct gpio_chip *chip; -}; - static int stx104_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { @@ -266,12 +256,38 @@ static void stx104_gpio_set(struct gpio_chip *chip, unsigned int offset, spin_unlock_irqrestore(&stx104gpio->lock, flags); } +#define STX104_NGPIO 8 +static const char *stx104_names[STX104_NGPIO] = { + "DIN0", "DIN1", "DIN2", "DIN3", "DOUT0", "DOUT1", "DOUT2", "DOUT3" +}; + +static void stx104_gpio_set_multiple(struct gpio_chip *chip, + unsigned long *mask, unsigned long *bits) +{ + struct stx104_gpio *const stx104gpio = gpiochip_get_data(chip); + unsigned long flags; + + /* verify masked GPIO are output */ + if (!(*mask & 0xF0)) + return; + + *mask >>= 4; + *bits >>= 4; + + spin_lock_irqsave(&stx104gpio->lock, flags); + + stx104gpio->out_state &= ~*mask; + stx104gpio->out_state |= *mask & *bits; + outb(stx104gpio->out_state, stx104gpio->base); + + spin_unlock_irqrestore(&stx104gpio->lock, flags); +} + static int stx104_probe(struct device *dev, unsigned int id) { struct iio_dev *indio_dev; struct stx104_iio *priv; struct stx104_gpio *stx104gpio; - struct stx104_dev *stx104dev; int err; indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); @@ -282,10 +298,6 @@ static int stx104_probe(struct device *dev, unsigned int id) if (!stx104gpio) return -ENOMEM; - stx104dev = devm_kzalloc(dev, sizeof(*stx104dev), GFP_KERNEL); - if (!stx104dev) - return -ENOMEM; - if (!devm_request_region(dev, base[id], STX104_EXTENT, dev_name(dev))) { dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", @@ -324,45 +336,26 @@ static int stx104_probe(struct device *dev, unsigned int id) stx104gpio->chip.parent = dev; stx104gpio->chip.owner = THIS_MODULE; stx104gpio->chip.base = -1; - stx104gpio->chip.ngpio = 8; + stx104gpio->chip.ngpio = STX104_NGPIO; + stx104gpio->chip.names = stx104_names; stx104gpio->chip.get_direction = stx104_gpio_get_direction; stx104gpio->chip.direction_input = stx104_gpio_direction_input; stx104gpio->chip.direction_output = stx104_gpio_direction_output; stx104gpio->chip.get = stx104_gpio_get; stx104gpio->chip.set = stx104_gpio_set; + stx104gpio->chip.set_multiple = stx104_gpio_set_multiple; stx104gpio->base = base[id] + 3; stx104gpio->out_state = 0x0; spin_lock_init(&stx104gpio->lock); - stx104dev->indio_dev = indio_dev; - stx104dev->chip = &stx104gpio->chip; - dev_set_drvdata(dev, stx104dev); - - err = gpiochip_add_data(&stx104gpio->chip, stx104gpio); + err = devm_gpiochip_add_data(dev, &stx104gpio->chip, stx104gpio); if (err) { dev_err(dev, "GPIO registering failed (%d)\n", err); return err; } - err = iio_device_register(indio_dev); - if (err) { - dev_err(dev, "IIO device registering failed (%d)\n", err); - gpiochip_remove(&stx104gpio->chip); - return err; - } - - return 0; -} - -static int stx104_remove(struct device *dev, unsigned int id) -{ - struct stx104_dev *const stx104dev = dev_get_drvdata(dev); - - iio_device_unregister(stx104dev->indio_dev); - gpiochip_remove(stx104dev->chip); - - return 0; + return devm_iio_device_register(dev, indio_dev); } static struct isa_driver stx104_driver = { @@ -370,7 +363,6 @@ static struct isa_driver stx104_driver = { .driver = { .name = "stx104" }, - .remove = stx104_remove }; module_isa_driver(stx104_driver, num_stx104); diff --git a/drivers/iio/adc/ti-ads1015.c b/drivers/iio/adc/ti-ads1015.c index cde6f130a99a..422b314f5a3f 100644 --- a/drivers/iio/adc/ti-ads1015.c +++ b/drivers/iio/adc/ti-ads1015.c @@ -472,14 +472,14 @@ static const struct attribute_group ads1115_attribute_group = { .attrs = ads1115_attributes, }; -static struct iio_info ads1015_info = { +static const struct iio_info ads1015_info = { .driver_module = THIS_MODULE, .read_raw = ads1015_read_raw, .write_raw = ads1015_write_raw, .attrs = &ads1015_attribute_group, }; -static struct iio_info ads1115_info = { +static const struct iio_info ads1115_info = { .driver_module = THIS_MODULE, .read_raw = ads1015_read_raw, .write_raw = ads1015_write_raw, diff --git a/drivers/iio/adc/ti-ads7950.c b/drivers/iio/adc/ti-ads7950.c new file mode 100644 index 000000000000..16a06633332c --- /dev/null +++ b/drivers/iio/adc/ti-ads7950.c @@ -0,0 +1,490 @@ +/* + * Texas Instruments ADS7950 SPI ADC driver + * + * Copyright 2016 David Lechner <david@lechnology.com> + * + * Based on iio/ad7923.c: + * Copyright 2011 Analog Devices Inc + * Copyright 2012 CS Systemes d'Information + * + * And also on hwmon/ads79xx.c + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/ + * Nishanth Menon + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define TI_ADS7950_CR_MANUAL BIT(12) +#define TI_ADS7950_CR_WRITE BIT(11) +#define TI_ADS7950_CR_CHAN(ch) ((ch) << 7) +#define TI_ADS7950_CR_RANGE_5V BIT(6) + +#define TI_ADS7950_MAX_CHAN 16 + +#define TI_ADS7950_TIMESTAMP_SIZE (sizeof(int64_t) / sizeof(__be16)) + +/* val = value, dec = left shift, bits = number of bits of the mask */ +#define TI_ADS7950_EXTRACT(val, dec, bits) \ + (((val) >> (dec)) & ((1 << (bits)) - 1)) + +struct ti_ads7950_state { + struct spi_device *spi; + struct spi_transfer ring_xfer[TI_ADS7950_MAX_CHAN + 2]; + struct spi_transfer scan_single_xfer[3]; + struct spi_message ring_msg; + struct spi_message scan_single_msg; + + struct regulator *reg; + + unsigned int settings; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + __be16 rx_buf[TI_ADS7950_MAX_CHAN + TI_ADS7950_TIMESTAMP_SIZE] + ____cacheline_aligned; + __be16 tx_buf[TI_ADS7950_MAX_CHAN]; +}; + +struct ti_ads7950_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +enum ti_ads7950_id { + TI_ADS7950, + TI_ADS7951, + TI_ADS7952, + TI_ADS7953, + TI_ADS7954, + TI_ADS7955, + TI_ADS7956, + TI_ADS7957, + TI_ADS7958, + TI_ADS7959, + TI_ADS7960, + TI_ADS7961, +}; + +#define TI_ADS7950_V_CHAN(index, bits) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .datasheet_name = "CH##index", \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = bits, \ + .storagebits = 16, \ + .shift = 12 - (bits), \ + .endianness = IIO_BE, \ + }, \ +} + +#define DECLARE_TI_ADS7950_4_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + TI_ADS7950_V_CHAN(0, bits), \ + TI_ADS7950_V_CHAN(1, bits), \ + TI_ADS7950_V_CHAN(2, bits), \ + TI_ADS7950_V_CHAN(3, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(4), \ +} + +#define DECLARE_TI_ADS7950_8_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + TI_ADS7950_V_CHAN(0, bits), \ + TI_ADS7950_V_CHAN(1, bits), \ + TI_ADS7950_V_CHAN(2, bits), \ + TI_ADS7950_V_CHAN(3, bits), \ + TI_ADS7950_V_CHAN(4, bits), \ + TI_ADS7950_V_CHAN(5, bits), \ + TI_ADS7950_V_CHAN(6, bits), \ + TI_ADS7950_V_CHAN(7, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(8), \ +} + +#define DECLARE_TI_ADS7950_12_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + TI_ADS7950_V_CHAN(0, bits), \ + TI_ADS7950_V_CHAN(1, bits), \ + TI_ADS7950_V_CHAN(2, bits), \ + TI_ADS7950_V_CHAN(3, bits), \ + TI_ADS7950_V_CHAN(4, bits), \ + TI_ADS7950_V_CHAN(5, bits), \ + TI_ADS7950_V_CHAN(6, bits), \ + TI_ADS7950_V_CHAN(7, bits), \ + TI_ADS7950_V_CHAN(8, bits), \ + TI_ADS7950_V_CHAN(9, bits), \ + TI_ADS7950_V_CHAN(10, bits), \ + TI_ADS7950_V_CHAN(11, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(12), \ +} + +#define DECLARE_TI_ADS7950_16_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + TI_ADS7950_V_CHAN(0, bits), \ + TI_ADS7950_V_CHAN(1, bits), \ + TI_ADS7950_V_CHAN(2, bits), \ + TI_ADS7950_V_CHAN(3, bits), \ + TI_ADS7950_V_CHAN(4, bits), \ + TI_ADS7950_V_CHAN(5, bits), \ + TI_ADS7950_V_CHAN(6, bits), \ + TI_ADS7950_V_CHAN(7, bits), \ + TI_ADS7950_V_CHAN(8, bits), \ + TI_ADS7950_V_CHAN(9, bits), \ + TI_ADS7950_V_CHAN(10, bits), \ + TI_ADS7950_V_CHAN(11, bits), \ + TI_ADS7950_V_CHAN(12, bits), \ + TI_ADS7950_V_CHAN(13, bits), \ + TI_ADS7950_V_CHAN(14, bits), \ + TI_ADS7950_V_CHAN(15, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(16), \ +} + +static DECLARE_TI_ADS7950_4_CHANNELS(ti_ads7950, 12); +static DECLARE_TI_ADS7950_8_CHANNELS(ti_ads7951, 12); +static DECLARE_TI_ADS7950_12_CHANNELS(ti_ads7952, 12); +static DECLARE_TI_ADS7950_16_CHANNELS(ti_ads7953, 12); +static DECLARE_TI_ADS7950_4_CHANNELS(ti_ads7954, 10); +static DECLARE_TI_ADS7950_8_CHANNELS(ti_ads7955, 10); +static DECLARE_TI_ADS7950_12_CHANNELS(ti_ads7956, 10); +static DECLARE_TI_ADS7950_16_CHANNELS(ti_ads7957, 10); +static DECLARE_TI_ADS7950_4_CHANNELS(ti_ads7958, 8); +static DECLARE_TI_ADS7950_8_CHANNELS(ti_ads7959, 8); +static DECLARE_TI_ADS7950_12_CHANNELS(ti_ads7960, 8); +static DECLARE_TI_ADS7950_16_CHANNELS(ti_ads7961, 8); + +static const struct ti_ads7950_chip_info ti_ads7950_chip_info[] = { + [TI_ADS7950] = { + .channels = ti_ads7950_channels, + .num_channels = ARRAY_SIZE(ti_ads7950_channels), + }, + [TI_ADS7951] = { + .channels = ti_ads7951_channels, + .num_channels = ARRAY_SIZE(ti_ads7951_channels), + }, + [TI_ADS7952] = { + .channels = ti_ads7952_channels, + .num_channels = ARRAY_SIZE(ti_ads7952_channels), + }, + [TI_ADS7953] = { + .channels = ti_ads7953_channels, + .num_channels = ARRAY_SIZE(ti_ads7953_channels), + }, + [TI_ADS7954] = { + .channels = ti_ads7954_channels, + .num_channels = ARRAY_SIZE(ti_ads7954_channels), + }, + [TI_ADS7955] = { + .channels = ti_ads7955_channels, + .num_channels = ARRAY_SIZE(ti_ads7955_channels), + }, + [TI_ADS7956] = { + .channels = ti_ads7956_channels, + .num_channels = ARRAY_SIZE(ti_ads7956_channels), + }, + [TI_ADS7957] = { + .channels = ti_ads7957_channels, + .num_channels = ARRAY_SIZE(ti_ads7957_channels), + }, + [TI_ADS7958] = { + .channels = ti_ads7958_channels, + .num_channels = ARRAY_SIZE(ti_ads7958_channels), + }, + [TI_ADS7959] = { + .channels = ti_ads7959_channels, + .num_channels = ARRAY_SIZE(ti_ads7959_channels), + }, + [TI_ADS7960] = { + .channels = ti_ads7960_channels, + .num_channels = ARRAY_SIZE(ti_ads7960_channels), + }, + [TI_ADS7961] = { + .channels = ti_ads7961_channels, + .num_channels = ARRAY_SIZE(ti_ads7961_channels), + }, +}; + +/* + * ti_ads7950_update_scan_mode() setup the spi transfer buffer for the new + * scan mask + */ +static int ti_ads7950_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ti_ads7950_state *st = iio_priv(indio_dev); + int i, cmd, len; + + len = 0; + for_each_set_bit(i, active_scan_mask, indio_dev->num_channels) { + cmd = TI_ADS7950_CR_WRITE | TI_ADS7950_CR_CHAN(i) | st->settings; + st->tx_buf[len++] = cpu_to_be16(cmd); + } + + /* Data for the 1st channel is not returned until the 3rd transfer */ + len += 2; + for (i = 0; i < len; i++) { + if ((i + 2) < len) + st->ring_xfer[i].tx_buf = &st->tx_buf[i]; + if (i >= 2) + st->ring_xfer[i].rx_buf = &st->rx_buf[i - 2]; + st->ring_xfer[i].len = 2; + st->ring_xfer[i].cs_change = 1; + } + /* make sure last transfer's cs_change is not set */ + st->ring_xfer[len - 1].cs_change = 0; + + spi_message_init_with_transfers(&st->ring_msg, st->ring_xfer, len); + + return 0; +} + +static irqreturn_t ti_ads7950_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ti_ads7950_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_sync(st->spi, &st->ring_msg); + if (ret < 0) + goto out; + + iio_push_to_buffers_with_timestamp(indio_dev, st->rx_buf, + iio_get_time_ns(indio_dev)); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ti_ads7950_scan_direct(struct ti_ads7950_state *st, unsigned int ch) +{ + int ret, cmd; + + cmd = TI_ADS7950_CR_WRITE | TI_ADS7950_CR_CHAN(ch) | st->settings; + st->tx_buf[0] = cpu_to_be16(cmd); + + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret) + return ret; + + return be16_to_cpu(st->rx_buf[0]); +} + +static int ti_ads7950_get_range(struct ti_ads7950_state *st) +{ + int vref; + + vref = regulator_get_voltage(st->reg); + if (vref < 0) + return vref; + + vref /= 1000; + + if (st->settings & TI_ADS7950_CR_RANGE_5V) + vref *= 2; + + return vref; +} + +static int ti_ads7950_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + struct ti_ads7950_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret < 0) + return ret; + + ret = ti_ads7950_scan_direct(st, chan->address); + iio_device_release_direct_mode(indio_dev); + if (ret < 0) + return ret; + + if (chan->address != TI_ADS7950_EXTRACT(ret, 12, 4)) + return -EIO; + + *val = TI_ADS7950_EXTRACT(ret, chan->scan_type.shift, + chan->scan_type.realbits); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = ti_ads7950_get_range(st); + if (ret < 0) + return ret; + + *val = ret; + *val2 = (1 << chan->scan_type.realbits) - 1; + + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static const struct iio_info ti_ads7950_info = { + .read_raw = &ti_ads7950_read_raw, + .update_scan_mode = ti_ads7950_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static int ti_ads7950_probe(struct spi_device *spi) +{ + struct ti_ads7950_state *st; + struct iio_dev *indio_dev; + const struct ti_ads7950_chip_info *info; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + st->settings = TI_ADS7950_CR_MANUAL | TI_ADS7950_CR_RANGE_5V; + + info = &ti_ads7950_chip_info[spi_get_device_id(spi)->driver_data]; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + indio_dev->info = &ti_ads7950_info; + + /* + * Setup default message. The sample is read at the end of the first + * transfer, then it takes one full cycle to convert the sample and one + * more cycle to send the value. The conversion process is driven by + * the SPI clock, which is why we have 3 transfers. The middle one is + * just dummy data sent while the chip is converting the sample that + * was read at the end of the first transfer. + */ + + st->scan_single_xfer[0].tx_buf = &st->tx_buf[0]; + st->scan_single_xfer[0].len = 2; + st->scan_single_xfer[0].cs_change = 1; + st->scan_single_xfer[1].tx_buf = &st->tx_buf[0]; + st->scan_single_xfer[1].len = 2; + st->scan_single_xfer[1].cs_change = 1; + st->scan_single_xfer[2].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[2].len = 2; + + spi_message_init_with_transfers(&st->scan_single_msg, + st->scan_single_xfer, 3); + + st->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(st->reg)) { + dev_err(&spi->dev, "Failed get get regulator \"vref\"\n"); + return PTR_ERR(st->reg); + } + + ret = regulator_enable(st->reg); + if (ret) { + dev_err(&spi->dev, "Failed to enable regulator \"vref\"\n"); + return ret; + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &ti_ads7950_trigger_handler, NULL); + if (ret) { + dev_err(&spi->dev, "Failed to setup triggered buffer\n"); + goto error_disable_reg; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to register iio device\n"); + goto error_cleanup_ring; + } + + return 0; + +error_cleanup_ring: + iio_triggered_buffer_cleanup(indio_dev); +error_disable_reg: + regulator_disable(st->reg); + + return ret; +} + +static int ti_ads7950_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ti_ads7950_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ti_ads7950_id[] = { + { "ads7950", TI_ADS7950 }, + { "ads7951", TI_ADS7951 }, + { "ads7952", TI_ADS7952 }, + { "ads7953", TI_ADS7953 }, + { "ads7954", TI_ADS7954 }, + { "ads7955", TI_ADS7955 }, + { "ads7956", TI_ADS7956 }, + { "ads7957", TI_ADS7957 }, + { "ads7958", TI_ADS7958 }, + { "ads7959", TI_ADS7959 }, + { "ads7960", TI_ADS7960 }, + { "ads7961", TI_ADS7961 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ti_ads7950_id); + +static struct spi_driver ti_ads7950_driver = { + .driver = { + .name = "ads7950", + }, + .probe = ti_ads7950_probe, + .remove = ti_ads7950_remove, + .id_table = ti_ads7950_id, +}; +module_spi_driver(ti_ads7950_driver); + +MODULE_AUTHOR("David Lechner <david@lechnology.com>"); +MODULE_DESCRIPTION("TI TI_ADS7950 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-tlc4541.c b/drivers/iio/adc/ti-tlc4541.c new file mode 100644 index 000000000000..78d91a069ea4 --- /dev/null +++ b/drivers/iio/adc/ti-tlc4541.c @@ -0,0 +1,271 @@ +/* + * TI tlc4541 ADC Driver + * + * Copyright (C) 2017 Phil Reid + * + * Datasheets can be found here: + * http://www.ti.com/lit/gpn/tlc3541 + * http://www.ti.com/lit/gpn/tlc4541 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The tlc4541 requires 24 clock cycles to start a transfer. + * Conversion then takes 2.94us to complete before data is ready + * Data is returned MSB first. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> + +struct tlc4541_state { + struct spi_device *spi; + struct regulator *reg; + struct spi_transfer scan_single_xfer[3]; + struct spi_message scan_single_msg; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + * 2 bytes data + 6 bytes padding + 8 bytes timestamp when + * call iio_push_to_buffers_with_timestamp. + */ + __be16 rx_buf[8] ____cacheline_aligned; +}; + +struct tlc4541_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +enum tlc4541_id { + TLC3541, + TLC4541, +}; + +#define TLC4541_V_CHAN(bits, bitshift) { \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = (bitshift), \ + .endianness = IIO_BE, \ + }, \ + } + +#define DECLARE_TLC4541_CHANNELS(name, bits, bitshift) \ +const struct iio_chan_spec name ## _channels[] = { \ + TLC4541_V_CHAN(bits, bitshift), \ + IIO_CHAN_SOFT_TIMESTAMP(1), \ +} + +static DECLARE_TLC4541_CHANNELS(tlc3541, 14, 2); +static DECLARE_TLC4541_CHANNELS(tlc4541, 16, 0); + +static const struct tlc4541_chip_info tlc4541_chip_info[] = { + [TLC3541] = { + .channels = tlc3541_channels, + .num_channels = ARRAY_SIZE(tlc3541_channels), + }, + [TLC4541] = { + .channels = tlc4541_channels, + .num_channels = ARRAY_SIZE(tlc4541_channels), + }, +}; + +static irqreturn_t tlc4541_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tlc4541_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, st->rx_buf, + iio_get_time_ns(indio_dev)); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int tlc4541_get_range(struct tlc4541_state *st) +{ + int vref; + + vref = regulator_get_voltage(st->reg); + if (vref < 0) + return vref; + + vref /= 1000; + + return vref; +} + +static int tlc4541_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret = 0; + struct tlc4541_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = spi_sync(st->spi, &st->scan_single_msg); + iio_device_release_direct_mode(indio_dev); + if (ret < 0) + return ret; + *val = be16_to_cpu(st->rx_buf[0]); + *val = *val >> chan->scan_type.shift; + *val &= GENMASK(chan->scan_type.realbits - 1, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = tlc4541_get_range(st); + if (ret < 0) + return ret; + *val = ret; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static const struct iio_info tlc4541_info = { + .read_raw = &tlc4541_read_raw, + .driver_module = THIS_MODULE, +}; + +static int tlc4541_probe(struct spi_device *spi) +{ + struct tlc4541_state *st; + struct iio_dev *indio_dev; + const struct tlc4541_chip_info *info; + int ret; + int8_t device_init = 0; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + info = &tlc4541_chip_info[spi_get_device_id(spi)->driver_data]; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + indio_dev->info = &tlc4541_info; + + /* perform reset */ + spi_write(spi, &device_init, 1); + + /* Setup default message */ + st->scan_single_xfer[0].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[0].len = 3; + st->scan_single_xfer[1].delay_usecs = 3; + st->scan_single_xfer[2].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[2].len = 2; + + spi_message_init_with_transfers(&st->scan_single_msg, + st->scan_single_xfer, 3); + + st->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &tlc4541_trigger_handler, NULL); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer; + + return 0; + +error_cleanup_buffer: + iio_triggered_buffer_cleanup(indio_dev); +error_disable_reg: + regulator_disable(st->reg); + + return ret; +} + +static int tlc4541_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct tlc4541_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(st->reg); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id tlc4541_dt_ids[] = { + { .compatible = "ti,tlc3541", }, + { .compatible = "ti,tlc4541", }, + {} +}; +MODULE_DEVICE_TABLE(of, tlc4541_dt_ids); +#endif + +static const struct spi_device_id tlc4541_id[] = { + {"tlc3541", TLC3541}, + {"tlc4541", TLC4541}, + {} +}; +MODULE_DEVICE_TABLE(spi, tlc4541_id); + +static struct spi_driver tlc4541_driver = { + .driver = { + .name = "tlc4541", + .of_match_table = of_match_ptr(tlc4541_dt_ids), + }, + .probe = tlc4541_probe, + .remove = tlc4541_remove, + .id_table = tlc4541_id, +}; +module_spi_driver(tlc4541_driver); + +MODULE_AUTHOR("Phil Reid <preid@electromag.com.au>"); +MODULE_DESCRIPTION("Texas Instruments TLC4541 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c index b8f550e47d3d..4847534700e7 100644 --- a/drivers/iio/buffer/industrialio-buffer-cb.c +++ b/drivers/iio/buffer/industrialio-buffer-cb.c @@ -10,7 +10,8 @@ #include <linux/slab.h> #include <linux/err.h> #include <linux/export.h> -#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer_impl.h> #include <linux/iio/consumer.h> struct iio_cb_buffer { diff --git a/drivers/iio/buffer/kfifo_buf.c b/drivers/iio/buffer/kfifo_buf.c index c5b999f0c519..047fe757ab97 100644 --- a/drivers/iio/buffer/kfifo_buf.c +++ b/drivers/iio/buffer/kfifo_buf.c @@ -5,7 +5,10 @@ #include <linux/workqueue.h> #include <linux/kfifo.h> #include <linux/mutex.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> #include <linux/iio/kfifo_buf.h> +#include <linux/iio/buffer_impl.h> #include <linux/sched.h> #include <linux/poll.h> diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c index 7ef94a90ecf7..7afdac42ed42 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -58,6 +58,10 @@ static struct { {HID_USAGE_SENSOR_PRESSURE, 0, 100, 0}, {HID_USAGE_SENSOR_PRESSURE, HID_USAGE_SENSOR_UNITS_PASCAL, 0, 1000000}, + + {HID_USAGE_SENSOR_TIME_TIMESTAMP, 0, 1000000000, 0}, + {HID_USAGE_SENSOR_TIME_TIMESTAMP, HID_USAGE_SENSOR_UNITS_MILLISECOND, + 1000000, 0}, }; static int pow_10(unsigned power) @@ -346,6 +350,13 @@ int hid_sensor_format_scale(u32 usage_id, } EXPORT_SYMBOL(hid_sensor_format_scale); +int64_t hid_sensor_convert_timestamp(struct hid_sensor_common *st, + int64_t raw_value) +{ + return st->timestamp_ns_scale * raw_value; +} +EXPORT_SYMBOL(hid_sensor_convert_timestamp); + static int hid_sensor_get_reporting_interval(struct hid_sensor_hub_device *hsdev, u32 usage_id, @@ -367,6 +378,7 @@ int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev, struct hid_sensor_common *st) { + struct hid_sensor_hub_attribute_info timestamp; hid_sensor_get_reporting_interval(hsdev, usage_id, st); @@ -385,11 +397,25 @@ int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev, HID_USAGE_SENSOR_PROP_SENSITIVITY_ABS, &st->sensitivity); - hid_dbg(hsdev->hdev, "common attributes: %x:%x, %x:%x, %x:%x %x:%x\n", - st->poll.index, st->poll.report_id, - st->report_state.index, st->report_state.report_id, - st->power_state.index, st->power_state.report_id, - st->sensitivity.index, st->sensitivity.report_id); + sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, usage_id, + HID_USAGE_SENSOR_TIME_TIMESTAMP, + ×tamp); + if (timestamp.index >= 0 && timestamp.report_id) { + int val0, val1; + + hid_sensor_format_scale(HID_USAGE_SENSOR_TIME_TIMESTAMP, + ×tamp, &val0, &val1); + st->timestamp_ns_scale = val0; + } else + st->timestamp_ns_scale = 1000000000; + + hid_dbg(hsdev->hdev, "common attributes: %x:%x, %x:%x, %x:%x %x:%x %x:%x\n", + st->poll.index, st->poll.report_id, + st->report_state.index, st->report_state.report_id, + st->power_state.index, st->power_state.report_id, + st->sensitivity.index, st->sensitivity.report_id, + timestamp.index, timestamp.report_id); return 0; } diff --git a/drivers/iio/common/ssp_sensors/ssp_iio.c b/drivers/iio/common/ssp_sensors/ssp_iio.c index a3ae165f8d9f..645f2e3975db 100644 --- a/drivers/iio/common/ssp_sensors/ssp_iio.c +++ b/drivers/iio/common/ssp_sensors/ssp_iio.c @@ -14,6 +14,7 @@ */ #include <linux/iio/common/ssp_sensors.h> +#include <linux/iio/buffer.h> #include <linux/iio/kfifo_buf.h> #include <linux/module.h> #include <linux/slab.h> diff --git a/drivers/iio/common/st_sensors/st_sensors_i2c.c b/drivers/iio/common/st_sensors/st_sensors_i2c.c index b43aa36031f8..c83df4dbfcd7 100644 --- a/drivers/iio/common/st_sensors/st_sensors_i2c.c +++ b/drivers/iio/common/st_sensors/st_sensors_i2c.c @@ -13,6 +13,7 @@ #include <linux/slab.h> #include <linux/iio/iio.h> #include <linux/of_device.h> +#include <linux/acpi.h> #include <linux/iio/common/st_sensors_i2c.h> @@ -107,6 +108,25 @@ void st_sensors_of_i2c_probe(struct i2c_client *client, EXPORT_SYMBOL(st_sensors_of_i2c_probe); #endif +#ifdef CONFIG_ACPI +int st_sensors_match_acpi_device(struct device *dev) +{ + const struct acpi_device_id *acpi_id; + kernel_ulong_t driver_data = 0; + + if (ACPI_HANDLE(dev)) { + acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!acpi_id) { + dev_err(dev, "No driver data\n"); + return -EINVAL; + } + driver_data = acpi_id->driver_data; + } + return driver_data; +} +EXPORT_SYMBOL(st_sensors_match_acpi_device); +#endif + MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); MODULE_DESCRIPTION("STMicroelectronics ST-sensors i2c driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5592r.c b/drivers/iio/dac/ad5592r.c index 6eed5b7729be..6a12a3c9d94f 100644 --- a/drivers/iio/dac/ad5592r.c +++ b/drivers/iio/dac/ad5592r.c @@ -13,6 +13,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/spi/spi.h> +#include <linux/acpi.h> #define AD5592R_GPIO_READBACK_EN BIT(10) #define AD5592R_LDAC_READBACK_EN BIT(6) @@ -148,10 +149,17 @@ static const struct of_device_id ad5592r_of_match[] = { }; MODULE_DEVICE_TABLE(of, ad5592r_of_match); +static const struct acpi_device_id ad5592r_acpi_match[] = { + {"ADS5592", }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ad5592r_acpi_match); + static struct spi_driver ad5592r_spi_driver = { .driver = { .name = "ad5592r", .of_match_table = of_match_ptr(ad5592r_of_match), + .acpi_match_table = ACPI_PTR(ad5592r_acpi_match), }, .probe = ad5592r_spi_probe, .remove = ad5592r_spi_remove, diff --git a/drivers/iio/dac/ad5593r.c b/drivers/iio/dac/ad5593r.c index dca158a88f47..fc11ea098f98 100644 --- a/drivers/iio/dac/ad5593r.c +++ b/drivers/iio/dac/ad5593r.c @@ -13,6 +13,7 @@ #include <linux/i2c.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/acpi.h> #define AD5593R_MODE_CONF (0 << 4) #define AD5593R_MODE_DAC_WRITE (1 << 4) @@ -115,10 +116,17 @@ static const struct of_device_id ad5593r_of_match[] = { }; MODULE_DEVICE_TABLE(of, ad5593r_of_match); +static const struct acpi_device_id ad5593r_acpi_match[] = { + {"ADS5593", }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ad5593r_acpi_match); + static struct i2c_driver ad5593r_driver = { .driver = { .name = "ad5593r", .of_match_table = of_match_ptr(ad5593r_of_match), + .acpi_match_table = ACPI_PTR(ad5593r_acpi_match), }, .probe = ad5593r_i2c_probe, .remove = ad5593r_i2c_remove, diff --git a/drivers/iio/dummy/iio_simple_dummy.h b/drivers/iio/dummy/iio_simple_dummy.h index b9069a180672..f7005c3f5df3 100644 --- a/drivers/iio/dummy/iio_simple_dummy.h +++ b/drivers/iio/dummy/iio_simple_dummy.h @@ -88,11 +88,11 @@ static inline int iio_simple_dummy_events_register(struct iio_dev *indio_dev) { return 0; -}; +} static inline void iio_simple_dummy_events_unregister(struct iio_dev *indio_dev) -{ }; +{} #endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS*/ @@ -119,11 +119,11 @@ void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev); static inline int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) { return 0; -}; +} static inline void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev) -{}; +{} #endif /* CONFIG_IIO_SIMPLE_DUMMY_BUFFER */ #endif /* _IIO_SIMPLE_DUMMY_H_ */ diff --git a/drivers/iio/dummy/iio_simple_dummy_buffer.c b/drivers/iio/dummy/iio_simple_dummy_buffer.c index b383892a5193..744ca92c3c99 100644 --- a/drivers/iio/dummy/iio_simple_dummy_buffer.c +++ b/drivers/iio/dummy/iio_simple_dummy_buffer.c @@ -20,6 +20,7 @@ #include <linux/iio/iio.h> #include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> #include <linux/iio/kfifo_buf.h> #include "iio_simple_dummy.h" @@ -131,9 +132,6 @@ int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) iio_device_attach_buffer(indio_dev, buffer); - /* Enable timestamps by default */ - buffer->scan_timestamp = true; - /* * Tell the core what device type specific functions should * be run on either side of buffer capture enable / disable. diff --git a/drivers/iio/gyro/ssp_gyro_sensor.c b/drivers/iio/gyro/ssp_gyro_sensor.c index 1f25f406c545..2dacd8e01045 100644 --- a/drivers/iio/gyro/ssp_gyro_sensor.c +++ b/drivers/iio/gyro/ssp_gyro_sensor.c @@ -15,6 +15,7 @@ #include <linux/iio/common/ssp_sensors.h> #include <linux/iio/iio.h> +#include <linux/iio/buffer.h> #include <linux/iio/kfifo_buf.h> #include <linux/module.h> #include <linux/platform_device.h> @@ -134,7 +135,7 @@ static int ssp_gyro_probe(struct platform_device *pdev) platform_set_drvdata(pdev, indio_dev); - ret = iio_device_register(indio_dev); + ret = devm_iio_device_register(&pdev->dev, indio_dev); if (ret < 0) return ret; @@ -144,21 +145,11 @@ static int ssp_gyro_probe(struct platform_device *pdev) return 0; } -static int ssp_gyro_remove(struct platform_device *pdev) -{ - struct iio_dev *indio_dev = platform_get_drvdata(pdev); - - iio_device_unregister(indio_dev); - - return 0; -} - static struct platform_driver ssp_gyro_driver = { .driver = { .name = SSP_GYROSCOPE_NAME, }, .probe = ssp_gyro_probe, - .remove = ssp_gyro_remove, }; module_platform_driver(ssp_gyro_driver); diff --git a/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c index 183c14329d6e..f6e283c4d686 100644 --- a/drivers/iio/health/max30100.c +++ b/drivers/iio/health/max30100.c @@ -378,7 +378,7 @@ static int max30100_get_temp(struct max30100_data *data, int *val) if (ret) return ret; - usleep_range(35000, 50000); + msleep(35); return max30100_read_temp(data, val); } diff --git a/drivers/iio/humidity/hts221_i2c.c b/drivers/iio/humidity/hts221_i2c.c index 367ecd509f31..8333c0296c0e 100644 --- a/drivers/iio/humidity/hts221_i2c.c +++ b/drivers/iio/humidity/hts221_i2c.c @@ -10,6 +10,7 @@ #include <linux/kernel.h> #include <linux/module.h> +#include <linux/acpi.h> #include <linux/i2c.h> #include <linux/slab.h> #include "hts221.h" @@ -83,6 +84,12 @@ static int hts221_i2c_probe(struct i2c_client *client, return hts221_probe(iio_dev); } +static const struct acpi_device_id hts221_acpi_match[] = { + {"SMO9100", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, hts221_acpi_match); + static const struct of_device_id hts221_i2c_of_match[] = { { .compatible = "st,hts221", }, {}, @@ -99,6 +106,7 @@ static struct i2c_driver hts221_driver = { .driver = { .name = "hts221_i2c", .of_match_table = of_match_ptr(hts221_i2c_of_match), + .acpi_match_table = ACPI_PTR(hts221_acpi_match), }, .probe = hts221_i2c_probe, .id_table = hts221_i2c_id_table, diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig index 1f1ad41ef881..156630a21696 100644 --- a/drivers/iio/imu/Kconfig +++ b/drivers/iio/imu/Kconfig @@ -39,6 +39,7 @@ config KMX61 be called kmx61. source "drivers/iio/imu/inv_mpu6050/Kconfig" +source "drivers/iio/imu/st_lsm6dsx/Kconfig" endmenu diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile index c71bcd30dc38..8b563c3323b5 100644 --- a/drivers/iio/imu/Makefile +++ b/drivers/iio/imu/Makefile @@ -17,3 +17,5 @@ obj-y += bmi160/ obj-y += inv_mpu6050/ obj-$(CONFIG_KMX61) += kmx61.o + +obj-y += st_lsm6dsx/ diff --git a/drivers/iio/imu/bmi160/bmi160_core.c b/drivers/iio/imu/bmi160/bmi160_core.c index c9e319bff58b..cfd225ed1c8d 100644 --- a/drivers/iio/imu/bmi160/bmi160_core.c +++ b/drivers/iio/imu/bmi160/bmi160_core.c @@ -325,9 +325,9 @@ static int bmi160_get_data(struct bmi160_data *data, int chan_type, __le16 sample; enum bmi160_sensor_type t = bmi160_to_sensor(chan_type); - reg = bmi160_regs[t].data + (axis - IIO_MOD_X) * sizeof(__le16); + reg = bmi160_regs[t].data + (axis - IIO_MOD_X) * sizeof(sample); - ret = regmap_bulk_read(data->regmap, reg, &sample, sizeof(__le16)); + ret = regmap_bulk_read(data->regmap, reg, &sample, sizeof(sample)); if (ret < 0) return ret; @@ -392,8 +392,8 @@ static irqreturn_t bmi160_trigger_handler(int irq, void *p) for_each_set_bit(i, indio_dev->active_scan_mask, indio_dev->masklength) { - ret = regmap_bulk_read(data->regmap, base + i * sizeof(__le16), - &sample, sizeof(__le16)); + ret = regmap_bulk_read(data->regmap, base + i * sizeof(sample), + &sample, sizeof(sample)); if (ret < 0) goto done; buf[j++] = sample; diff --git a/drivers/iio/imu/bmi160/bmi160_i2c.c b/drivers/iio/imu/bmi160/bmi160_i2c.c index 07a179d8fb48..155a31f72445 100644 --- a/drivers/iio/imu/bmi160/bmi160_i2c.c +++ b/drivers/iio/imu/bmi160/bmi160_i2c.c @@ -11,10 +11,11 @@ * - 0x68 if SDO is pulled to GND * - 0x69 if SDO is pulled to VDDIO */ -#include <linux/module.h> +#include <linux/acpi.h> #include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> #include <linux/regmap.h> -#include <linux/acpi.h> #include "bmi160.h" @@ -56,10 +57,19 @@ static const struct acpi_device_id bmi160_acpi_match[] = { }; MODULE_DEVICE_TABLE(acpi, bmi160_acpi_match); +#ifdef CONFIG_OF +static const struct of_device_id bmi160_of_match[] = { + { .compatible = "bosch,bmi160" }, + { }, +}; +MODULE_DEVICE_TABLE(of, bmi160_of_match); +#endif + static struct i2c_driver bmi160_i2c_driver = { .driver = { .name = "bmi160_i2c", .acpi_match_table = ACPI_PTR(bmi160_acpi_match), + .of_match_table = of_match_ptr(bmi160_of_match), }, .probe = bmi160_i2c_probe, .remove = bmi160_i2c_remove, diff --git a/drivers/iio/imu/bmi160/bmi160_spi.c b/drivers/iio/imu/bmi160/bmi160_spi.c index 1ec8b12bd984..d34dfdfd1a7d 100644 --- a/drivers/iio/imu/bmi160/bmi160_spi.c +++ b/drivers/iio/imu/bmi160/bmi160_spi.c @@ -7,10 +7,11 @@ * the GNU General Public License. See the file COPYING in the main * directory of this archive for more details. */ +#include <linux/acpi.h> #include <linux/module.h> -#include <linux/spi/spi.h> +#include <linux/of.h> #include <linux/regmap.h> -#include <linux/acpi.h> +#include <linux/spi/spi.h> #include "bmi160.h" @@ -47,13 +48,22 @@ static const struct acpi_device_id bmi160_acpi_match[] = { }; MODULE_DEVICE_TABLE(acpi, bmi160_acpi_match); +#ifdef CONFIG_OF +static const struct of_device_id bmi160_of_match[] = { + { .compatible = "bosch,bmi160" }, + { }, +}; +MODULE_DEVICE_TABLE(of, bmi160_of_match); +#endif + static struct spi_driver bmi160_spi_driver = { .probe = bmi160_spi_probe, .remove = bmi160_spi_remove, .id_table = bmi160_spi_id, .driver = { - .acpi_match_table = ACPI_PTR(bmi160_acpi_match), - .name = "bmi160_spi", + .acpi_match_table = ACPI_PTR(bmi160_acpi_match), + .of_match_table = of_match_ptr(bmi160_of_match), + .name = "bmi160_spi", }, }; module_spi_driver(bmi160_spi_driver); diff --git a/drivers/iio/imu/st_lsm6dsx/Kconfig b/drivers/iio/imu/st_lsm6dsx/Kconfig new file mode 100644 index 000000000000..935d4cd071a3 --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/Kconfig @@ -0,0 +1,22 @@ + +config IIO_ST_LSM6DSX + tristate "ST_LSM6DSx driver for STM 6-axis IMU MEMS sensors" + depends on (I2C || SPI) + select IIO_BUFFER + select IIO_KFIFO_BUF + select IIO_ST_LSM6DSX_I2C if (I2C) + select IIO_ST_LSM6DSX_SPI if (SPI_MASTER) + help + Say yes here to build support for STMicroelectronics LSM6DSx imu + sensor. Supported devices: lsm6ds3, lsm6dsm + + To compile this driver as a module, choose M here: the module + will be called st_lsm6dsx. + +config IIO_ST_LSM6DSX_I2C + tristate + depends on IIO_ST_LSM6DSX + +config IIO_ST_LSM6DSX_SPI + tristate + depends on IIO_ST_LSM6DSX diff --git a/drivers/iio/imu/st_lsm6dsx/Makefile b/drivers/iio/imu/st_lsm6dsx/Makefile new file mode 100644 index 000000000000..35919febea2a --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/Makefile @@ -0,0 +1,5 @@ +st_lsm6dsx-y := st_lsm6dsx_core.o st_lsm6dsx_buffer.o + +obj-$(CONFIG_IIO_ST_LSM6DSX) += st_lsm6dsx.o +obj-$(CONFIG_IIO_ST_LSM6DSX_I2C) += st_lsm6dsx_i2c.o +obj-$(CONFIG_IIO_ST_LSM6DSX_SPI) += st_lsm6dsx_spi.o diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h new file mode 100644 index 000000000000..69deafe1c10d --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -0,0 +1,141 @@ +/* + * STMicroelectronics st_lsm6dsx sensor driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi <lorenzo.bianconi@st.com> + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#ifndef ST_LSM6DSX_H +#define ST_LSM6DSX_H + +#include <linux/device.h> + +#define ST_LSM6DS3_DEV_NAME "lsm6ds3" +#define ST_LSM6DSM_DEV_NAME "lsm6dsm" + +enum st_lsm6dsx_hw_id { + ST_LSM6DS3_ID, + ST_LSM6DSM_ID, +}; + +#define ST_LSM6DSX_CHAN_SIZE 2 +#define ST_LSM6DSX_SAMPLE_SIZE 6 +#define ST_LSM6DSX_SAMPLE_DEPTH (ST_LSM6DSX_SAMPLE_SIZE / \ + ST_LSM6DSX_CHAN_SIZE) + +#if defined(CONFIG_SPI_MASTER) +#define ST_LSM6DSX_RX_MAX_LENGTH 256 +#define ST_LSM6DSX_TX_MAX_LENGTH 8 + +struct st_lsm6dsx_transfer_buffer { + u8 rx_buf[ST_LSM6DSX_RX_MAX_LENGTH]; + u8 tx_buf[ST_LSM6DSX_TX_MAX_LENGTH] ____cacheline_aligned; +}; +#endif /* CONFIG_SPI_MASTER */ + +struct st_lsm6dsx_transfer_function { + int (*read)(struct device *dev, u8 addr, int len, u8 *data); + int (*write)(struct device *dev, u8 addr, int len, u8 *data); +}; + +struct st_lsm6dsx_reg { + u8 addr; + u8 mask; +}; + +struct st_lsm6dsx_settings { + u8 wai; + u16 max_fifo_size; + enum st_lsm6dsx_hw_id id; +}; + +enum st_lsm6dsx_sensor_id { + ST_LSM6DSX_ID_ACC, + ST_LSM6DSX_ID_GYRO, + ST_LSM6DSX_ID_MAX, +}; + +enum st_lsm6dsx_fifo_mode { + ST_LSM6DSX_FIFO_BYPASS = 0x0, + ST_LSM6DSX_FIFO_CONT = 0x6, +}; + +/** + * struct st_lsm6dsx_sensor - ST IMU sensor instance + * @id: Sensor identifier. + * @hw: Pointer to instance of struct st_lsm6dsx_hw. + * @gain: Configured sensor sensitivity. + * @odr: Output data rate of the sensor [Hz]. + * @watermark: Sensor watermark level. + * @sip: Number of samples in a given pattern. + * @decimator: FIFO decimation factor. + * @decimator_mask: Sensor mask for decimation register. + * @delta_ts: Delta time between two consecutive interrupts. + * @ts: Latest timestamp from the interrupt handler. + */ +struct st_lsm6dsx_sensor { + enum st_lsm6dsx_sensor_id id; + struct st_lsm6dsx_hw *hw; + + u32 gain; + u16 odr; + + u16 watermark; + u8 sip; + u8 decimator; + u8 decimator_mask; + + s64 delta_ts; + s64 ts; +}; + +/** + * struct st_lsm6dsx_hw - ST IMU MEMS hw instance + * @dev: Pointer to instance of struct device (I2C or SPI). + * @irq: Device interrupt line (I2C or SPI). + * @lock: Mutex to protect read and write operations. + * @fifo_lock: Mutex to prevent concurrent access to the hw FIFO. + * @fifo_mode: FIFO operating mode supported by the device. + * @enable_mask: Enabled sensor bitmask. + * @sip: Total number of samples (acc/gyro) in a given pattern. + * @iio_devs: Pointers to acc/gyro iio_dev instances. + * @settings: Pointer to the specific sensor settings in use. + * @tf: Transfer function structure used by I/O operations. + * @tb: Transfer buffers used by SPI I/O operations. + */ +struct st_lsm6dsx_hw { + struct device *dev; + int irq; + + struct mutex lock; + struct mutex fifo_lock; + + enum st_lsm6dsx_fifo_mode fifo_mode; + u8 enable_mask; + u8 sip; + + struct iio_dev *iio_devs[ST_LSM6DSX_ID_MAX]; + + const struct st_lsm6dsx_settings *settings; + + const struct st_lsm6dsx_transfer_function *tf; +#if defined(CONFIG_SPI_MASTER) + struct st_lsm6dsx_transfer_buffer tb; +#endif /* CONFIG_SPI_MASTER */ +}; + +int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, + const struct st_lsm6dsx_transfer_function *tf_ops); +int st_lsm6dsx_sensor_enable(struct st_lsm6dsx_sensor *sensor); +int st_lsm6dsx_sensor_disable(struct st_lsm6dsx_sensor *sensor); +int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw); +int st_lsm6dsx_write_with_mask(struct st_lsm6dsx_hw *hw, u8 addr, u8 mask, + u8 val); +int st_lsm6dsx_update_watermark(struct st_lsm6dsx_sensor *sensor, + u16 watermark); + +#endif /* ST_LSM6DSX_H */ diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c new file mode 100644 index 000000000000..78532ce07449 --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c @@ -0,0 +1,454 @@ +/* + * STMicroelectronics st_lsm6dsx FIFO buffer library driver + * + * LSM6DS3/LSM6DSM: The FIFO buffer can be configured to store data + * from gyroscope and accelerometer. Samples are queued without any tag + * according to a specific pattern based on 'FIFO data sets' (6 bytes each): + * - 1st data set is reserved for gyroscope data + * - 2nd data set is reserved for accelerometer data + * The FIFO pattern changes depending on the ODRs and decimation factors + * assigned to the FIFO data sets. The first sequence of data stored in FIFO + * buffer contains the data of all the enabled FIFO data sets + * (e.g. Gx, Gy, Gz, Ax, Ay, Az), then data are repeated depending on the + * value of the decimation factor and ODR set for each FIFO data set. + * FIFO supported modes: + * - BYPASS: FIFO disabled + * - CONTINUOUS: FIFO enabled. When the buffer is full, the FIFO index + * restarts from the beginning and the oldest sample is overwritten + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi <lorenzo.bianconi@st.com> + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> + +#include "st_lsm6dsx.h" + +#define ST_LSM6DSX_REG_FIFO_THL_ADDR 0x06 +#define ST_LSM6DSX_REG_FIFO_THH_ADDR 0x07 +#define ST_LSM6DSX_FIFO_TH_MASK GENMASK(11, 0) +#define ST_LSM6DSX_REG_FIFO_DEC_GXL_ADDR 0x08 +#define ST_LSM6DSX_REG_FIFO_MODE_ADDR 0x0a +#define ST_LSM6DSX_FIFO_MODE_MASK GENMASK(2, 0) +#define ST_LSM6DSX_FIFO_ODR_MASK GENMASK(6, 3) +#define ST_LSM6DSX_REG_FIFO_DIFFL_ADDR 0x3a +#define ST_LSM6DSX_FIFO_DIFF_MASK GENMASK(11, 0) +#define ST_LSM6DSX_FIFO_EMPTY_MASK BIT(12) +#define ST_LSM6DSX_REG_FIFO_OUTL_ADDR 0x3e + +#define ST_LSM6DSX_MAX_FIFO_ODR_VAL 0x08 + +struct st_lsm6dsx_decimator_entry { + u8 decimator; + u8 val; +}; + +static const +struct st_lsm6dsx_decimator_entry st_lsm6dsx_decimator_table[] = { + { 0, 0x0 }, + { 1, 0x1 }, + { 2, 0x2 }, + { 3, 0x3 }, + { 4, 0x4 }, + { 8, 0x5 }, + { 16, 0x6 }, + { 32, 0x7 }, +}; + +static int st_lsm6dsx_get_decimator_val(u8 val) +{ + const int max_size = ARRAY_SIZE(st_lsm6dsx_decimator_table); + int i; + + for (i = 0; i < max_size; i++) + if (st_lsm6dsx_decimator_table[i].decimator == val) + break; + + return i == max_size ? 0 : st_lsm6dsx_decimator_table[i].val; +} + +static void st_lsm6dsx_get_max_min_odr(struct st_lsm6dsx_hw *hw, + u16 *max_odr, u16 *min_odr) +{ + struct st_lsm6dsx_sensor *sensor; + int i; + + *max_odr = 0, *min_odr = ~0; + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + sensor = iio_priv(hw->iio_devs[i]); + + if (!(hw->enable_mask & BIT(sensor->id))) + continue; + + *max_odr = max_t(u16, *max_odr, sensor->odr); + *min_odr = min_t(u16, *min_odr, sensor->odr); + } +} + +static int st_lsm6dsx_update_decimators(struct st_lsm6dsx_hw *hw) +{ + struct st_lsm6dsx_sensor *sensor; + u16 max_odr, min_odr, sip = 0; + int err, i; + u8 data; + + st_lsm6dsx_get_max_min_odr(hw, &max_odr, &min_odr); + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + sensor = iio_priv(hw->iio_devs[i]); + + /* update fifo decimators and sample in pattern */ + if (hw->enable_mask & BIT(sensor->id)) { + sensor->sip = sensor->odr / min_odr; + sensor->decimator = max_odr / sensor->odr; + data = st_lsm6dsx_get_decimator_val(sensor->decimator); + } else { + sensor->sip = 0; + sensor->decimator = 0; + data = 0; + } + + err = st_lsm6dsx_write_with_mask(hw, + ST_LSM6DSX_REG_FIFO_DEC_GXL_ADDR, + sensor->decimator_mask, data); + if (err < 0) + return err; + + sip += sensor->sip; + } + hw->sip = sip; + + return 0; +} + +static int st_lsm6dsx_set_fifo_mode(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_fifo_mode fifo_mode) +{ + u8 data; + int err; + + switch (fifo_mode) { + case ST_LSM6DSX_FIFO_BYPASS: + data = fifo_mode; + break; + case ST_LSM6DSX_FIFO_CONT: + data = (ST_LSM6DSX_MAX_FIFO_ODR_VAL << + __ffs(ST_LSM6DSX_FIFO_ODR_MASK)) | fifo_mode; + break; + default: + return -EINVAL; + } + + err = hw->tf->write(hw->dev, ST_LSM6DSX_REG_FIFO_MODE_ADDR, + sizeof(data), &data); + if (err < 0) + return err; + + hw->fifo_mode = fifo_mode; + + return 0; +} + +int st_lsm6dsx_update_watermark(struct st_lsm6dsx_sensor *sensor, u16 watermark) +{ + u16 fifo_watermark = ~0, cur_watermark, sip = 0; + struct st_lsm6dsx_hw *hw = sensor->hw; + struct st_lsm6dsx_sensor *cur_sensor; + __le16 wdata; + int i, err; + u8 data; + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + cur_sensor = iio_priv(hw->iio_devs[i]); + + if (!(hw->enable_mask & BIT(cur_sensor->id))) + continue; + + cur_watermark = (cur_sensor == sensor) ? watermark + : cur_sensor->watermark; + + fifo_watermark = min_t(u16, fifo_watermark, cur_watermark); + sip += cur_sensor->sip; + } + + if (!sip) + return 0; + + fifo_watermark = max_t(u16, fifo_watermark, sip); + fifo_watermark = (fifo_watermark / sip) * sip; + fifo_watermark = fifo_watermark * ST_LSM6DSX_SAMPLE_DEPTH; + + mutex_lock(&hw->lock); + + err = hw->tf->read(hw->dev, ST_LSM6DSX_REG_FIFO_THH_ADDR, + sizeof(data), &data); + if (err < 0) + goto out; + + fifo_watermark = ((data & ~ST_LSM6DSX_FIFO_TH_MASK) << 8) | + (fifo_watermark & ST_LSM6DSX_FIFO_TH_MASK); + + wdata = cpu_to_le16(fifo_watermark); + err = hw->tf->write(hw->dev, ST_LSM6DSX_REG_FIFO_THL_ADDR, + sizeof(wdata), (u8 *)&wdata); +out: + mutex_unlock(&hw->lock); + + return err < 0 ? err : 0; +} + +/** + * st_lsm6dsx_read_fifo() - LSM6DS3-LSM6DSM read FIFO routine + * @hw: Pointer to instance of struct st_lsm6dsx_hw. + * + * Read samples from the hw FIFO and push them to IIO buffers. + * + * Return: Number of bytes read from the FIFO + */ +static int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw) +{ + u16 fifo_len, pattern_len = hw->sip * ST_LSM6DSX_SAMPLE_SIZE; + int err, acc_sip, gyro_sip, read_len, samples, offset; + struct st_lsm6dsx_sensor *acc_sensor, *gyro_sensor; + s64 acc_ts, acc_delta_ts, gyro_ts, gyro_delta_ts; + u8 iio_buff[ALIGN(ST_LSM6DSX_SAMPLE_SIZE, sizeof(s64)) + sizeof(s64)]; + u8 buff[pattern_len]; + __le16 fifo_status; + + err = hw->tf->read(hw->dev, ST_LSM6DSX_REG_FIFO_DIFFL_ADDR, + sizeof(fifo_status), (u8 *)&fifo_status); + if (err < 0) + return err; + + if (fifo_status & cpu_to_le16(ST_LSM6DSX_FIFO_EMPTY_MASK)) + return 0; + + fifo_len = (le16_to_cpu(fifo_status) & ST_LSM6DSX_FIFO_DIFF_MASK) * + ST_LSM6DSX_CHAN_SIZE; + samples = fifo_len / ST_LSM6DSX_SAMPLE_SIZE; + fifo_len = (fifo_len / pattern_len) * pattern_len; + + /* + * compute delta timestamp between two consecutive samples + * in order to estimate queueing time of data generated + * by the sensor + */ + acc_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]); + acc_ts = acc_sensor->ts - acc_sensor->delta_ts; + acc_delta_ts = div_s64(acc_sensor->delta_ts * acc_sensor->decimator, + samples); + + gyro_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_GYRO]); + gyro_ts = gyro_sensor->ts - gyro_sensor->delta_ts; + gyro_delta_ts = div_s64(gyro_sensor->delta_ts * gyro_sensor->decimator, + samples); + + for (read_len = 0; read_len < fifo_len; read_len += pattern_len) { + err = hw->tf->read(hw->dev, ST_LSM6DSX_REG_FIFO_OUTL_ADDR, + sizeof(buff), buff); + if (err < 0) + return err; + + /* + * Data are written to the FIFO with a specific pattern + * depending on the configured ODRs. The first sequence of data + * stored in FIFO contains the data of all enabled sensors + * (e.g. Gx, Gy, Gz, Ax, Ay, Az), then data are repeated + * depending on the value of the decimation factor set for each + * sensor. + * + * Supposing the FIFO is storing data from gyroscope and + * accelerometer at different ODRs: + * - gyroscope ODR = 208Hz, accelerometer ODR = 104Hz + * Since the gyroscope ODR is twice the accelerometer one, the + * following pattern is repeated every 9 samples: + * - Gx, Gy, Gz, Ax, Ay, Az, Gx, Gy, Gz + */ + gyro_sip = gyro_sensor->sip; + acc_sip = acc_sensor->sip; + offset = 0; + + while (acc_sip > 0 || gyro_sip > 0) { + if (gyro_sip-- > 0) { + memcpy(iio_buff, &buff[offset], + ST_LSM6DSX_SAMPLE_SIZE); + iio_push_to_buffers_with_timestamp( + hw->iio_devs[ST_LSM6DSX_ID_GYRO], + iio_buff, gyro_ts); + offset += ST_LSM6DSX_SAMPLE_SIZE; + gyro_ts += gyro_delta_ts; + } + + if (acc_sip-- > 0) { + memcpy(iio_buff, &buff[offset], + ST_LSM6DSX_SAMPLE_SIZE); + iio_push_to_buffers_with_timestamp( + hw->iio_devs[ST_LSM6DSX_ID_ACC], + iio_buff, acc_ts); + offset += ST_LSM6DSX_SAMPLE_SIZE; + acc_ts += acc_delta_ts; + } + } + } + + return read_len; +} + +static int st_lsm6dsx_flush_fifo(struct st_lsm6dsx_hw *hw) +{ + int err; + + mutex_lock(&hw->fifo_lock); + + st_lsm6dsx_read_fifo(hw); + err = st_lsm6dsx_set_fifo_mode(hw, ST_LSM6DSX_FIFO_BYPASS); + + mutex_unlock(&hw->fifo_lock); + + return err; +} + +static int st_lsm6dsx_update_fifo(struct iio_dev *iio_dev, bool enable) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + int err; + + if (hw->fifo_mode != ST_LSM6DSX_FIFO_BYPASS) { + err = st_lsm6dsx_flush_fifo(hw); + if (err < 0) + return err; + } + + if (enable) { + err = st_lsm6dsx_sensor_enable(sensor); + if (err < 0) + return err; + } else { + err = st_lsm6dsx_sensor_disable(sensor); + if (err < 0) + return err; + } + + err = st_lsm6dsx_update_decimators(hw); + if (err < 0) + return err; + + err = st_lsm6dsx_update_watermark(sensor, sensor->watermark); + if (err < 0) + return err; + + if (hw->enable_mask) { + err = st_lsm6dsx_set_fifo_mode(hw, ST_LSM6DSX_FIFO_CONT); + if (err < 0) + return err; + + /* + * store enable buffer timestamp as reference to compute + * first delta timestamp + */ + sensor->ts = iio_get_time_ns(iio_dev); + } + + return 0; +} + +static irqreturn_t st_lsm6dsx_handler_irq(int irq, void *private) +{ + struct st_lsm6dsx_hw *hw = (struct st_lsm6dsx_hw *)private; + struct st_lsm6dsx_sensor *sensor; + int i; + + if (!hw->sip) + return IRQ_NONE; + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + sensor = iio_priv(hw->iio_devs[i]); + + if (sensor->sip > 0) { + s64 timestamp; + + timestamp = iio_get_time_ns(hw->iio_devs[i]); + sensor->delta_ts = timestamp - sensor->ts; + sensor->ts = timestamp; + } + } + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t st_lsm6dsx_handler_thread(int irq, void *private) +{ + struct st_lsm6dsx_hw *hw = (struct st_lsm6dsx_hw *)private; + int count; + + mutex_lock(&hw->fifo_lock); + count = st_lsm6dsx_read_fifo(hw); + mutex_unlock(&hw->fifo_lock); + + return !count ? IRQ_NONE : IRQ_HANDLED; +} + +static int st_lsm6dsx_buffer_preenable(struct iio_dev *iio_dev) +{ + return st_lsm6dsx_update_fifo(iio_dev, true); +} + +static int st_lsm6dsx_buffer_postdisable(struct iio_dev *iio_dev) +{ + return st_lsm6dsx_update_fifo(iio_dev, false); +} + +static const struct iio_buffer_setup_ops st_lsm6dsx_buffer_ops = { + .preenable = st_lsm6dsx_buffer_preenable, + .postdisable = st_lsm6dsx_buffer_postdisable, +}; + +int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw) +{ + struct iio_buffer *buffer; + unsigned long irq_type; + int i, err; + + irq_type = irqd_get_trigger_type(irq_get_irq_data(hw->irq)); + + switch (irq_type) { + case IRQF_TRIGGER_HIGH: + case IRQF_TRIGGER_RISING: + break; + default: + dev_info(hw->dev, "mode %lx unsupported\n", irq_type); + return -EINVAL; + } + + err = devm_request_threaded_irq(hw->dev, hw->irq, + st_lsm6dsx_handler_irq, + st_lsm6dsx_handler_thread, + irq_type | IRQF_ONESHOT, + "lsm6dsx", hw); + if (err) { + dev_err(hw->dev, "failed to request trigger irq %d\n", + hw->irq); + return err; + } + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + buffer = devm_iio_kfifo_allocate(hw->dev); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(hw->iio_devs[i], buffer); + hw->iio_devs[i]->modes |= INDIO_BUFFER_SOFTWARE; + hw->iio_devs[i]->setup_ops = &st_lsm6dsx_buffer_ops; + } + + return 0; +} diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c new file mode 100644 index 000000000000..c92ddcc190e2 --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -0,0 +1,720 @@ +/* + * STMicroelectronics st_lsm6dsx sensor driver + * + * The ST LSM6DSx IMU MEMS series consists of 3D digital accelerometer + * and 3D digital gyroscope system-in-package with a digital I2C/SPI serial + * interface standard output. + * LSM6DSx IMU MEMS series has a dynamic user-selectable full-scale + * acceleration range of +-2/+-4/+-8/+-16 g and an angular rate range of + * +-125/+-245/+-500/+-1000/+-2000 dps + * LSM6DSx series has an integrated First-In-First-Out (FIFO) buffer + * allowing dynamic batching of sensor data. + * + * Supported sensors: + * - LSM6DS3: + * - Accelerometer/Gyroscope supported ODR [Hz]: 13, 26, 52, 104, 208, 416 + * - Accelerometer supported full-scale [g]: +-2/+-4/+-8/+-16 + * - Gyroscope supported full-scale [dps]: +-125/+-245/+-500/+-1000/+-2000 + * - FIFO size: 8KB + * + * - LSM6DSM: + * - Accelerometer/Gyroscope supported ODR [Hz]: 13, 26, 52, 104, 208, 416 + * - Accelerometer supported full-scale [g]: +-2/+-4/+-8/+-16 + * - Gyroscope supported full-scale [dps]: +-125/+-245/+-500/+-1000/+-2000 + * - FIFO size: 4KB + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi <lorenzo.bianconi@st.com> + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#include <linux/platform_data/st_sensors_pdata.h> + +#include "st_lsm6dsx.h" + +#define ST_LSM6DSX_REG_ACC_DEC_MASK GENMASK(2, 0) +#define ST_LSM6DSX_REG_GYRO_DEC_MASK GENMASK(5, 3) +#define ST_LSM6DSX_REG_INT1_ADDR 0x0d +#define ST_LSM6DSX_REG_INT2_ADDR 0x0e +#define ST_LSM6DSX_REG_FIFO_FTH_IRQ_MASK BIT(3) +#define ST_LSM6DSX_REG_WHOAMI_ADDR 0x0f +#define ST_LSM6DSX_REG_RESET_ADDR 0x12 +#define ST_LSM6DSX_REG_RESET_MASK BIT(0) +#define ST_LSM6DSX_REG_BDU_ADDR 0x12 +#define ST_LSM6DSX_REG_BDU_MASK BIT(6) +#define ST_LSM6DSX_REG_INT2_ON_INT1_ADDR 0x13 +#define ST_LSM6DSX_REG_INT2_ON_INT1_MASK BIT(5) +#define ST_LSM6DSX_REG_ROUNDING_ADDR 0x16 +#define ST_LSM6DSX_REG_ROUNDING_MASK BIT(2) +#define ST_LSM6DSX_REG_LIR_ADDR 0x58 +#define ST_LSM6DSX_REG_LIR_MASK BIT(0) + +#define ST_LSM6DSX_REG_ACC_ODR_ADDR 0x10 +#define ST_LSM6DSX_REG_ACC_ODR_MASK GENMASK(7, 4) +#define ST_LSM6DSX_REG_ACC_FS_ADDR 0x10 +#define ST_LSM6DSX_REG_ACC_FS_MASK GENMASK(3, 2) +#define ST_LSM6DSX_REG_ACC_OUT_X_L_ADDR 0x28 +#define ST_LSM6DSX_REG_ACC_OUT_Y_L_ADDR 0x2a +#define ST_LSM6DSX_REG_ACC_OUT_Z_L_ADDR 0x2c + +#define ST_LSM6DSX_REG_GYRO_ODR_ADDR 0x11 +#define ST_LSM6DSX_REG_GYRO_ODR_MASK GENMASK(7, 4) +#define ST_LSM6DSX_REG_GYRO_FS_ADDR 0x11 +#define ST_LSM6DSX_REG_GYRO_FS_MASK GENMASK(3, 2) +#define ST_LSM6DSX_REG_GYRO_OUT_X_L_ADDR 0x22 +#define ST_LSM6DSX_REG_GYRO_OUT_Y_L_ADDR 0x24 +#define ST_LSM6DSX_REG_GYRO_OUT_Z_L_ADDR 0x26 + +#define ST_LSM6DS3_WHOAMI 0x69 +#define ST_LSM6DSM_WHOAMI 0x6a + +#define ST_LSM6DS3_MAX_FIFO_SIZE 8192 +#define ST_LSM6DSM_MAX_FIFO_SIZE 4096 + +#define ST_LSM6DSX_ACC_FS_2G_GAIN IIO_G_TO_M_S_2(61) +#define ST_LSM6DSX_ACC_FS_4G_GAIN IIO_G_TO_M_S_2(122) +#define ST_LSM6DSX_ACC_FS_8G_GAIN IIO_G_TO_M_S_2(244) +#define ST_LSM6DSX_ACC_FS_16G_GAIN IIO_G_TO_M_S_2(488) + +#define ST_LSM6DSX_GYRO_FS_245_GAIN IIO_DEGREE_TO_RAD(8750) +#define ST_LSM6DSX_GYRO_FS_500_GAIN IIO_DEGREE_TO_RAD(17500) +#define ST_LSM6DSX_GYRO_FS_1000_GAIN IIO_DEGREE_TO_RAD(35000) +#define ST_LSM6DSX_GYRO_FS_2000_GAIN IIO_DEGREE_TO_RAD(70000) + +struct st_lsm6dsx_odr { + u16 hz; + u8 val; +}; + +#define ST_LSM6DSX_ODR_LIST_SIZE 6 +struct st_lsm6dsx_odr_table_entry { + struct st_lsm6dsx_reg reg; + struct st_lsm6dsx_odr odr_avl[ST_LSM6DSX_ODR_LIST_SIZE]; +}; + +static const struct st_lsm6dsx_odr_table_entry st_lsm6dsx_odr_table[] = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = ST_LSM6DSX_REG_ACC_ODR_ADDR, + .mask = ST_LSM6DSX_REG_ACC_ODR_MASK, + }, + .odr_avl[0] = { 13, 0x01 }, + .odr_avl[1] = { 26, 0x02 }, + .odr_avl[2] = { 52, 0x03 }, + .odr_avl[3] = { 104, 0x04 }, + .odr_avl[4] = { 208, 0x05 }, + .odr_avl[5] = { 416, 0x06 }, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = ST_LSM6DSX_REG_GYRO_ODR_ADDR, + .mask = ST_LSM6DSX_REG_GYRO_ODR_MASK, + }, + .odr_avl[0] = { 13, 0x01 }, + .odr_avl[1] = { 26, 0x02 }, + .odr_avl[2] = { 52, 0x03 }, + .odr_avl[3] = { 104, 0x04 }, + .odr_avl[4] = { 208, 0x05 }, + .odr_avl[5] = { 416, 0x06 }, + } +}; + +struct st_lsm6dsx_fs { + u32 gain; + u8 val; +}; + +#define ST_LSM6DSX_FS_LIST_SIZE 4 +struct st_lsm6dsx_fs_table_entry { + struct st_lsm6dsx_reg reg; + struct st_lsm6dsx_fs fs_avl[ST_LSM6DSX_FS_LIST_SIZE]; +}; + +static const struct st_lsm6dsx_fs_table_entry st_lsm6dsx_fs_table[] = { + [ST_LSM6DSX_ID_ACC] = { + .reg = { + .addr = ST_LSM6DSX_REG_ACC_FS_ADDR, + .mask = ST_LSM6DSX_REG_ACC_FS_MASK, + }, + .fs_avl[0] = { ST_LSM6DSX_ACC_FS_2G_GAIN, 0x0 }, + .fs_avl[1] = { ST_LSM6DSX_ACC_FS_4G_GAIN, 0x2 }, + .fs_avl[2] = { ST_LSM6DSX_ACC_FS_8G_GAIN, 0x3 }, + .fs_avl[3] = { ST_LSM6DSX_ACC_FS_16G_GAIN, 0x1 }, + }, + [ST_LSM6DSX_ID_GYRO] = { + .reg = { + .addr = ST_LSM6DSX_REG_GYRO_FS_ADDR, + .mask = ST_LSM6DSX_REG_GYRO_FS_MASK, + }, + .fs_avl[0] = { ST_LSM6DSX_GYRO_FS_245_GAIN, 0x0 }, + .fs_avl[1] = { ST_LSM6DSX_GYRO_FS_500_GAIN, 0x1 }, + .fs_avl[2] = { ST_LSM6DSX_GYRO_FS_1000_GAIN, 0x2 }, + .fs_avl[3] = { ST_LSM6DSX_GYRO_FS_2000_GAIN, 0x3 }, + } +}; + +static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { + { + .wai = ST_LSM6DS3_WHOAMI, + .max_fifo_size = ST_LSM6DS3_MAX_FIFO_SIZE, + .id = ST_LSM6DS3_ID, + }, + { + .wai = ST_LSM6DSM_WHOAMI, + .max_fifo_size = ST_LSM6DSM_MAX_FIFO_SIZE, + .id = ST_LSM6DSM_ID, + }, +}; + +#define ST_LSM6DSX_CHANNEL(chan_type, addr, mod, scan_idx) \ +{ \ + .type = chan_type, \ + .address = addr, \ + .modified = 1, \ + .channel2 = mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = scan_idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = { + ST_LSM6DSX_CHANNEL(IIO_ACCEL, ST_LSM6DSX_REG_ACC_OUT_X_L_ADDR, + IIO_MOD_X, 0), + ST_LSM6DSX_CHANNEL(IIO_ACCEL, ST_LSM6DSX_REG_ACC_OUT_Y_L_ADDR, + IIO_MOD_Y, 1), + ST_LSM6DSX_CHANNEL(IIO_ACCEL, ST_LSM6DSX_REG_ACC_OUT_Z_L_ADDR, + IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec st_lsm6dsx_gyro_channels[] = { + ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, ST_LSM6DSX_REG_GYRO_OUT_X_L_ADDR, + IIO_MOD_X, 0), + ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, ST_LSM6DSX_REG_GYRO_OUT_Y_L_ADDR, + IIO_MOD_Y, 1), + ST_LSM6DSX_CHANNEL(IIO_ANGL_VEL, ST_LSM6DSX_REG_GYRO_OUT_Z_L_ADDR, + IIO_MOD_Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +int st_lsm6dsx_write_with_mask(struct st_lsm6dsx_hw *hw, u8 addr, u8 mask, + u8 val) +{ + u8 data; + int err; + + mutex_lock(&hw->lock); + + err = hw->tf->read(hw->dev, addr, sizeof(data), &data); + if (err < 0) { + dev_err(hw->dev, "failed to read %02x register\n", addr); + goto out; + } + + data = (data & ~mask) | ((val << __ffs(mask)) & mask); + + err = hw->tf->write(hw->dev, addr, sizeof(data), &data); + if (err < 0) + dev_err(hw->dev, "failed to write %02x register\n", addr); + +out: + mutex_unlock(&hw->lock); + + return err; +} + +static int st_lsm6dsx_check_whoami(struct st_lsm6dsx_hw *hw, int id) +{ + int err, i; + u8 data; + + for (i = 0; i < ARRAY_SIZE(st_lsm6dsx_sensor_settings); i++) { + if (id == st_lsm6dsx_sensor_settings[i].id) + break; + } + + if (i == ARRAY_SIZE(st_lsm6dsx_sensor_settings)) { + dev_err(hw->dev, "unsupported hw id [%02x]\n", id); + return -ENODEV; + } + + err = hw->tf->read(hw->dev, ST_LSM6DSX_REG_WHOAMI_ADDR, sizeof(data), + &data); + if (err < 0) { + dev_err(hw->dev, "failed to read whoami register\n"); + return err; + } + + if (data != st_lsm6dsx_sensor_settings[i].wai) { + dev_err(hw->dev, "unsupported whoami [%02x]\n", data); + return -ENODEV; + } + + hw->settings = &st_lsm6dsx_sensor_settings[i]; + + return 0; +} + +static int st_lsm6dsx_set_full_scale(struct st_lsm6dsx_sensor *sensor, + u32 gain) +{ + enum st_lsm6dsx_sensor_id id = sensor->id; + int i, err; + u8 val; + + for (i = 0; i < ST_LSM6DSX_FS_LIST_SIZE; i++) + if (st_lsm6dsx_fs_table[id].fs_avl[i].gain == gain) + break; + + if (i == ST_LSM6DSX_FS_LIST_SIZE) + return -EINVAL; + + val = st_lsm6dsx_fs_table[id].fs_avl[i].val; + err = st_lsm6dsx_write_with_mask(sensor->hw, + st_lsm6dsx_fs_table[id].reg.addr, + st_lsm6dsx_fs_table[id].reg.mask, + val); + if (err < 0) + return err; + + sensor->gain = gain; + + return 0; +} + +static int st_lsm6dsx_set_odr(struct st_lsm6dsx_sensor *sensor, u16 odr) +{ + enum st_lsm6dsx_sensor_id id = sensor->id; + int i, err; + u8 val; + + for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++) + if (st_lsm6dsx_odr_table[id].odr_avl[i].hz == odr) + break; + + if (i == ST_LSM6DSX_ODR_LIST_SIZE) + return -EINVAL; + + val = st_lsm6dsx_odr_table[id].odr_avl[i].val; + err = st_lsm6dsx_write_with_mask(sensor->hw, + st_lsm6dsx_odr_table[id].reg.addr, + st_lsm6dsx_odr_table[id].reg.mask, + val); + if (err < 0) + return err; + + sensor->odr = odr; + + return 0; +} + +int st_lsm6dsx_sensor_enable(struct st_lsm6dsx_sensor *sensor) +{ + int err; + + err = st_lsm6dsx_set_odr(sensor, sensor->odr); + if (err < 0) + return err; + + sensor->hw->enable_mask |= BIT(sensor->id); + + return 0; +} + +int st_lsm6dsx_sensor_disable(struct st_lsm6dsx_sensor *sensor) +{ + enum st_lsm6dsx_sensor_id id = sensor->id; + int err; + + err = st_lsm6dsx_write_with_mask(sensor->hw, + st_lsm6dsx_odr_table[id].reg.addr, + st_lsm6dsx_odr_table[id].reg.mask, 0); + if (err < 0) + return err; + + sensor->hw->enable_mask &= ~BIT(id); + + return 0; +} + +static int st_lsm6dsx_read_oneshot(struct st_lsm6dsx_sensor *sensor, + u8 addr, int *val) +{ + int err, delay; + __le16 data; + + err = st_lsm6dsx_sensor_enable(sensor); + if (err < 0) + return err; + + delay = 1000000 / sensor->odr; + usleep_range(delay, 2 * delay); + + err = sensor->hw->tf->read(sensor->hw->dev, addr, sizeof(data), + (u8 *)&data); + if (err < 0) + return err; + + st_lsm6dsx_sensor_disable(sensor); + + *val = (s16)data; + + return IIO_VAL_INT; +} + +static int st_lsm6dsx_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_device_claim_direct_mode(iio_dev); + if (ret) + break; + + ret = st_lsm6dsx_read_oneshot(sensor, ch->address, val); + iio_device_release_direct_mode(iio_dev); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = sensor->odr; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = sensor->gain; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + int err; + + err = iio_device_claim_direct_mode(iio_dev); + if (err) + return err; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + err = st_lsm6dsx_set_full_scale(sensor, val2); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + err = st_lsm6dsx_set_odr(sensor, val); + break; + default: + err = -EINVAL; + break; + } + + iio_device_release_direct_mode(iio_dev); + + return err; +} + +static int st_lsm6dsx_set_watermark(struct iio_dev *iio_dev, unsigned int val) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + struct st_lsm6dsx_hw *hw = sensor->hw; + int err, max_fifo_len; + + max_fifo_len = hw->settings->max_fifo_size / ST_LSM6DSX_SAMPLE_SIZE; + if (val < 1 || val > max_fifo_len) + return -EINVAL; + + err = st_lsm6dsx_update_watermark(sensor, val); + if (err < 0) + return err; + + sensor->watermark = val; + + return 0; +} + +static ssize_t +st_lsm6dsx_sysfs_sampling_frequency_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev)); + enum st_lsm6dsx_sensor_id id = sensor->id; + int i, len = 0; + + for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", + st_lsm6dsx_odr_table[id].odr_avl[i].hz); + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t st_lsm6dsx_sysfs_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev)); + enum st_lsm6dsx_sensor_id id = sensor->id; + int i, len = 0; + + for (i = 0; i < ST_LSM6DSX_FS_LIST_SIZE; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ", + st_lsm6dsx_fs_table[id].fs_avl[i].gain); + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(st_lsm6dsx_sysfs_sampling_frequency_avail); +static IIO_DEVICE_ATTR(in_accel_scale_available, 0444, + st_lsm6dsx_sysfs_scale_avail, NULL, 0); +static IIO_DEVICE_ATTR(in_anglvel_scale_available, 0444, + st_lsm6dsx_sysfs_scale_avail, NULL, 0); + +static struct attribute *st_lsm6dsx_acc_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_accel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_lsm6dsx_acc_attribute_group = { + .attrs = st_lsm6dsx_acc_attributes, +}; + +static const struct iio_info st_lsm6dsx_acc_info = { + .driver_module = THIS_MODULE, + .attrs = &st_lsm6dsx_acc_attribute_group, + .read_raw = st_lsm6dsx_read_raw, + .write_raw = st_lsm6dsx_write_raw, + .hwfifo_set_watermark = st_lsm6dsx_set_watermark, +}; + +static struct attribute *st_lsm6dsx_gyro_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_anglvel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_lsm6dsx_gyro_attribute_group = { + .attrs = st_lsm6dsx_gyro_attributes, +}; + +static const struct iio_info st_lsm6dsx_gyro_info = { + .driver_module = THIS_MODULE, + .attrs = &st_lsm6dsx_gyro_attribute_group, + .read_raw = st_lsm6dsx_read_raw, + .write_raw = st_lsm6dsx_write_raw, + .hwfifo_set_watermark = st_lsm6dsx_set_watermark, +}; + +static const unsigned long st_lsm6dsx_available_scan_masks[] = {0x7, 0x0}; + +static int st_lsm6dsx_of_get_drdy_pin(struct st_lsm6dsx_hw *hw, int *drdy_pin) +{ + struct device_node *np = hw->dev->of_node; + int err; + + if (!np) + return -EINVAL; + + err = of_property_read_u32(np, "st,drdy-int-pin", drdy_pin); + if (err == -ENODATA) { + /* if the property has not been specified use default value */ + *drdy_pin = 1; + err = 0; + } + + return err; +} + +static int st_lsm6dsx_get_drdy_reg(struct st_lsm6dsx_hw *hw, u8 *drdy_reg) +{ + int err = 0, drdy_pin; + + if (st_lsm6dsx_of_get_drdy_pin(hw, &drdy_pin) < 0) { + struct st_sensors_platform_data *pdata; + struct device *dev = hw->dev; + + pdata = (struct st_sensors_platform_data *)dev->platform_data; + drdy_pin = pdata ? pdata->drdy_int_pin : 1; + } + + switch (drdy_pin) { + case 1: + *drdy_reg = ST_LSM6DSX_REG_INT1_ADDR; + break; + case 2: + *drdy_reg = ST_LSM6DSX_REG_INT2_ADDR; + break; + default: + dev_err(hw->dev, "unsupported data ready pin\n"); + err = -EINVAL; + break; + } + + return err; +} + +static int st_lsm6dsx_init_device(struct st_lsm6dsx_hw *hw) +{ + u8 data, drdy_int_reg; + int err; + + data = ST_LSM6DSX_REG_RESET_MASK; + err = hw->tf->write(hw->dev, ST_LSM6DSX_REG_RESET_ADDR, sizeof(data), + &data); + if (err < 0) + return err; + + msleep(200); + + /* latch interrupts */ + err = st_lsm6dsx_write_with_mask(hw, ST_LSM6DSX_REG_LIR_ADDR, + ST_LSM6DSX_REG_LIR_MASK, 1); + if (err < 0) + return err; + + /* enable Block Data Update */ + err = st_lsm6dsx_write_with_mask(hw, ST_LSM6DSX_REG_BDU_ADDR, + ST_LSM6DSX_REG_BDU_MASK, 1); + if (err < 0) + return err; + + err = st_lsm6dsx_write_with_mask(hw, ST_LSM6DSX_REG_ROUNDING_ADDR, + ST_LSM6DSX_REG_ROUNDING_MASK, 1); + if (err < 0) + return err; + + /* enable FIFO watermak interrupt */ + err = st_lsm6dsx_get_drdy_reg(hw, &drdy_int_reg); + if (err < 0) + return err; + + return st_lsm6dsx_write_with_mask(hw, drdy_int_reg, + ST_LSM6DSX_REG_FIFO_FTH_IRQ_MASK, 1); +} + +static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_sensor_id id) +{ + struct st_lsm6dsx_sensor *sensor; + struct iio_dev *iio_dev; + + iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor)); + if (!iio_dev) + return NULL; + + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->dev.parent = hw->dev; + iio_dev->available_scan_masks = st_lsm6dsx_available_scan_masks; + + sensor = iio_priv(iio_dev); + sensor->id = id; + sensor->hw = hw; + sensor->odr = st_lsm6dsx_odr_table[id].odr_avl[0].hz; + sensor->gain = st_lsm6dsx_fs_table[id].fs_avl[0].gain; + sensor->watermark = 1; + + switch (id) { + case ST_LSM6DSX_ID_ACC: + iio_dev->channels = st_lsm6dsx_acc_channels; + iio_dev->num_channels = ARRAY_SIZE(st_lsm6dsx_acc_channels); + iio_dev->name = "lsm6dsx_accel"; + iio_dev->info = &st_lsm6dsx_acc_info; + + sensor->decimator_mask = ST_LSM6DSX_REG_ACC_DEC_MASK; + break; + case ST_LSM6DSX_ID_GYRO: + iio_dev->channels = st_lsm6dsx_gyro_channels; + iio_dev->num_channels = ARRAY_SIZE(st_lsm6dsx_gyro_channels); + iio_dev->name = "lsm6dsx_gyro"; + iio_dev->info = &st_lsm6dsx_gyro_info; + + sensor->decimator_mask = ST_LSM6DSX_REG_GYRO_DEC_MASK; + break; + default: + return NULL; + } + + return iio_dev; +} + +int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, + const struct st_lsm6dsx_transfer_function *tf_ops) +{ + struct st_lsm6dsx_hw *hw; + int i, err; + + hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL); + if (!hw) + return -ENOMEM; + + dev_set_drvdata(dev, (void *)hw); + + mutex_init(&hw->lock); + mutex_init(&hw->fifo_lock); + + hw->dev = dev; + hw->irq = irq; + hw->tf = tf_ops; + + err = st_lsm6dsx_check_whoami(hw, hw_id); + if (err < 0) + return err; + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + hw->iio_devs[i] = st_lsm6dsx_alloc_iiodev(hw, i); + if (!hw->iio_devs[i]) + return -ENOMEM; + } + + err = st_lsm6dsx_init_device(hw); + if (err < 0) + return err; + + if (hw->irq > 0) { + err = st_lsm6dsx_fifo_setup(hw); + if (err < 0) + return err; + } + + for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) { + err = devm_iio_device_register(hw->dev, hw->iio_devs[i]); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL(st_lsm6dsx_probe); + +MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi@st.com>"); +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics st_lsm6dsx driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c new file mode 100644 index 000000000000..ea3041186e1e --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_i2c.c @@ -0,0 +1,101 @@ +/* + * STMicroelectronics st_lsm6dsx i2c driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi <lorenzo.bianconi@st.com> + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/of.h> + +#include "st_lsm6dsx.h" + +static int st_lsm6dsx_i2c_read(struct device *dev, u8 addr, int len, u8 *data) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_msg msg[2]; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].len = 1; + msg[0].buf = &addr; + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].len = len; + msg[1].buf = data; + + return i2c_transfer(client->adapter, msg, 2); +} + +static int st_lsm6dsx_i2c_write(struct device *dev, u8 addr, int len, u8 *data) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_msg msg; + u8 send[len + 1]; + + send[0] = addr; + memcpy(&send[1], data, len * sizeof(u8)); + + msg.addr = client->addr; + msg.flags = client->flags; + msg.len = len + 1; + msg.buf = send; + + return i2c_transfer(client->adapter, &msg, 1); +} + +static const struct st_lsm6dsx_transfer_function st_lsm6dsx_transfer_fn = { + .read = st_lsm6dsx_i2c_read, + .write = st_lsm6dsx_i2c_write, +}; + +static int st_lsm6dsx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return st_lsm6dsx_probe(&client->dev, client->irq, + (int)id->driver_data, + &st_lsm6dsx_transfer_fn); +} + +static const struct of_device_id st_lsm6dsx_i2c_of_match[] = { + { + .compatible = "st,lsm6ds3", + .data = (void *)ST_LSM6DS3_ID, + }, + { + .compatible = "st,lsm6dsm", + .data = (void *)ST_LSM6DSM_ID, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, st_lsm6dsx_i2c_of_match); + +static const struct i2c_device_id st_lsm6dsx_i2c_id_table[] = { + { ST_LSM6DS3_DEV_NAME, ST_LSM6DS3_ID }, + { ST_LSM6DSM_DEV_NAME, ST_LSM6DSM_ID }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, st_lsm6dsx_i2c_id_table); + +static struct i2c_driver st_lsm6dsx_driver = { + .driver = { + .name = "st_lsm6dsx_i2c", + .of_match_table = of_match_ptr(st_lsm6dsx_i2c_of_match), + }, + .probe = st_lsm6dsx_i2c_probe, + .id_table = st_lsm6dsx_i2c_id_table, +}; +module_i2c_driver(st_lsm6dsx_driver); + +MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi@st.com>"); +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics st_lsm6dsx i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c new file mode 100644 index 000000000000..fbe72470ed1e --- /dev/null +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_spi.c @@ -0,0 +1,118 @@ +/* + * STMicroelectronics st_lsm6dsx spi driver + * + * Copyright 2016 STMicroelectronics Inc. + * + * Lorenzo Bianconi <lorenzo.bianconi@st.com> + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/of.h> + +#include "st_lsm6dsx.h" + +#define SENSORS_SPI_READ BIT(7) + +static int st_lsm6dsx_spi_read(struct device *dev, u8 addr, int len, + u8 *data) +{ + struct spi_device *spi = to_spi_device(dev); + struct st_lsm6dsx_hw *hw = spi_get_drvdata(spi); + int err; + + struct spi_transfer xfers[] = { + { + .tx_buf = hw->tb.tx_buf, + .bits_per_word = 8, + .len = 1, + }, + { + .rx_buf = hw->tb.rx_buf, + .bits_per_word = 8, + .len = len, + } + }; + + hw->tb.tx_buf[0] = addr | SENSORS_SPI_READ; + + err = spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers)); + if (err < 0) + return err; + + memcpy(data, hw->tb.rx_buf, len * sizeof(u8)); + + return len; +} + +static int st_lsm6dsx_spi_write(struct device *dev, u8 addr, int len, + u8 *data) +{ + struct st_lsm6dsx_hw *hw; + struct spi_device *spi; + + if (len >= ST_LSM6DSX_TX_MAX_LENGTH) + return -ENOMEM; + + spi = to_spi_device(dev); + hw = spi_get_drvdata(spi); + + hw->tb.tx_buf[0] = addr; + memcpy(&hw->tb.tx_buf[1], data, len); + + return spi_write(spi, hw->tb.tx_buf, len + 1); +} + +static const struct st_lsm6dsx_transfer_function st_lsm6dsx_transfer_fn = { + .read = st_lsm6dsx_spi_read, + .write = st_lsm6dsx_spi_write, +}; + +static int st_lsm6dsx_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + + return st_lsm6dsx_probe(&spi->dev, spi->irq, + (int)id->driver_data, + &st_lsm6dsx_transfer_fn); +} + +static const struct of_device_id st_lsm6dsx_spi_of_match[] = { + { + .compatible = "st,lsm6ds3", + .data = (void *)ST_LSM6DS3_ID, + }, + { + .compatible = "st,lsm6dsm", + .data = (void *)ST_LSM6DSM_ID, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, st_lsm6dsx_spi_of_match); + +static const struct spi_device_id st_lsm6dsx_spi_id_table[] = { + { ST_LSM6DS3_DEV_NAME, ST_LSM6DS3_ID }, + { ST_LSM6DSM_DEV_NAME, ST_LSM6DSM_ID }, + {}, +}; +MODULE_DEVICE_TABLE(spi, st_lsm6dsx_spi_id_table); + +static struct spi_driver st_lsm6dsx_driver = { + .driver = { + .name = "st_lsm6dsx_spi", + .of_match_table = of_match_ptr(st_lsm6dsx_spi_of_match), + }, + .probe = st_lsm6dsx_spi_probe, + .id_table = st_lsm6dsx_spi_id_table, +}; +module_spi_driver(st_lsm6dsx_driver); + +MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi@st.com>"); +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics st_lsm6dsx spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index b12830b09c7d..4972986f6455 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -26,6 +26,7 @@ #include "iio_core.h" #include <linux/iio/sysfs.h> #include <linux/iio/buffer.h> +#include <linux/iio/buffer_impl.h> static const char * const iio_endian_prefix[] = { [IIO_BE] = "be", @@ -209,6 +210,18 @@ void iio_buffer_init(struct iio_buffer *buffer) } EXPORT_SYMBOL(iio_buffer_init); +/** + * iio_buffer_set_attrs - Set buffer specific attributes + * @buffer: The buffer for which we are setting attributes + * @attrs: Pointer to a null terminated list of pointers to attributes + */ +void iio_buffer_set_attrs(struct iio_buffer *buffer, + const struct attribute **attrs) +{ + buffer->attrs = attrs; +} +EXPORT_SYMBOL_GPL(iio_buffer_set_attrs); + static ssize_t iio_show_scan_index(struct device *dev, struct device_attribute *attr, char *buf) @@ -346,6 +359,19 @@ static int iio_scan_mask_clear(struct iio_buffer *buffer, int bit) return 0; } +static int iio_scan_mask_query(struct iio_dev *indio_dev, + struct iio_buffer *buffer, int bit) +{ + if (bit > indio_dev->masklength) + return -EINVAL; + + if (!buffer->scan_mask) + return 0; + + /* Ensure return value is 0 or 1. */ + return !!test_bit(bit, buffer->scan_mask); +}; + static ssize_t iio_scan_el_store(struct device *dev, struct device_attribute *attr, const char *buf, @@ -751,6 +777,135 @@ static int iio_verify_update(struct iio_dev *indio_dev, return 0; } +/** + * struct iio_demux_table - table describing demux memcpy ops + * @from: index to copy from + * @to: index to copy to + * @length: how many bytes to copy + * @l: list head used for management + */ +struct iio_demux_table { + unsigned from; + unsigned to; + unsigned length; + struct list_head l; +}; + +static void iio_buffer_demux_free(struct iio_buffer *buffer) +{ + struct iio_demux_table *p, *q; + list_for_each_entry_safe(p, q, &buffer->demux_list, l) { + list_del(&p->l); + kfree(p); + } +} + +static int iio_buffer_add_demux(struct iio_buffer *buffer, + struct iio_demux_table **p, unsigned int in_loc, unsigned int out_loc, + unsigned int length) +{ + + if (*p && (*p)->from + (*p)->length == in_loc && + (*p)->to + (*p)->length == out_loc) { + (*p)->length += length; + } else { + *p = kmalloc(sizeof(**p), GFP_KERNEL); + if (*p == NULL) + return -ENOMEM; + (*p)->from = in_loc; + (*p)->to = out_loc; + (*p)->length = length; + list_add_tail(&(*p)->l, &buffer->demux_list); + } + + return 0; +} + +static int iio_buffer_update_demux(struct iio_dev *indio_dev, + struct iio_buffer *buffer) +{ + int ret, in_ind = -1, out_ind, length; + unsigned in_loc = 0, out_loc = 0; + struct iio_demux_table *p = NULL; + + /* Clear out any old demux */ + iio_buffer_demux_free(buffer); + kfree(buffer->demux_bounce); + buffer->demux_bounce = NULL; + + /* First work out which scan mode we will actually have */ + if (bitmap_equal(indio_dev->active_scan_mask, + buffer->scan_mask, + indio_dev->masklength)) + return 0; + + /* Now we have the two masks, work from least sig and build up sizes */ + for_each_set_bit(out_ind, + buffer->scan_mask, + indio_dev->masklength) { + in_ind = find_next_bit(indio_dev->active_scan_mask, + indio_dev->masklength, + in_ind + 1); + while (in_ind != out_ind) { + in_ind = find_next_bit(indio_dev->active_scan_mask, + indio_dev->masklength, + in_ind + 1); + length = iio_storage_bytes_for_si(indio_dev, in_ind); + /* Make sure we are aligned */ + in_loc = roundup(in_loc, length) + length; + } + length = iio_storage_bytes_for_si(indio_dev, in_ind); + out_loc = roundup(out_loc, length); + in_loc = roundup(in_loc, length); + ret = iio_buffer_add_demux(buffer, &p, in_loc, out_loc, length); + if (ret) + goto error_clear_mux_table; + out_loc += length; + in_loc += length; + } + /* Relies on scan_timestamp being last */ + if (buffer->scan_timestamp) { + length = iio_storage_bytes_for_timestamp(indio_dev); + out_loc = roundup(out_loc, length); + in_loc = roundup(in_loc, length); + ret = iio_buffer_add_demux(buffer, &p, in_loc, out_loc, length); + if (ret) + goto error_clear_mux_table; + out_loc += length; + in_loc += length; + } + buffer->demux_bounce = kzalloc(out_loc, GFP_KERNEL); + if (buffer->demux_bounce == NULL) { + ret = -ENOMEM; + goto error_clear_mux_table; + } + return 0; + +error_clear_mux_table: + iio_buffer_demux_free(buffer); + + return ret; +} + +static int iio_update_demux(struct iio_dev *indio_dev) +{ + struct iio_buffer *buffer; + int ret; + + list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) { + ret = iio_buffer_update_demux(indio_dev, buffer); + if (ret < 0) + goto error_clear_mux_table; + } + return 0; + +error_clear_mux_table: + list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) + iio_buffer_demux_free(buffer); + + return ret; +} + static int iio_enable_buffers(struct iio_dev *indio_dev, struct iio_device_config *config) { @@ -1199,34 +1354,6 @@ bool iio_validate_scan_mask_onehot(struct iio_dev *indio_dev, } EXPORT_SYMBOL_GPL(iio_validate_scan_mask_onehot); -int iio_scan_mask_query(struct iio_dev *indio_dev, - struct iio_buffer *buffer, int bit) -{ - if (bit > indio_dev->masklength) - return -EINVAL; - - if (!buffer->scan_mask) - return 0; - - /* Ensure return value is 0 or 1. */ - return !!test_bit(bit, buffer->scan_mask); -}; -EXPORT_SYMBOL_GPL(iio_scan_mask_query); - -/** - * struct iio_demux_table - table describing demux memcpy ops - * @from: index to copy from - * @to: index to copy to - * @length: how many bytes to copy - * @l: list head used for management - */ -struct iio_demux_table { - unsigned from; - unsigned to; - unsigned length; - struct list_head l; -}; - static const void *iio_demux(struct iio_buffer *buffer, const void *datain) { @@ -1258,16 +1385,11 @@ static int iio_push_to_buffer(struct iio_buffer *buffer, const void *data) return 0; } -static void iio_buffer_demux_free(struct iio_buffer *buffer) -{ - struct iio_demux_table *p, *q; - list_for_each_entry_safe(p, q, &buffer->demux_list, l) { - list_del(&p->l); - kfree(p); - } -} - - +/** + * iio_push_to_buffers() - push to a registered buffer. + * @indio_dev: iio_dev structure for device. + * @data: Full scan. + */ int iio_push_to_buffers(struct iio_dev *indio_dev, const void *data) { int ret; @@ -1283,113 +1405,6 @@ int iio_push_to_buffers(struct iio_dev *indio_dev, const void *data) } EXPORT_SYMBOL_GPL(iio_push_to_buffers); -static int iio_buffer_add_demux(struct iio_buffer *buffer, - struct iio_demux_table **p, unsigned int in_loc, unsigned int out_loc, - unsigned int length) -{ - - if (*p && (*p)->from + (*p)->length == in_loc && - (*p)->to + (*p)->length == out_loc) { - (*p)->length += length; - } else { - *p = kmalloc(sizeof(**p), GFP_KERNEL); - if (*p == NULL) - return -ENOMEM; - (*p)->from = in_loc; - (*p)->to = out_loc; - (*p)->length = length; - list_add_tail(&(*p)->l, &buffer->demux_list); - } - - return 0; -} - -static int iio_buffer_update_demux(struct iio_dev *indio_dev, - struct iio_buffer *buffer) -{ - int ret, in_ind = -1, out_ind, length; - unsigned in_loc = 0, out_loc = 0; - struct iio_demux_table *p = NULL; - - /* Clear out any old demux */ - iio_buffer_demux_free(buffer); - kfree(buffer->demux_bounce); - buffer->demux_bounce = NULL; - - /* First work out which scan mode we will actually have */ - if (bitmap_equal(indio_dev->active_scan_mask, - buffer->scan_mask, - indio_dev->masklength)) - return 0; - - /* Now we have the two masks, work from least sig and build up sizes */ - for_each_set_bit(out_ind, - buffer->scan_mask, - indio_dev->masklength) { - in_ind = find_next_bit(indio_dev->active_scan_mask, - indio_dev->masklength, - in_ind + 1); - while (in_ind != out_ind) { - in_ind = find_next_bit(indio_dev->active_scan_mask, - indio_dev->masklength, - in_ind + 1); - length = iio_storage_bytes_for_si(indio_dev, in_ind); - /* Make sure we are aligned */ - in_loc = roundup(in_loc, length) + length; - } - length = iio_storage_bytes_for_si(indio_dev, in_ind); - out_loc = roundup(out_loc, length); - in_loc = roundup(in_loc, length); - ret = iio_buffer_add_demux(buffer, &p, in_loc, out_loc, length); - if (ret) - goto error_clear_mux_table; - out_loc += length; - in_loc += length; - } - /* Relies on scan_timestamp being last */ - if (buffer->scan_timestamp) { - length = iio_storage_bytes_for_timestamp(indio_dev); - out_loc = roundup(out_loc, length); - in_loc = roundup(in_loc, length); - ret = iio_buffer_add_demux(buffer, &p, in_loc, out_loc, length); - if (ret) - goto error_clear_mux_table; - out_loc += length; - in_loc += length; - } - buffer->demux_bounce = kzalloc(out_loc, GFP_KERNEL); - if (buffer->demux_bounce == NULL) { - ret = -ENOMEM; - goto error_clear_mux_table; - } - return 0; - -error_clear_mux_table: - iio_buffer_demux_free(buffer); - - return ret; -} - -int iio_update_demux(struct iio_dev *indio_dev) -{ - struct iio_buffer *buffer; - int ret; - - list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) { - ret = iio_buffer_update_demux(indio_dev, buffer); - if (ret < 0) - goto error_clear_mux_table; - } - return 0; - -error_clear_mux_table: - list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) - iio_buffer_demux_free(buffer); - - return ret; -} -EXPORT_SYMBOL_GPL(iio_update_demux); - /** * iio_buffer_release() - Free a buffer's resources * @ref: Pointer to the kref embedded in the iio_buffer struct @@ -1431,3 +1446,19 @@ void iio_buffer_put(struct iio_buffer *buffer) kref_put(&buffer->ref, iio_buffer_release); } EXPORT_SYMBOL_GPL(iio_buffer_put); + +/** + * iio_device_attach_buffer - Attach a buffer to a IIO device + * @indio_dev: The device the buffer should be attached to + * @buffer: The buffer to attach to the device + * + * This function attaches a buffer to a IIO device. The buffer stays attached to + * the device until the device is freed. The function should only be called at + * most once per device. + */ +void iio_device_attach_buffer(struct iio_dev *indio_dev, + struct iio_buffer *buffer) +{ + indio_dev->buffer = iio_buffer_get(buffer); +} +EXPORT_SYMBOL_GPL(iio_device_attach_buffer); diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index aaca42862389..d18ded45bedd 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -32,6 +32,7 @@ #include <linux/iio/sysfs.h> #include <linux/iio/events.h> #include <linux/iio/buffer.h> +#include <linux/iio/buffer_impl.h> /* IDA to assign each registered device a unique id */ static DEFINE_IDA(iio_ida); @@ -83,6 +84,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_ELECTRICALCONDUCTIVITY] = "electricalconductivity", [IIO_COUNT] = "count", [IIO_INDEX] = "index", + [IIO_GRAVITY] = "gravity", }; static const char * const iio_modifier_names[] = { diff --git a/drivers/iio/industrialio-trigger.c b/drivers/iio/industrialio-trigger.c index 978729f6d7c4..978e1592c2a3 100644 --- a/drivers/iio/industrialio-trigger.c +++ b/drivers/iio/industrialio-trigger.c @@ -147,8 +147,7 @@ static struct iio_trigger *__iio_trigger_find_by_name(const char *name) return NULL; } -static struct iio_trigger *iio_trigger_find_by_name(const char *name, - size_t len) +static struct iio_trigger *iio_trigger_acquire_by_name(const char *name) { struct iio_trigger *trig = NULL, *iter; @@ -156,6 +155,7 @@ static struct iio_trigger *iio_trigger_find_by_name(const char *name, list_for_each_entry(iter, &iio_trigger_list, list) if (sysfs_streq(iter->name, name)) { trig = iter; + iio_trigger_get(trig); break; } mutex_unlock(&iio_trigger_list_lock); @@ -416,20 +416,22 @@ static ssize_t iio_trigger_write_current(struct device *dev, } mutex_unlock(&indio_dev->mlock); - trig = iio_trigger_find_by_name(buf, len); - if (oldtrig == trig) - return len; + trig = iio_trigger_acquire_by_name(buf); + if (oldtrig == trig) { + ret = len; + goto out_trigger_put; + } if (trig && indio_dev->info->validate_trigger) { ret = indio_dev->info->validate_trigger(indio_dev, trig); if (ret) - return ret; + goto out_trigger_put; } if (trig && trig->ops->validate_device) { ret = trig->ops->validate_device(trig, indio_dev); if (ret) - return ret; + goto out_trigger_put; } indio_dev->trig = trig; @@ -441,13 +443,16 @@ static ssize_t iio_trigger_write_current(struct device *dev, iio_trigger_put(oldtrig); } if (indio_dev->trig) { - iio_trigger_get(indio_dev->trig); if (indio_dev->modes & INDIO_EVENT_TRIGGERED) iio_trigger_attach_poll_func(indio_dev->trig, indio_dev->pollfunc_event); } return len; + +out_trigger_put: + iio_trigger_put(trig); + return ret; } static DEVICE_ATTR(current_trigger, S_IRUGO | S_IWUSR, @@ -487,7 +492,7 @@ static void iio_trig_release(struct device *device) kfree(trig); } -static struct device_type iio_trig_type = { +static const struct device_type iio_trig_type = { .release = iio_trig_release, .groups = iio_trig_dev_groups, }; @@ -513,46 +518,45 @@ static void iio_trig_subirqunmask(struct irq_data *d) static struct iio_trigger *viio_trigger_alloc(const char *fmt, va_list vargs) { struct iio_trigger *trig; + int i; + trig = kzalloc(sizeof *trig, GFP_KERNEL); - if (trig) { - int i; - trig->dev.type = &iio_trig_type; - trig->dev.bus = &iio_bus_type; - device_initialize(&trig->dev); - - mutex_init(&trig->pool_lock); - trig->subirq_base - = irq_alloc_descs(-1, 0, - CONFIG_IIO_CONSUMERS_PER_TRIGGER, - 0); - if (trig->subirq_base < 0) { - kfree(trig); - return NULL; - } + if (!trig) + return NULL; - trig->name = kvasprintf(GFP_KERNEL, fmt, vargs); - if (trig->name == NULL) { - irq_free_descs(trig->subirq_base, - CONFIG_IIO_CONSUMERS_PER_TRIGGER); - kfree(trig); - return NULL; - } - trig->subirq_chip.name = trig->name; - trig->subirq_chip.irq_mask = &iio_trig_subirqmask; - trig->subirq_chip.irq_unmask = &iio_trig_subirqunmask; - for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { - irq_set_chip(trig->subirq_base + i, - &trig->subirq_chip); - irq_set_handler(trig->subirq_base + i, - &handle_simple_irq); - irq_modify_status(trig->subirq_base + i, - IRQ_NOREQUEST | IRQ_NOAUTOEN, - IRQ_NOPROBE); - } - get_device(&trig->dev); + trig->dev.type = &iio_trig_type; + trig->dev.bus = &iio_bus_type; + device_initialize(&trig->dev); + + mutex_init(&trig->pool_lock); + trig->subirq_base = irq_alloc_descs(-1, 0, + CONFIG_IIO_CONSUMERS_PER_TRIGGER, + 0); + if (trig->subirq_base < 0) + goto free_trig; + + trig->name = kvasprintf(GFP_KERNEL, fmt, vargs); + if (trig->name == NULL) + goto free_descs; + + trig->subirq_chip.name = trig->name; + trig->subirq_chip.irq_mask = &iio_trig_subirqmask; + trig->subirq_chip.irq_unmask = &iio_trig_subirqunmask; + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { + irq_set_chip(trig->subirq_base + i, &trig->subirq_chip); + irq_set_handler(trig->subirq_base + i, &handle_simple_irq); + irq_modify_status(trig->subirq_base + i, + IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE); } + get_device(&trig->dev); return trig; + +free_descs: + irq_free_descs(trig->subirq_base, CONFIG_IIO_CONSUMERS_PER_TRIGGER); +free_trig: + kfree(trig); + return NULL; } struct iio_trigger *iio_trigger_alloc(const char *fmt, ...) diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c index b0f4630a163f..7a13535dc3e9 100644 --- a/drivers/iio/inkern.c +++ b/drivers/iio/inkern.c @@ -601,8 +601,14 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan, scale_type = iio_channel_read(chan, &scale_val, &scale_val2, IIO_CHAN_INFO_SCALE); - if (scale_type < 0) - return scale_type; + if (scale_type < 0) { + /* + * Just pass raw values as processed if no scaling is + * available. + */ + *processed = raw; + return 0; + } switch (scale_type) { case IIO_VAL_INT: diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 298ea5081a96..5f731ead9d46 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -115,6 +115,16 @@ config CM3323 To compile this driver as a module, choose M here: the module will be called cm3323. +config CM3605 + tristate "Capella CM3605 ambient light and proximity sensor" + depends on OF + help + Say Y here if you want to build a driver for Capella CM3605 + ambient light and short range proximity sensor. + + To compile this driver as a module, choose M here: the module will + be called cm3605. + config CM36651 depends on I2C tristate "CM36651 driver" diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 4de520036e6e..c13a2399e6df 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_BH1780) += bh1780.o obj-$(CONFIG_CM32181) += cm32181.o obj-$(CONFIG_CM3232) += cm3232.o obj-$(CONFIG_CM3323) += cm3323.o +obj-$(CONFIG_CM3605) += cm3605.o obj-$(CONFIG_CM36651) += cm36651.o obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o diff --git a/drivers/iio/light/cm3232.c b/drivers/iio/light/cm3232.c index fe89b6823217..263e97235ea0 100644 --- a/drivers/iio/light/cm3232.c +++ b/drivers/iio/light/cm3232.c @@ -119,7 +119,7 @@ static int cm3232_reg_init(struct cm3232_chip *chip) if (ret < 0) dev_err(&chip->client->dev, "Error writing reg_cmd\n"); - return 0; + return ret; } /** diff --git a/drivers/iio/light/cm3605.c b/drivers/iio/light/cm3605.c new file mode 100644 index 000000000000..980624e9ffb5 --- /dev/null +++ b/drivers/iio/light/cm3605.c @@ -0,0 +1,330 @@ +/* + * CM3605 Ambient Light and Proximity Sensor + * + * Copyright (C) 2016 Linaro Ltd. + * Author: Linus Walleij <linus.walleij@linaro.org> + * + * This hardware was found in the very first Nexus One handset from Google/HTC + * and an early endavour into mobile light and proximity sensors. + */ + +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/consumer.h> /* To get our ADC channel */ +#include <linux/iio/types.h> /* To deal with our ADC channel */ +#include <linux/init.h> +#include <linux/leds.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/math64.h> +#include <linux/pm.h> + +#define CM3605_PROX_CHANNEL 0 +#define CM3605_ALS_CHANNEL 1 +#define CM3605_AOUT_TYP_MAX_MV 1550 +/* It should not go above 1.650V according to the data sheet */ +#define CM3605_AOUT_MAX_MV 1650 + +/** + * struct cm3605 - CM3605 state + * @dev: pointer to parent device + * @vdd: regulator controlling VDD + * @aset: sleep enable GPIO, high = sleep + * @aout: IIO ADC channel to convert the AOUT signal + * @als_max: maximum LUX detection (depends on RSET) + * @dir: proximity direction: start as FALLING + * @led: trigger for the infrared LED used by the proximity sensor + */ +struct cm3605 { + struct device *dev; + struct regulator *vdd; + struct gpio_desc *aset; + struct iio_channel *aout; + s32 als_max; + enum iio_event_direction dir; + struct led_trigger *led; +}; + +static irqreturn_t cm3605_prox_irq(int irq, void *d) +{ + struct iio_dev *indio_dev = d; + struct cm3605 *cm3605 = iio_priv(indio_dev); + u64 ev; + + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, CM3605_PROX_CHANNEL, + IIO_EV_TYPE_THRESH, cm3605->dir); + iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev)); + + /* Invert the edge for each event */ + if (cm3605->dir == IIO_EV_DIR_RISING) + cm3605->dir = IIO_EV_DIR_FALLING; + else + cm3605->dir = IIO_EV_DIR_RISING; + + return IRQ_HANDLED; +} + +static int cm3605_get_lux(struct cm3605 *cm3605) +{ + int ret, res; + s64 lux; + + ret = iio_read_channel_processed(cm3605->aout, &res); + if (ret < 0) + return ret; + + dev_dbg(cm3605->dev, "read %d mV from ADC\n", res); + + /* + * AOUT has an offset of ~30mV then linear at dark + * then goes from 2.54 up to 650 LUX yielding 1.55V + * (1550 mV) so scale the returned value to this interval + * using simple linear interpolation. + */ + if (res < 30) + return 0; + if (res > CM3605_AOUT_MAX_MV) + dev_err(cm3605->dev, "device out of range\n"); + + /* Remove bias */ + lux = res - 30; + + /* Linear interpolation between 0 and ALS typ max */ + lux *= cm3605->als_max; + lux = div64_s64(lux, CM3605_AOUT_TYP_MAX_MV); + + return lux; +} + +static int cm3605_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm3605 *cm3605 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = cm3605_get_lux(cm3605); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info cm3605_info = { + .driver_module = THIS_MODULE, + .read_raw = cm3605_read_raw, +}; + +static const struct iio_event_spec cm3605_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec cm3605_channels[] = { + { + .type = IIO_PROXIMITY, + .event_spec = cm3605_events, + .num_event_specs = ARRAY_SIZE(cm3605_events), + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .channel = CM3605_ALS_CHANNEL, + }, +}; + +static int cm3605_probe(struct platform_device *pdev) +{ + struct cm3605 *cm3605; + struct iio_dev *indio_dev; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + enum iio_chan_type ch_type; + u32 rset; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*cm3605)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + cm3605 = iio_priv(indio_dev); + cm3605->dev = dev; + cm3605->dir = IIO_EV_DIR_FALLING; + + ret = of_property_read_u32(np, "capella,aset-resistance-ohms", &rset); + if (ret) { + dev_info(dev, "no RSET specified, assuming 100K\n"); + rset = 100000; + } + switch (rset) { + case 50000: + cm3605->als_max = 650; + break; + case 100000: + cm3605->als_max = 300; + break; + case 300000: + cm3605->als_max = 100; + break; + case 600000: + cm3605->als_max = 50; + break; + default: + dev_info(dev, "non-standard resistance\n"); + return -EINVAL; + } + + cm3605->aout = devm_iio_channel_get(dev, "aout"); + if (IS_ERR(cm3605->aout)) { + if (PTR_ERR(cm3605->aout) == -ENODEV) { + dev_err(dev, "no ADC, deferring...\n"); + return -EPROBE_DEFER; + } + dev_err(dev, "failed to get AOUT ADC channel\n"); + return PTR_ERR(cm3605->aout); + } + ret = iio_get_channel_type(cm3605->aout, &ch_type); + if (ret < 0) + return ret; + if (ch_type != IIO_VOLTAGE) { + dev_err(dev, "wrong type of IIO channel specified for AOUT\n"); + return -EINVAL; + } + + cm3605->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(cm3605->vdd)) { + dev_err(dev, "failed to get VDD regulator\n"); + return PTR_ERR(cm3605->vdd); + } + ret = regulator_enable(cm3605->vdd); + if (ret) { + dev_err(dev, "failed to enable VDD regulator\n"); + return ret; + } + + cm3605->aset = devm_gpiod_get(dev, "aset", GPIOD_OUT_HIGH); + if (IS_ERR(cm3605->aset)) { + dev_err(dev, "no ASET GPIO\n"); + ret = PTR_ERR(cm3605->aset); + goto out_disable_vdd; + } + + ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0), + cm3605_prox_irq, NULL, 0, "cm3605", indio_dev); + if (ret) { + dev_err(dev, "unable to request IRQ\n"); + goto out_disable_aset; + } + + /* Just name the trigger the same as the driver */ + led_trigger_register_simple("cm3605", &cm3605->led); + led_trigger_event(cm3605->led, LED_FULL); + + indio_dev->dev.parent = dev; + indio_dev->info = &cm3605_info; + indio_dev->name = "cm3605"; + indio_dev->channels = cm3605_channels; + indio_dev->num_channels = ARRAY_SIZE(cm3605_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto out_remove_trigger; + dev_info(dev, "Capella Microsystems CM3605 enabled range 0..%d LUX\n", + cm3605->als_max); + + return 0; + +out_remove_trigger: + led_trigger_event(cm3605->led, LED_OFF); + led_trigger_unregister_simple(cm3605->led); +out_disable_aset: + gpiod_set_value_cansleep(cm3605->aset, 0); +out_disable_vdd: + regulator_disable(cm3605->vdd); + return ret; +} + +static int cm3605_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct cm3605 *cm3605 = iio_priv(indio_dev); + + led_trigger_event(cm3605->led, LED_OFF); + led_trigger_unregister_simple(cm3605->led); + gpiod_set_value_cansleep(cm3605->aset, 0); + iio_device_unregister(indio_dev); + regulator_disable(cm3605->vdd); + + return 0; +} + +static int __maybe_unused cm3605_pm_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct cm3605 *cm3605 = iio_priv(indio_dev); + + led_trigger_event(cm3605->led, LED_OFF); + regulator_disable(cm3605->vdd); + + return 0; +} + +static int __maybe_unused cm3605_pm_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct cm3605 *cm3605 = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(cm3605->vdd); + if (ret) + dev_err(dev, "failed to enable regulator in resume path\n"); + led_trigger_event(cm3605->led, LED_FULL); + + return 0; +} + +static const struct dev_pm_ops cm3605_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cm3605_pm_suspend, + cm3605_pm_resume) +}; + +static const struct of_device_id cm3605_of_match[] = { + {.compatible = "capella,cm3605"}, + { }, +}; +MODULE_DEVICE_TABLE(of, cm3605_of_match); + +static struct platform_driver cm3605_driver = { + .driver = { + .name = "cm3605", + .of_match_table = cm3605_of_match, + .pm = &cm3605_dev_pm_ops, + }, + .probe = cm3605_probe, + .remove = cm3605_remove, +}; +module_platform_driver(cm3605_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("CM3605 ambient light and proximity sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c index 8bb1f90ecd51..059d964772c7 100644 --- a/drivers/iio/light/hid-sensor-als.c +++ b/drivers/iio/light/hid-sensor-als.c @@ -31,13 +31,17 @@ #include <linux/iio/triggered_buffer.h> #include "../common/hid-sensors/hid-sensor-trigger.h" -#define CHANNEL_SCAN_INDEX_ILLUM 0 +enum { + CHANNEL_SCAN_INDEX_INTENSITY = 0, + CHANNEL_SCAN_INDEX_ILLUM = 1, + CHANNEL_SCAN_INDEX_MAX +}; struct als_state { struct hid_sensor_hub_callbacks callbacks; struct hid_sensor_common common_attributes; struct hid_sensor_hub_attribute_info als_illum; - u32 illum; + u32 illum[CHANNEL_SCAN_INDEX_MAX]; int scale_pre_decml; int scale_post_decml; int scale_precision; @@ -55,6 +59,15 @@ static const struct iio_chan_spec als_channels[] = { BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ) | BIT(IIO_CHAN_INFO_HYSTERESIS), + .scan_index = CHANNEL_SCAN_INDEX_INTENSITY, + }, + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS), .scan_index = CHANNEL_SCAN_INDEX_ILLUM, } }; @@ -86,6 +99,7 @@ static int als_read_raw(struct iio_dev *indio_dev, switch (mask) { case 0: switch (chan->scan_index) { + case CHANNEL_SCAN_INDEX_INTENSITY: case CHANNEL_SCAN_INDEX_ILLUM: report_id = als_state->als_illum.report_id; address = @@ -202,10 +216,12 @@ static int als_capture_sample(struct hid_sensor_hub_device *hsdev, struct iio_dev *indio_dev = platform_get_drvdata(priv); struct als_state *als_state = iio_priv(indio_dev); int ret = -EINVAL; + u32 sample_data = *(u32 *)raw_data; switch (usage_id) { case HID_USAGE_SENSOR_LIGHT_ILLUM: - als_state->illum = *(u32 *)raw_data; + als_state->illum[CHANNEL_SCAN_INDEX_INTENSITY] = sample_data; + als_state->illum[CHANNEL_SCAN_INDEX_ILLUM] = sample_data; ret = 0; break; default: @@ -230,6 +246,8 @@ static int als_parse_report(struct platform_device *pdev, &st->als_illum); if (ret < 0) return ret; + als_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_INTENSITY, + st->als_illum.size); als_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_ILLUM, st->als_illum.size); diff --git a/drivers/iio/light/opt3001.c b/drivers/iio/light/opt3001.c index 78c9b3a6453a..b91ebc3483ce 100644 --- a/drivers/iio/light/opt3001.c +++ b/drivers/iio/light/opt3001.c @@ -840,6 +840,7 @@ static const struct of_device_id opt3001_of_match[] = { { .compatible = "ti,opt3001" }, { } }; +MODULE_DEVICE_TABLE(of, opt3001_of_match); static struct i2c_driver opt3001_driver = { .probe = opt3001_probe, diff --git a/drivers/iio/magnetometer/ak8974.c b/drivers/iio/magnetometer/ak8974.c index ce09d771c1fb..6dd8cbd7ce95 100644 --- a/drivers/iio/magnetometer/ak8974.c +++ b/drivers/iio/magnetometer/ak8974.c @@ -278,13 +278,9 @@ static int ak8974_await_drdy(struct ak8974 *ak8974) if (val & AK8974_STATUS_DRDY) return 0; } while (--timeout); - if (!timeout) { - dev_err(&ak8974->i2c->dev, - "timeout waiting for DRDY\n"); - return -ETIMEDOUT; - } - return 0; + dev_err(&ak8974->i2c->dev, "timeout waiting for DRDY\n"); + return -ETIMEDOUT; } static int ak8974_getresult(struct ak8974 *ak8974, __le16 *result) diff --git a/drivers/iio/magnetometer/mag3110.c b/drivers/iio/magnetometer/mag3110.c index f2b3bd7bf862..b4f643fb3b1e 100644 --- a/drivers/iio/magnetometer/mag3110.c +++ b/drivers/iio/magnetometer/mag3110.c @@ -222,29 +222,39 @@ static int mag3110_write_raw(struct iio_dev *indio_dev, int val, int val2, long mask) { struct mag3110_data *data = iio_priv(indio_dev); - int rate; + int rate, ret; - if (iio_buffer_enabled(indio_dev)) - return -EBUSY; + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: rate = mag3110_get_samp_freq_index(data, val, val2); - if (rate < 0) - return -EINVAL; + if (rate < 0) { + ret = -EINVAL; + break; + } data->ctrl_reg1 &= ~MAG3110_CTRL_DR_MASK; data->ctrl_reg1 |= rate << MAG3110_CTRL_DR_SHIFT; - return i2c_smbus_write_byte_data(data->client, + ret = i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1, data->ctrl_reg1); + break; case IIO_CHAN_INFO_CALIBBIAS: - if (val < -10000 || val > 10000) - return -EINVAL; - return i2c_smbus_write_word_swapped(data->client, + if (val < -10000 || val > 10000) { + ret = -EINVAL; + break; + } + ret = i2c_smbus_write_word_swapped(data->client, MAG3110_OFF_X + 2 * chan->scan_index, val << 1); + break; default: - return -EINVAL; + ret = -EINVAL; + break; } + iio_device_release_direct_mode(indio_dev); + return ret; } static irqreturn_t mag3110_trigger_handler(int irq, void *p) diff --git a/drivers/iio/potentiometer/Kconfig b/drivers/iio/potentiometer/Kconfig index 2e9da1cf3297..8bf282510be6 100644 --- a/drivers/iio/potentiometer/Kconfig +++ b/drivers/iio/potentiometer/Kconfig @@ -15,6 +15,17 @@ config DS1803 To compile this driver as a module, choose M here: the module will be called ds1803. +config MAX5481 + tristate "Maxim MAX5481-MAX5484 Digital Potentiometer driver" + depends on SPI + help + Say yes here to build support for the Maxim + MAX5481, MAX5482, MAX5483, MAX5484 digital potentiometer + chips. + + To compile this driver as a module, choose M here: the + module will be called max5481. + config MAX5487 tristate "Maxim MAX5487/MAX5488/MAX5489 Digital Potentiometer driver" depends on SPI diff --git a/drivers/iio/potentiometer/Makefile b/drivers/iio/potentiometer/Makefile index 8adb58f38c0b..2260d40e0936 100644 --- a/drivers/iio/potentiometer/Makefile +++ b/drivers/iio/potentiometer/Makefile @@ -4,6 +4,7 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_DS1803) += ds1803.o +obj-$(CONFIG_MAX5481) += max5481.o obj-$(CONFIG_MAX5487) += max5487.o obj-$(CONFIG_MCP4131) += mcp4131.o obj-$(CONFIG_MCP4531) += mcp4531.o diff --git a/drivers/iio/potentiometer/max5481.c b/drivers/iio/potentiometer/max5481.c new file mode 100644 index 000000000000..926554991244 --- /dev/null +++ b/drivers/iio/potentiometer/max5481.c @@ -0,0 +1,223 @@ +/* + * Maxim Integrated MAX5481-MAX5484 digital potentiometer driver + * Copyright 2016 Rockwell Collins + * + * Datasheet: + * http://datasheets.maximintegrated.com/en/ds/MAX5481-MAX5484.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 + * published by the free software foundation. + * + */ + +#include <linux/acpi.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/spi/spi.h> + +/* write wiper reg */ +#define MAX5481_WRITE_WIPER (0 << 4) +/* copy wiper reg to NV reg */ +#define MAX5481_COPY_AB_TO_NV (2 << 4) +/* copy NV reg to wiper reg */ +#define MAX5481_COPY_NV_TO_AB (3 << 4) + +#define MAX5481_MAX_POS 1023 + +enum max5481_variant { + max5481, + max5482, + max5483, + max5484, +}; + +struct max5481_cfg { + int kohms; +}; + +static const struct max5481_cfg max5481_cfg[] = { + [max5481] = { .kohms = 10, }, + [max5482] = { .kohms = 50, }, + [max5483] = { .kohms = 10, }, + [max5484] = { .kohms = 50, }, +}; + +struct max5481_data { + struct spi_device *spi; + const struct max5481_cfg *cfg; + u8 msg[3] ____cacheline_aligned; +}; + +#define MAX5481_CHANNEL { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec max5481_channels[] = { + MAX5481_CHANNEL, +}; + +static int max5481_write_cmd(struct max5481_data *data, u8 cmd, u16 val) +{ + struct spi_device *spi = data->spi; + + data->msg[0] = cmd; + + switch (cmd) { + case MAX5481_WRITE_WIPER: + data->msg[1] = val >> 2; + data->msg[2] = (val & 0x3) << 6; + return spi_write(spi, data->msg, 3); + + case MAX5481_COPY_AB_TO_NV: + case MAX5481_COPY_NV_TO_AB: + return spi_write(spi, data->msg, 1); + + default: + return -EIO; + } +} + +static int max5481_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max5481_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_SCALE) + return -EINVAL; + + *val = 1000 * data->cfg->kohms; + *val2 = MAX5481_MAX_POS; + + return IIO_VAL_FRACTIONAL; +} + +static int max5481_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max5481_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if (val < 0 || val > MAX5481_MAX_POS) + return -EINVAL; + + return max5481_write_cmd(data, MAX5481_WRITE_WIPER, val); +} + +static const struct iio_info max5481_info = { + .read_raw = max5481_read_raw, + .write_raw = max5481_write_raw, + .driver_module = THIS_MODULE, +}; + +#if defined(CONFIG_OF) +static const struct of_device_id max5481_match[] = { + { .compatible = "maxim,max5481", .data = &max5481_cfg[max5481] }, + { .compatible = "maxim,max5482", .data = &max5481_cfg[max5482] }, + { .compatible = "maxim,max5483", .data = &max5481_cfg[max5483] }, + { .compatible = "maxim,max5484", .data = &max5481_cfg[max5484] }, + { } +}; +MODULE_DEVICE_TABLE(of, max5481_match); +#endif + +static int max5481_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct max5481_data *data; + const struct spi_device_id *id = spi_get_device_id(spi); + const struct of_device_id *match; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + dev_set_drvdata(&spi->dev, indio_dev); + data = iio_priv(indio_dev); + + data->spi = spi; + + match = of_match_device(of_match_ptr(max5481_match), &spi->dev); + if (match) + data->cfg = of_device_get_match_data(&spi->dev); + else + data->cfg = &max5481_cfg[id->driver_data]; + + indio_dev->name = id->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* variant specific configuration */ + indio_dev->info = &max5481_info; + indio_dev->channels = max5481_channels; + indio_dev->num_channels = ARRAY_SIZE(max5481_channels); + + /* restore wiper from NV */ + ret = max5481_write_cmd(data, MAX5481_COPY_NV_TO_AB, 0); + if (ret < 0) + return ret; + + return iio_device_register(indio_dev); +} + +static int max5481_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = dev_get_drvdata(&spi->dev); + struct max5481_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + /* save wiper reg to NV reg */ + return max5481_write_cmd(data, MAX5481_COPY_AB_TO_NV, 0); +} + +static const struct spi_device_id max5481_id_table[] = { + { "max5481", max5481 }, + { "max5482", max5482 }, + { "max5483", max5483 }, + { "max5484", max5484 }, + { } +}; +MODULE_DEVICE_TABLE(spi, max5481_id_table); + +#if defined(CONFIG_ACPI) +static const struct acpi_device_id max5481_acpi_match[] = { + { "max5481", max5481 }, + { "max5482", max5482 }, + { "max5483", max5483 }, + { "max5484", max5484 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, max5481_acpi_match); +#endif + +static struct spi_driver max5481_driver = { + .driver = { + .name = "max5481", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(max5481_match), + .acpi_match_table = ACPI_PTR(max5481_acpi_match), + }, + .probe = max5481_probe, + .remove = max5481_remove, + .id_table = max5481_id_table, +}; + +module_spi_driver(max5481_driver); + +MODULE_AUTHOR("Maury Anderson <maury.anderson@rockwellcollins.com>"); +MODULE_DESCRIPTION("max5481 SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/mcp4531.c b/drivers/iio/potentiometer/mcp4531.c index 0d1bcf89ae17..314353d7ab59 100644 --- a/drivers/iio/potentiometer/mcp4531.c +++ b/drivers/iio/potentiometer/mcp4531.c @@ -284,6 +284,7 @@ static const struct of_device_id mcp4531_of_match[] = { MCP4531_COMPATIBLE("microchip,mcp4662-104", MCP466x_104), { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, mcp4531_of_match); #endif static int mcp4531_probe(struct i2c_client *client, diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index bd8d96b96771..5d16b252ab6b 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig @@ -42,6 +42,16 @@ config BMP280_SPI depends on SPI_MASTER select REGMAP +config IIO_CROS_EC_BARO + tristate "ChromeOS EC Barometer Sensor" + depends on IIO_CROS_EC_SENSORS_CORE + help + Say yes here to build support for the Barometer sensor when + presented by the ChromeOS EC Sensor hub. + + To compile this driver as a module, choose M here: the module + will be called cros_ec_baro. + config HID_SENSOR_PRESS depends on HID_SENSOR_HUB select IIO_BUFFER diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile index de3dbc81dc5a..838642789389 100644 --- a/drivers/iio/pressure/Makefile +++ b/drivers/iio/pressure/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_BMP280) += bmp280.o bmp280-objs := bmp280-core.o bmp280-regmap.o obj-$(CONFIG_BMP280_I2C) += bmp280-i2c.o obj-$(CONFIG_BMP280_SPI) += bmp280-spi.o +obj-$(CONFIG_IIO_CROS_EC_BARO) += cros_ec_baro.o obj-$(CONFIG_HID_SENSOR_PRESS) += hid-sensor-press.o obj-$(CONFIG_HP03) += hp03.o obj-$(CONFIG_MPL115) += mpl115.o diff --git a/drivers/iio/pressure/bmp280-core.c b/drivers/iio/pressure/bmp280-core.c index e5a533cbd53f..4d18826ac63c 100644 --- a/drivers/iio/pressure/bmp280-core.c +++ b/drivers/iio/pressure/bmp280-core.c @@ -65,7 +65,7 @@ struct bmp280_data { struct bmp180_calib calib; struct regulator *vddd; struct regulator *vdda; - unsigned int start_up_time; /* in milliseconds */ + unsigned int start_up_time; /* in microseconds */ /* log of base 2 of oversampling rate */ u8 oversampling_press; @@ -935,14 +935,14 @@ int bmp280_common_probe(struct device *dev, data->chip_info = &bmp180_chip_info; data->oversampling_press = ilog2(8); data->oversampling_temp = ilog2(1); - data->start_up_time = 10; + data->start_up_time = 10000; break; case BMP280_CHIP_ID: indio_dev->num_channels = 2; data->chip_info = &bmp280_chip_info; data->oversampling_press = ilog2(16); data->oversampling_temp = ilog2(2); - data->start_up_time = 2; + data->start_up_time = 2000; break; case BME280_CHIP_ID: indio_dev->num_channels = 3; @@ -950,7 +950,7 @@ int bmp280_common_probe(struct device *dev, data->oversampling_press = ilog2(16); data->oversampling_humid = ilog2(16); data->oversampling_temp = ilog2(2); - data->start_up_time = 2; + data->start_up_time = 2000; break; default: return -EINVAL; @@ -979,7 +979,7 @@ int bmp280_common_probe(struct device *dev, goto out_disable_vddd; } /* Wait to make sure we started up properly */ - mdelay(data->start_up_time); + usleep_range(data->start_up_time, data->start_up_time + 100); /* Bring chip out of reset if there is an assigned GPIO line */ gpiod = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); @@ -1038,7 +1038,7 @@ int bmp280_common_probe(struct device *dev, * Set autosuspend to two orders of magnitude larger than the * start-up time. */ - pm_runtime_set_autosuspend_delay(dev, data->start_up_time *100); + pm_runtime_set_autosuspend_delay(dev, data->start_up_time / 10); pm_runtime_use_autosuspend(dev); pm_runtime_put(dev); @@ -1101,7 +1101,7 @@ static int bmp280_runtime_resume(struct device *dev) ret = regulator_enable(data->vdda); if (ret) return ret; - msleep(data->start_up_time); + usleep_range(data->start_up_time, data->start_up_time + 100); return data->chip_info->chip_config(data); } #endif /* CONFIG_PM */ diff --git a/drivers/iio/pressure/cros_ec_baro.c b/drivers/iio/pressure/cros_ec_baro.c new file mode 100644 index 000000000000..48b2a30f57ae --- /dev/null +++ b/drivers/iio/pressure/cros_ec_baro.c @@ -0,0 +1,220 @@ +/* + * cros_ec_baro - Driver for barometer sensor behind CrosEC. + * + * Copyright (C) 2017 Google, Inc + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/trigger.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/kernel.h> +#include <linux/mfd/cros_ec.h> +#include <linux/mfd/cros_ec_commands.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/platform_device.h> + +#include "../common/cros_ec_sensors/cros_ec_sensors_core.h" + +/* + * One channel for pressure, the other for timestamp. + */ +#define CROS_EC_BARO_MAX_CHANNELS (1 + 1) + +/* State data for ec_sensors iio driver. */ +struct cros_ec_baro_state { + /* Shared by all sensors */ + struct cros_ec_sensors_core_state core; + + struct iio_chan_spec channels[CROS_EC_BARO_MAX_CHANNELS]; +}; + +static int cros_ec_baro_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cros_ec_baro_state *st = iio_priv(indio_dev); + u16 data = 0; + int ret = IIO_VAL_INT; + int idx = chan->scan_index; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (cros_ec_sensors_read_cmd(indio_dev, 1 << idx, + (s16 *)&data) < 0) + ret = -EIO; + *val = data; + break; + case IIO_CHAN_INFO_SCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE; + + if (cros_ec_motion_send_host_cmd(&st->core, 0)) { + ret = -EIO; + break; + } + *val = st->core.resp->sensor_range.ret; + + /* scale * in_pressure_raw --> kPa */ + *val2 = 10 << CROS_EC_SENSOR_BITS; + ret = IIO_VAL_FRACTIONAL; + break; + default: + ret = cros_ec_sensors_core_read(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static int cros_ec_baro_write(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cros_ec_baro_state *st = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&st->core.cmd_lock); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; + st->core.param.sensor_range.data = val; + + /* Always roundup, so caller gets at least what it asks for. */ + st->core.param.sensor_range.roundup = 1; + + if (cros_ec_motion_send_host_cmd(&st->core, 0)) + ret = -EIO; + break; + default: + ret = cros_ec_sensors_core_write(&st->core, chan, val, val2, + mask); + break; + } + + mutex_unlock(&st->core.cmd_lock); + + return ret; +} + +static const struct iio_info cros_ec_baro_info = { + .read_raw = &cros_ec_baro_read, + .write_raw = &cros_ec_baro_write, + .driver_module = THIS_MODULE, +}; + +static int cros_ec_baro_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); + struct cros_ec_device *ec_device; + struct iio_dev *indio_dev; + struct cros_ec_baro_state *state; + struct iio_chan_spec *channel; + int ret; + + if (!ec_dev || !ec_dev->ec_dev) { + dev_warn(dev, "No CROS EC device found.\n"); + return -EINVAL; + } + ec_device = ec_dev->ec_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); + if (!indio_dev) + return -ENOMEM; + + ret = cros_ec_sensors_core_init(pdev, indio_dev, true); + if (ret) + return ret; + + indio_dev->info = &cros_ec_baro_info; + state = iio_priv(indio_dev); + state->core.type = state->core.resp->info.type; + state->core.loc = state->core.resp->info.location; + channel = state->channels; + /* Common part */ + channel->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + channel->info_mask_shared_by_all = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_FREQUENCY); + channel->scan_type.realbits = CROS_EC_SENSOR_BITS; + channel->scan_type.storagebits = CROS_EC_SENSOR_BITS; + channel->scan_type.shift = 0; + channel->scan_index = 0; + channel->ext_info = cros_ec_sensors_ext_info; + channel->scan_type.sign = 'u'; + + state->core.calib[0] = 0; + + /* Sensor specific */ + switch (state->core.type) { + case MOTIONSENSE_TYPE_BARO: + channel->type = IIO_PRESSURE; + break; + default: + dev_warn(dev, "Unknown motion sensor\n"); + return -EINVAL; + } + + /* Timestamp */ + channel++; + channel->type = IIO_TIMESTAMP; + channel->channel = -1; + channel->scan_index = 1; + channel->scan_type.sign = 's'; + channel->scan_type.realbits = 64; + channel->scan_type.storagebits = 64; + + indio_dev->channels = state->channels; + indio_dev->num_channels = CROS_EC_BARO_MAX_CHANNELS; + + state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + cros_ec_sensors_capture, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct platform_device_id cros_ec_baro_ids[] = { + { + .name = "cros-ec-baro", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, cros_ec_baro_ids); + +static struct platform_driver cros_ec_baro_platform_driver = { + .driver = { + .name = "cros-ec-baro", + }, + .probe = cros_ec_baro_probe, + .id_table = cros_ec_baro_ids, +}; +module_platform_driver(cros_ec_baro_platform_driver); + +MODULE_DESCRIPTION("ChromeOS EC barometer sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/mpl115.c b/drivers/iio/pressure/mpl115.c index 73f2f0c46e62..8f2bce213248 100644 --- a/drivers/iio/pressure/mpl115.c +++ b/drivers/iio/pressure/mpl115.c @@ -137,6 +137,7 @@ static const struct iio_chan_spec mpl115_channels[] = { { .type = IIO_TEMP, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), }, }; diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c index cc3f84139157..525644a7442d 100644 --- a/drivers/iio/pressure/mpl3115.c +++ b/drivers/iio/pressure/mpl3115.c @@ -190,7 +190,7 @@ static const struct iio_chan_spec mpl3115_channels[] = { { .type = IIO_PRESSURE, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), - BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), .scan_index = 0, .scan_type = { .sign = 'u', @@ -203,7 +203,7 @@ static const struct iio_chan_spec mpl3115_channels[] = { { .type = IIO_TEMP, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), - BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), .scan_index = 1, .scan_type = { .sign = 's', diff --git a/drivers/iio/pressure/ms5611_core.c b/drivers/iio/pressure/ms5611_core.c index 6bd53e702667..2a77a2f15752 100644 --- a/drivers/iio/pressure/ms5611_core.c +++ b/drivers/iio/pressure/ms5611_core.c @@ -308,6 +308,7 @@ static int ms5611_write_raw(struct iio_dev *indio_dev, { struct ms5611_state *st = iio_priv(indio_dev); const struct ms5611_osr *osr = NULL; + int ret; if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO) return -EINVAL; @@ -321,12 +322,11 @@ static int ms5611_write_raw(struct iio_dev *indio_dev, if (!osr) return -EINVAL; - mutex_lock(&st->lock); + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; - if (iio_buffer_enabled(indio_dev)) { - mutex_unlock(&st->lock); - return -EBUSY; - } + mutex_lock(&st->lock); if (chan->type == IIO_TEMP) st->temp_osr = osr; @@ -334,6 +334,8 @@ static int ms5611_write_raw(struct iio_dev *indio_dev, st->pressure_osr = osr; mutex_unlock(&st->lock); + iio_device_release_direct_mode(indio_dev); + return 0; } diff --git a/drivers/iio/pressure/st_pressure.h b/drivers/iio/pressure/st_pressure.h index 903a21e46874..7d995937adba 100644 --- a/drivers/iio/pressure/st_pressure.h +++ b/drivers/iio/pressure/st_pressure.h @@ -14,6 +14,14 @@ #include <linux/types.h> #include <linux/iio/common/st_sensors.h> +enum st_press_type { + LPS001WP, + LPS25H, + LPS331AP, + LPS22HB, + ST_PRESS_MAX, +}; + #define LPS001WP_PRESS_DEV_NAME "lps001wp" #define LPS25H_PRESS_DEV_NAME "lps25h" #define LPS331AP_PRESS_DEV_NAME "lps331ap" diff --git a/drivers/iio/pressure/st_pressure_core.c b/drivers/iio/pressure/st_pressure_core.c index e19e0787864c..5f2680855552 100644 --- a/drivers/iio/pressure/st_pressure_core.c +++ b/drivers/iio/pressure/st_pressure_core.c @@ -136,20 +136,21 @@ static const struct iio_chan_spec st_press_1_channels[] = { .address = ST_PRESS_1_OUT_XL_ADDR, .scan_index = 0, .scan_type = { - .sign = 'u', + .sign = 's', .realbits = 24, .storagebits = 32, .endianness = IIO_LE, }, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), }, { .type = IIO_TEMP, .address = ST_TEMP_1_OUT_L_ADDR, .scan_index = 1, .scan_type = { - .sign = 'u', + .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_LE, @@ -158,6 +159,7 @@ static const struct iio_chan_spec st_press_1_channels[] = { BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), }, IIO_CHAN_SOFT_TIMESTAMP(2) }; @@ -168,7 +170,7 @@ static const struct iio_chan_spec st_press_lps001wp_channels[] = { .address = ST_PRESS_LPS001WP_OUT_L_ADDR, .scan_index = 0, .scan_type = { - .sign = 'u', + .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_LE, @@ -182,7 +184,7 @@ static const struct iio_chan_spec st_press_lps001wp_channels[] = { .address = ST_TEMP_LPS001WP_OUT_L_ADDR, .scan_index = 1, .scan_type = { - .sign = 'u', + .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_LE, @@ -200,7 +202,7 @@ static const struct iio_chan_spec st_press_lps22hb_channels[] = { .address = ST_PRESS_1_OUT_XL_ADDR, .scan_index = 0, .scan_type = { - .sign = 'u', + .sign = 's', .realbits = 24, .storagebits = 32, .endianness = IIO_LE, diff --git a/drivers/iio/pressure/st_pressure_i2c.c b/drivers/iio/pressure/st_pressure_i2c.c index ed18701c68c9..17417a4d5a5f 100644 --- a/drivers/iio/pressure/st_pressure_i2c.c +++ b/drivers/iio/pressure/st_pressure_i2c.c @@ -11,6 +11,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/acpi.h> #include <linux/i2c.h> #include <linux/iio/iio.h> @@ -43,25 +44,56 @@ MODULE_DEVICE_TABLE(of, st_press_of_match); #define st_press_of_match NULL #endif +#ifdef CONFIG_ACPI +static const struct acpi_device_id st_press_acpi_match[] = { + {"SNO9210", LPS22HB}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, st_press_acpi_match); +#else +#define st_press_acpi_match NULL +#endif + +static const struct i2c_device_id st_press_id_table[] = { + { LPS001WP_PRESS_DEV_NAME, LPS001WP }, + { LPS25H_PRESS_DEV_NAME, LPS25H }, + { LPS331AP_PRESS_DEV_NAME, LPS331AP }, + { LPS22HB_PRESS_DEV_NAME, LPS22HB }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, st_press_id_table); + static int st_press_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct iio_dev *indio_dev; struct st_sensor_data *press_data; - int err; + int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*press_data)); if (!indio_dev) return -ENOMEM; press_data = iio_priv(indio_dev); - st_sensors_of_i2c_probe(client, st_press_of_match); + + if (client->dev.of_node) { + st_sensors_of_i2c_probe(client, st_press_of_match); + } else if (ACPI_HANDLE(&client->dev)) { + ret = st_sensors_match_acpi_device(&client->dev); + if ((ret < 0) || (ret >= ST_PRESS_MAX)) + return -ENODEV; + + strncpy(client->name, st_press_id_table[ret].name, + sizeof(client->name)); + client->name[sizeof(client->name) - 1] = '\0'; + } else if (!id) + return -ENODEV; st_sensors_i2c_configure(indio_dev, client, press_data); - err = st_press_common_probe(indio_dev); - if (err < 0) - return err; + ret = st_press_common_probe(indio_dev); + if (ret < 0) + return ret; return 0; } @@ -73,18 +105,11 @@ static int st_press_i2c_remove(struct i2c_client *client) return 0; } -static const struct i2c_device_id st_press_id_table[] = { - { LPS001WP_PRESS_DEV_NAME }, - { LPS25H_PRESS_DEV_NAME }, - { LPS331AP_PRESS_DEV_NAME }, - {}, -}; -MODULE_DEVICE_TABLE(i2c, st_press_id_table); - static struct i2c_driver st_press_driver = { .driver = { .name = "st-press-i2c", .of_match_table = of_match_ptr(st_press_of_match), + .acpi_match_table = ACPI_PTR(st_press_acpi_match), }, .probe = st_press_i2c_probe, .remove = st_press_i2c_remove, diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig index ef4c73db5b53..ab96cb7a0054 100644 --- a/drivers/iio/proximity/Kconfig +++ b/drivers/iio/proximity/Kconfig @@ -18,7 +18,7 @@ config AS3935 endmenu -menu "Proximity sensors" +menu "Proximity and distance sensors" config LIDAR_LITE_V2 tristate "PulsedLight LIDAR sensor" @@ -45,4 +45,15 @@ config SX9500 To compile this driver as a module, choose M here: the module will be called sx9500. +config SRF08 + tristate "Devantech SRF08 ultrasonic ranger sensor" + depends on I2C + help + Say Y here to build a driver for Devantech SRF08 ultrasonic + ranger sensor. This driver can be used to measure the distance + of objects. + + To compile this driver as a module, choose M here: the + module will be called srf08. + endmenu diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile index 9aadd9a8ee99..e914c2a5dd49 100644 --- a/drivers/iio/proximity/Makefile +++ b/drivers/iio/proximity/Makefile @@ -5,4 +5,5 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AS3935) += as3935.o obj-$(CONFIG_LIDAR_LITE_V2) += pulsedlight-lidar-lite-v2.o +obj-$(CONFIG_SRF08) += srf08.o obj-$(CONFIG_SX9500) += sx9500.o diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c index 1fa9eefa0982..20c16a08c9d9 100644 --- a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c +++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c @@ -326,12 +326,14 @@ static int lidar_remove(struct i2c_client *client) static const struct i2c_device_id lidar_id[] = { {"lidar-lite-v2", 0}, + {"lidar-lite-v3", 0}, { }, }; MODULE_DEVICE_TABLE(i2c, lidar_id); static const struct of_device_id lidar_dt_ids[] = { { .compatible = "pulsedlight,lidar-lite-v2" }, + { .compatible = "grmn,lidar-lite-v3" }, { } }; MODULE_DEVICE_TABLE(of, lidar_dt_ids); diff --git a/drivers/iio/proximity/srf08.c b/drivers/iio/proximity/srf08.c new file mode 100644 index 000000000000..49316cbf7c60 --- /dev/null +++ b/drivers/iio/proximity/srf08.c @@ -0,0 +1,398 @@ +/* + * srf08.c - Support for Devantech SRF08 ultrasonic ranger + * + * Copyright (c) 2016 Andreas Klinger <ak@it-klinger.de> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * For details about the device see: + * http://www.robot-electronics.co.uk/htm/srf08tech.html + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/bitops.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* registers of SRF08 device */ +#define SRF08_WRITE_COMMAND 0x00 /* Command Register */ +#define SRF08_WRITE_MAX_GAIN 0x01 /* Max Gain Register: 0 .. 31 */ +#define SRF08_WRITE_RANGE 0x02 /* Range Register: 0 .. 255 */ +#define SRF08_READ_SW_REVISION 0x00 /* Software Revision */ +#define SRF08_READ_LIGHT 0x01 /* Light Sensor during last echo */ +#define SRF08_READ_ECHO_1_HIGH 0x02 /* Range of first echo received */ +#define SRF08_READ_ECHO_1_LOW 0x03 /* Range of first echo received */ + +#define SRF08_CMD_RANGING_CM 0x51 /* Ranging Mode - Result in cm */ + +#define SRF08_DEFAULT_GAIN 1025 /* default analogue value of Gain */ +#define SRF08_DEFAULT_RANGE 6020 /* default value of Range in mm */ + +struct srf08_data { + struct i2c_client *client; + int sensitivity; /* Gain */ + int range_mm; /* max. Range in mm */ + struct mutex lock; +}; + +/* + * in the documentation one can read about the "Gain" of the device + * which is used here for amplifying the signal and filtering out unwanted + * ones. + * But with ADC's this term is already used differently and that's why it + * is called "Sensitivity" here. + */ +static const int srf08_sensitivity[] = { + 94, 97, 100, 103, 107, 110, 114, 118, + 123, 128, 133, 139, 145, 152, 159, 168, + 177, 187, 199, 212, 227, 245, 265, 288, + 317, 352, 395, 450, 524, 626, 777, 1025 }; + +static int srf08_read_ranging(struct srf08_data *data) +{ + struct i2c_client *client = data->client; + int ret, i; + int waittime; + + mutex_lock(&data->lock); + + ret = i2c_smbus_write_byte_data(data->client, + SRF08_WRITE_COMMAND, SRF08_CMD_RANGING_CM); + if (ret < 0) { + dev_err(&client->dev, "write command - err: %d\n", ret); + mutex_unlock(&data->lock); + return ret; + } + + /* + * we read here until a correct version number shows up as + * suggested by the documentation + * + * with an ultrasonic speed of 343 m/s and a roundtrip of it + * sleep the expected duration and try to read from the device + * if nothing useful is read try it in a shorter grid + * + * polling for not more than 20 ms should be enough + */ + waittime = 1 + data->range_mm / 172; + msleep(waittime); + for (i = 0; i < 4; i++) { + ret = i2c_smbus_read_byte_data(data->client, + SRF08_READ_SW_REVISION); + + /* check if a valid version number is read */ + if (ret < 255 && ret > 0) + break; + msleep(5); + } + + if (ret >= 255 || ret <= 0) { + dev_err(&client->dev, "device not ready\n"); + mutex_unlock(&data->lock); + return -EIO; + } + + ret = i2c_smbus_read_word_swapped(data->client, + SRF08_READ_ECHO_1_HIGH); + if (ret < 0) { + dev_err(&client->dev, "cannot read distance: ret=%d\n", ret); + mutex_unlock(&data->lock); + return ret; + } + + mutex_unlock(&data->lock); + + return ret; +} + +static int srf08_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct srf08_data *data = iio_priv(indio_dev); + int ret; + + if (channel->type != IIO_DISTANCE) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = srf08_read_ranging(data); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* 1 LSB is 1 cm */ + *val = 0; + *val2 = 10000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static ssize_t srf08_show_range_mm_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "[0.043 0.043 11.008]\n"); +} + +static IIO_DEVICE_ATTR(sensor_max_range_available, S_IRUGO, + srf08_show_range_mm_available, NULL, 0); + +static ssize_t srf08_show_range_mm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct srf08_data *data = iio_priv(indio_dev); + + return sprintf(buf, "%d.%03d\n", data->range_mm / 1000, + data->range_mm % 1000); +} + +/* + * set the range of the sensor to an even multiple of 43 mm + * which corresponds to 1 LSB in the register + * + * register value corresponding range + * 0x00 43 mm + * 0x01 86 mm + * 0x02 129 mm + * ... + * 0xFF 11008 mm + */ +static ssize_t srf08_write_range_mm(struct srf08_data *data, unsigned int val) +{ + int ret; + struct i2c_client *client = data->client; + unsigned int mod; + u8 regval; + + ret = val / 43 - 1; + mod = val % 43; + + if (mod || (ret < 0) || (ret > 255)) + return -EINVAL; + + regval = ret; + + mutex_lock(&data->lock); + + ret = i2c_smbus_write_byte_data(client, SRF08_WRITE_RANGE, regval); + if (ret < 0) { + dev_err(&client->dev, "write_range - err: %d\n", ret); + mutex_unlock(&data->lock); + return ret; + } + + data->range_mm = val; + + mutex_unlock(&data->lock); + + return 0; +} + +static ssize_t srf08_store_range_mm(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct srf08_data *data = iio_priv(indio_dev); + int ret; + int integer, fract; + + ret = iio_str_to_fixpoint(buf, 100, &integer, &fract); + if (ret) + return ret; + + ret = srf08_write_range_mm(data, integer * 1000 + fract); + if (ret < 0) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR(sensor_max_range, S_IRUGO | S_IWUSR, + srf08_show_range_mm, srf08_store_range_mm, 0); + +static ssize_t srf08_show_sensitivity_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0; + + for (i = 0; i < ARRAY_SIZE(srf08_sensitivity); i++) + len += sprintf(buf + len, "%d ", srf08_sensitivity[i]); + + len += sprintf(buf + len, "\n"); + + return len; +} + +static IIO_DEVICE_ATTR(sensor_sensitivity_available, S_IRUGO, + srf08_show_sensitivity_available, NULL, 0); + +static ssize_t srf08_show_sensitivity(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct srf08_data *data = iio_priv(indio_dev); + int len; + + len = sprintf(buf, "%d\n", data->sensitivity); + + return len; +} + +static ssize_t srf08_write_sensitivity(struct srf08_data *data, + unsigned int val) +{ + struct i2c_client *client = data->client; + int ret, i; + u8 regval; + + for (i = 0; i < ARRAY_SIZE(srf08_sensitivity); i++) + if (val == srf08_sensitivity[i]) { + regval = i; + break; + } + + if (i >= ARRAY_SIZE(srf08_sensitivity)) + return -EINVAL; + + mutex_lock(&data->lock); + + ret = i2c_smbus_write_byte_data(client, + SRF08_WRITE_MAX_GAIN, regval); + if (ret < 0) { + dev_err(&client->dev, "write_sensitivity - err: %d\n", ret); + mutex_unlock(&data->lock); + return ret; + } + + data->sensitivity = val; + + mutex_unlock(&data->lock); + + return 0; +} + +static ssize_t srf08_store_sensitivity(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct srf08_data *data = iio_priv(indio_dev); + int ret; + unsigned int val; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + ret = srf08_write_sensitivity(data, val); + if (ret < 0) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR(sensor_sensitivity, S_IRUGO | S_IWUSR, + srf08_show_sensitivity, srf08_store_sensitivity, 0); + +static struct attribute *srf08_attributes[] = { + &iio_dev_attr_sensor_max_range.dev_attr.attr, + &iio_dev_attr_sensor_max_range_available.dev_attr.attr, + &iio_dev_attr_sensor_sensitivity.dev_attr.attr, + &iio_dev_attr_sensor_sensitivity_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group srf08_attribute_group = { + .attrs = srf08_attributes, +}; + +static const struct iio_chan_spec srf08_channels[] = { + { + .type = IIO_DISTANCE, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct iio_info srf08_info = { + .read_raw = srf08_read_raw, + .attrs = &srf08_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int srf08_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct srf08_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA | + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + 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; + + indio_dev->name = "srf08"; + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &srf08_info; + indio_dev->channels = srf08_channels; + indio_dev->num_channels = ARRAY_SIZE(srf08_channels); + + mutex_init(&data->lock); + + /* + * set default values of device here + * these register values cannot be read from the hardware + * therefore set driver specific default values + */ + ret = srf08_write_range_mm(data, SRF08_DEFAULT_RANGE); + if (ret < 0) + return ret; + + ret = srf08_write_sensitivity(data, SRF08_DEFAULT_GAIN); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id srf08_id[] = { + { "srf08", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, srf08_id); + +static struct i2c_driver srf08_driver = { + .driver = { + .name = "srf08", + }, + .probe = srf08_probe, + .id_table = srf08_id, +}; +module_i2c_driver(srf08_driver); + +MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>"); +MODULE_DESCRIPTION("Devantech SRF08 ultrasonic ranger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/sx9500.c b/drivers/iio/proximity/sx9500.c index 1f06282ec793..9ea147f1a50d 100644 --- a/drivers/iio/proximity/sx9500.c +++ b/drivers/iio/proximity/sx9500.c @@ -387,14 +387,18 @@ static int sx9500_read_raw(struct iio_dev *indio_dev, int *val, int *val2, long mask) { struct sx9500_data *data = iio_priv(indio_dev); + int ret; switch (chan->type) { case IIO_PROXIMITY: switch (mask) { case IIO_CHAN_INFO_RAW: - if (iio_buffer_enabled(indio_dev)) - return -EBUSY; - return sx9500_read_proximity(data, chan, val); + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + ret = sx9500_read_proximity(data, chan, val); + iio_device_release_direct_mode(indio_dev); + return ret; case IIO_CHAN_INFO_SAMP_FREQ: return sx9500_read_samp_freq(data, val, val2); default: diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig index 5ea77a7e261d..3089e8d0a32d 100644 --- a/drivers/iio/temperature/Kconfig +++ b/drivers/iio/temperature/Kconfig @@ -39,6 +39,16 @@ config TMP006 This driver can also be built as a module. If so, the module will be called tmp006. +config TMP007 + tristate "TMP007 infrared thermopile sensor with Integrated Math Engine" + depends on I2C + help + If you say yes here you get support for the Texas Instruments + TMP007 infrared thermopile sensor with Integrated Math Engine. + + This driver can also be built as a module. If so, the module will + be called tmp007. + config TSYS01 tristate "Measurement Specialties TSYS01 temperature sensor using I2C bus connection" depends on I2C diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile index 78c3de0dc3f0..4c4377480726 100644 --- a/drivers/iio/temperature/Makefile +++ b/drivers/iio/temperature/Makefile @@ -5,5 +5,6 @@ obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o obj-$(CONFIG_MLX90614) += mlx90614.o obj-$(CONFIG_TMP006) += tmp006.o +obj-$(CONFIG_TMP007) += tmp007.o obj-$(CONFIG_TSYS01) += tsys01.o obj-$(CONFIG_TSYS02D) += tsys02d.o diff --git a/drivers/iio/temperature/tmp007.c b/drivers/iio/temperature/tmp007.c new file mode 100644 index 000000000000..f04d0d1f6ac8 --- /dev/null +++ b/drivers/iio/temperature/tmp007.c @@ -0,0 +1,345 @@ +/* + * tmp007.c - Support for TI TMP007 IR thermopile sensor with integrated math engine + * + * Copyright (c) 2017 Manivannan Sadhasivam <manivannanece23@gmail.com> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * Driver for the Texas Instruments I2C 16-bit IR thermopile sensor + * + * (7-bit I2C slave address (0x40 - 0x47), changeable via ADR pins) + * + * Note: This driver assumes that the sensor has been calibrated beforehand + * + * TODO: ALERT irq, limit threshold events + * + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/of.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define TMP007_TDIE 0x01 +#define TMP007_CONFIG 0x02 +#define TMP007_TOBJECT 0x03 +#define TMP007_STATUS 0x04 +#define TMP007_STATUS_MASK 0x05 +#define TMP007_MANUFACTURER_ID 0x1e +#define TMP007_DEVICE_ID 0x1f + +#define TMP007_CONFIG_CONV_EN BIT(12) +#define TMP007_CONFIG_COMP_EN BIT(5) +#define TMP007_CONFIG_TC_EN BIT(6) +#define TMP007_CONFIG_CR_MASK GENMASK(11, 9) +#define TMP007_CONFIG_CR_SHIFT 9 + +#define TMP007_STATUS_CONV_READY BIT(14) +#define TMP007_STATUS_DATA_VALID BIT(9) + +#define TMP007_MANUFACTURER_MAGIC 0x5449 +#define TMP007_DEVICE_MAGIC 0x0078 + +#define TMP007_TEMP_SHIFT 2 + +struct tmp007_data { + struct i2c_client *client; + u16 config; +}; + +static const int tmp007_avgs[5][2] = { {4, 0}, {2, 0}, {1, 0}, + {0, 500000}, {0, 250000} }; + +static int tmp007_read_temperature(struct tmp007_data *data, u8 reg) +{ + s32 ret; + int tries = 50; + + while (tries-- > 0) { + ret = i2c_smbus_read_word_swapped(data->client, + TMP007_STATUS); + if (ret < 0) + return ret; + if ((ret & TMP007_STATUS_CONV_READY) && + !(ret & TMP007_STATUS_DATA_VALID)) + break; + msleep(100); + } + + if (tries < 0) + return -EIO; + + return i2c_smbus_read_word_swapped(data->client, reg); +} + +static int tmp007_powerdown(struct tmp007_data *data) +{ + return i2c_smbus_write_word_swapped(data->client, TMP007_CONFIG, + data->config & ~TMP007_CONFIG_CONV_EN); +} + +static int tmp007_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct tmp007_data *data = iio_priv(indio_dev); + s32 ret; + int conv_rate; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (channel->channel2) { + case IIO_MOD_TEMP_AMBIENT: /* LSB: 0.03125 degree Celsius */ + ret = i2c_smbus_read_word_swapped(data->client, TMP007_TDIE); + if (ret < 0) + return ret; + break; + case IIO_MOD_TEMP_OBJECT: + ret = tmp007_read_temperature(data, TMP007_TOBJECT); + if (ret < 0) + return ret; + break; + default: + return -EINVAL; + } + + *val = sign_extend32(ret, 15) >> TMP007_TEMP_SHIFT; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 31; + *val2 = 250000; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + conv_rate = (data->config & TMP007_CONFIG_CR_MASK) + >> TMP007_CONFIG_CR_SHIFT; + *val = tmp007_avgs[conv_rate][0]; + *val2 = tmp007_avgs[conv_rate][1]; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int tmp007_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int val, + int val2, long mask) +{ + struct tmp007_data *data = iio_priv(indio_dev); + int i; + u16 tmp; + + if (mask == IIO_CHAN_INFO_SAMP_FREQ) { + for (i = 0; i < ARRAY_SIZE(tmp007_avgs); i++) { + if ((val == tmp007_avgs[i][0]) && + (val2 == tmp007_avgs[i][1])) { + tmp = data->config & ~TMP007_CONFIG_CR_MASK; + tmp |= (i << TMP007_CONFIG_CR_SHIFT); + + return i2c_smbus_write_word_swapped(data->client, + TMP007_CONFIG, + data->config = tmp); + } + } + } + + return -EINVAL; +} + +static IIO_CONST_ATTR(sampling_frequency_available, "4 2 1 0.5 0.25"); + +static struct attribute *tmp007_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tmp007_attribute_group = { + .attrs = tmp007_attributes, +}; + +static const struct iio_chan_spec tmp007_channels[] = { + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_AMBIENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_OBJECT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + } +}; + +static const struct iio_info tmp007_info = { + .read_raw = tmp007_read_raw, + .write_raw = tmp007_write_raw, + .attrs = &tmp007_attribute_group, + .driver_module = THIS_MODULE, +}; + +static bool tmp007_identify(struct i2c_client *client) +{ + int manf_id, dev_id; + + manf_id = i2c_smbus_read_word_swapped(client, TMP007_MANUFACTURER_ID); + if (manf_id < 0) + return false; + + dev_id = i2c_smbus_read_word_swapped(client, TMP007_DEVICE_ID); + if (dev_id < 0) + return false; + + return (manf_id == TMP007_MANUFACTURER_MAGIC && dev_id == TMP007_DEVICE_MAGIC); +} + +static int tmp007_probe(struct i2c_client *client, + const struct i2c_device_id *tmp007_id) +{ + struct tmp007_data *data; + struct iio_dev *indio_dev; + int ret; + u16 status; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + if (!tmp007_identify(client)) { + dev_err(&client->dev, "TMP007 not found\n"); + return -ENODEV; + } + + 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; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = "tmp007"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &tmp007_info; + + indio_dev->channels = tmp007_channels; + indio_dev->num_channels = ARRAY_SIZE(tmp007_channels); + + /* + * Set Configuration register: + * 1. Conversion ON + * 2. Comparator mode + * 3. Transient correction enable + */ + + ret = i2c_smbus_read_word_swapped(data->client, TMP007_CONFIG); + if (ret < 0) + return ret; + + data->config = ret; + data->config |= (TMP007_CONFIG_CONV_EN | TMP007_CONFIG_COMP_EN | TMP007_CONFIG_TC_EN); + + ret = i2c_smbus_write_word_swapped(data->client, TMP007_CONFIG, + data->config); + if (ret < 0) + return ret; + + /* + * Set Status Mask register: + * 1. Conversion ready enable + * 2. Data valid enable + */ + + ret = i2c_smbus_read_word_swapped(data->client, TMP007_STATUS_MASK); + if (ret < 0) + goto error_powerdown; + + status = ret; + status |= (TMP007_STATUS_CONV_READY | TMP007_STATUS_DATA_VALID); + + ret = i2c_smbus_write_word_swapped(data->client, TMP007_STATUS_MASK, status); + if (ret < 0) + goto error_powerdown; + + return iio_device_register(indio_dev); + +error_powerdown: + tmp007_powerdown(data); + + return ret; +} + +static int tmp007_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct tmp007_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + tmp007_powerdown(data); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tmp007_suspend(struct device *dev) +{ + struct tmp007_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + + return tmp007_powerdown(data); +} + +static int tmp007_resume(struct device *dev) +{ + struct tmp007_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + + return i2c_smbus_write_word_swapped(data->client, TMP007_CONFIG, + data->config | TMP007_CONFIG_CONV_EN); +} +#endif + +static SIMPLE_DEV_PM_OPS(tmp007_pm_ops, tmp007_suspend, tmp007_resume); + +static const struct of_device_id tmp007_of_match[] = { + { .compatible = "ti,tmp007", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tmp007_of_match); + +static const struct i2c_device_id tmp007_id[] = { + { "tmp007", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp007_id); + +static struct i2c_driver tmp007_driver = { + .driver = { + .name = "tmp007", + .of_match_table = of_match_ptr(tmp007_of_match), + .pm = &tmp007_pm_ops, + }, + .probe = tmp007_probe, + .remove = tmp007_remove, + .id_table = tmp007_id, +}; +module_i2c_driver(tmp007_driver); + +MODULE_AUTHOR("Manivannan Sadhasivam <manivannanece23@gmail.com>"); +MODULE_DESCRIPTION("TI TMP007 IR thermopile sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/trigger/Kconfig b/drivers/iio/trigger/Kconfig index 809b2e7d58fa..e4d4e63434db 100644 --- a/drivers/iio/trigger/Kconfig +++ b/drivers/iio/trigger/Kconfig @@ -24,6 +24,15 @@ config IIO_INTERRUPT_TRIGGER To compile this driver as a module, choose M here: the module will be called iio-trig-interrupt. +config IIO_STM32_TIMER_TRIGGER + tristate "STM32 Timer Trigger" + depends on (ARCH_STM32 && OF && MFD_STM32_TIMERS) || COMPILE_TEST + help + Select this option to enable STM32 Timer Trigger + + To compile this driver as a module, choose M here: the + module will be called stm32-timer-trigger. + config IIO_TIGHTLOOP_TRIGGER tristate "A kthread based hammering loop trigger" depends on IIO_SW_TRIGGER diff --git a/drivers/iio/trigger/Makefile b/drivers/iio/trigger/Makefile index aab4dc23303d..5c4ecd380653 100644 --- a/drivers/iio/trigger/Makefile +++ b/drivers/iio/trigger/Makefile @@ -6,5 +6,6 @@ obj-$(CONFIG_IIO_HRTIMER_TRIGGER) += iio-trig-hrtimer.o obj-$(CONFIG_IIO_INTERRUPT_TRIGGER) += iio-trig-interrupt.o +obj-$(CONFIG_IIO_STM32_TIMER_TRIGGER) += stm32-timer-trigger.o obj-$(CONFIG_IIO_SYSFS_TRIGGER) += iio-trig-sysfs.o obj-$(CONFIG_IIO_TIGHTLOOP_TRIGGER) += iio-trig-loop.o diff --git a/drivers/iio/trigger/iio-trig-interrupt.c b/drivers/iio/trigger/iio-trig-interrupt.c index 572bc6f02ca8..e18f12b74610 100644 --- a/drivers/iio/trigger/iio-trig-interrupt.c +++ b/drivers/iio/trigger/iio-trig-interrupt.c @@ -58,7 +58,7 @@ static int iio_interrupt_trigger_probe(struct platform_device *pdev) trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); if (!trig_info) { ret = -ENOMEM; - goto error_put_trigger; + goto error_free_trigger; } iio_trigger_set_drvdata(trig, trig_info); trig_info->irq = irq; @@ -83,8 +83,8 @@ error_release_irq: free_irq(irq, trig); error_free_trig_info: kfree(trig_info); -error_put_trigger: - iio_trigger_put(trig); +error_free_trigger: + iio_trigger_free(trig); error_ret: return ret; } @@ -99,7 +99,7 @@ static int iio_interrupt_trigger_remove(struct platform_device *pdev) iio_trigger_unregister(trig); free_irq(trig_info->irq, trig); kfree(trig_info); - iio_trigger_put(trig); + iio_trigger_free(trig); return 0; } diff --git a/drivers/iio/trigger/iio-trig-sysfs.c b/drivers/iio/trigger/iio-trig-sysfs.c index 3dfab2bc6d69..202e8b89caf2 100644 --- a/drivers/iio/trigger/iio-trig-sysfs.c +++ b/drivers/iio/trigger/iio-trig-sysfs.c @@ -174,7 +174,7 @@ static int iio_sysfs_trigger_probe(int id) return 0; out2: - iio_trigger_put(t->trig); + iio_trigger_free(t->trig); free_t: kfree(t); out1: diff --git a/drivers/iio/trigger/stm32-timer-trigger.c b/drivers/iio/trigger/stm32-timer-trigger.c new file mode 100644 index 000000000000..994b96d19750 --- /dev/null +++ b/drivers/iio/trigger/stm32-timer-trigger.c @@ -0,0 +1,342 @@ +/* + * Copyright (C) STMicroelectronics 2016 + * + * Author: Benjamin Gaignard <benjamin.gaignard@st.com> + * + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/timer/stm32-timer-trigger.h> +#include <linux/iio/trigger.h> +#include <linux/mfd/stm32-timers.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#define MAX_TRIGGERS 6 + +/* List the triggers created by each timer */ +static const void *triggers_table[][MAX_TRIGGERS] = { + { TIM1_TRGO, TIM1_CH1, TIM1_CH2, TIM1_CH3, TIM1_CH4,}, + { TIM2_TRGO, TIM2_CH1, TIM2_CH2, TIM2_CH3, TIM2_CH4,}, + { TIM3_TRGO, TIM3_CH1, TIM3_CH2, TIM3_CH3, TIM3_CH4,}, + { TIM4_TRGO, TIM4_CH1, TIM4_CH2, TIM4_CH3, TIM4_CH4,}, + { TIM5_TRGO, TIM5_CH1, TIM5_CH2, TIM5_CH3, TIM5_CH4,}, + { TIM6_TRGO,}, + { TIM7_TRGO,}, + { TIM8_TRGO, TIM8_CH1, TIM8_CH2, TIM8_CH3, TIM8_CH4,}, + { TIM9_TRGO, TIM9_CH1, TIM9_CH2,}, + { }, /* timer 10 */ + { }, /* timer 11 */ + { TIM12_TRGO, TIM12_CH1, TIM12_CH2,}, +}; + +struct stm32_timer_trigger { + struct device *dev; + struct regmap *regmap; + struct clk *clk; + u32 max_arr; + const void *triggers; +}; + +static int stm32_timer_start(struct stm32_timer_trigger *priv, + unsigned int frequency) +{ + unsigned long long prd, div; + int prescaler = 0; + u32 ccer, cr1; + + /* Period and prescaler values depends of clock rate */ + div = (unsigned long long)clk_get_rate(priv->clk); + + do_div(div, frequency); + + prd = div; + + /* + * Increase prescaler value until we get a result that fit + * with auto reload register maximum value. + */ + while (div > priv->max_arr) { + prescaler++; + div = prd; + do_div(div, (prescaler + 1)); + } + prd = div; + + if (prescaler > MAX_TIM_PSC) { + dev_err(priv->dev, "prescaler exceeds the maximum value\n"); + return -EINVAL; + } + + /* Check if nobody else use the timer */ + regmap_read(priv->regmap, TIM_CCER, &ccer); + if (ccer & TIM_CCER_CCXE) + return -EBUSY; + + regmap_read(priv->regmap, TIM_CR1, &cr1); + if (!(cr1 & TIM_CR1_CEN)) + clk_enable(priv->clk); + + regmap_write(priv->regmap, TIM_PSC, prescaler); + regmap_write(priv->regmap, TIM_ARR, prd - 1); + regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE, TIM_CR1_ARPE); + + /* Force master mode to update mode */ + regmap_update_bits(priv->regmap, TIM_CR2, TIM_CR2_MMS, 0x20); + + /* Make sure that registers are updated */ + regmap_update_bits(priv->regmap, TIM_EGR, TIM_EGR_UG, TIM_EGR_UG); + + /* Enable controller */ + regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, TIM_CR1_CEN); + + return 0; +} + +static void stm32_timer_stop(struct stm32_timer_trigger *priv) +{ + u32 ccer, cr1; + + regmap_read(priv->regmap, TIM_CCER, &ccer); + if (ccer & TIM_CCER_CCXE) + return; + + regmap_read(priv->regmap, TIM_CR1, &cr1); + if (cr1 & TIM_CR1_CEN) + clk_disable(priv->clk); + + /* Stop timer */ + regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0); + regmap_write(priv->regmap, TIM_PSC, 0); + regmap_write(priv->regmap, TIM_ARR, 0); + + /* Make sure that registers are updated */ + regmap_update_bits(priv->regmap, TIM_EGR, TIM_EGR_UG, TIM_EGR_UG); +} + +static ssize_t stm32_tt_store_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct stm32_timer_trigger *priv = iio_trigger_get_drvdata(trig); + unsigned int freq; + int ret; + + ret = kstrtouint(buf, 10, &freq); + if (ret) + return ret; + + if (freq == 0) { + stm32_timer_stop(priv); + } else { + ret = stm32_timer_start(priv, freq); + if (ret) + return ret; + } + + return len; +} + +static ssize_t stm32_tt_read_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct stm32_timer_trigger *priv = iio_trigger_get_drvdata(trig); + u32 psc, arr, cr1; + unsigned long long freq = 0; + + regmap_read(priv->regmap, TIM_CR1, &cr1); + regmap_read(priv->regmap, TIM_PSC, &psc); + regmap_read(priv->regmap, TIM_ARR, &arr); + + if (psc && arr && (cr1 & TIM_CR1_CEN)) { + freq = (unsigned long long)clk_get_rate(priv->clk); + do_div(freq, psc); + do_div(freq, arr); + } + + return sprintf(buf, "%d\n", (unsigned int)freq); +} + +static IIO_DEV_ATTR_SAMP_FREQ(0660, + stm32_tt_read_frequency, + stm32_tt_store_frequency); + +static char *master_mode_table[] = { + "reset", + "enable", + "update", + "compare_pulse", + "OC1REF", + "OC2REF", + "OC3REF", + "OC4REF" +}; + +static ssize_t stm32_tt_show_master_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + u32 cr2; + + regmap_read(priv->regmap, TIM_CR2, &cr2); + cr2 = (cr2 & TIM_CR2_MMS) >> TIM_CR2_MMS_SHIFT; + + return snprintf(buf, PAGE_SIZE, "%s\n", master_mode_table[cr2]); +} + +static ssize_t stm32_tt_store_master_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct stm32_timer_trigger *priv = iio_priv(indio_dev); + int i; + + for (i = 0; i < ARRAY_SIZE(master_mode_table); i++) { + if (!strncmp(master_mode_table[i], buf, + strlen(master_mode_table[i]))) { + regmap_update_bits(priv->regmap, TIM_CR2, + TIM_CR2_MMS, i << TIM_CR2_MMS_SHIFT); + /* Make sure that registers are updated */ + regmap_update_bits(priv->regmap, TIM_EGR, + TIM_EGR_UG, TIM_EGR_UG); + return len; + } + } + + return -EINVAL; +} + +static IIO_CONST_ATTR(master_mode_available, + "reset enable update compare_pulse OC1REF OC2REF OC3REF OC4REF"); + +static IIO_DEVICE_ATTR(master_mode, 0660, + stm32_tt_show_master_mode, + stm32_tt_store_master_mode, + 0); + +static struct attribute *stm32_trigger_attrs[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_master_mode.dev_attr.attr, + &iio_const_attr_master_mode_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group stm32_trigger_attr_group = { + .attrs = stm32_trigger_attrs, +}; + +static const struct attribute_group *stm32_trigger_attr_groups[] = { + &stm32_trigger_attr_group, + NULL, +}; + +static const struct iio_trigger_ops timer_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int stm32_setup_iio_triggers(struct stm32_timer_trigger *priv) +{ + int ret; + const char * const *cur = priv->triggers; + + while (cur && *cur) { + struct iio_trigger *trig; + + trig = devm_iio_trigger_alloc(priv->dev, "%s", *cur); + if (!trig) + return -ENOMEM; + + trig->dev.parent = priv->dev->parent; + trig->ops = &timer_trigger_ops; + + /* + * sampling frequency and master mode attributes + * should only be available on trgo trigger which + * is always the first in the list. + */ + if (cur == priv->triggers) + trig->dev.groups = stm32_trigger_attr_groups; + + iio_trigger_set_drvdata(trig, priv); + + ret = devm_iio_trigger_register(priv->dev, trig); + if (ret) + return ret; + cur++; + } + + return 0; +} + +/** + * is_stm32_timer_trigger + * @trig: trigger to be checked + * + * return true if the trigger is a valid stm32 iio timer trigger + * either return false + */ +bool is_stm32_timer_trigger(struct iio_trigger *trig) +{ + return (trig->ops == &timer_trigger_ops); +} +EXPORT_SYMBOL(is_stm32_timer_trigger); + +static int stm32_timer_trigger_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stm32_timer_trigger *priv; + struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent); + unsigned int index; + int ret; + + if (of_property_read_u32(dev->of_node, "reg", &index)) + return -EINVAL; + + if (index >= ARRAY_SIZE(triggers_table)) + return -EINVAL; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->regmap = ddata->regmap; + priv->clk = ddata->clk; + priv->max_arr = ddata->max_arr; + priv->triggers = triggers_table[index]; + + ret = stm32_setup_iio_triggers(priv); + if (ret) + return ret; + + platform_set_drvdata(pdev, priv); + + return 0; +} + +static const struct of_device_id stm32_trig_of_match[] = { + { .compatible = "st,stm32-timer-trigger", }, + { /* end node */ }, +}; +MODULE_DEVICE_TABLE(of, stm32_trig_of_match); + +static struct platform_driver stm32_timer_trigger_driver = { + .probe = stm32_timer_trigger_probe, + .driver = { + .name = "stm32-timer-trigger", + .of_match_table = stm32_trig_of_match, + }, +}; +module_platform_driver(stm32_timer_trigger_driver); + +MODULE_ALIAS("platform: stm32-timer-trigger"); +MODULE_DESCRIPTION("STMicroelectronics STM32 Timer Trigger driver"); +MODULE_LICENSE("GPL v2"); |