diff options
Diffstat (limited to 'drivers/iio')
-rw-r--r-- | drivers/iio/Kconfig | 3 | ||||
-rw-r--r-- | drivers/iio/Makefile | 1 | ||||
-rw-r--r-- | drivers/iio/accel/Kconfig | 1 | ||||
-rw-r--r-- | drivers/iio/adc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/iio/adc/Makefile | 1 | ||||
-rw-r--r-- | drivers/iio/adc/exynos_adc.c | 12 | ||||
-rw-r--r-- | drivers/iio/adc/mcp320x.c | 257 | ||||
-rw-r--r-- | drivers/iio/gyro/Kconfig | 1 | ||||
-rw-r--r-- | drivers/iio/industrialio-buffer.c | 10 | ||||
-rw-r--r-- | drivers/iio/light/hid-sensor-als.c | 2 | ||||
-rw-r--r-- | drivers/iio/magnetometer/Kconfig | 1 | ||||
-rw-r--r-- | drivers/iio/magnetometer/ak8975.c | 103 | ||||
-rw-r--r-- | drivers/iio/trigger/Kconfig | 17 | ||||
-rw-r--r-- | drivers/iio/trigger/Makefile | 5 | ||||
-rw-r--r-- | drivers/iio/trigger/iio-trig-sysfs.c | 227 |
15 files changed, 630 insertions, 21 deletions
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index b2f963be3993..daa3dddbc77f 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -70,5 +70,8 @@ source "drivers/iio/gyro/Kconfig" source "drivers/iio/imu/Kconfig" source "drivers/iio/light/Kconfig" source "drivers/iio/magnetometer/Kconfig" +if IIO_TRIGGER + source "drivers/iio/trigger/Kconfig" +endif #IIO_TRIGGER endif # IIO diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index a0e8cdd67e4d..a349a9605d1f 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -21,3 +21,4 @@ obj-y += frequency/ obj-y += imu/ obj-y += light/ obj-y += magnetometer/ +obj-y += trigger/ diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index bb594963f91e..719d83fe51dd 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -28,7 +28,6 @@ config IIO_ST_ACCEL_3AXIS select IIO_ST_ACCEL_I2C_3AXIS if (I2C) select IIO_ST_ACCEL_SPI_3AXIS if (SPI_MASTER) select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) - select IIO_ST_ACCEL_BUFFER if (IIO_TRIGGERED_BUFFER) help Say yes here to build support for STMicroelectronics accelerometers: LSM303DLH, LSM303DLHC, LIS3DH, LSM330D, LSM330DL, LSM330DLC, diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index ab0767e6727e..93129ec4b649 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -133,6 +133,16 @@ config MAX1363 max11646, max11647) Provides direct access via sysfs and buffered data via the iio dev interface. +config MCP320X + tristate "Microchip Technology MCP3204/08" + depends on SPI + help + Say yes here to build support for Microchip Technology's MCP3204 or + MCP3208 analog to digital converter. + + This driver can also be built as a module. If so, the module will be + called mcp320x. + config TI_ADC081C tristate "Texas Instruments ADC081C021/027" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 0a825bed43f6..8f475d31fe4d 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_AT91_ADC) += at91_adc.o obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o obj-$(CONFIG_MAX1363) += max1363.o +obj-$(CONFIG_MCP320X) += mcp320x.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index b3d03d335948..9809fc9a35d2 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -270,16 +270,16 @@ static int exynos_adc_probe(struct platform_device *pdev) info = iio_priv(indio_dev); mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - info->regs = devm_request_and_ioremap(&pdev->dev, mem); - if (!info->regs) { - ret = -ENOMEM; + info->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(info->regs)) { + ret = PTR_ERR(info->regs); goto err_iio; } mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); - info->enable_reg = devm_request_and_ioremap(&pdev->dev, mem); - if (!info->enable_reg) { - ret = -ENOMEM; + info->enable_reg = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(info->enable_reg)) { + ret = PTR_ERR(info->enable_reg); goto err_iio; } diff --git a/drivers/iio/adc/mcp320x.c b/drivers/iio/adc/mcp320x.c new file mode 100644 index 000000000000..ebc015922a79 --- /dev/null +++ b/drivers/iio/adc/mcp320x.c @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2013 Oskar Andero <oskar.andero@gmail.com> + * + * Driver for Microchip Technology's MCP3204 and MCP3208 ADC chips. + * Datasheet can be found here: + * http://ww1.microchip.com/downloads/en/devicedoc/21298c.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/err.h> +#include <linux/spi/spi.h> +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/regulator/consumer.h> + +#define MCP_SINGLE_ENDED (1 << 3) +#define MCP_START_BIT (1 << 4) + +enum { + mcp3204, + mcp3208, +}; + +struct mcp320x { + struct spi_device *spi; + struct spi_message msg; + struct spi_transfer transfer[2]; + + u8 tx_buf; + u8 rx_buf[2]; + + struct regulator *reg; + struct mutex lock; +}; + +static int mcp320x_adc_conversion(struct mcp320x *adc, u8 msg) +{ + int ret; + + adc->tx_buf = msg; + ret = spi_sync(adc->spi, &adc->msg); + if (ret < 0) + return ret; + + return ((adc->rx_buf[0] & 0x3f) << 6) | + (adc->rx_buf[1] >> 2); +} + +static int mcp320x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct mcp320x *adc = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&adc->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (channel->differential) + ret = mcp320x_adc_conversion(adc, + MCP_START_BIT | channel->address); + else + ret = mcp320x_adc_conversion(adc, + MCP_START_BIT | MCP_SINGLE_ENDED | + channel->address); + if (ret < 0) + goto out; + + *val = ret; + ret = IIO_VAL_INT; + break; + + case IIO_CHAN_INFO_SCALE: + /* Digital output code = (4096 * Vin) / Vref */ + ret = regulator_get_voltage(adc->reg); + if (ret < 0) + goto out; + + *val = ret / 1000; + *val2 = 12; + ret = IIO_VAL_FRACTIONAL_LOG2; + break; + + default: + break; + } + +out: + mutex_unlock(&adc->lock); + + return ret; +} + +#define MCP320X_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (num), \ + .address = (num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + } + +#define MCP320X_VOLTAGE_CHANNEL_DIFF(num) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (num * 2), \ + .channel2 = (num * 2 + 1), \ + .address = (num * 2), \ + .differential = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + } + +static const struct iio_chan_spec mcp3204_channels[] = { + MCP320X_VOLTAGE_CHANNEL(0), + MCP320X_VOLTAGE_CHANNEL(1), + MCP320X_VOLTAGE_CHANNEL(2), + MCP320X_VOLTAGE_CHANNEL(3), + MCP320X_VOLTAGE_CHANNEL_DIFF(0), + MCP320X_VOLTAGE_CHANNEL_DIFF(1), +}; + +static const struct iio_chan_spec mcp3208_channels[] = { + MCP320X_VOLTAGE_CHANNEL(0), + MCP320X_VOLTAGE_CHANNEL(1), + MCP320X_VOLTAGE_CHANNEL(2), + MCP320X_VOLTAGE_CHANNEL(3), + MCP320X_VOLTAGE_CHANNEL(4), + MCP320X_VOLTAGE_CHANNEL(5), + MCP320X_VOLTAGE_CHANNEL(6), + MCP320X_VOLTAGE_CHANNEL(7), + MCP320X_VOLTAGE_CHANNEL_DIFF(0), + MCP320X_VOLTAGE_CHANNEL_DIFF(1), + MCP320X_VOLTAGE_CHANNEL_DIFF(2), + MCP320X_VOLTAGE_CHANNEL_DIFF(3), +}; + +static const struct iio_info mcp320x_info = { + .read_raw = mcp320x_read_raw, + .driver_module = THIS_MODULE, +}; + +struct mcp3208_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +static const struct mcp3208_chip_info mcp3208_chip_infos[] = { + [mcp3204] = { + .channels = mcp3204_channels, + .num_channels = ARRAY_SIZE(mcp3204_channels) + }, + [mcp3208] = { + .channels = mcp3208_channels, + .num_channels = ARRAY_SIZE(mcp3208_channels) + }, +}; + +static int mcp320x_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct mcp320x *adc; + const struct mcp3208_chip_info *chip_info; + int ret; + + indio_dev = iio_device_alloc(sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mcp320x_info; + + chip_info = &mcp3208_chip_infos[spi_get_device_id(spi)->driver_data]; + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; + + adc->transfer[0].tx_buf = &adc->tx_buf; + adc->transfer[0].len = sizeof(adc->tx_buf); + adc->transfer[1].rx_buf = adc->rx_buf; + adc->transfer[1].len = sizeof(adc->rx_buf); + + spi_message_init_with_transfers(&adc->msg, adc->transfer, + ARRAY_SIZE(adc->transfer)); + + adc->reg = regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) { + ret = PTR_ERR(adc->reg); + goto iio_free; + } + + ret = regulator_enable(adc->reg); + if (ret < 0) + goto reg_free; + + mutex_init(&adc->lock); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto reg_disable; + + return 0; + +reg_disable: + regulator_disable(adc->reg); +reg_free: + regulator_put(adc->reg); +iio_free: + iio_device_free(indio_dev); + + return ret; +} + +static int mcp320x_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct mcp320x *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(adc->reg); + regulator_put(adc->reg); + iio_device_free(indio_dev); + + return 0; +} + +static const struct spi_device_id mcp320x_id[] = { + { "mcp3204", mcp3204 }, + { "mcp3208", mcp3208 }, + { } +}; +MODULE_DEVICE_TABLE(spi, mcp320x_id); + +static struct spi_driver mcp320x_driver = { + .driver = { + .name = "mcp320x", + .owner = THIS_MODULE, + }, + .probe = mcp320x_probe, + .remove = mcp320x_remove, + .id_table = mcp320x_id, +}; +module_spi_driver(mcp320x_driver); + +MODULE_AUTHOR("Oskar Andero <oskar.andero@gmail.com>"); +MODULE_DESCRIPTION("Microchip Technology MCP3204/08"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/Kconfig b/drivers/iio/gyro/Kconfig index 6be4628faffe..b8daf1b2ea06 100644 --- a/drivers/iio/gyro/Kconfig +++ b/drivers/iio/gyro/Kconfig @@ -47,7 +47,6 @@ config IIO_ST_GYRO_3AXIS select IIO_ST_GYRO_I2C_3AXIS if (I2C) select IIO_ST_GYRO_SPI_3AXIS if (SPI_MASTER) select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) - select IIO_ST_GYRO_BUFFER if (IIO_TRIGGERED_BUFFER) help Say yes here to build support for STMicroelectronics gyroscopes: L3G4200D, LSM330DL, L3GD20, L3GD20H, LSM330DLC, L3G4IS, LSM330. diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index aaadd32f9f0d..e73033f3839a 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -542,8 +542,7 @@ int iio_update_buffers(struct iio_dev *indio_dev, ret = indio_dev->setup_ops->preenable(indio_dev); if (ret) { printk(KERN_ERR - "Buffer not started:" - "buffer preenable failed\n"); + "Buffer not started: buffer preenable failed (%d)\n", ret); goto error_remove_inserted; } } @@ -556,8 +555,7 @@ int iio_update_buffers(struct iio_dev *indio_dev, ret = buffer->access->request_update(buffer); if (ret) { printk(KERN_INFO - "Buffer not started:" - "buffer parameter update failed\n"); + "Buffer not started: buffer parameter update failed (%d)\n", ret); goto error_run_postdisable; } } @@ -566,7 +564,7 @@ int iio_update_buffers(struct iio_dev *indio_dev, ->update_scan_mode(indio_dev, indio_dev->active_scan_mask); if (ret < 0) { - printk(KERN_INFO "update scan mode failed\n"); + printk(KERN_INFO "Buffer not started: update scan mode failed (%d)\n", ret); goto error_run_postdisable; } } @@ -590,7 +588,7 @@ int iio_update_buffers(struct iio_dev *indio_dev, ret = indio_dev->setup_ops->postenable(indio_dev); if (ret) { printk(KERN_INFO - "Buffer not started: postenable failed\n"); + "Buffer not started: postenable failed (%d)\n", ret); indio_dev->currentmode = INDIO_DIRECT_MODE; if (indio_dev->setup_ops->postdisable) indio_dev->setup_ops->postdisable(indio_dev); diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c index 80d68ff02d29..cdc2cad0f01b 100644 --- a/drivers/iio/light/hid-sensor-als.c +++ b/drivers/iio/light/hid-sensor-als.c @@ -31,7 +31,7 @@ #include "../common/hid-sensors/hid-sensor-trigger.h" /*Format: HID-SENSOR-usage_id_in_hex*/ -/*Usage ID from spec for Accelerometer-3D: 0x200041*/ +/*Usage ID from spec for Ambiant-Light: 0x200041*/ #define DRIVER_NAME "HID-SENSOR-200041" #define CHANNEL_SCAN_INDEX_ILLUM 0 diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index bd1cfb666695..c332b0ae4a3b 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -32,7 +32,6 @@ config IIO_ST_MAGN_3AXIS select IIO_ST_MAGN_I2C_3AXIS if (I2C) select IIO_ST_MAGN_SPI_3AXIS if (SPI_MASTER) select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) - select IIO_ST_MAGN_BUFFER if (IIO_TRIGGERED_BUFFER) help Say yes here to build support for STMicroelectronics magnetometers: LSM303DLHC, LSM303DLM, LIS3MDL. diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index af6c320a534e..7105f22d6cd7 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -24,11 +24,13 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/i2c.h> +#include <linux/interrupt.h> #include <linux/err.h> #include <linux/mutex.h> #include <linux/delay.h> - +#include <linux/bitops.h> #include <linux/gpio.h> +#include <linux/of_gpio.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> @@ -82,6 +84,7 @@ */ #define AK8975_MAX_CONVERSION_TIMEOUT 500 #define AK8975_CONVERSION_DONE_POLL_TIME 10 +#define AK8975_DATA_READY_TIMEOUT ((100*HZ)/1000) /* * Per-instance context data for the device. @@ -94,6 +97,9 @@ struct ak8975_data { long raw_to_gauss[3]; u8 reg_cache[AK8975_MAX_REGS]; int eoc_gpio; + int eoc_irq; + wait_queue_head_t data_ready_queue; + unsigned long flags; }; static const int ak8975_index_to_reg[] = { @@ -123,6 +129,51 @@ static int ak8975_write_data(struct i2c_client *client, } /* + * Handle data ready irq + */ +static irqreturn_t ak8975_irq_handler(int irq, void *data) +{ + struct ak8975_data *ak8975 = data; + + set_bit(0, &ak8975->flags); + wake_up(&ak8975->data_ready_queue); + + return IRQ_HANDLED; +} + +/* + * Install data ready interrupt handler + */ +static int ak8975_setup_irq(struct ak8975_data *data) +{ + struct i2c_client *client = data->client; + int rc; + int irq; + + if (client->irq) + irq = client->irq; + else + irq = gpio_to_irq(data->eoc_gpio); + + rc = request_irq(irq, ak8975_irq_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + dev_name(&client->dev), data); + if (rc < 0) { + dev_err(&client->dev, + "irq %d request failed, (gpio %d): %d\n", + irq, data->eoc_gpio, rc); + return rc; + } + + init_waitqueue_head(&data->data_ready_queue); + clear_bit(0, &data->flags); + data->eoc_irq = irq; + + return rc; +} + + +/* * Perform some start-of-day setup, including reading the asa calibration * values and caching them. */ @@ -170,6 +221,16 @@ static int ak8975_setup(struct i2c_client *client) AK8975_REG_CNTL_MODE_POWER_DOWN, AK8975_REG_CNTL_MODE_MASK, AK8975_REG_CNTL_MODE_SHIFT); + + if (data->eoc_gpio > 0 || client->irq) { + ret = ak8975_setup_irq(data); + if (ret < 0) { + dev_err(&client->dev, + "Error setting data ready interrupt\n"); + return ret; + } + } + if (ret < 0) { dev_err(&client->dev, "Error in setting power-down mode\n"); return ret; @@ -266,9 +327,23 @@ static int wait_conversion_complete_polled(struct ak8975_data *data) dev_err(&client->dev, "Conversion timeout happened\n"); return -EINVAL; } + return read_status; } +/* Returns 0 if the end of conversion interrupt occured or -ETIME otherwise */ +static int wait_conversion_complete_interrupt(struct ak8975_data *data) +{ + int ret; + + ret = wait_event_timeout(data->data_ready_queue, + test_bit(0, &data->flags), + AK8975_DATA_READY_TIMEOUT); + clear_bit(0, &data->flags); + + return ret > 0 ? 0 : -ETIME; +} + /* * Emits the raw flux value for the x, y, or z axis. */ @@ -294,13 +369,16 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) } /* Wait for the conversion to complete. */ - if (gpio_is_valid(data->eoc_gpio)) + if (data->eoc_irq) + ret = wait_conversion_complete_interrupt(data); + else if (gpio_is_valid(data->eoc_gpio)) ret = wait_conversion_complete_gpio(data); else ret = wait_conversion_complete_polled(data); if (ret < 0) goto exit; + /* This will be executed only for non-interrupt based waiting case */ if (ret & AK8975_REG_ST1_DRDY_MASK) { ret = i2c_smbus_read_byte_data(client, AK8975_REG_ST2); if (ret < 0) { @@ -384,10 +462,15 @@ static int ak8975_probe(struct i2c_client *client, int err; /* Grab and set up the supplied GPIO. */ - if (client->dev.platform_data == NULL) - eoc_gpio = -1; - else + if (client->dev.platform_data) eoc_gpio = *(int *)(client->dev.platform_data); + else if (client->dev.of_node) + eoc_gpio = of_get_gpio(client->dev.of_node, 0); + else + eoc_gpio = -1; + + if (eoc_gpio == -EPROBE_DEFER) + return -EPROBE_DEFER; /* We may not have a GPIO based IRQ to scan, that is fine, we will poll if so */ @@ -409,6 +492,11 @@ static int ak8975_probe(struct i2c_client *client, } data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); + + data->client = client; + data->eoc_gpio = eoc_gpio; + data->eoc_irq = 0; + /* Perform some basic start-of-day setup of the device. */ err = ak8975_setup(client); if (err < 0) { @@ -433,6 +521,8 @@ static int ak8975_probe(struct i2c_client *client, exit_free_iio: iio_device_free(indio_dev); + if (data->eoc_irq) + free_irq(data->eoc_irq, data); exit_gpio: if (gpio_is_valid(eoc_gpio)) gpio_free(eoc_gpio); @@ -447,6 +537,9 @@ static int ak8975_remove(struct i2c_client *client) iio_device_unregister(indio_dev); + if (data->eoc_irq) + free_irq(data->eoc_irq, data); + if (gpio_is_valid(data->eoc_gpio)) gpio_free(data->eoc_gpio); diff --git a/drivers/iio/trigger/Kconfig b/drivers/iio/trigger/Kconfig new file mode 100644 index 000000000000..a4e68db2f23f --- /dev/null +++ b/drivers/iio/trigger/Kconfig @@ -0,0 +1,17 @@ +# +# Industrial I/O standalone triggers +# +menu "Triggers - standalone" + +config IIO_SYSFS_TRIGGER + tristate "SYSFS trigger" + depends on SYSFS + select IRQ_WORK + help + Provides support for using SYSFS entry as IIO triggers. + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called iio-trig-sysfs. + +endmenu diff --git a/drivers/iio/trigger/Makefile b/drivers/iio/trigger/Makefile new file mode 100644 index 000000000000..e0b21831072f --- /dev/null +++ b/drivers/iio/trigger/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for triggers not associated with iio-devices +# + +obj-$(CONFIG_IIO_SYSFS_TRIGGER) += iio-trig-sysfs.o diff --git a/drivers/iio/trigger/iio-trig-sysfs.c b/drivers/iio/trigger/iio-trig-sysfs.c new file mode 100644 index 000000000000..b727bde8b7fe --- /dev/null +++ b/drivers/iio/trigger/iio-trig-sysfs.c @@ -0,0 +1,227 @@ +/* + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/irq_work.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> + +struct iio_sysfs_trig { + struct iio_trigger *trig; + struct irq_work work; + int id; + struct list_head l; +}; + +static LIST_HEAD(iio_sysfs_trig_list); +static DEFINE_MUTEX(iio_syfs_trig_list_mut); + +static int iio_sysfs_trigger_probe(int id); +static ssize_t iio_sysfs_trig_add(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + unsigned long input; + + ret = strict_strtoul(buf, 10, &input); + if (ret) + return ret; + ret = iio_sysfs_trigger_probe(input); + if (ret) + return ret; + return len; +} +static DEVICE_ATTR(add_trigger, S_IWUSR, NULL, &iio_sysfs_trig_add); + +static int iio_sysfs_trigger_remove(int id); +static ssize_t iio_sysfs_trig_remove(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + unsigned long input; + + ret = strict_strtoul(buf, 10, &input); + if (ret) + return ret; + ret = iio_sysfs_trigger_remove(input); + if (ret) + return ret; + return len; +} + +static DEVICE_ATTR(remove_trigger, S_IWUSR, NULL, &iio_sysfs_trig_remove); + +static struct attribute *iio_sysfs_trig_attrs[] = { + &dev_attr_add_trigger.attr, + &dev_attr_remove_trigger.attr, + NULL, +}; + +static const struct attribute_group iio_sysfs_trig_group = { + .attrs = iio_sysfs_trig_attrs, +}; + +static const struct attribute_group *iio_sysfs_trig_groups[] = { + &iio_sysfs_trig_group, + NULL +}; + + +/* Nothing to actually do upon release */ +static void iio_trigger_sysfs_release(struct device *dev) +{ +} + +static struct device iio_sysfs_trig_dev = { + .bus = &iio_bus_type, + .groups = iio_sysfs_trig_groups, + .release = &iio_trigger_sysfs_release, +}; + +static void iio_sysfs_trigger_work(struct irq_work *work) +{ + struct iio_sysfs_trig *trig = container_of(work, struct iio_sysfs_trig, + work); + + iio_trigger_poll(trig->trig, 0); +} + +static ssize_t iio_sysfs_trigger_poll(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_sysfs_trig *sysfs_trig = iio_trigger_get_drvdata(trig); + + irq_work_queue(&sysfs_trig->work); + + return count; +} + +static DEVICE_ATTR(trigger_now, S_IWUSR, NULL, iio_sysfs_trigger_poll); + +static struct attribute *iio_sysfs_trigger_attrs[] = { + &dev_attr_trigger_now.attr, + NULL, +}; + +static const struct attribute_group iio_sysfs_trigger_attr_group = { + .attrs = iio_sysfs_trigger_attrs, +}; + +static const struct attribute_group *iio_sysfs_trigger_attr_groups[] = { + &iio_sysfs_trigger_attr_group, + NULL +}; + +static const struct iio_trigger_ops iio_sysfs_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int iio_sysfs_trigger_probe(int id) +{ + struct iio_sysfs_trig *t; + int ret; + bool foundit = false; + mutex_lock(&iio_syfs_trig_list_mut); + list_for_each_entry(t, &iio_sysfs_trig_list, l) + if (id == t->id) { + foundit = true; + break; + } + if (foundit) { + ret = -EINVAL; + goto out1; + } + t = kmalloc(sizeof(*t), GFP_KERNEL); + if (t == NULL) { + ret = -ENOMEM; + goto out1; + } + t->id = id; + t->trig = iio_trigger_alloc("sysfstrig%d", id); + if (!t->trig) { + ret = -ENOMEM; + goto free_t; + } + + t->trig->dev.groups = iio_sysfs_trigger_attr_groups; + t->trig->ops = &iio_sysfs_trigger_ops; + t->trig->dev.parent = &iio_sysfs_trig_dev; + iio_trigger_set_drvdata(t->trig, t); + + init_irq_work(&t->work, iio_sysfs_trigger_work); + + ret = iio_trigger_register(t->trig); + if (ret) + goto out2; + list_add(&t->l, &iio_sysfs_trig_list); + __module_get(THIS_MODULE); + mutex_unlock(&iio_syfs_trig_list_mut); + return 0; + +out2: + iio_trigger_put(t->trig); +free_t: + kfree(t); +out1: + mutex_unlock(&iio_syfs_trig_list_mut); + return ret; +} + +static int iio_sysfs_trigger_remove(int id) +{ + bool foundit = false; + struct iio_sysfs_trig *t; + mutex_lock(&iio_syfs_trig_list_mut); + list_for_each_entry(t, &iio_sysfs_trig_list, l) + if (id == t->id) { + foundit = true; + break; + } + if (!foundit) { + mutex_unlock(&iio_syfs_trig_list_mut); + return -EINVAL; + } + + iio_trigger_unregister(t->trig); + iio_trigger_free(t->trig); + + list_del(&t->l); + kfree(t); + module_put(THIS_MODULE); + mutex_unlock(&iio_syfs_trig_list_mut); + return 0; +} + + +static int __init iio_sysfs_trig_init(void) +{ + device_initialize(&iio_sysfs_trig_dev); + dev_set_name(&iio_sysfs_trig_dev, "iio_sysfs_trigger"); + return device_add(&iio_sysfs_trig_dev); +} +module_init(iio_sysfs_trig_init); + +static void __exit iio_sysfs_trig_exit(void) +{ + device_unregister(&iio_sysfs_trig_dev); +} +module_exit(iio_sysfs_trig_exit); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Sysfs based trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:iio-trig-sysfs"); |