diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-01-31 19:43:49 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-01-31 19:43:49 +0300 |
commit | 89c468263befb4617aaab36ae8c54ba04164c7b0 (patch) | |
tree | 9da22d6a4ff19d8f4d09fa8daac4872b298bfde7 /drivers | |
parent | 71e365ed0ca893cae8d72cbd4b476a9589003098 (diff) | |
parent | 67460e8c9a5c59f66e1d476fd9341086cd792e5a (diff) | |
download | linux-89c468263befb4617aaab36ae8c54ba04164c7b0.tar.xz |
Merge tag 'iio-for-3.20b' of git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio into staging-testing
Jonathan writes:
Second round of IIO new drivers, functionality and cleanups for the 3.20 cycle.
New drivers and device support:
* Capella cm3232 ambient light sensor driver.
* Freescale MMA9553 pedometer and activity monitor. Note this involved a
refactor of the mma9551 driver to pull out shared elements.
* Samsung sensor hub (as used in the galaxy gear 2 watch) Core support
and initial drivers (gyro and accelerometer) more to follow.
An additional fix was applied on top of this for a build issue
thrown up by the autobuilders on some platforms.
* Cosmic Circuits 10001 ADC driver
* Qualcomm SPMI PMIC voltage ADC driver (current adc merged a while back).
* Add binding for AK8963 (in capitals) as unfortunately there are bios'
out there not using lower case.
New functionality
* Add newe operating mode to the core to allow for non triggered software
buffers. This is mostly semantics as previously drivers just claimed they
had a hardware buffer (when they didn't).
* Add distance channel type.
* Add energy channel type.
* Add velocity channel type and IIO_MOD_ROOT_SUM_SQUARED (i.e. speed when
our channel type is velocity).
* Add _debounce_count and _debounce_time filter attributes. Only really
make sense for counting types of measurements. First use is for avoiding
miss detection of steps prior to walking.
* Add change event type. This replaces the briefly present INSTANCE type
(which hadn't gained any users). It is more generic as it allows for
events say every 10 steps rather than every step.
* Add _calibweight attributes to the ABI (and core support). Used by
activity monitors to estimate energy use. Can imagine there will be
other uses for this one.
Driver new functionality
* mma9551 gains runtime pm support.
* hid-sensors gain PM support.
Cleanup
* Change calibheight unit to m from cm. As there are no prior users
and this was inconsistent with other distance units, it makes sense
to fix it before hte mma9553 driver which uses it.
* mpu6050 cleanups and devm_ use.
* as3935 switch over to PM ops.
* Fix a few format strings for signed vs unsigned.
* Fix tcs3414 missindentation
* Typo in industrialio-event
* Stop requiring IIO_TRIGGER for IIO_KFIFO_BUF as we have drivers that don't
need it. No one is quite sure why that dependency was there and it
seems to not matter.
Diffstat (limited to 'drivers')
49 files changed, 6466 insertions, 507 deletions
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 345395e9dc6e..4132935dc929 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -27,7 +27,6 @@ boolean "IIO callback buffer used for push in-kernel interfaces" usage. That is, those where the data is pushed to the consumer. config IIO_KFIFO_BUF - select IIO_TRIGGER tristate "Industrial I/O buffering based on kfifo" help A simple fifo based on kfifo. Note that this currently provides diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 9f67c10291bd..7c9a9a94a8ce 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -111,9 +111,14 @@ config KXCJK1013 To compile this driver as a module, choose M here: the module will be called kxcjk-1013. +config MMA9551_CORE + tristate + config MMA9551 tristate "Freescale MMA9551L Intelligent Motion-Sensing Platform Driver" depends on I2C + select MMA9551_CORE + help Say yes here to build support for the Freescale MMA9551L Intelligent Motion-Sensing Platform Driver. @@ -121,4 +126,14 @@ config MMA9551 To compile this driver as a module, choose M here: the module will be called mma9551. +config MMA9553 + tristate "Freescale MMA9553L Intelligent Pedometer Platform Driver" + depends on I2C + select MMA9551_CORE + help + Say yes here to build support for the Freescale MMA9553L + Intelligent Pedometer Platform Driver. + + To compile this driver as a module, choose M here: the module + will be called mma9553. endmenu diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index de5b9cb9670f..99d89e46cad1 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -9,7 +9,12 @@ obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o obj-$(CONFIG_KXCJK1013) += kxcjk-1013.o obj-$(CONFIG_KXSD9) += kxsd9.o obj-$(CONFIG_MMA8452) += mma8452.o -obj-$(CONFIG_MMA9551) += mma9551.o + +obj-$(CONFIG_MMA9551_CORE) += mma9551_core.o +obj-$(CONFIG_MMA9551) += mma9551.o +obj-$(CONFIG_MMA9553) += mma9553.o + +obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_accel_sensor.o obj-$(CONFIG_IIO_ST_ACCEL_3AXIS) += st_accel.o st_accel-y := st_accel_core.o diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c index d5d95317003a..df6a593bd4bd 100644 --- a/drivers/iio/accel/hid-sensor-accel-3d.c +++ b/drivers/iio/accel/hid-sensor-accel-3d.c @@ -111,19 +111,12 @@ static int accel_3d_read_raw(struct iio_dev *indio_dev, int report_id = -1; u32 address; int ret_type; - s32 poll_value; *val = 0; *val2 = 0; switch (mask) { case 0: - poll_value = hid_sensor_read_poll_value( - &accel_state->common_attributes); - if (poll_value < 0) - return -EINVAL; - hid_sensor_power_state(&accel_state->common_attributes, true); - msleep_interruptible(poll_value * 2); report_id = accel_state->accel[chan->scan_index].report_id; address = accel_3d_addresses[chan->scan_index]; if (report_id >= 0) @@ -419,6 +412,7 @@ static struct platform_driver hid_accel_3d_platform_driver = { .id_table = hid_accel_3d_ids, .driver = { .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, }, .probe = hid_accel_3d_probe, .remove = hid_accel_3d_remove, diff --git a/drivers/iio/accel/mma9551.c b/drivers/iio/accel/mma9551.c index 6563e267b8ae..46c38351c6a3 100644 --- a/drivers/iio/accel/mma9551.c +++ b/drivers/iio/accel/mma9551.c @@ -22,62 +22,14 @@ #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> #include <linux/iio/events.h> +#include <linux/pm_runtime.h> +#include "mma9551_core.h" #define MMA9551_DRV_NAME "mma9551" #define MMA9551_IRQ_NAME "mma9551_event" #define MMA9551_GPIO_NAME "mma9551_int" #define MMA9551_GPIO_COUNT 4 -/* Applications IDs */ -#define MMA9551_APPID_VERSION 0x00 -#define MMA9551_APPID_GPIO 0x03 -#define MMA9551_APPID_AFE 0x06 -#define MMA9551_APPID_TILT 0x0B -#define MMA9551_APPID_SLEEP_WAKE 0x12 -#define MMA9551_APPID_RESET 0x17 -#define MMA9551_APPID_NONE 0xff - -/* Command masks for mailbox write command */ -#define MMA9551_CMD_READ_VERSION_INFO 0x00 -#define MMA9551_CMD_READ_CONFIG 0x10 -#define MMA9551_CMD_WRITE_CONFIG 0x20 -#define MMA9551_CMD_READ_STATUS 0x30 - -enum mma9551_gpio_pin { - mma9551_gpio6 = 0, - mma9551_gpio7, - mma9551_gpio8, - mma9551_gpio9, - mma9551_gpio_max = mma9551_gpio9, -}; - -/* Mailbox read command */ -#define MMA9551_RESPONSE_COCO BIT(7) - -/* Error-Status codes returned in mailbox read command */ -#define MMA9551_MCI_ERROR_NONE 0x00 -#define MMA9551_MCI_ERROR_PARAM 0x04 -#define MMA9551_MCI_INVALID_COUNT 0x19 -#define MMA9551_MCI_ERROR_COMMAND 0x1C -#define MMA9551_MCI_ERROR_INVALID_LENGTH 0x21 -#define MMA9551_MCI_ERROR_FIFO_BUSY 0x22 -#define MMA9551_MCI_ERROR_FIFO_ALLOCATED 0x23 -#define MMA9551_MCI_ERROR_FIFO_OVERSIZE 0x24 - -/* GPIO Application */ -#define MMA9551_GPIO_POL_MSB 0x08 -#define MMA9551_GPIO_POL_LSB 0x09 - -/* Sleep/Wake application */ -#define MMA9551_SLEEP_CFG 0x06 -#define MMA9551_SLEEP_CFG_SNCEN BIT(0) -#define MMA9551_SLEEP_CFG_SCHEN BIT(2) - -/* AFE application */ -#define MMA9551_AFE_X_ACCEL_REG 0x00 -#define MMA9551_AFE_Y_ACCEL_REG 0x02 -#define MMA9551_AFE_Z_ACCEL_REG 0x04 - /* Tilt application (inclination in IIO terms). */ #define MMA9551_TILT_XZ_ANG_REG 0x00 #define MMA9551_TILT_YZ_ANG_REG 0x01 @@ -90,6 +42,8 @@ enum mma9551_gpio_pin { #define MMA9551_TILT_CFG_REG 0x01 #define MMA9551_TILT_ANG_THRESH_MASK GENMASK(3, 0) +#define MMA9551_DEFAULT_SAMPLE_RATE 122 /* Hz */ + /* Tilt events are mapped to the first three GPIO pins. */ enum mma9551_tilt_axis { mma9551_x = 0, @@ -97,61 +51,6 @@ enum mma9551_tilt_axis { mma9551_z, }; -/* - * A response is composed of: - * - control registers: MB0-3 - * - data registers: MB4-31 - * - * A request is composed of: - * - mbox to write to (always 0) - * - control registers: MB1-4 - * - data registers: MB5-31 - */ -#define MMA9551_MAILBOX_CTRL_REGS 4 -#define MMA9551_MAX_MAILBOX_DATA_REGS 28 -#define MMA9551_MAILBOX_REGS 32 - -#define MMA9551_I2C_READ_RETRIES 5 -#define MMA9551_I2C_READ_DELAY 50 /* us */ - -struct mma9551_mbox_request { - u8 start_mbox; /* Always 0. */ - u8 app_id; - /* - * See Section 5.3.1 of the MMA955xL Software Reference Manual. - * - * Bit 7: reserved, always 0 - * Bits 6-4: command - * Bits 3-0: upper bits of register offset - */ - u8 cmd_off; - u8 lower_off; - u8 nbytes; - u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS - 1]; -} __packed; - -struct mma9551_mbox_response { - u8 app_id; - /* - * See Section 5.3.3 of the MMA955xL Software Reference Manual. - * - * Bit 7: COCO - * Bits 6-0: Error code. - */ - u8 coco_err; - u8 nbytes; - u8 req_bytes; - u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS]; -} __packed; - -struct mma9551_version_info { - __be32 device_id; - u8 rom_version[2]; - u8 fw_version[2]; - u8 hw_version[2]; - u8 fw_build[2]; -}; - struct mma9551_data { struct i2c_client *client; struct mutex mutex; @@ -159,246 +58,6 @@ struct mma9551_data { int irqs[MMA9551_GPIO_COUNT]; }; -static int mma9551_transfer(struct i2c_client *client, - u8 app_id, u8 command, u16 offset, - u8 *inbytes, int num_inbytes, - u8 *outbytes, int num_outbytes) -{ - struct mma9551_mbox_request req; - struct mma9551_mbox_response rsp; - struct i2c_msg in, out; - u8 req_len, err_code; - int ret, retries; - - if (offset >= 1 << 12) { - dev_err(&client->dev, "register offset too large\n"); - return -EINVAL; - } - - req_len = 1 + MMA9551_MAILBOX_CTRL_REGS + num_inbytes; - req.start_mbox = 0; - req.app_id = app_id; - req.cmd_off = command | (offset >> 8); - req.lower_off = offset; - - if (command == MMA9551_CMD_WRITE_CONFIG) - req.nbytes = num_inbytes; - else - req.nbytes = num_outbytes; - if (num_inbytes) - memcpy(req.buf, inbytes, num_inbytes); - - out.addr = client->addr; - out.flags = 0; - out.len = req_len; - out.buf = (u8 *)&req; - - ret = i2c_transfer(client->adapter, &out, 1); - if (ret < 0) { - dev_err(&client->dev, "i2c write failed\n"); - return ret; - } - - retries = MMA9551_I2C_READ_RETRIES; - do { - udelay(MMA9551_I2C_READ_DELAY); - - in.addr = client->addr; - in.flags = I2C_M_RD; - in.len = sizeof(rsp); - in.buf = (u8 *)&rsp; - - ret = i2c_transfer(client->adapter, &in, 1); - if (ret < 0) { - dev_err(&client->dev, "i2c read failed\n"); - return ret; - } - - if (rsp.coco_err & MMA9551_RESPONSE_COCO) - break; - } while (--retries > 0); - - if (retries == 0) { - dev_err(&client->dev, - "timed out while waiting for command response\n"); - return -ETIMEDOUT; - } - - if (rsp.app_id != app_id) { - dev_err(&client->dev, - "app_id mismatch in response got %02x expected %02x\n", - rsp.app_id, app_id); - return -EINVAL; - } - - err_code = rsp.coco_err & ~MMA9551_RESPONSE_COCO; - if (err_code != MMA9551_MCI_ERROR_NONE) { - dev_err(&client->dev, "read returned error %x\n", err_code); - return -EINVAL; - } - - if (rsp.nbytes != rsp.req_bytes) { - dev_err(&client->dev, - "output length mismatch got %d expected %d\n", - rsp.nbytes, rsp.req_bytes); - return -EINVAL; - } - - if (num_outbytes) - memcpy(outbytes, rsp.buf, num_outbytes); - - return 0; -} - -static int mma9551_read_config_byte(struct i2c_client *client, u8 app_id, - u16 reg, u8 *val) -{ - return mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG, - reg, NULL, 0, val, 1); -} - -static int mma9551_write_config_byte(struct i2c_client *client, u8 app_id, - u16 reg, u8 val) -{ - return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, reg, - &val, 1, NULL, 0); -} - -static int mma9551_read_status_byte(struct i2c_client *client, u8 app_id, - u16 reg, u8 *val) -{ - return mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS, - reg, NULL, 0, val, 1); -} - -static int mma9551_read_status_word(struct i2c_client *client, u8 app_id, - u16 reg, u16 *val) -{ - int ret; - __be16 v; - - ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS, - reg, NULL, 0, (u8 *)&v, 2); - *val = be16_to_cpu(v); - - return ret; -} - -static int mma9551_update_config_bits(struct i2c_client *client, u8 app_id, - u16 reg, u8 mask, u8 val) -{ - int ret; - u8 tmp, orig; - - ret = mma9551_read_config_byte(client, app_id, reg, &orig); - if (ret < 0) - return ret; - - tmp = orig & ~mask; - tmp |= val & mask; - - if (tmp == orig) - return 0; - - return mma9551_write_config_byte(client, app_id, reg, tmp); -} - -/* - * The polarity parameter is described in section 6.2.2, page 66, of the - * Software Reference Manual. Basically, polarity=0 means the interrupt - * line has the same value as the selected bit, while polarity=1 means - * the line is inverted. - */ -static int mma9551_gpio_config(struct i2c_client *client, - enum mma9551_gpio_pin pin, - u8 app_id, u8 bitnum, int polarity) -{ - u8 reg, pol_mask, pol_val; - int ret; - - if (pin > mma9551_gpio_max) { - dev_err(&client->dev, "bad GPIO pin\n"); - return -EINVAL; - } - - /* - * Pin 6 is configured by regs 0x00 and 0x01, pin 7 by 0x02 and - * 0x03, and so on. - */ - reg = pin * 2; - - ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO, - reg, app_id); - if (ret < 0) { - dev_err(&client->dev, "error setting GPIO app_id\n"); - return ret; - } - - ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO, - reg + 1, bitnum); - if (ret < 0) { - dev_err(&client->dev, "error setting GPIO bit number\n"); - return ret; - } - - switch (pin) { - case mma9551_gpio6: - reg = MMA9551_GPIO_POL_LSB; - pol_mask = 1 << 6; - break; - case mma9551_gpio7: - reg = MMA9551_GPIO_POL_LSB; - pol_mask = 1 << 7; - break; - case mma9551_gpio8: - reg = MMA9551_GPIO_POL_MSB; - pol_mask = 1 << 0; - break; - case mma9551_gpio9: - reg = MMA9551_GPIO_POL_MSB; - pol_mask = 1 << 1; - break; - } - pol_val = polarity ? pol_mask : 0; - - ret = mma9551_update_config_bits(client, MMA9551_APPID_GPIO, reg, - pol_mask, pol_val); - if (ret < 0) - dev_err(&client->dev, "error setting GPIO polarity\n"); - - return ret; -} - -static int mma9551_read_version(struct i2c_client *client) -{ - struct mma9551_version_info info; - int ret; - - ret = mma9551_transfer(client, MMA9551_APPID_VERSION, 0x00, 0x00, - NULL, 0, (u8 *)&info, sizeof(info)); - if (ret < 0) - return ret; - - dev_info(&client->dev, "Device ID 0x%x, firmware version %02x.%02x\n", - be32_to_cpu(info.device_id), info.fw_version[0], - info.fw_version[1]); - - return 0; -} - -/* - * Use 'false' as the second parameter to cause the device to enter - * sleep. - */ -static int mma9551_set_device_state(struct i2c_client *client, - bool enable) -{ - return mma9551_update_config_bits(client, MMA9551_APPID_SLEEP_WAKE, - MMA9551_SLEEP_CFG, - MMA9551_SLEEP_CFG_SNCEN, - enable ? 0 : MMA9551_SLEEP_CFG_SNCEN); -} - static int mma9551_read_incli_chan(struct i2c_client *client, const struct iio_chan_spec *chan, int *val) @@ -424,15 +83,19 @@ static int mma9551_read_incli_chan(struct i2c_client *client, return -EINVAL; } + ret = mma9551_set_power_state(client, true); + if (ret < 0) + return ret; + ret = mma9551_read_status_byte(client, MMA9551_APPID_TILT, reg_addr, &angle); if (ret < 0) - return ret; + goto out_poweroff; ret = mma9551_read_status_byte(client, MMA9551_APPID_TILT, MMA9551_TILT_QUAD_REG, &quadrant); if (ret < 0) - return ret; + goto out_poweroff; angle &= ~MMA9551_TILT_ANGFLG; quadrant = (quadrant >> quad_shift) & 0x03; @@ -442,39 +105,11 @@ static int mma9551_read_incli_chan(struct i2c_client *client, else *val = angle + 90 * quadrant; - return IIO_VAL_INT; -} - -static int mma9551_read_accel_chan(struct i2c_client *client, - const struct iio_chan_spec *chan, - int *val, int *val2) -{ - u16 reg_addr; - s16 raw_accel; - int ret; + ret = IIO_VAL_INT; - switch (chan->channel2) { - case IIO_MOD_X: - reg_addr = MMA9551_AFE_X_ACCEL_REG; - break; - case IIO_MOD_Y: - reg_addr = MMA9551_AFE_Y_ACCEL_REG; - break; - case IIO_MOD_Z: - reg_addr = MMA9551_AFE_Z_ACCEL_REG; - break; - default: - return -EINVAL; - } - - ret = mma9551_read_status_word(client, MMA9551_APPID_AFE, - reg_addr, &raw_accel); - if (ret < 0) - return ret; - - *val = raw_accel; - - return IIO_VAL_INT; +out_poweroff: + mma9551_set_power_state(client, false); + return ret; } static int mma9551_read_raw(struct iio_dev *indio_dev, @@ -509,9 +144,7 @@ static int mma9551_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_SCALE: switch (chan->type) { case IIO_ACCEL: - *val = 0; - *val2 = 2440; - return IIO_VAL_INT_PLUS_MICRO; + return mma9551_read_accel_scale(val, val2); default: return -EINVAL; } @@ -556,6 +189,10 @@ static int mma9551_config_incli_event(struct iio_dev *indio_dev, MMA9551_APPID_NONE, 0, 0); if (ret < 0) return ret; + + ret = mma9551_set_power_state(data->client, false); + if (ret < 0) + return ret; } else { int bitnum; @@ -574,11 +211,18 @@ static int mma9551_config_incli_event(struct iio_dev *indio_dev, return -EINVAL; } + + ret = mma9551_set_power_state(data->client, true); + if (ret < 0) + return ret; + ret = mma9551_gpio_config(data->client, (enum mma9551_gpio_pin)mma_axis, MMA9551_APPID_TILT, bitnum, 0); - if (ret < 0) + if (ret < 0) { + mma9551_set_power_state(data->client, false); return ret; + } } data->event_enabled[mma_axis] = state; @@ -669,14 +313,6 @@ static const struct iio_event_spec mma9551_incli_event = { .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), }; -#define MMA9551_ACCEL_CHANNEL(axis) { \ - .type = IIO_ACCEL, \ - .modified = 1, \ - .channel2 = axis, \ - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ -} - #define MMA9551_INCLI_CHANNEL(axis) { \ .type = IIO_INCLI, \ .modified = 1, \ @@ -771,12 +407,7 @@ static int mma9551_init(struct mma9551_data *data) if (ret) return ret; - /* Power on chip and enable doze mode. */ - return mma9551_update_config_bits(data->client, - MMA9551_APPID_SLEEP_WAKE, - MMA9551_SLEEP_CFG, - MMA9551_SLEEP_CFG_SCHEN | MMA9551_SLEEP_CFG_SNCEN, - MMA9551_SLEEP_CFG_SCHEN); + return mma9551_set_device_state(data->client, true); } static int mma9551_gpio_probe(struct iio_dev *indio_dev) @@ -869,8 +500,19 @@ static int mma9551_probe(struct i2c_client *client, goto out_poweroff; } + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_iio_unregister; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + MMA9551_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + return 0; +out_iio_unregister: + iio_device_unregister(indio_dev); out_poweroff: mma9551_set_device_state(client, false); @@ -882,6 +524,10 @@ static int mma9551_remove(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); struct mma9551_data *data = iio_priv(indio_dev); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + iio_device_unregister(indio_dev); mutex_lock(&data->mutex); mma9551_set_device_state(data->client, false); @@ -890,37 +536,72 @@ static int mma9551_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int mma9551_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + if (ret < 0) { + dev_err(&data->client->dev, "powering off device failed\n"); + return -EAGAIN; + } + + return 0; +} + +static int mma9551_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + ret = mma9551_set_device_state(data->client, true); + if (ret < 0) + return ret; + + mma9551_sleep(MMA9551_DEFAULT_SAMPLE_RATE); + + return 0; +} +#endif + #ifdef CONFIG_PM_SLEEP static int mma9551_suspend(struct device *dev) { struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); struct mma9551_data *data = iio_priv(indio_dev); + int ret; mutex_lock(&data->mutex); - mma9551_set_device_state(data->client, false); + ret = mma9551_set_device_state(data->client, false); mutex_unlock(&data->mutex); - return 0; + return ret; } static int mma9551_resume(struct device *dev) { struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); struct mma9551_data *data = iio_priv(indio_dev); + int ret; mutex_lock(&data->mutex); - mma9551_set_device_state(data->client, true); + ret = mma9551_set_device_state(data->client, true); mutex_unlock(&data->mutex); - return 0; + return ret; } -#else -#define mma9551_suspend NULL -#define mma9551_resume NULL #endif static const struct dev_pm_ops mma9551_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(mma9551_suspend, mma9551_resume) + SET_RUNTIME_PM_OPS(mma9551_runtime_suspend, + mma9551_runtime_resume, NULL) }; static const struct acpi_device_id mma9551_acpi_match[] = { diff --git a/drivers/iio/accel/mma9551_core.c b/drivers/iio/accel/mma9551_core.c new file mode 100644 index 000000000000..7f55a6d7cd03 --- /dev/null +++ b/drivers/iio/accel/mma9551_core.c @@ -0,0 +1,798 @@ +/* + * Common code for Freescale MMA955x Intelligent Sensor Platform drivers + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/i2c.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/pm_runtime.h> +#include "mma9551_core.h" + +/* Command masks for mailbox write command */ +#define MMA9551_CMD_READ_VERSION_INFO 0x00 +#define MMA9551_CMD_READ_CONFIG 0x10 +#define MMA9551_CMD_WRITE_CONFIG 0x20 +#define MMA9551_CMD_READ_STATUS 0x30 + +/* Mailbox read command */ +#define MMA9551_RESPONSE_COCO BIT(7) + +/* Error-Status codes returned in mailbox read command */ +#define MMA9551_MCI_ERROR_NONE 0x00 +#define MMA9551_MCI_ERROR_PARAM 0x04 +#define MMA9551_MCI_INVALID_COUNT 0x19 +#define MMA9551_MCI_ERROR_COMMAND 0x1C +#define MMA9551_MCI_ERROR_INVALID_LENGTH 0x21 +#define MMA9551_MCI_ERROR_FIFO_BUSY 0x22 +#define MMA9551_MCI_ERROR_FIFO_ALLOCATED 0x23 +#define MMA9551_MCI_ERROR_FIFO_OVERSIZE 0x24 + +/* GPIO Application */ +#define MMA9551_GPIO_POL_MSB 0x08 +#define MMA9551_GPIO_POL_LSB 0x09 + +/* Sleep/Wake application */ +#define MMA9551_SLEEP_CFG 0x06 +#define MMA9551_SLEEP_CFG_SNCEN BIT(0) +#define MMA9551_SLEEP_CFG_FLEEN BIT(1) +#define MMA9551_SLEEP_CFG_SCHEN BIT(2) + +/* AFE application */ +#define MMA9551_AFE_X_ACCEL_REG 0x00 +#define MMA9551_AFE_Y_ACCEL_REG 0x02 +#define MMA9551_AFE_Z_ACCEL_REG 0x04 + +/* Reset/Suspend/Clear application */ +#define MMA9551_RSC_RESET 0x00 +#define MMA9551_RSC_OFFSET(mask) (3 - (ffs(mask) - 1) / 8) +#define MMA9551_RSC_VAL(mask) (mask >> (((ffs(mask) - 1) / 8) * 8)) + +/* + * A response is composed of: + * - control registers: MB0-3 + * - data registers: MB4-31 + * + * A request is composed of: + * - mbox to write to (always 0) + * - control registers: MB1-4 + * - data registers: MB5-31 + */ +#define MMA9551_MAILBOX_CTRL_REGS 4 +#define MMA9551_MAX_MAILBOX_DATA_REGS 28 +#define MMA9551_MAILBOX_REGS 32 + +#define MMA9551_I2C_READ_RETRIES 5 +#define MMA9551_I2C_READ_DELAY 50 /* us */ + +struct mma9551_mbox_request { + u8 start_mbox; /* Always 0. */ + u8 app_id; + /* + * See Section 5.3.1 of the MMA955xL Software Reference Manual. + * + * Bit 7: reserved, always 0 + * Bits 6-4: command + * Bits 3-0: upper bits of register offset + */ + u8 cmd_off; + u8 lower_off; + u8 nbytes; + u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS - 1]; +} __packed; + +struct mma9551_mbox_response { + u8 app_id; + /* + * See Section 5.3.3 of the MMA955xL Software Reference Manual. + * + * Bit 7: COCO + * Bits 6-0: Error code. + */ + u8 coco_err; + u8 nbytes; + u8 req_bytes; + u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS]; +} __packed; + +struct mma9551_version_info { + __be32 device_id; + u8 rom_version[2]; + u8 fw_version[2]; + u8 hw_version[2]; + u8 fw_build[2]; +}; + +static int mma9551_transfer(struct i2c_client *client, + u8 app_id, u8 command, u16 offset, + u8 *inbytes, int num_inbytes, + u8 *outbytes, int num_outbytes) +{ + struct mma9551_mbox_request req; + struct mma9551_mbox_response rsp; + struct i2c_msg in, out; + u8 req_len, err_code; + int ret, retries; + + if (offset >= 1 << 12) { + dev_err(&client->dev, "register offset too large\n"); + return -EINVAL; + } + + req_len = 1 + MMA9551_MAILBOX_CTRL_REGS + num_inbytes; + req.start_mbox = 0; + req.app_id = app_id; + req.cmd_off = command | (offset >> 8); + req.lower_off = offset; + + if (command == MMA9551_CMD_WRITE_CONFIG) + req.nbytes = num_inbytes; + else + req.nbytes = num_outbytes; + if (num_inbytes) + memcpy(req.buf, inbytes, num_inbytes); + + out.addr = client->addr; + out.flags = 0; + out.len = req_len; + out.buf = (u8 *)&req; + + ret = i2c_transfer(client->adapter, &out, 1); + if (ret < 0) { + dev_err(&client->dev, "i2c write failed\n"); + return ret; + } + + retries = MMA9551_I2C_READ_RETRIES; + do { + udelay(MMA9551_I2C_READ_DELAY); + + in.addr = client->addr; + in.flags = I2C_M_RD; + in.len = sizeof(rsp); + in.buf = (u8 *)&rsp; + + ret = i2c_transfer(client->adapter, &in, 1); + if (ret < 0) { + dev_err(&client->dev, "i2c read failed\n"); + return ret; + } + + if (rsp.coco_err & MMA9551_RESPONSE_COCO) + break; + } while (--retries > 0); + + if (retries == 0) { + dev_err(&client->dev, + "timed out while waiting for command response\n"); + return -ETIMEDOUT; + } + + if (rsp.app_id != app_id) { + dev_err(&client->dev, + "app_id mismatch in response got %02x expected %02x\n", + rsp.app_id, app_id); + return -EINVAL; + } + + err_code = rsp.coco_err & ~MMA9551_RESPONSE_COCO; + if (err_code != MMA9551_MCI_ERROR_NONE) { + dev_err(&client->dev, "read returned error %x\n", err_code); + return -EINVAL; + } + + if (rsp.nbytes != rsp.req_bytes) { + dev_err(&client->dev, + "output length mismatch got %d expected %d\n", + rsp.nbytes, rsp.req_bytes); + return -EINVAL; + } + + if (num_outbytes) + memcpy(outbytes, rsp.buf, num_outbytes); + + return 0; +} + +/** + * mma9551_read_config_byte() - read 1 configuration byte + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one configuration byte from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed + * by one or more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val) +{ + return mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG, + reg, NULL, 0, val, 1); +} +EXPORT_SYMBOL(mma9551_read_config_byte); + +/** + * mma9551_write_config_byte() - write 1 configuration byte + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Value to write + * + * Write one configuration byte from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_write_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 val) +{ + return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, reg, + &val, 1, NULL, 0); +} +EXPORT_SYMBOL(mma9551_write_config_byte); + +/** + * mma9551_read_status_byte() - read 1 status byte + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one status byte from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_status_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val) +{ + return mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS, + reg, NULL, 0, val, 1); +} +EXPORT_SYMBOL(mma9551_read_status_byte); + +/** + * mma9551_read_config_word() - read 1 config word + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one configuration word from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val) +{ + int ret; + __be16 v; + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG, + reg, NULL, 0, (u8 *)&v, 2); + *val = be16_to_cpu(v); + + return ret; +} +EXPORT_SYMBOL(mma9551_read_config_word); + +/** + * mma9551_write_config_word() - write 1 config word + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Value to write + * + * Write one configuration word from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_write_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 val) +{ + __be16 v = cpu_to_be16(val); + + return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, reg, + (u8 *) &v, 2, NULL, 0); +} +EXPORT_SYMBOL(mma9551_write_config_word); + +/** + * mma9551_read_status_word() - read 1 status word + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one status word from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_status_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val) +{ + int ret; + __be16 v; + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS, + reg, NULL, 0, (u8 *)&v, 2); + *val = be16_to_cpu(v); + + return ret; +} +EXPORT_SYMBOL(mma9551_read_status_word); + +/** + * mma9551_read_config_words() - read multiple config words + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @len: Length of array to read in bytes + * @val: Array of words to read + * + * Read multiple configuration registers (word-sized registers). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf) +{ + int ret, i; + int len_words = len / sizeof(u16); + __be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS]; + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG, + reg, NULL, 0, (u8 *) be_buf, len); + if (ret < 0) + return ret; + + for (i = 0; i < len_words; i++) + buf[i] = be16_to_cpu(be_buf[i]); + + return 0; +} +EXPORT_SYMBOL(mma9551_read_config_words); + +/** + * mma9551_read_status_words() - read multiple status words + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @len: Length of array to read in bytes + * @val: Array of words to read + * + * Read multiple status registers (word-sized registers). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_status_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf) +{ + int ret, i; + int len_words = len / sizeof(u16); + __be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS]; + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS, + reg, NULL, 0, (u8 *) be_buf, len); + if (ret < 0) + return ret; + + for (i = 0; i < len_words; i++) + buf[i] = be16_to_cpu(be_buf[i]); + + return 0; +} +EXPORT_SYMBOL(mma9551_read_status_words); + +/** + * mma9551_write_config_words() - write multiple config words + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @len: Length of array to write in bytes + * @val: Array of words to write + * + * Write multiple configuration registers (word-sized registers). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_write_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf) +{ + int i; + int len_words = len / sizeof(u16); + __be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS]; + + for (i = 0; i < len_words; i++) + be_buf[i] = cpu_to_be16(buf[i]); + + return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, + reg, (u8 *) be_buf, len, NULL, 0); +} +EXPORT_SYMBOL(mma9551_write_config_words); + +/** + * mma9551_update_config_bits() - update bits in register + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @mask: Mask for the bits to update + * @val: Value of the bits to update + * + * Update bits in the given register using a bit mask. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_update_config_bits(struct i2c_client *client, u8 app_id, + u16 reg, u8 mask, u8 val) +{ + int ret; + u8 tmp, orig; + + ret = mma9551_read_config_byte(client, app_id, reg, &orig); + if (ret < 0) + return ret; + + tmp = orig & ~mask; + tmp |= val & mask; + + if (tmp == orig) + return 0; + + return mma9551_write_config_byte(client, app_id, reg, tmp); +} +EXPORT_SYMBOL(mma9551_update_config_bits); + +/** + * mma9551_gpio_config() - configure gpio + * @client: I2C client + * @pin: GPIO pin to configure + * @app_id: Application ID + * @bitnum: Bit number of status register being assigned to the GPIO pin. + * @polarity: The polarity parameter is described in section 6.2.2, page 66, + * of the Software Reference Manual. Basically, polarity=0 means + * the interrupt line has the same value as the selected bit, + * while polarity=1 means the line is inverted. + * + * Assign a bit from an application’s status register to a specific GPIO pin. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_gpio_config(struct i2c_client *client, enum mma9551_gpio_pin pin, + u8 app_id, u8 bitnum, int polarity) +{ + u8 reg, pol_mask, pol_val; + int ret; + + if (pin > mma9551_gpio_max) { + dev_err(&client->dev, "bad GPIO pin\n"); + return -EINVAL; + } + + /* + * Pin 6 is configured by regs 0x00 and 0x01, pin 7 by 0x02 and + * 0x03, and so on. + */ + reg = pin * 2; + + ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO, + reg, app_id); + if (ret < 0) { + dev_err(&client->dev, "error setting GPIO app_id\n"); + return ret; + } + + ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO, + reg + 1, bitnum); + if (ret < 0) { + dev_err(&client->dev, "error setting GPIO bit number\n"); + return ret; + } + + switch (pin) { + case mma9551_gpio6: + reg = MMA9551_GPIO_POL_LSB; + pol_mask = 1 << 6; + break; + case mma9551_gpio7: + reg = MMA9551_GPIO_POL_LSB; + pol_mask = 1 << 7; + break; + case mma9551_gpio8: + reg = MMA9551_GPIO_POL_MSB; + pol_mask = 1 << 0; + break; + case mma9551_gpio9: + reg = MMA9551_GPIO_POL_MSB; + pol_mask = 1 << 1; + break; + } + pol_val = polarity ? pol_mask : 0; + + ret = mma9551_update_config_bits(client, MMA9551_APPID_GPIO, reg, + pol_mask, pol_val); + if (ret < 0) + dev_err(&client->dev, "error setting GPIO polarity\n"); + + return ret; +} +EXPORT_SYMBOL(mma9551_gpio_config); + +/** + * mma9551_read_version() - read device version information + * @client: I2C client + * + * Read version information and print device id and firmware version. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_version(struct i2c_client *client) +{ + struct mma9551_version_info info; + int ret; + + ret = mma9551_transfer(client, MMA9551_APPID_VERSION, 0x00, 0x00, + NULL, 0, (u8 *)&info, sizeof(info)); + if (ret < 0) + return ret; + + dev_info(&client->dev, "device ID 0x%x, firmware version %02x.%02x\n", + be32_to_cpu(info.device_id), info.fw_version[0], + info.fw_version[1]); + + return 0; +} +EXPORT_SYMBOL(mma9551_read_version); + +/** + * mma9551_set_device_state() - sets HW power mode + * @client: I2C client + * @enable: Use true to power on device, false to cause the device + * to enter sleep. + * + * Set power on/off for device using the Sleep/Wake Application. + * When enable is true, power on chip and enable doze mode. + * When enable is false, enter sleep mode (device remains in the + * lowest-power mode). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_set_device_state(struct i2c_client *client, bool enable) +{ + return mma9551_update_config_bits(client, MMA9551_APPID_SLEEP_WAKE, + MMA9551_SLEEP_CFG, + MMA9551_SLEEP_CFG_SNCEN | + MMA9551_SLEEP_CFG_FLEEN | + MMA9551_SLEEP_CFG_SCHEN, + enable ? MMA9551_SLEEP_CFG_SCHEN | + MMA9551_SLEEP_CFG_FLEEN : + MMA9551_SLEEP_CFG_SNCEN); +} +EXPORT_SYMBOL(mma9551_set_device_state); + +/** + * mma9551_set_power_state() - sets runtime PM state + * @client: I2C client + * @on: Use true to power on device, false to power off + * + * Resume or suspend the device using Runtime PM. + * The device will suspend after the autosuspend delay. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_set_power_state(struct i2c_client *client, bool on) +{ +#ifdef CONFIG_PM + int ret; + + if (on) + ret = pm_runtime_get_sync(&client->dev); + else { + pm_runtime_mark_last_busy(&client->dev); + ret = pm_runtime_put_autosuspend(&client->dev); + } + + if (ret < 0) { + dev_err(&client->dev, + "failed to change power state to %d\n", on); + if (on) + pm_runtime_put_noidle(&client->dev); + + return ret; + } +#endif + + return 0; +} +EXPORT_SYMBOL(mma9551_set_power_state); + +/** + * mma9551_sleep() - sleep + * @freq: Application frequency + * + * Firmware applications run at a certain frequency on the + * device. Sleep for one application cycle to make sure the + * application had time to run once and initialize set values. + */ +void mma9551_sleep(int freq) +{ + int sleep_val = 1000 / freq; + + if (sleep_val < 20) + usleep_range(sleep_val * 1000, 20000); + else + msleep_interruptible(sleep_val); +} +EXPORT_SYMBOL(mma9551_sleep); + +/** + * mma9551_read_accel_chan() - read accelerometer channel + * @client: I2C client + * @chan: IIO channel + * @val: Pointer to the accelerometer value read + * @val2: Unused + * + * Read accelerometer value for the specified channel. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: IIO_VAL_INT on success, negative value on failure. + */ +int mma9551_read_accel_chan(struct i2c_client *client, + const struct iio_chan_spec *chan, + int *val, int *val2) +{ + u16 reg_addr; + s16 raw_accel; + int ret; + + switch (chan->channel2) { + case IIO_MOD_X: + reg_addr = MMA9551_AFE_X_ACCEL_REG; + break; + case IIO_MOD_Y: + reg_addr = MMA9551_AFE_Y_ACCEL_REG; + break; + case IIO_MOD_Z: + reg_addr = MMA9551_AFE_Z_ACCEL_REG; + break; + default: + return -EINVAL; + } + + ret = mma9551_set_power_state(client, true); + if (ret < 0) + return ret; + + ret = mma9551_read_status_word(client, MMA9551_APPID_AFE, + reg_addr, &raw_accel); + if (ret < 0) + goto out_poweroff; + + *val = raw_accel; + + ret = IIO_VAL_INT; + +out_poweroff: + mma9551_set_power_state(client, false); + return ret; +} +EXPORT_SYMBOL(mma9551_read_accel_chan); + +/** + * mma9551_read_accel_scale() - read accelerometer scale + * @val: Pointer to the accelerometer scale (int value) + * @val2: Pointer to the accelerometer scale (micro value) + * + * Read accelerometer scale. + * + * Returns: IIO_VAL_INT_PLUS_MICRO. + */ +int mma9551_read_accel_scale(int *val, int *val2) +{ + *val = 0; + *val2 = 2440; + + return IIO_VAL_INT_PLUS_MICRO; +} +EXPORT_SYMBOL(mma9551_read_accel_scale); + +/** + * mma9551_app_reset() - reset application + * @client: I2C client + * @app_mask: Application to reset + * + * Reset the given application (using the Reset/Suspend/Clear + * Control Application) + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_app_reset(struct i2c_client *client, u32 app_mask) +{ + return mma9551_write_config_byte(client, MMA9551_APPID_RCS, + MMA9551_RSC_RESET + + MMA9551_RSC_OFFSET(app_mask), + MMA9551_RSC_VAL(app_mask)); +} +EXPORT_SYMBOL(mma9551_app_reset); + +MODULE_AUTHOR("Irina Tirdea <irina.tirdea@intel.com>"); +MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MMA955xL sensors core"); diff --git a/drivers/iio/accel/mma9551_core.h b/drivers/iio/accel/mma9551_core.h new file mode 100644 index 000000000000..edaa56b1078e --- /dev/null +++ b/drivers/iio/accel/mma9551_core.h @@ -0,0 +1,81 @@ +/* + * Common code for Freescale MMA955x Intelligent Sensor Platform drivers + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef _MMA9551_CORE_H_ +#define _MMA9551_CORE_H_ + +/* Applications IDs */ +#define MMA9551_APPID_VERSION 0x00 +#define MMA9551_APPID_GPIO 0x03 +#define MMA9551_APPID_AFE 0x06 +#define MMA9551_APPID_TILT 0x0B +#define MMA9551_APPID_SLEEP_WAKE 0x12 +#define MMA9551_APPID_PEDOMETER 0x15 +#define MMA9551_APPID_RCS 0x17 +#define MMA9551_APPID_NONE 0xff + +/* Reset/Suspend/Clear application app masks */ +#define MMA9551_RSC_PED BIT(21) + +#define MMA9551_AUTO_SUSPEND_DELAY_MS 2000 + +enum mma9551_gpio_pin { + mma9551_gpio6 = 0, + mma9551_gpio7, + mma9551_gpio8, + mma9551_gpio9, + mma9551_gpio_max = mma9551_gpio9, +}; + +#define MMA9551_ACCEL_CHANNEL(axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +int mma9551_read_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val); +int mma9551_write_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 val); +int mma9551_read_status_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val); +int mma9551_read_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val); +int mma9551_write_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 val); +int mma9551_read_status_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val); +int mma9551_read_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf); +int mma9551_read_status_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf); +int mma9551_write_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf); +int mma9551_update_config_bits(struct i2c_client *client, u8 app_id, + u16 reg, u8 mask, u8 val); +int mma9551_gpio_config(struct i2c_client *client, enum mma9551_gpio_pin pin, + u8 app_id, u8 bitnum, int polarity); +int mma9551_read_version(struct i2c_client *client); +int mma9551_set_device_state(struct i2c_client *client, bool enable); +int mma9551_set_power_state(struct i2c_client *client, bool on); +void mma9551_sleep(int freq); +int mma9551_read_accel_chan(struct i2c_client *client, + const struct iio_chan_spec *chan, + int *val, int *val2); +int mma9551_read_accel_scale(int *val, int *val2); +int mma9551_app_reset(struct i2c_client *client, u32 app_mask); + +#endif /* _MMA9551_CORE_H_ */ diff --git a/drivers/iio/accel/mma9553.c b/drivers/iio/accel/mma9553.c new file mode 100644 index 000000000000..d23ebf192f63 --- /dev/null +++ b/drivers/iio/accel/mma9553.c @@ -0,0 +1,1334 @@ +/* + * Freescale MMA9553L Intelligent Pedometer driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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/i2c.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/acpi.h> +#include <linux/gpio/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/pm_runtime.h> +#include "mma9551_core.h" + +#define MMA9553_DRV_NAME "mma9553" +#define MMA9553_IRQ_NAME "mma9553_event" +#define MMA9553_GPIO_NAME "mma9553_int" + +/* Pedometer configuration registers (R/W) */ +#define MMA9553_REG_CONF_SLEEPMIN 0x00 +#define MMA9553_REG_CONF_SLEEPMAX 0x02 +#define MMA9553_REG_CONF_SLEEPTHD 0x04 +#define MMA9553_MASK_CONF_WORD GENMASK(15, 0) + +#define MMA9553_REG_CONF_CONF_STEPLEN 0x06 +#define MMA9553_MASK_CONF_CONFIG BIT(15) +#define MMA9553_MASK_CONF_ACT_DBCNTM BIT(14) +#define MMA9553_MASK_CONF_SLP_DBCNTM BIT(13) +#define MMA9553_MASK_CONF_STEPLEN GENMASK(7, 0) + +#define MMA9553_REG_CONF_HEIGHT_WEIGHT 0x08 +#define MMA9553_MASK_CONF_HEIGHT GENMASK(15, 8) +#define MMA9553_MASK_CONF_WEIGHT GENMASK(7, 0) + +#define MMA9553_REG_CONF_FILTER 0x0A +#define MMA9553_MASK_CONF_FILTSTEP GENMASK(15, 8) +#define MMA9553_MASK_CONF_MALE BIT(7) +#define MMA9553_MASK_CONF_FILTTIME GENMASK(6, 0) + +#define MMA9553_REG_CONF_SPEED_STEP 0x0C +#define MMA9553_MASK_CONF_SPDPRD GENMASK(15, 8) +#define MMA9553_MASK_CONF_STEPCOALESCE GENMASK(7, 0) + +#define MMA9553_REG_CONF_ACTTHD 0x0E + +/* Pedometer status registers (R-only) */ +#define MMA9553_REG_STATUS 0x00 +#define MMA9553_MASK_STATUS_MRGFL BIT(15) +#define MMA9553_MASK_STATUS_SUSPCHG BIT(14) +#define MMA9553_MASK_STATUS_STEPCHG BIT(13) +#define MMA9553_MASK_STATUS_ACTCHG BIT(12) +#define MMA9553_MASK_STATUS_SUSP BIT(11) +#define MMA9553_MASK_STATUS_ACTIVITY (BIT(10) | BIT(9) | BIT(8)) +#define MMA9553_MASK_STATUS_VERSION 0x00FF + +#define MMA9553_REG_STEPCNT 0x02 +#define MMA9553_REG_DISTANCE 0x04 +#define MMA9553_REG_SPEED 0x06 +#define MMA9553_REG_CALORIES 0x08 +#define MMA9553_REG_SLEEPCNT 0x0A + +/* Pedometer events are always mapped to this pin. */ +#define MMA9553_DEFAULT_GPIO_PIN mma9551_gpio6 +#define MMA9553_DEFAULT_GPIO_POLARITY 0 + +/* Bitnum used for gpio configuration = bit number in high status byte */ +#define STATUS_TO_BITNUM(bit) (ffs(bit) - 9) + +#define MMA9553_DEFAULT_SAMPLE_RATE 30 /* Hz */ + +/* + * The internal activity level must be stable for ACTTHD samples before + * ACTIVITY is updated.The ACTIVITY variable contains the current activity + * level and is updated every time a step is detected or once a second + * if there are no steps. + */ +#define MMA9553_ACTIVITY_THD_TO_SEC(thd) ((thd) / MMA9553_DEFAULT_SAMPLE_RATE) +#define MMA9553_ACTIVITY_SEC_TO_THD(sec) ((sec) * MMA9553_DEFAULT_SAMPLE_RATE) + +/* + * Autonomously suspend pedometer if acceleration vector magnitude + * is near 1g (4096 at 0.244 mg/LSB resolution) for 30 seconds. + */ +#define MMA9553_DEFAULT_SLEEPMIN 3688 /* 0,9 g */ +#define MMA9553_DEFAULT_SLEEPMAX 4508 /* 1,1 g */ +#define MMA9553_DEFAULT_SLEEPTHD (MMA9553_DEFAULT_SAMPLE_RATE * 30) + +#define MMA9553_CONFIG_RETRIES 2 + +/* Status register - activity field */ +enum activity_level { + ACTIVITY_UNKNOWN, + ACTIVITY_REST, + ACTIVITY_WALKING, + ACTIVITY_JOGGING, + ACTIVITY_RUNNING, +}; + +static struct mma9553_event_info { + enum iio_chan_type type; + enum iio_modifier mod; + enum iio_event_direction dir; +} mma9553_events_info[] = { + { + .type = IIO_STEPS, + .mod = IIO_NO_MOD, + .dir = IIO_EV_DIR_NONE, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_STILL, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_STILL, + .dir = IIO_EV_DIR_FALLING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_WALKING, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_WALKING, + .dir = IIO_EV_DIR_FALLING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_JOGGING, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_JOGGING, + .dir = IIO_EV_DIR_FALLING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_RUNNING, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_RUNNING, + .dir = IIO_EV_DIR_FALLING, + }, +}; + +#define MMA9553_EVENTS_INFO_SIZE ARRAY_SIZE(mma9553_events_info) + +struct mma9553_event { + struct mma9553_event_info *info; + bool enabled; +}; + +struct mma9553_conf_regs { + u16 sleepmin; + u16 sleepmax; + u16 sleepthd; + u16 config; + u16 height_weight; + u16 filter; + u16 speed_step; + u16 actthd; +} __packed; + +struct mma9553_data { + struct i2c_client *client; + struct mutex mutex; + struct mma9553_conf_regs conf; + struct mma9553_event events[MMA9553_EVENTS_INFO_SIZE]; + int num_events; + u8 gpio_bitnum; + /* + * This is used for all features that depend on step count: + * step count, distance, speed, calories. + */ + bool stepcnt_enabled; + u16 stepcnt; + u8 activity; + s64 timestamp; +}; + +static u8 mma9553_get_bits(u16 val, u16 mask) +{ + return (val & mask) >> (ffs(mask) - 1); +} + +static u16 mma9553_set_bits(u16 current_val, u16 val, u16 mask) +{ + return (current_val & ~mask) | (val << (ffs(mask) - 1)); +} + +static enum iio_modifier mma9553_activity_to_mod(enum activity_level activity) +{ + switch (activity) { + case ACTIVITY_RUNNING: + return IIO_MOD_RUNNING; + case ACTIVITY_JOGGING: + return IIO_MOD_JOGGING; + case ACTIVITY_WALKING: + return IIO_MOD_WALKING; + case ACTIVITY_REST: + return IIO_MOD_STILL; + case ACTIVITY_UNKNOWN: + default: + return IIO_NO_MOD; + } +} + +static void mma9553_init_events(struct mma9553_data *data) +{ + int i; + + data->num_events = MMA9553_EVENTS_INFO_SIZE; + for (i = 0; i < data->num_events; i++) { + data->events[i].info = &mma9553_events_info[i]; + data->events[i].enabled = false; + } +} + +static struct mma9553_event *mma9553_get_event(struct mma9553_data *data, + enum iio_chan_type type, + enum iio_modifier mod, + enum iio_event_direction dir) +{ + int i; + + for (i = 0; i < data->num_events; i++) + if (data->events[i].info->type == type && + data->events[i].info->mod == mod && + data->events[i].info->dir == dir) + return &data->events[i]; + + return NULL; +} + +static bool mma9553_is_any_event_enabled(struct mma9553_data *data, + bool check_type, + enum iio_chan_type type) +{ + int i; + + for (i = 0; i < data->num_events; i++) + if ((check_type && data->events[i].info->type == type && + data->events[i].enabled) || + (!check_type && data->events[i].enabled)) + return true; + + return false; +} + +static int mma9553_set_config(struct mma9553_data *data, u16 reg, + u16 *p_reg_val, u16 val, u16 mask) +{ + int ret, retries; + u16 reg_val, config; + + reg_val = *p_reg_val; + if (val == mma9553_get_bits(reg_val, mask)) + return 0; + + reg_val = mma9553_set_bits(reg_val, val, mask); + ret = mma9551_write_config_word(data->client, MMA9551_APPID_PEDOMETER, + reg, reg_val); + if (ret < 0) { + dev_err(&data->client->dev, + "error writing config register 0x%x\n", reg); + return ret; + } + + *p_reg_val = reg_val; + + /* Reinitializes the pedometer with current configuration values */ + config = mma9553_set_bits(data->conf.config, 1, + MMA9553_MASK_CONF_CONFIG); + + ret = mma9551_write_config_word(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_CONF_STEPLEN, config); + if (ret < 0) { + dev_err(&data->client->dev, + "error writing config register 0x%x\n", + MMA9553_REG_CONF_CONF_STEPLEN); + return ret; + } + + retries = MMA9553_CONFIG_RETRIES; + do { + mma9551_sleep(MMA9553_DEFAULT_SAMPLE_RATE); + ret = mma9551_read_config_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_CONF_STEPLEN, + &config); + if (ret < 0) + return ret; + } while (mma9553_get_bits(config, MMA9553_MASK_CONF_CONFIG) && + --retries > 0); + + return 0; +} + +static int mma9553_read_activity_stepcnt(struct mma9553_data *data, + u8 *activity, u16 *stepcnt) +{ + u32 status_stepcnt; + u16 status; + int ret; + + ret = mma9551_read_status_words(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_STATUS, sizeof(u32), + (u16 *) &status_stepcnt); + if (ret < 0) { + dev_err(&data->client->dev, + "error reading status and stepcnt\n"); + return ret; + } + + status = status_stepcnt & MMA9553_MASK_CONF_WORD; + *activity = mma9553_get_bits(status, MMA9553_MASK_STATUS_ACTIVITY); + *stepcnt = status_stepcnt >> 16; + + return 0; +} + +static int mma9553_conf_gpio(struct mma9553_data *data) +{ + u8 bitnum = 0, appid = MMA9551_APPID_PEDOMETER; + int ret; + struct mma9553_event *ev_step_detect; + bool activity_enabled; + + activity_enabled = + mma9553_is_any_event_enabled(data, true, IIO_ACTIVITY); + ev_step_detect = + mma9553_get_event(data, IIO_STEPS, IIO_NO_MOD, IIO_EV_DIR_NONE); + + /* + * If both step detector and activity are enabled, use the MRGFL bit. + * This bit is the logical OR of the SUSPCHG, STEPCHG, and ACTCHG flags. + */ + if (activity_enabled && ev_step_detect->enabled) + bitnum = STATUS_TO_BITNUM(MMA9553_MASK_STATUS_MRGFL); + else if (ev_step_detect->enabled) + bitnum = STATUS_TO_BITNUM(MMA9553_MASK_STATUS_STEPCHG); + else if (activity_enabled) + bitnum = STATUS_TO_BITNUM(MMA9553_MASK_STATUS_ACTCHG); + else /* Reset */ + appid = MMA9551_APPID_NONE; + + if (data->gpio_bitnum == bitnum) + return 0; + + /* Save initial values for activity and stepcnt */ + if (activity_enabled || ev_step_detect->enabled) + mma9553_read_activity_stepcnt(data, &data->activity, + &data->stepcnt); + + ret = mma9551_gpio_config(data->client, + MMA9553_DEFAULT_GPIO_PIN, + appid, bitnum, MMA9553_DEFAULT_GPIO_POLARITY); + if (ret < 0) + return ret; + data->gpio_bitnum = bitnum; + + return 0; +} + +static int mma9553_init(struct mma9553_data *data) +{ + int ret; + + ret = mma9551_read_version(data->client); + if (ret) + return ret; + + /* + * Read all the pedometer configuration registers. This is used as + * a device identification command to differentiate the MMA9553L + * from the MMA9550L. + */ + ret = + mma9551_read_config_words(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_SLEEPMIN, + sizeof(data->conf), (u16 *) &data->conf); + if (ret < 0) { + dev_err(&data->client->dev, + "device is not MMA9553L: failed to read cfg regs\n"); + return ret; + } + + + /* Reset gpio */ + data->gpio_bitnum = -1; + ret = mma9553_conf_gpio(data); + if (ret < 0) + return ret; + + ret = mma9551_app_reset(data->client, MMA9551_RSC_PED); + if (ret < 0) + return ret; + + /* Init config registers */ + data->conf.sleepmin = MMA9553_DEFAULT_SLEEPMIN; + data->conf.sleepmax = MMA9553_DEFAULT_SLEEPMAX; + data->conf.sleepthd = MMA9553_DEFAULT_SLEEPTHD; + data->conf.config = + mma9553_set_bits(data->conf.config, 1, MMA9553_MASK_CONF_CONFIG); + /* + * Clear the activity debounce counter when the activity level changes, + * so that the confidence level applies for any activity level. + */ + data->conf.config = mma9553_set_bits(data->conf.config, 1, + MMA9553_MASK_CONF_ACT_DBCNTM); + ret = + mma9551_write_config_words(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_SLEEPMIN, + sizeof(data->conf), (u16 *) &data->conf); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to write configuration registers\n"); + return ret; + } + + return mma9551_set_device_state(data->client, true); +} + +static int mma9553_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + u16 tmp; + u8 activity; + bool powered_on; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + /* + * The HW only counts steps and other dependent + * parameters (speed, distance, calories, activity) + * if power is on (from enabling an event or the + * step counter */ + powered_on = + mma9553_is_any_event_enabled(data, false, 0) || + data->stepcnt_enabled; + if (!powered_on) { + dev_err(&data->client->dev, + "No channels enabled\n"); + return -EINVAL; + } + mutex_lock(&data->mutex); + ret = mma9551_read_status_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_STEPCNT, + &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_DISTANCE: + powered_on = + mma9553_is_any_event_enabled(data, false, 0) || + data->stepcnt_enabled; + if (!powered_on) { + dev_err(&data->client->dev, + "No channels enabled\n"); + return -EINVAL; + } + mutex_lock(&data->mutex); + ret = mma9551_read_status_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_DISTANCE, + &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_ACTIVITY: + powered_on = + mma9553_is_any_event_enabled(data, false, 0) || + data->stepcnt_enabled; + if (!powered_on) { + dev_err(&data->client->dev, + "No channels enabled\n"); + return -EINVAL; + } + mutex_lock(&data->mutex); + ret = mma9551_read_status_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_STATUS, + &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + + activity = + mma9553_get_bits(tmp, MMA9553_MASK_STATUS_ACTIVITY); + + /* + * The device does not support confidence value levels, + * so we will always have 100% for current activity and + * 0% for the others. + */ + if (chan->channel2 == mma9553_activity_to_mod(activity)) + *val = 100; + else + *val = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VELOCITY: /* m/h */ + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + powered_on = + mma9553_is_any_event_enabled(data, false, 0) || + data->stepcnt_enabled; + if (!powered_on) { + dev_err(&data->client->dev, + "No channels enabled\n"); + return -EINVAL; + } + mutex_lock(&data->mutex); + ret = mma9551_read_status_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_SPEED, &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_ENERGY: /* Cal or kcal */ + powered_on = + mma9553_is_any_event_enabled(data, false, 0) || + data->stepcnt_enabled; + if (!powered_on) { + dev_err(&data->client->dev, + "No channels enabled\n"); + return -EINVAL; + } + mutex_lock(&data->mutex); + ret = mma9551_read_status_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_CALORIES, + &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_ACCEL: + mutex_lock(&data->mutex); + ret = mma9551_read_accel_chan(data->client, + chan, val, val2); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VELOCITY: /* m/h to m/s */ + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + *val = 0; + *val2 = 277; /* 0.000277 */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ENERGY: /* Cal or kcal to J */ + *val = 4184; + return IIO_VAL_INT; + case IIO_ACCEL: + return mma9551_read_accel_scale(val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_ENABLE: + *val = data->stepcnt_enabled; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBHEIGHT: + tmp = mma9553_get_bits(data->conf.height_weight, + MMA9553_MASK_CONF_HEIGHT); + *val = tmp / 100; /* cm to m */ + *val2 = (tmp % 100) * 10000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBWEIGHT: + *val = mma9553_get_bits(data->conf.height_weight, + MMA9553_MASK_CONF_WEIGHT); + return IIO_VAL_INT; + case IIO_CHAN_INFO_DEBOUNCE_COUNT: + switch (chan->type) { + case IIO_STEPS: + *val = mma9553_get_bits(data->conf.filter, + MMA9553_MASK_CONF_FILTSTEP); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_DEBOUNCE_TIME: + switch (chan->type) { + case IIO_STEPS: + *val = mma9553_get_bits(data->conf.filter, + MMA9553_MASK_CONF_FILTTIME); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_VELOCITY: + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + *val = mma9553_get_bits(data->conf.speed_step, + MMA9553_MASK_CONF_SPDPRD); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mma9553_data *data = iio_priv(indio_dev); + int ret, tmp; + + switch (mask) { + case IIO_CHAN_INFO_ENABLE: + if (data->stepcnt_enabled == !!val) + return 0; + mutex_lock(&data->mutex); + ret = mma9551_set_power_state(data->client, val); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + data->stepcnt_enabled = val; + mutex_unlock(&data->mutex); + return 0; + case IIO_CHAN_INFO_CALIBHEIGHT: + /* m to cm */ + tmp = val * 100 + val2 / 10000; + if (tmp < 0 || tmp > 255) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_HEIGHT_WEIGHT, + &data->conf.height_weight, + tmp, MMA9553_MASK_CONF_HEIGHT); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_CALIBWEIGHT: + if (val < 0 || val > 255) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_HEIGHT_WEIGHT, + &data->conf.height_weight, + val, MMA9553_MASK_CONF_WEIGHT); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_DEBOUNCE_COUNT: + switch (chan->type) { + case IIO_STEPS: + /* + * Set to 0 to disable step filtering. If the value + * specified is greater than 6, then 6 will be used. + */ + if (val < 0) + return -EINVAL; + if (val > 6) + val = 6; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER, + &data->conf.filter, val, + MMA9553_MASK_CONF_FILTSTEP); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_DEBOUNCE_TIME: + switch (chan->type) { + case IIO_STEPS: + if (val < 0 || val > 127) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER, + &data->conf.filter, val, + MMA9553_MASK_CONF_FILTTIME); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_VELOCITY: + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + /* + * If set to a value greater than 5, then 5 will be + * used. Warning: Do not set SPDPRD to 0 or 1 as + * this may cause undesirable behavior. + */ + if (val < 2) + return -EINVAL; + if (val > 5) + val = 5; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_SPEED_STEP, + &data->conf.speed_step, val, + MMA9553_MASK_CONF_SPDPRD); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + + struct mma9553_data *data = iio_priv(indio_dev); + struct mma9553_event *event; + + event = mma9553_get_event(data, chan->type, chan->channel2, dir); + if (!event) + return -EINVAL; + + return event->enabled; +} + +static int mma9553_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, int state) +{ + struct mma9553_data *data = iio_priv(indio_dev); + struct mma9553_event *event; + int ret; + + event = mma9553_get_event(data, chan->type, chan->channel2, dir); + if (!event) + return -EINVAL; + + if (event->enabled == state) + return 0; + + mutex_lock(&data->mutex); + + ret = mma9551_set_power_state(data->client, state); + if (ret < 0) + goto err_out; + event->enabled = state; + + ret = mma9553_conf_gpio(data); + if (ret < 0) + goto err_conf_gpio; + + mutex_unlock(&data->mutex); + + return ret; + +err_conf_gpio: + if (state) { + event->enabled = false; + mma9551_set_power_state(data->client, false); + } +err_out: + mutex_unlock(&data->mutex); + return ret; +} + +static int mma9553_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct mma9553_data *data = iio_priv(indio_dev); + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + switch (chan->type) { + case IIO_STEPS: + *val = mma9553_get_bits(data->conf.speed_step, + MMA9553_MASK_CONF_STEPCOALESCE); + return IIO_VAL_INT; + case IIO_ACTIVITY: + /* + * The device does not support confidence value levels. + * We set an average of 50%. + */ + *val = 50; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + switch (chan->type) { + case IIO_ACTIVITY: + *val = MMA9553_ACTIVITY_THD_TO_SEC(data->conf.actthd); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (chan->type) { + case IIO_STEPS: + if (val < 0 || val > 255) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_SPEED_STEP, + &data->conf.speed_step, val, + MMA9553_MASK_CONF_STEPCOALESCE); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + switch (chan->type) { + case IIO_ACTIVITY: + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_ACTTHD, + &data->conf.actthd, + MMA9553_ACTIVITY_SEC_TO_THD + (val), MMA9553_MASK_CONF_WORD); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_get_calibgender_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mma9553_data *data = iio_priv(indio_dev); + u8 gender; + + gender = mma9553_get_bits(data->conf.filter, MMA9553_MASK_CONF_MALE); + /* + * HW expects 0 for female and 1 for male, + * while iio index is 0 for male and 1 for female + */ + return !gender; +} + +static int mma9553_set_calibgender_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct mma9553_data *data = iio_priv(indio_dev); + u8 gender = !mode; + int ret; + + if ((mode != 0) && (mode != 1)) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER, + &data->conf.filter, gender, + MMA9553_MASK_CONF_MALE); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_event_spec mma9553_step_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), +}; + +static const struct iio_event_spec mma9553_activity_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + +static const char * const calibgender_modes[] = { "male", "female" }; + +static const struct iio_enum mma9553_calibgender_enum = { + .items = calibgender_modes, + .num_items = ARRAY_SIZE(calibgender_modes), + .get = mma9553_get_calibgender_mode, + .set = mma9553_set_calibgender_mode, +}; + +static const struct iio_chan_spec_ext_info mma9553_ext_info[] = { + IIO_ENUM("calibgender", IIO_SHARED_BY_TYPE, &mma9553_calibgender_enum), + IIO_ENUM_AVAILABLE("calibgender", &mma9553_calibgender_enum), + {}, +}; + +#define MMA9553_PEDOMETER_CHANNEL(_type, _mask) { \ + .type = _type, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \ + BIT(IIO_CHAN_INFO_CALIBHEIGHT) | \ + _mask, \ + .ext_info = mma9553_ext_info, \ +} + +#define MMA9553_ACTIVITY_CHANNEL(_chan2) { \ + .type = IIO_ACTIVITY, \ + .modified = 1, \ + .channel2 = _chan2, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBHEIGHT), \ + .event_spec = mma9553_activity_events, \ + .num_event_specs = ARRAY_SIZE(mma9553_activity_events), \ + .ext_info = mma9553_ext_info, \ +} + +static const struct iio_chan_spec mma9553_channels[] = { + MMA9551_ACCEL_CHANNEL(IIO_MOD_X), + MMA9551_ACCEL_CHANNEL(IIO_MOD_Y), + MMA9551_ACCEL_CHANNEL(IIO_MOD_Z), + + { + .type = IIO_STEPS, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_DEBOUNCE_COUNT) | + BIT(IIO_CHAN_INFO_DEBOUNCE_TIME), + .event_spec = &mma9553_step_event, + .num_event_specs = 1, + }, + + MMA9553_PEDOMETER_CHANNEL(IIO_DISTANCE, BIT(IIO_CHAN_INFO_PROCESSED)), + { + .type = IIO_VELOCITY, + .modified = 1, + .channel2 = IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_ENABLE), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBHEIGHT), + .ext_info = mma9553_ext_info, + }, + MMA9553_PEDOMETER_CHANNEL(IIO_ENERGY, BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBWEIGHT)), + + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_RUNNING), + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_JOGGING), + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_WALKING), + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_STILL), +}; + +static const struct iio_info mma9553_info = { + .driver_module = THIS_MODULE, + .read_raw = mma9553_read_raw, + .write_raw = mma9553_write_raw, + .read_event_config = mma9553_read_event_config, + .write_event_config = mma9553_write_event_config, + .read_event_value = mma9553_read_event_value, + .write_event_value = mma9553_write_event_value, +}; + +static irqreturn_t mma9553_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct mma9553_data *data = iio_priv(indio_dev); + + data->timestamp = iio_get_time_ns(); + /* + * Since we only configure the interrupt pin when an + * event is enabled, we are sure we have at least + * one event enabled at this point. + */ + return IRQ_WAKE_THREAD; +} + +static irqreturn_t mma9553_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct mma9553_data *data = iio_priv(indio_dev); + u16 stepcnt; + u8 activity; + struct mma9553_event *ev_activity, *ev_prev_activity, *ev_step_detect; + int ret; + + mutex_lock(&data->mutex); + ret = mma9553_read_activity_stepcnt(data, &activity, &stepcnt); + if (ret < 0) { + mutex_unlock(&data->mutex); + return IRQ_HANDLED; + } + + ev_prev_activity = + mma9553_get_event(data, IIO_ACTIVITY, + mma9553_activity_to_mod(data->activity), + IIO_EV_DIR_FALLING); + ev_activity = + mma9553_get_event(data, IIO_ACTIVITY, + mma9553_activity_to_mod(activity), + IIO_EV_DIR_RISING); + ev_step_detect = + mma9553_get_event(data, IIO_STEPS, IIO_NO_MOD, IIO_EV_DIR_NONE); + + if (ev_step_detect->enabled && (stepcnt != data->stepcnt)) { + data->stepcnt = stepcnt; + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_STEPS, 0, IIO_NO_MOD, + IIO_EV_DIR_NONE, IIO_EV_TYPE_CHANGE, 0, 0, 0), + data->timestamp); + } + + if (activity != data->activity) { + data->activity = activity; + /* ev_activity can be NULL if activity == ACTIVITY_UNKNOWN */ + if (ev_prev_activity && ev_prev_activity->enabled) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_ACTIVITY, 0, + ev_prev_activity->info->mod, + IIO_EV_DIR_FALLING, + IIO_EV_TYPE_THRESH, 0, 0, 0), + data->timestamp); + + if (ev_activity && ev_activity->enabled) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_ACTIVITY, 0, + ev_activity->info->mod, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, 0, 0, 0), + data->timestamp); + } + mutex_unlock(&data->mutex); + + return IRQ_HANDLED; +} + +static int mma9553_gpio_probe(struct i2c_client *client) +{ + struct device *dev; + struct gpio_desc *gpio; + int ret; + + if (!client) + return -EINVAL; + + dev = &client->dev; + + /* data ready gpio interrupt pin */ + gpio = devm_gpiod_get_index(dev, MMA9553_GPIO_NAME, 0); + if (IS_ERR(gpio)) { + dev_err(dev, "acpi gpio get index failed\n"); + return PTR_ERR(gpio); + } + + ret = gpiod_direction_input(gpio); + if (ret) + return ret; + + ret = gpiod_to_irq(gpio); + + dev_dbg(dev, "gpio resource, no:%d irq:%d\n", desc_to_gpio(gpio), ret); + + return ret; +} + +static const char *mma9553_match_acpi_device(struct device *dev) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + + return dev_name(dev); +} + +static int mma9553_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mma9553_data *data; + struct iio_dev *indio_dev; + const char *name = NULL; + int ret; + + 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; + + if (id) + name = id->name; + else if (ACPI_HANDLE(&client->dev)) + name = mma9553_match_acpi_device(&client->dev); + else + return -ENOSYS; + + mutex_init(&data->mutex); + mma9553_init_events(data); + + ret = mma9553_init(data); + if (ret < 0) + return ret; + + indio_dev->dev.parent = &client->dev; + indio_dev->channels = mma9553_channels; + indio_dev->num_channels = ARRAY_SIZE(mma9553_channels); + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mma9553_info; + + if (client->irq < 0) + client->irq = mma9553_gpio_probe(client); + + if (client->irq >= 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + mma9553_irq_handler, + mma9553_event_handler, + IRQF_TRIGGER_RISING, + MMA9553_IRQ_NAME, indio_dev); + if (ret < 0) { + dev_err(&client->dev, "request irq %d failed\n", + client->irq); + goto out_poweroff; + } + + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto out_poweroff; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_iio_unregister; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + MMA9551_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + dev_dbg(&indio_dev->dev, "Registered device %s\n", name); + + return 0; + +out_iio_unregister: + iio_device_unregister(indio_dev); +out_poweroff: + mma9551_set_device_state(client, false); + return ret; +} + +static int mma9553_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mma9553_data *data = iio_priv(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + iio_device_unregister(indio_dev); + mutex_lock(&data->mutex); + mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + + return 0; +} + +#ifdef CONFIG_PM +static int mma9553_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + if (ret < 0) { + dev_err(&data->client->dev, "powering off device failed\n"); + return -EAGAIN; + } + + return 0; +} + +static int mma9553_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + ret = mma9551_set_device_state(data->client, true); + if (ret < 0) + return ret; + + mma9551_sleep(MMA9553_DEFAULT_SAMPLE_RATE); + + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int mma9553_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static int mma9553_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, true); + mutex_unlock(&data->mutex); + + return ret; +} +#endif + +static const struct dev_pm_ops mma9553_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mma9553_suspend, mma9553_resume) + SET_RUNTIME_PM_OPS(mma9553_runtime_suspend, + mma9553_runtime_resume, NULL) +}; + +static const struct acpi_device_id mma9553_acpi_match[] = { + {"MMA9553", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(acpi, mma9553_acpi_match); + +static const struct i2c_device_id mma9553_id[] = { + {"mma9553", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mma9553_id); + +static struct i2c_driver mma9553_driver = { + .driver = { + .name = MMA9553_DRV_NAME, + .acpi_match_table = ACPI_PTR(mma9553_acpi_match), + .pm = &mma9553_pm_ops, + }, + .probe = mma9553_probe, + .remove = mma9553_remove, + .id_table = mma9553_id, +}; + +module_i2c_driver(mma9553_driver); + +MODULE_AUTHOR("Irina Tirdea <irina.tirdea@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MMA9553L pedometer platform driver"); diff --git a/drivers/iio/accel/ssp_accel_sensor.c b/drivers/iio/accel/ssp_accel_sensor.c new file mode 100644 index 000000000000..4ae05fce9f24 --- /dev/null +++ b/drivers/iio/accel/ssp_accel_sensor.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. 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 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/iio/common/ssp_sensors.h> +#include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include "../common/ssp_sensors/ssp_iio_sensor.h" + +#define SSP_CHANNEL_COUNT 3 + +#define SSP_ACCEL_NAME "ssp-accelerometer" +static const char ssp_accel_device_name[] = SSP_ACCEL_NAME; + +enum ssp_accel_3d_channel { + SSP_CHANNEL_SCAN_INDEX_X, + SSP_CHANNEL_SCAN_INDEX_Y, + SSP_CHANNEL_SCAN_INDEX_Z, + SSP_CHANNEL_SCAN_INDEX_TIME, +}; + +static int ssp_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + u32 t; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + t = ssp_get_sensor_delay(data, SSP_ACCELEROMETER_SENSOR); + ssp_convert_to_freq(t, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + + return -EINVAL; +} + +static int ssp_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + int ret; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ssp_convert_to_time(val, val2); + ret = ssp_change_delay(data, SSP_ACCELEROMETER_SENSOR, ret); + if (ret < 0) + dev_err(&indio_dev->dev, "accel sensor enable fail\n"); + + return ret; + default: + break; + } + + return -EINVAL; +} + +static struct iio_info ssp_accel_iio_info = { + .read_raw = &ssp_accel_read_raw, + .write_raw = &ssp_accel_write_raw, +}; + +static const unsigned long ssp_accel_scan_mask[] = { 0x7, 0, }; + +static const struct iio_chan_spec ssp_acc_channels[] = { + SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_X, SSP_CHANNEL_SCAN_INDEX_X), + SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_Y, SSP_CHANNEL_SCAN_INDEX_Y), + SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_Z, SSP_CHANNEL_SCAN_INDEX_Z), + SSP_CHAN_TIMESTAMP(SSP_CHANNEL_SCAN_INDEX_TIME), +}; + +static int ssp_process_accel_data(struct iio_dev *indio_dev, void *buf, + int64_t timestamp) +{ + return ssp_common_process_data(indio_dev, buf, SSP_ACCELEROMETER_SIZE, + timestamp); +} + +static const struct iio_buffer_setup_ops ssp_accel_buffer_ops = { + .postenable = &ssp_common_buffer_postenable, + .postdisable = &ssp_common_buffer_postdisable, +}; + +static int ssp_accel_probe(struct platform_device *pdev) +{ + int ret; + struct iio_dev *indio_dev; + struct ssp_sensor_data *spd; + struct iio_buffer *buffer; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*spd)); + if (!indio_dev) + return -ENOMEM; + + spd = iio_priv(indio_dev); + + spd->process_data = ssp_process_accel_data; + spd->type = SSP_ACCELEROMETER_SENSOR; + + indio_dev->name = ssp_accel_device_name; + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &ssp_accel_iio_info; + indio_dev->modes = INDIO_BUFFER_SOFTWARE; + indio_dev->channels = ssp_acc_channels; + indio_dev->num_channels = ARRAY_SIZE(ssp_acc_channels); + indio_dev->available_scan_masks = ssp_accel_scan_mask; + + buffer = devm_iio_kfifo_allocate(&pdev->dev); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + indio_dev->setup_ops = &ssp_accel_buffer_ops; + + platform_set_drvdata(pdev, indio_dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + return ret; + + /* ssp registering should be done after all iio setup */ + ssp_register_consumer(indio_dev, SSP_ACCELEROMETER_SENSOR); + + 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); + +MODULE_AUTHOR("Karol Wrona <k.wrona@samsung.com>"); +MODULE_DESCRIPTION("Samsung sensorhub accelerometers driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 0f79e4725763..202daf889be2 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -135,6 +135,17 @@ config AXP288_ADC device. Depending on platform configuration, this general purpose ADC can be used for sampling sensors such as thermal resistors. +config CC10001_ADC + tristate "Cosmic Circuits 10001 ADC driver" + depends on HAS_IOMEM || HAVE_CLK || REGULATOR + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Cosmic Circuits 10001 ADC. + + This driver can also be built as a module. If so, the module will be + called cc10001_adc. + config EXYNOS_ADC tristate "Exynos ADC driver support" depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST) @@ -228,6 +239,20 @@ config QCOM_SPMI_IADC To compile this driver as a module, choose M here: the module will be called qcom-spmi-iadc. +config QCOM_SPMI_VADC + tristate "Qualcomm SPMI PMIC voltage ADC" + depends on SPMI + select REGMAP_SPMI + help + This is the IIO Voltage ADC driver for Qualcomm QPNP VADC Chip. + + The driver supports multiple channels read. The VADC is a 15-bit + sigma-delta ADC. Some of the channels are internally used for + calibration. + + To compile this driver as a module, choose M here: the module will + be called qcom-spmi-vadc. + config ROCKCHIP_SARADC tristate "Rockchip SARADC driver" depends on ARCH_ROCKCHIP || (ARM && COMPILE_TEST) diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 701fdb7c96aa..0315af640866 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_AD7887) += ad7887.o obj-$(CONFIG_AD799X) += ad799x.o obj-$(CONFIG_AT91_ADC) += at91_adc.o obj-$(CONFIG_AXP288_ADC) += axp288_adc.o +obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o obj-$(CONFIG_MAX1027) += max1027.o @@ -24,6 +25,7 @@ obj-$(CONFIG_MCP3422) += mcp3422.o obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o +obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o diff --git a/drivers/iio/adc/cc10001_adc.c b/drivers/iio/adc/cc10001_adc.c new file mode 100644 index 000000000000..51e2a83c9404 --- /dev/null +++ b/drivers/iio/adc/cc10001_adc.c @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2014-2015 Imagination Technologies Ltd. + * + * 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/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* Registers */ +#define CC10001_ADC_CONFIG 0x00 +#define CC10001_ADC_START_CONV BIT(4) +#define CC10001_ADC_MODE_SINGLE_CONV BIT(5) + +#define CC10001_ADC_DDATA_OUT 0x04 +#define CC10001_ADC_EOC 0x08 +#define CC10001_ADC_EOC_SET BIT(0) + +#define CC10001_ADC_CHSEL_SAMPLED 0x0c +#define CC10001_ADC_POWER_UP 0x10 +#define CC10001_ADC_POWER_UP_SET BIT(0) +#define CC10001_ADC_DEBUG 0x14 +#define CC10001_ADC_DATA_COUNT 0x20 + +#define CC10001_ADC_DATA_MASK GENMASK(9, 0) +#define CC10001_ADC_NUM_CHANNELS 8 +#define CC10001_ADC_CH_MASK GENMASK(2, 0) + +#define CC10001_INVALID_SAMPLED 0xffff +#define CC10001_MAX_POLL_COUNT 20 + +/* + * As per device specification, wait six clock cycles after power-up to + * activate START. Since adding two more clock cycles delay does not + * impact the performance too much, we are adding two additional cycles delay + * intentionally here. + */ +#define CC10001_WAIT_CYCLES 8 + +struct cc10001_adc_device { + void __iomem *reg_base; + struct clk *adc_clk; + struct regulator *reg; + u16 *buf; + + struct mutex lock; + unsigned long channel_map; + unsigned int start_delay_ns; + unsigned int eoc_delay_ns; +}; + +static inline void cc10001_adc_write_reg(struct cc10001_adc_device *adc_dev, + u32 reg, u32 val) +{ + writel(val, adc_dev->reg_base + reg); +} + +static inline u32 cc10001_adc_read_reg(struct cc10001_adc_device *adc_dev, + u32 reg) +{ + return readl(adc_dev->reg_base + reg); +} + +static void cc10001_adc_start(struct cc10001_adc_device *adc_dev, + unsigned int channel) +{ + u32 val; + + /* Channel selection and mode of operation */ + val = (channel & CC10001_ADC_CH_MASK) | CC10001_ADC_MODE_SINGLE_CONV; + cc10001_adc_write_reg(adc_dev, CC10001_ADC_CONFIG, val); + + val = cc10001_adc_read_reg(adc_dev, CC10001_ADC_CONFIG); + val = val | CC10001_ADC_START_CONV; + cc10001_adc_write_reg(adc_dev, CC10001_ADC_CONFIG, val); +} + +static u16 cc10001_adc_poll_done(struct iio_dev *indio_dev, + unsigned int channel, + unsigned int delay) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + unsigned int poll_count = 0; + + while (!(cc10001_adc_read_reg(adc_dev, CC10001_ADC_EOC) & + CC10001_ADC_EOC_SET)) { + + ndelay(delay); + if (poll_count++ == CC10001_MAX_POLL_COUNT) + return CC10001_INVALID_SAMPLED; + } + + poll_count = 0; + while ((cc10001_adc_read_reg(adc_dev, CC10001_ADC_CHSEL_SAMPLED) & + CC10001_ADC_CH_MASK) != channel) { + + ndelay(delay); + if (poll_count++ == CC10001_MAX_POLL_COUNT) + return CC10001_INVALID_SAMPLED; + } + + /* Read the 10 bit output register */ + return cc10001_adc_read_reg(adc_dev, CC10001_ADC_DDATA_OUT) & + CC10001_ADC_DATA_MASK; +} + +static irqreturn_t cc10001_adc_trigger_h(int irq, void *p) +{ + struct cc10001_adc_device *adc_dev; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev; + unsigned int delay_ns; + unsigned int channel; + bool sample_invalid; + u16 *data; + int i; + + indio_dev = pf->indio_dev; + adc_dev = iio_priv(indio_dev); + data = adc_dev->buf; + + mutex_lock(&adc_dev->lock); + + cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_UP, + CC10001_ADC_POWER_UP_SET); + + /* Wait for 8 (6+2) clock cycles before activating START */ + ndelay(adc_dev->start_delay_ns); + + /* Calculate delay step for eoc and sampled data */ + delay_ns = adc_dev->eoc_delay_ns / CC10001_MAX_POLL_COUNT; + + i = 0; + sample_invalid = false; + for_each_set_bit(channel, indio_dev->active_scan_mask, + indio_dev->masklength) { + + cc10001_adc_start(adc_dev, channel); + + data[i] = cc10001_adc_poll_done(indio_dev, channel, delay_ns); + if (data[i] == CC10001_INVALID_SAMPLED) { + dev_warn(&indio_dev->dev, + "invalid sample on channel %d\n", channel); + sample_invalid = true; + goto done; + } + i++; + } + +done: + cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_UP, 0); + + mutex_unlock(&adc_dev->lock); + + if (!sample_invalid) + iio_push_to_buffers_with_timestamp(indio_dev, data, + iio_get_time_ns()); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static u16 cc10001_adc_read_raw_voltage(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + unsigned int delay_ns; + u16 val; + + cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_UP, + CC10001_ADC_POWER_UP_SET); + + /* Wait for 8 (6+2) clock cycles before activating START */ + ndelay(adc_dev->start_delay_ns); + + /* Calculate delay step for eoc and sampled data */ + delay_ns = adc_dev->eoc_delay_ns / CC10001_MAX_POLL_COUNT; + + cc10001_adc_start(adc_dev, chan->channel); + + val = cc10001_adc_poll_done(indio_dev, chan->channel, delay_ns); + + cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_UP, 0); + + return val; +} + +static int cc10001_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + mutex_lock(&adc_dev->lock); + *val = cc10001_adc_read_raw_voltage(indio_dev, chan); + mutex_unlock(&adc_dev->lock); + + if (*val == CC10001_INVALID_SAMPLED) + return -EIO; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(adc_dev->reg); + if (ret) + return ret; + + *val = ret / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static int cc10001_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + + kfree(adc_dev->buf); + adc_dev->buf = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!adc_dev->buf) + return -ENOMEM; + + return 0; +} + +static const struct iio_info cc10001_adc_info = { + .driver_module = THIS_MODULE, + .read_raw = &cc10001_adc_read_raw, + .update_scan_mode = &cc10001_update_scan_mode, +}; + +static int cc10001_adc_channel_init(struct iio_dev *indio_dev) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + struct iio_chan_spec *chan_array, *timestamp; + unsigned int bit, idx = 0; + + indio_dev->num_channels = bitmap_weight(&adc_dev->channel_map, + CC10001_ADC_NUM_CHANNELS); + + chan_array = devm_kcalloc(&indio_dev->dev, indio_dev->num_channels + 1, + sizeof(struct iio_chan_spec), + GFP_KERNEL); + if (!chan_array) + return -ENOMEM; + + for_each_set_bit(bit, &adc_dev->channel_map, CC10001_ADC_NUM_CHANNELS) { + struct iio_chan_spec *chan = &chan_array[idx]; + + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->channel = bit; + chan->scan_index = idx; + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = 10; + chan->scan_type.storagebits = 16; + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + idx++; + } + + timestamp = &chan_array[idx]; + timestamp->type = IIO_TIMESTAMP; + timestamp->channel = -1; + timestamp->scan_index = idx; + timestamp->scan_type.sign = 's'; + timestamp->scan_type.realbits = 64; + timestamp->scan_type.storagebits = 64; + + indio_dev->channels = chan_array; + + return 0; +} + +static int cc10001_adc_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct cc10001_adc_device *adc_dev; + unsigned long adc_clk_rate; + struct resource *res; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc_dev)); + if (indio_dev == NULL) + return -ENOMEM; + + adc_dev = iio_priv(indio_dev); + + adc_dev->channel_map = GENMASK(CC10001_ADC_NUM_CHANNELS - 1, 0); + if (!of_property_read_u32(node, "adc-reserved-channels", &ret)) + adc_dev->channel_map &= ~ret; + + adc_dev->reg = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(adc_dev->reg)) + return PTR_ERR(adc_dev->reg); + + ret = regulator_enable(adc_dev->reg); + if (ret) + return ret; + + indio_dev->dev.parent = &pdev->dev; + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &cc10001_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + adc_dev->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(adc_dev->reg_base)) { + ret = PTR_ERR(adc_dev->reg_base); + goto err_disable_reg; + } + + adc_dev->adc_clk = devm_clk_get(&pdev->dev, "adc"); + if (IS_ERR(adc_dev->adc_clk)) { + dev_err(&pdev->dev, "failed to get the clock\n"); + ret = PTR_ERR(adc_dev->adc_clk); + goto err_disable_reg; + } + + ret = clk_prepare_enable(adc_dev->adc_clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable the clock\n"); + goto err_disable_reg; + } + + adc_clk_rate = clk_get_rate(adc_dev->adc_clk); + if (!adc_clk_rate) { + ret = -EINVAL; + dev_err(&pdev->dev, "null clock rate!\n"); + goto err_disable_clk; + } + + adc_dev->eoc_delay_ns = NSEC_PER_SEC / adc_clk_rate; + adc_dev->start_delay_ns = adc_dev->eoc_delay_ns * CC10001_WAIT_CYCLES; + + /* Setup the ADC channels available on the device */ + ret = cc10001_adc_channel_init(indio_dev); + if (ret < 0) + goto err_disable_clk; + + mutex_init(&adc_dev->lock); + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &cc10001_adc_trigger_h, NULL); + if (ret < 0) + goto err_disable_clk; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto err_cleanup_buffer; + + platform_set_drvdata(pdev, indio_dev); + + return 0; + +err_cleanup_buffer: + iio_triggered_buffer_cleanup(indio_dev); +err_disable_clk: + clk_disable_unprepare(adc_dev->adc_clk); +err_disable_reg: + regulator_disable(adc_dev->reg); + return ret; +} + +static int cc10001_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + clk_disable_unprepare(adc_dev->adc_clk); + regulator_disable(adc_dev->reg); + + return 0; +} + +static const struct of_device_id cc10001_adc_dt_ids[] = { + { .compatible = "cosmic,10001-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, cc10001_adc_dt_ids); + +static struct platform_driver cc10001_adc_driver = { + .driver = { + .name = "cc10001-adc", + .of_match_table = cc10001_adc_dt_ids, + }, + .probe = cc10001_adc_probe, + .remove = cc10001_adc_remove, +}; +module_platform_driver(cc10001_adc_driver); + +MODULE_AUTHOR("Phani Movva <Phani.Movva@imgtec.com>"); +MODULE_DESCRIPTION("Cosmic Circuits ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/qcom-spmi-vadc.c b/drivers/iio/adc/qcom-spmi-vadc.c new file mode 100644 index 000000000000..3211729bcb0b --- /dev/null +++ b/drivers/iio/adc/qcom-spmi-vadc.c @@ -0,0 +1,1016 @@ +/* + * Copyright (c) 2012-2014, 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 + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/bitops.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/iio/iio.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/log2.h> + +#include <dt-bindings/iio/qcom,spmi-vadc.h> + +/* VADC register and bit definitions */ +#define VADC_REVISION2 0x1 +#define VADC_REVISION2_SUPPORTED_VADC 1 + +#define VADC_PERPH_TYPE 0x4 +#define VADC_PERPH_TYPE_ADC 8 + +#define VADC_PERPH_SUBTYPE 0x5 +#define VADC_PERPH_SUBTYPE_VADC 1 + +#define VADC_STATUS1 0x8 +#define VADC_STATUS1_OP_MODE 4 +#define VADC_STATUS1_REQ_STS BIT(1) +#define VADC_STATUS1_EOC BIT(0) +#define VADC_STATUS1_REQ_STS_EOC_MASK 0x3 + +#define VADC_MODE_CTL 0x40 +#define VADC_OP_MODE_SHIFT 3 +#define VADC_OP_MODE_NORMAL 0 +#define VADC_AMUX_TRIM_EN BIT(1) +#define VADC_ADC_TRIM_EN BIT(0) + +#define VADC_EN_CTL1 0x46 +#define VADC_EN_CTL1_SET BIT(7) + +#define VADC_ADC_CH_SEL_CTL 0x48 + +#define VADC_ADC_DIG_PARAM 0x50 +#define VADC_ADC_DIG_DEC_RATIO_SEL_SHIFT 2 + +#define VADC_HW_SETTLE_DELAY 0x51 + +#define VADC_CONV_REQ 0x52 +#define VADC_CONV_REQ_SET BIT(7) + +#define VADC_FAST_AVG_CTL 0x5a +#define VADC_FAST_AVG_EN 0x5b +#define VADC_FAST_AVG_EN_SET BIT(7) + +#define VADC_ACCESS 0xd0 +#define VADC_ACCESS_DATA 0xa5 + +#define VADC_PERH_RESET_CTL3 0xda +#define VADC_FOLLOW_WARM_RB BIT(2) + +#define VADC_DATA 0x60 /* 16 bits */ + +#define VADC_CONV_TIME_MIN_US 2000 +#define VADC_CONV_TIME_MAX_US 2100 + +/* Min ADC code represents 0V */ +#define VADC_MIN_ADC_CODE 0x6000 +/* Max ADC code represents full-scale range of 1.8V */ +#define VADC_MAX_ADC_CODE 0xa800 + +#define VADC_ABSOLUTE_RANGE_UV 625000 +#define VADC_RATIOMETRIC_RANGE_UV 1800000 + +#define VADC_DEF_PRESCALING 0 /* 1:1 */ +#define VADC_DEF_DECIMATION 0 /* 512 */ +#define VADC_DEF_HW_SETTLE_TIME 0 /* 0 us */ +#define VADC_DEF_AVG_SAMPLES 0 /* 1 sample */ +#define VADC_DEF_CALIB_TYPE VADC_CALIB_ABSOLUTE + +#define VADC_DECIMATION_MIN 512 +#define VADC_DECIMATION_MAX 4096 + +#define VADC_HW_SETTLE_DELAY_MAX 10000 +#define VADC_AVG_SAMPLES_MAX 512 + +#define KELVINMIL_CELSIUSMIL 273150 + +#define VADC_CHAN_MIN VADC_USBIN +#define VADC_CHAN_MAX VADC_LR_MUX3_BUF_PU1_PU2_XO_THERM + +/* + * VADC_CALIB_ABSOLUTE: uses the 625mV and 1.25V as reference channels. + * VADC_CALIB_RATIOMETRIC: uses the reference voltage (1.8V) and GND for + * calibration. + */ +enum vadc_calibration { + VADC_CALIB_ABSOLUTE = 0, + VADC_CALIB_RATIOMETRIC +}; + +/** + * struct vadc_linear_graph - Represent ADC characteristics. + * @dy: numerator slope to calculate the gain. + * @dx: denominator slope to calculate the gain. + * @gnd: A/D word of the ground reference used for the channel. + * + * Each ADC device has different offset and gain parameters which are + * computed to calibrate the device. + */ +struct vadc_linear_graph { + s32 dy; + s32 dx; + s32 gnd; +}; + +/** + * struct vadc_prescale_ratio - Represent scaling ratio for ADC input. + * @num: the inverse numerator of the gain applied to the input channel. + * @den: the inverse denominator of the gain applied to the input channel. + */ +struct vadc_prescale_ratio { + u32 num; + u32 den; +}; + +/** + * struct vadc_channel_prop - VADC channel property. + * @channel: channel number, refer to the channel list. + * @calibration: calibration type. + * @decimation: sampling rate supported for the channel. + * @prescale: channel scaling performed on the input signal. + * @hw_settle_time: the time between AMUX being configured and the + * start of conversion. + * @avg_samples: ability to provide single result from the ADC + * that is an average of multiple measurements. + */ +struct vadc_channel_prop { + unsigned int channel; + enum vadc_calibration calibration; + unsigned int decimation; + unsigned int prescale; + unsigned int hw_settle_time; + unsigned int avg_samples; +}; + +/** + * struct vadc_priv - VADC private structure. + * @regmap: pointer to struct regmap. + * @dev: pointer to struct device. + * @base: base address for the ADC peripheral. + * @nchannels: number of VADC channels. + * @chan_props: array of VADC channel properties. + * @iio_chans: array of IIO channels specification. + * @are_ref_measured: are reference points measured. + * @poll_eoc: use polling instead of interrupt. + * @complete: VADC result notification after interrupt is received. + * @graph: store parameters for calibration. + * @lock: ADC lock for access to the peripheral. + */ +struct vadc_priv { + struct regmap *regmap; + struct device *dev; + u16 base; + unsigned int nchannels; + struct vadc_channel_prop *chan_props; + struct iio_chan_spec *iio_chans; + bool are_ref_measured; + bool poll_eoc; + struct completion complete; + struct vadc_linear_graph graph[2]; + struct mutex lock; +}; + +static const struct vadc_prescale_ratio vadc_prescale_ratios[] = { + {.num = 1, .den = 1}, + {.num = 1, .den = 3}, + {.num = 1, .den = 4}, + {.num = 1, .den = 6}, + {.num = 1, .den = 20}, + {.num = 1, .den = 8}, + {.num = 10, .den = 81}, + {.num = 1, .den = 10} +}; + +static int vadc_read(struct vadc_priv *vadc, u16 offset, u8 *data) +{ + return regmap_bulk_read(vadc->regmap, vadc->base + offset, data, 1); +} + +static int vadc_write(struct vadc_priv *vadc, u16 offset, u8 data) +{ + return regmap_write(vadc->regmap, vadc->base + offset, data); +} + +static int vadc_reset(struct vadc_priv *vadc) +{ + u8 data; + int ret; + + ret = vadc_write(vadc, VADC_ACCESS, VADC_ACCESS_DATA); + if (ret) + return ret; + + ret = vadc_read(vadc, VADC_PERH_RESET_CTL3, &data); + if (ret) + return ret; + + ret = vadc_write(vadc, VADC_ACCESS, VADC_ACCESS_DATA); + if (ret) + return ret; + + data |= VADC_FOLLOW_WARM_RB; + + return vadc_write(vadc, VADC_PERH_RESET_CTL3, data); +} + +static int vadc_set_state(struct vadc_priv *vadc, bool state) +{ + return vadc_write(vadc, VADC_EN_CTL1, state ? VADC_EN_CTL1_SET : 0); +} + +static void vadc_show_status(struct vadc_priv *vadc) +{ + u8 mode, sta1, chan, dig, en, req; + int ret; + + ret = vadc_read(vadc, VADC_MODE_CTL, &mode); + if (ret) + return; + + ret = vadc_read(vadc, VADC_ADC_DIG_PARAM, &dig); + if (ret) + return; + + ret = vadc_read(vadc, VADC_ADC_CH_SEL_CTL, &chan); + if (ret) + return; + + ret = vadc_read(vadc, VADC_CONV_REQ, &req); + if (ret) + return; + + ret = vadc_read(vadc, VADC_STATUS1, &sta1); + if (ret) + return; + + ret = vadc_read(vadc, VADC_EN_CTL1, &en); + if (ret) + return; + + dev_err(vadc->dev, + "mode:%02x en:%02x chan:%02x dig:%02x req:%02x sta1:%02x\n", + mode, en, chan, dig, req, sta1); +} + +static int vadc_configure(struct vadc_priv *vadc, + struct vadc_channel_prop *prop) +{ + u8 decimation, mode_ctrl; + int ret; + + /* Mode selection */ + mode_ctrl = (VADC_OP_MODE_NORMAL << VADC_OP_MODE_SHIFT) | + VADC_ADC_TRIM_EN | VADC_AMUX_TRIM_EN; + ret = vadc_write(vadc, VADC_MODE_CTL, mode_ctrl); + if (ret) + return ret; + + /* Channel selection */ + ret = vadc_write(vadc, VADC_ADC_CH_SEL_CTL, prop->channel); + if (ret) + return ret; + + /* Digital parameter setup */ + decimation = prop->decimation << VADC_ADC_DIG_DEC_RATIO_SEL_SHIFT; + ret = vadc_write(vadc, VADC_ADC_DIG_PARAM, decimation); + if (ret) + return ret; + + /* HW settle time delay */ + ret = vadc_write(vadc, VADC_HW_SETTLE_DELAY, prop->hw_settle_time); + if (ret) + return ret; + + ret = vadc_write(vadc, VADC_FAST_AVG_CTL, prop->avg_samples); + if (ret) + return ret; + + if (prop->avg_samples) + ret = vadc_write(vadc, VADC_FAST_AVG_EN, VADC_FAST_AVG_EN_SET); + else + ret = vadc_write(vadc, VADC_FAST_AVG_EN, 0); + + return ret; +} + +static int vadc_poll_wait_eoc(struct vadc_priv *vadc, unsigned int interval_us) +{ + unsigned int count, retry; + u8 sta1; + int ret; + + retry = interval_us / VADC_CONV_TIME_MIN_US; + + for (count = 0; count < retry; count++) { + ret = vadc_read(vadc, VADC_STATUS1, &sta1); + if (ret) + return ret; + + sta1 &= VADC_STATUS1_REQ_STS_EOC_MASK; + if (sta1 == VADC_STATUS1_EOC) + return 0; + + usleep_range(VADC_CONV_TIME_MIN_US, VADC_CONV_TIME_MAX_US); + } + + vadc_show_status(vadc); + + return -ETIMEDOUT; +} + +static int vadc_read_result(struct vadc_priv *vadc, u16 *data) +{ + int ret; + + ret = regmap_bulk_read(vadc->regmap, vadc->base + VADC_DATA, data, 2); + if (ret) + return ret; + + *data = clamp_t(u16, *data, VADC_MIN_ADC_CODE, VADC_MAX_ADC_CODE); + + return 0; +} + +static struct vadc_channel_prop *vadc_get_channel(struct vadc_priv *vadc, + unsigned int num) +{ + unsigned int i; + + for (i = 0; i < vadc->nchannels; i++) + if (vadc->chan_props[i].channel == num) + return &vadc->chan_props[i]; + + dev_dbg(vadc->dev, "no such channel %02x\n", num); + + return NULL; +} + +static int vadc_do_conversion(struct vadc_priv *vadc, + struct vadc_channel_prop *prop, u16 *data) +{ + unsigned int timeout; + int ret; + + mutex_lock(&vadc->lock); + + ret = vadc_configure(vadc, prop); + if (ret) + goto unlock; + + if (!vadc->poll_eoc) + reinit_completion(&vadc->complete); + + ret = vadc_set_state(vadc, true); + if (ret) + goto unlock; + + ret = vadc_write(vadc, VADC_CONV_REQ, VADC_CONV_REQ_SET); + if (ret) + goto err_disable; + + timeout = BIT(prop->avg_samples) * VADC_CONV_TIME_MIN_US * 2; + + if (vadc->poll_eoc) { + ret = vadc_poll_wait_eoc(vadc, timeout); + } else { + ret = wait_for_completion_timeout(&vadc->complete, timeout); + if (!ret) { + ret = -ETIMEDOUT; + goto err_disable; + } + + /* Double check conversion status */ + ret = vadc_poll_wait_eoc(vadc, VADC_CONV_TIME_MIN_US); + if (ret) + goto err_disable; + } + + ret = vadc_read_result(vadc, data); + +err_disable: + vadc_set_state(vadc, false); + if (ret) + dev_err(vadc->dev, "conversion failed\n"); +unlock: + mutex_unlock(&vadc->lock); + return ret; +} + +static int vadc_measure_ref_points(struct vadc_priv *vadc) +{ + struct vadc_channel_prop *prop; + u16 read_1, read_2; + int ret; + + vadc->graph[VADC_CALIB_RATIOMETRIC].dx = VADC_RATIOMETRIC_RANGE_UV; + vadc->graph[VADC_CALIB_ABSOLUTE].dx = VADC_ABSOLUTE_RANGE_UV; + + prop = vadc_get_channel(vadc, VADC_REF_1250MV); + ret = vadc_do_conversion(vadc, prop, &read_1); + if (ret) + goto err; + + /* Try with buffered 625mV channel first */ + prop = vadc_get_channel(vadc, VADC_SPARE1); + if (!prop) + prop = vadc_get_channel(vadc, VADC_REF_625MV); + + ret = vadc_do_conversion(vadc, prop, &read_2); + if (ret) + goto err; + + if (read_1 == read_2) { + ret = -EINVAL; + goto err; + } + + vadc->graph[VADC_CALIB_ABSOLUTE].dy = read_1 - read_2; + vadc->graph[VADC_CALIB_ABSOLUTE].gnd = read_2; + + /* Ratiometric calibration */ + prop = vadc_get_channel(vadc, VADC_VDD_VADC); + ret = vadc_do_conversion(vadc, prop, &read_1); + if (ret) + goto err; + + prop = vadc_get_channel(vadc, VADC_GND_REF); + ret = vadc_do_conversion(vadc, prop, &read_2); + if (ret) + goto err; + + if (read_1 == read_2) { + ret = -EINVAL; + goto err; + } + + vadc->graph[VADC_CALIB_RATIOMETRIC].dy = read_1 - read_2; + vadc->graph[VADC_CALIB_RATIOMETRIC].gnd = read_2; +err: + if (ret) + dev_err(vadc->dev, "measure reference points failed\n"); + + return ret; +} + +static s32 vadc_calibrate(struct vadc_priv *vadc, + const struct vadc_channel_prop *prop, u16 adc_code) +{ + const struct vadc_prescale_ratio *prescale; + s32 voltage; + + voltage = adc_code - vadc->graph[prop->calibration].gnd; + voltage *= vadc->graph[prop->calibration].dx; + voltage = voltage / vadc->graph[prop->calibration].dy; + + if (prop->calibration == VADC_CALIB_ABSOLUTE) + voltage += vadc->graph[prop->calibration].dx; + + if (voltage < 0) + voltage = 0; + + prescale = &vadc_prescale_ratios[prop->prescale]; + + voltage = voltage * prescale->den; + + return voltage / prescale->num; +} + +static int vadc_decimation_from_dt(u32 value) +{ + if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN || + value > VADC_DECIMATION_MAX) + return -EINVAL; + + return __ffs64(value / VADC_DECIMATION_MIN); +} + +static int vadc_prescaling_from_dt(u32 num, u32 den) +{ + unsigned int pre; + + for (pre = 0; pre < ARRAY_SIZE(vadc_prescale_ratios); pre++) + if (vadc_prescale_ratios[pre].num == num && + vadc_prescale_ratios[pre].den == den) + break; + + if (pre == ARRAY_SIZE(vadc_prescale_ratios)) + return -EINVAL; + + return pre; +} + +static int vadc_hw_settle_time_from_dt(u32 value) +{ + if ((value <= 1000 && value % 100) || (value > 1000 && value % 2000)) + return -EINVAL; + + if (value <= 1000) + value /= 100; + else + value = value / 2000 + 10; + + return value; +} + +static int vadc_avg_samples_from_dt(u32 value) +{ + if (!is_power_of_2(value) || value > VADC_AVG_SAMPLES_MAX) + return -EINVAL; + + return __ffs64(value); +} + +static int vadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct vadc_priv *vadc = iio_priv(indio_dev); + struct vadc_channel_prop *prop; + u16 adc_code; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + prop = &vadc->chan_props[chan->address]; + ret = vadc_do_conversion(vadc, prop, &adc_code); + if (ret) + break; + + *val = vadc_calibrate(vadc, prop, adc_code); + + /* 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]; + ret = vadc_do_conversion(vadc, prop, &adc_code); + if (ret) + break; + + *val = vadc_calibrate(vadc, prop, 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; + } + + return ret; +} + +static int vadc_of_xlate(struct iio_dev *indio_dev, + const struct of_phandle_args *iiospec) +{ + struct vadc_priv *vadc = iio_priv(indio_dev); + unsigned int i; + + for (i = 0; i < vadc->nchannels; i++) + if (vadc->iio_chans[i].channel == iiospec->args[0]) + return i; + + return -EINVAL; +} + +static const struct iio_info vadc_info = { + .read_raw = vadc_read_raw, + .of_xlate = vadc_of_xlate, + .driver_module = THIS_MODULE, +}; + +struct vadc_channels { + const char *datasheet_name; + unsigned int prescale_index; + enum iio_chan_type type; + long info_mask; +}; + +#define VADC_CHAN(_dname, _type, _mask, _pre) \ + [VADC_##_dname] = { \ + .datasheet_name = __stringify(_dname), \ + .prescale_index = _pre, \ + .type = _type, \ + .info_mask = _mask \ + }, \ + +#define VADC_CHAN_TEMP(_dname, _pre) \ + VADC_CHAN(_dname, IIO_TEMP, BIT(IIO_CHAN_INFO_PROCESSED), _pre) \ + +#define VADC_CHAN_VOLT(_dname, _pre) \ + VADC_CHAN(_dname, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \ + _pre) \ + +/* + * The array represents all possible ADC channels found in the supported PMICs. + * Every index in the array is equal to the channel number per datasheet. The + * 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) +}; + +static int vadc_get_dt_channel_data(struct device *dev, + struct vadc_channel_prop *prop, + struct device_node *node) +{ + const char *name = node->name; + u32 chan, value, varr[2]; + int ret; + + ret = of_property_read_u32(node, "reg", &chan); + if (ret) { + dev_err(dev, "invalid channel number %s\n", name); + return ret; + } + + if (chan > VADC_CHAN_MAX || chan < VADC_CHAN_MIN) { + dev_err(dev, "%s invalid channel number %d\n", name, chan); + return -EINVAL; + } + + /* the channel has DT description */ + prop->channel = chan; + + ret = of_property_read_u32(node, "qcom,decimation", &value); + if (!ret) { + ret = vadc_decimation_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid decimation %d\n", + chan, value); + return ret; + } + prop->decimation = ret; + } else { + prop->decimation = VADC_DEF_DECIMATION; + } + + ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2); + if (!ret) { + ret = vadc_prescaling_from_dt(varr[0], varr[1]); + if (ret < 0) { + dev_err(dev, "%02x invalid pre-scaling <%d %d>\n", + chan, varr[0], varr[1]); + return ret; + } + prop->prescale = ret; + } else { + prop->prescale = vadc_chans[prop->channel].prescale_index; + } + + ret = of_property_read_u32(node, "qcom,hw-settle-time", &value); + if (!ret) { + ret = vadc_hw_settle_time_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid hw-settle-time %d us\n", + chan, value); + return ret; + } + prop->hw_settle_time = ret; + } else { + prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME; + } + + ret = of_property_read_u32(node, "qcom,avg-samples", &value); + if (!ret) { + ret = vadc_avg_samples_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid avg-samples %d\n", + chan, value); + return ret; + } + prop->avg_samples = ret; + } else { + prop->avg_samples = VADC_DEF_AVG_SAMPLES; + } + + if (of_property_read_bool(node, "qcom,ratiometric")) + prop->calibration = VADC_CALIB_RATIOMETRIC; + else + prop->calibration = VADC_CALIB_ABSOLUTE; + + dev_dbg(dev, "%02x name %s\n", chan, name); + + return 0; +} + +static int vadc_get_dt_data(struct vadc_priv *vadc, struct device_node *node) +{ + const struct vadc_channels *vadc_chan; + struct iio_chan_spec *iio_chan; + struct vadc_channel_prop prop; + struct device_node *child; + unsigned int index = 0; + int ret; + + vadc->nchannels = of_get_available_child_count(node); + if (!vadc->nchannels) + return -EINVAL; + + vadc->iio_chans = devm_kcalloc(vadc->dev, vadc->nchannels, + sizeof(*vadc->iio_chans), GFP_KERNEL); + if (!vadc->iio_chans) + return -ENOMEM; + + vadc->chan_props = devm_kcalloc(vadc->dev, vadc->nchannels, + sizeof(*vadc->chan_props), GFP_KERNEL); + if (!vadc->chan_props) + return -ENOMEM; + + iio_chan = vadc->iio_chans; + + for_each_available_child_of_node(node, child) { + ret = vadc_get_dt_channel_data(vadc->dev, &prop, child); + if (ret) + return ret; + + vadc->chan_props[index] = prop; + + vadc_chan = &vadc_chans[prop.channel]; + + iio_chan->channel = prop.channel; + iio_chan->datasheet_name = vadc_chan->datasheet_name; + iio_chan->info_mask_separate = vadc_chan->info_mask; + iio_chan->type = vadc_chan->type; + iio_chan->indexed = 1; + iio_chan->address = index++; + + iio_chan++; + } + + /* These channels are mandatory, they are used as reference points */ + if (!vadc_get_channel(vadc, VADC_REF_1250MV)) { + dev_err(vadc->dev, "Please define 1.25V channel\n"); + return -ENODEV; + } + + if (!vadc_get_channel(vadc, VADC_REF_625MV)) { + dev_err(vadc->dev, "Please define 0.625V channel\n"); + return -ENODEV; + } + + if (!vadc_get_channel(vadc, VADC_VDD_VADC)) { + dev_err(vadc->dev, "Please define VDD channel\n"); + return -ENODEV; + } + + if (!vadc_get_channel(vadc, VADC_GND_REF)) { + dev_err(vadc->dev, "Please define GND channel\n"); + return -ENODEV; + } + + return 0; +} + +static irqreturn_t vadc_isr(int irq, void *dev_id) +{ + struct vadc_priv *vadc = dev_id; + + complete(&vadc->complete); + + return IRQ_HANDLED; +} + +static int vadc_check_revision(struct vadc_priv *vadc) +{ + u8 val; + int ret; + + ret = vadc_read(vadc, VADC_PERPH_TYPE, &val); + if (ret) + return ret; + + if (val < VADC_PERPH_TYPE_ADC) { + dev_err(vadc->dev, "%d is not ADC\n", val); + return -ENODEV; + } + + ret = vadc_read(vadc, VADC_PERPH_SUBTYPE, &val); + if (ret) + return ret; + + if (val < VADC_PERPH_SUBTYPE_VADC) { + dev_err(vadc->dev, "%d is not VADC\n", val); + return -ENODEV; + } + + ret = vadc_read(vadc, VADC_REVISION2, &val); + if (ret) + return ret; + + if (val < VADC_REVISION2_SUPPORTED_VADC) { + dev_err(vadc->dev, "revision %d not supported\n", val); + return -ENODEV; + } + + return 0; +} + +static int vadc_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct vadc_priv *vadc; + struct regmap *regmap; + int ret, irq_eoc; + u32 reg; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + ret = of_property_read_u32(node, "reg", ®); + if (ret < 0) + return ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*vadc)); + if (!indio_dev) + return -ENOMEM; + + vadc = iio_priv(indio_dev); + vadc->regmap = regmap; + vadc->dev = dev; + vadc->base = reg; + vadc->are_ref_measured = false; + init_completion(&vadc->complete); + mutex_init(&vadc->lock); + + ret = vadc_check_revision(vadc); + if (ret) + return ret; + + ret = vadc_get_dt_data(vadc, node); + if (ret) + return ret; + + irq_eoc = platform_get_irq(pdev, 0); + if (irq_eoc < 0) { + if (irq_eoc == -EPROBE_DEFER || irq_eoc == -EINVAL) + return irq_eoc; + vadc->poll_eoc = true; + } else { + ret = devm_request_irq(dev, irq_eoc, vadc_isr, 0, + "spmi-vadc", vadc); + if (ret) + return ret; + } + + ret = vadc_reset(vadc); + if (ret) { + dev_err(dev, "reset failed\n"); + return ret; + } + + ret = vadc_measure_ref_points(vadc); + if (ret) + return ret; + + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = node; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &vadc_info; + indio_dev->channels = vadc->iio_chans; + indio_dev->num_channels = vadc->nchannels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id vadc_match_table[] = { + { .compatible = "qcom,spmi-vadc" }, + { } +}; +MODULE_DEVICE_TABLE(of, vadc_match_table); + +static struct platform_driver vadc_driver = { + .driver = { + .name = "qcom-spmi-vadc", + .of_match_table = vadc_match_table, + }, + .probe = vadc_probe, +}; +module_platform_driver(vadc_driver); + +MODULE_ALIAS("platform:qcom-spmi-vadc"); +MODULE_DESCRIPTION("Qualcomm SPMI PMIC voltage ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Stanimir Varbanov <svarbanov@mm-sol.com>"); +MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>"); diff --git a/drivers/iio/common/Kconfig b/drivers/iio/common/Kconfig index 0b6e97d18fa0..790f106d719c 100644 --- a/drivers/iio/common/Kconfig +++ b/drivers/iio/common/Kconfig @@ -3,4 +3,5 @@ # source "drivers/iio/common/hid-sensors/Kconfig" +source "drivers/iio/common/ssp_sensors/Kconfig" source "drivers/iio/common/st_sensors/Kconfig" diff --git a/drivers/iio/common/Makefile b/drivers/iio/common/Makefile index 3112df0060e9..b1e4d9c9591c 100644 --- a/drivers/iio/common/Makefile +++ b/drivers/iio/common/Makefile @@ -8,4 +8,5 @@ # When adding new entries keep the list in alphabetical order obj-y += hid-sensors/ +obj-y += ssp_sensors/ obj-y += st_sensors/ diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c index 92068cdbf8c7..2f1d535b94c4 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -22,16 +22,18 @@ #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/slab.h> +#include <linux/delay.h> #include <linux/hid-sensor-hub.h> #include <linux/iio/iio.h> #include <linux/iio/trigger.h> #include <linux/iio/sysfs.h> #include "hid-sensor-trigger.h" -int hid_sensor_power_state(struct hid_sensor_common *st, bool state) +static int _hid_sensor_power_state(struct hid_sensor_common *st, bool state) { int state_val; int report_val; + s32 poll_value = 0; if (state) { if (sensor_hub_device_open(st->hsdev)) @@ -47,6 +49,8 @@ int hid_sensor_power_state(struct hid_sensor_common *st, bool state) st->report_state.report_id, st->report_state.index, HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM); + + poll_value = hid_sensor_read_poll_value(st); } else { if (!atomic_dec_and_test(&st->data_ready)) return 0; @@ -78,10 +82,36 @@ int hid_sensor_power_state(struct hid_sensor_common *st, bool state) sensor_hub_get_feature(st->hsdev, st->power_state.report_id, st->power_state.index, &state_val); + if (state && poll_value) + msleep_interruptible(poll_value * 2); + return 0; } EXPORT_SYMBOL(hid_sensor_power_state); +int hid_sensor_power_state(struct hid_sensor_common *st, bool state) +{ +#ifdef CONFIG_PM + int ret; + + if (state) + ret = pm_runtime_get_sync(&st->pdev->dev); + else { + pm_runtime_mark_last_busy(&st->pdev->dev); + ret = pm_runtime_put_autosuspend(&st->pdev->dev); + } + if (ret < 0) { + if (state) + pm_runtime_put_noidle(&st->pdev->dev); + return ret; + } + + return 0; +#else + return _hid_sensor_power_state(st, state); +#endif +} + static int hid_sensor_data_rdy_trigger_set_state(struct iio_trigger *trig, bool state) { @@ -125,8 +155,21 @@ int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name, attrb->trigger = trig; indio_dev->trig = iio_trigger_get(trig); - return ret; + ret = pm_runtime_set_active(&indio_dev->dev); + if (ret) + goto error_unreg_trigger; + iio_device_set_drvdata(indio_dev, attrb); + pm_suspend_ignore_children(&attrb->pdev->dev, true); + pm_runtime_enable(&attrb->pdev->dev); + /* Default to 3 seconds, but can be changed from sysfs */ + pm_runtime_set_autosuspend_delay(&attrb->pdev->dev, + 3000); + pm_runtime_use_autosuspend(&attrb->pdev->dev); + + return ret; +error_unreg_trigger: + iio_trigger_unregister(trig); error_free_trig: iio_trigger_free(trig); error_ret: @@ -134,6 +177,34 @@ error_ret: } EXPORT_SYMBOL(hid_sensor_setup_trigger); +#ifdef CONFIG_PM +static int hid_sensor_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + + return _hid_sensor_power_state(attrb, false); +} + +static int hid_sensor_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + + return _hid_sensor_power_state(attrb, true); +} + +#endif + +const struct dev_pm_ops hid_sensor_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(hid_sensor_suspend, hid_sensor_resume) + SET_RUNTIME_PM_OPS(hid_sensor_suspend, + hid_sensor_resume, NULL) +}; +EXPORT_SYMBOL(hid_sensor_pm_ops); + MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>"); MODULE_DESCRIPTION("HID Sensor trigger processing"); MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.h b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h index 0f8e78c249d3..9f4713f42ecb 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.h +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h @@ -19,6 +19,11 @@ #ifndef _HID_SENSOR_TRIGGER_H #define _HID_SENSOR_TRIGGER_H +#include <linux/pm.h> +#include <linux/pm_runtime.h> + +extern const struct dev_pm_ops hid_sensor_pm_ops; + int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name, struct hid_sensor_common *attrb); void hid_sensor_remove_trigger(struct hid_sensor_common *attrb); diff --git a/drivers/iio/common/ssp_sensors/Kconfig b/drivers/iio/common/ssp_sensors/Kconfig new file mode 100644 index 000000000000..0ea4faf016d8 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/Kconfig @@ -0,0 +1,26 @@ +# +# SSP sensor drivers and commons configuration +# +menu "SSP Sensor Common" + +config IIO_SSP_SENSORS_COMMONS + tristate "Commons for all SSP Sensor IIO drivers" + depends on IIO_SSP_SENSORHUB + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build commons for SSP sensors. + To compile this as a module, choose M here: the module + will be called ssp_iio. + +config IIO_SSP_SENSORHUB + tristate "Samsung Sensorhub driver" + depends on SPI + select MFD_CORE + help + SSP driver for sensorhub. + If you say yes here you get ssp support for sensorhub. + To compile this driver as a module, choose M here: the + module will be called sensorhub. + +endmenu diff --git a/drivers/iio/common/ssp_sensors/Makefile b/drivers/iio/common/ssp_sensors/Makefile new file mode 100644 index 000000000000..1e0389eb0905 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for SSP sensor drivers and commons. +# + +sensorhub-objs := ssp_dev.o ssp_spi.o +obj-$(CONFIG_IIO_SSP_SENSORHUB) += sensorhub.o + +obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_iio.o diff --git a/drivers/iio/common/ssp_sensors/ssp.h b/drivers/iio/common/ssp_sensors/ssp.h new file mode 100644 index 000000000000..b910e91d7c0d --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp.h @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. 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 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. + * + */ + +#ifndef __SSP_SENSORHUB_H__ +#define __SSP_SENSORHUB_H__ + +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/iio/common/ssp_sensors.h> +#include <linux/iio/iio.h> +#include <linux/spi/spi.h> + +#define SSP_DEVICE_ID 0x55 + +#ifdef SSP_DBG +#define ssp_dbg(format, ...) pr_info("[SSP] "format, ##__VA_ARGS__) +#else +#define ssp_dbg(format, ...) +#endif + +#define SSP_SW_RESET_TIME 3000 +/* Sensor polling in ms */ +#define SSP_DEFAULT_POLLING_DELAY 200 +#define SSP_DEFAULT_RETRIES 3 +#define SSP_DATA_PACKET_SIZE 960 +#define SSP_HEADER_BUFFER_SIZE 4 + +enum { + SSP_KERNEL_BINARY = 0, + SSP_KERNEL_CRASHED_BINARY, +}; + +enum { + SSP_INITIALIZATION_STATE = 0, + SSP_NO_SENSOR_STATE, + SSP_ADD_SENSOR_STATE, + SSP_RUNNING_SENSOR_STATE, +}; + +/* Firmware download STATE */ +enum { + SSP_FW_DL_STATE_FAIL = -1, + SSP_FW_DL_STATE_NONE = 0, + SSP_FW_DL_STATE_NEED_TO_SCHEDULE, + SSP_FW_DL_STATE_SCHEDULED, + SSP_FW_DL_STATE_DOWNLOADING, + SSP_FW_DL_STATE_SYNC, + SSP_FW_DL_STATE_DONE, +}; + +#define SSP_INVALID_REVISION 99999 +#define SSP_INVALID_REVISION2 0xffffff + +/* AP -> SSP Instruction */ +#define SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD 0xa1 +#define SSP_MSG2SSP_INST_BYPASS_SENSOR_RM 0xa2 +#define SSP_MSG2SSP_INST_REMOVE_ALL 0xa3 +#define SSP_MSG2SSP_INST_CHANGE_DELAY 0xa4 +#define SSP_MSG2SSP_INST_LIBRARY_ADD 0xb1 +#define SSP_MSG2SSP_INST_LIBRARY_REMOVE 0xb2 +#define SSP_MSG2SSP_INST_LIB_NOTI 0xb4 +#define SSP_MSG2SSP_INST_LIB_DATA 0xc1 + +#define SSP_MSG2SSP_AP_MCU_SET_GYRO_CAL 0xcd +#define SSP_MSG2SSP_AP_MCU_SET_ACCEL_CAL 0xce +#define SSP_MSG2SSP_AP_STATUS_SHUTDOWN 0xd0 +#define SSP_MSG2SSP_AP_STATUS_WAKEUP 0xd1 +#define SSP_MSG2SSP_AP_STATUS_SLEEP 0xd2 +#define SSP_MSG2SSP_AP_STATUS_RESUME 0xd3 +#define SSP_MSG2SSP_AP_STATUS_SUSPEND 0xd4 +#define SSP_MSG2SSP_AP_STATUS_RESET 0xd5 +#define SSP_MSG2SSP_AP_STATUS_POW_CONNECTED 0xd6 +#define SSP_MSG2SSP_AP_STATUS_POW_DISCONNECTED 0xd7 +#define SSP_MSG2SSP_AP_TEMPHUMIDITY_CAL_DONE 0xda +#define SSP_MSG2SSP_AP_MCU_SET_DUMPMODE 0xdb +#define SSP_MSG2SSP_AP_MCU_DUMP_CHECK 0xdc +#define SSP_MSG2SSP_AP_MCU_BATCH_FLUSH 0xdd +#define SSP_MSG2SSP_AP_MCU_BATCH_COUNT 0xdf + +#define SSP_MSG2SSP_AP_WHOAMI 0x0f +#define SSP_MSG2SSP_AP_FIRMWARE_REV 0xf0 +#define SSP_MSG2SSP_AP_SENSOR_FORMATION 0xf1 +#define SSP_MSG2SSP_AP_SENSOR_PROXTHRESHOLD 0xf2 +#define SSP_MSG2SSP_AP_SENSOR_BARCODE_EMUL 0xf3 +#define SSP_MSG2SSP_AP_SENSOR_SCANNING 0xf4 +#define SSP_MSG2SSP_AP_SET_MAGNETIC_HWOFFSET 0xf5 +#define SSP_MSG2SSP_AP_GET_MAGNETIC_HWOFFSET 0xf6 +#define SSP_MSG2SSP_AP_SENSOR_GESTURE_CURRENT 0xf7 +#define SSP_MSG2SSP_AP_GET_THERM 0xf8 +#define SSP_MSG2SSP_AP_GET_BIG_DATA 0xf9 +#define SSP_MSG2SSP_AP_SET_BIG_DATA 0xfa +#define SSP_MSG2SSP_AP_START_BIG_DATA 0xfb +#define SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX 0xfd +#define SSP_MSG2SSP_AP_SENSOR_TILT 0xea +#define SSP_MSG2SSP_AP_MCU_SET_TIME 0xfe +#define SSP_MSG2SSP_AP_MCU_GET_TIME 0xff + +#define SSP_MSG2SSP_AP_FUSEROM 0x01 + +/* voice data */ +#define SSP_TYPE_WAKE_UP_VOICE_SERVICE 0x01 +#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_AM 0x01 +#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_GRAMMER 0x02 + +/* Factory Test */ +#define SSP_ACCELEROMETER_FACTORY 0x80 +#define SSP_GYROSCOPE_FACTORY 0x81 +#define SSP_GEOMAGNETIC_FACTORY 0x82 +#define SSP_PRESSURE_FACTORY 0x85 +#define SSP_GESTURE_FACTORY 0x86 +#define SSP_TEMPHUMIDITY_CRC_FACTORY 0x88 +#define SSP_GYROSCOPE_TEMP_FACTORY 0x8a +#define SSP_GYROSCOPE_DPS_FACTORY 0x8b +#define SSP_MCU_FACTORY 0x8c +#define SSP_MCU_SLEEP_FACTORY 0x8d + +/* SSP -> AP ACK about write CMD */ +#define SSP_MSG_ACK 0x80 /* ACK from SSP to AP */ +#define SSP_MSG_NAK 0x70 /* NAK from SSP to AP */ + +struct ssp_sensorhub_info { + char *fw_name; + char *fw_crashed_name; + unsigned int fw_rev; + const u8 * const mag_table; + const unsigned int mag_length; +}; + +/* ssp_msg options bit */ +#define SSP_RW 0 +#define SSP_INDEX 3 + +#define SSP_AP2HUB_READ 0 +#define SSP_AP2HUB_WRITE 1 +#define SSP_HUB2AP_WRITE 2 +#define SSP_AP2HUB_READY 3 +#define SSP_AP2HUB_RETURN 4 + +/** + * struct ssp_data - ssp platformdata structure + * @spi: spi device + * @sensorhub_info: info about sensorhub board specific features + * @wdt_timer: watchdog timer + * @work_wdt: watchdog work + * @work_firmware: firmware upgrade work queue + * @work_refresh: refresh work queue for reset request from MCU + * @shut_down: shut down flag + * @mcu_dump_mode: mcu dump mode for debug + * @time_syncing: time syncing indication flag + * @timestamp: previous time in ns calculated for time syncing + * @check_status: status table for each sensor + * @com_fail_cnt: communication fail count + * @reset_cnt: reset count + * @timeout_cnt: timeout count + * @available_sensors: available sensors seen by sensorhub (bit array) + * @cur_firm_rev: cached current firmware revision + * @last_resume_state: last AP resume/suspend state used to handle the PM + * state of ssp + * @last_ap_state: (obsolete) sleep notification for MCU + * @sensor_enable: sensor enable mask + * @delay_buf: data acquisition intervals table + * @batch_latency_buf: yet unknown but existing in communication protocol + * @batch_opt_buf: yet unknown but existing in communication protocol + * @accel_position: yet unknown but existing in communication protocol + * @mag_position: yet unknown but existing in communication protocol + * @fw_dl_state: firmware download state + * @comm_lock: lock protecting the handshake + * @pending_lock: lock protecting pending list and completion + * @mcu_reset_gpio: mcu reset line + * @ap_mcu_gpio: ap to mcu gpio line + * @mcu_ap_gpio: mcu to ap gpio line + * @pending_list: pending list for messages queued to be sent/read + * @sensor_devs: registered IIO devices table + * @enable_refcount: enable reference count for wdt (watchdog timer) + * @header_buffer: cache aligned buffer for packet header + */ +struct ssp_data { + struct spi_device *spi; + struct ssp_sensorhub_info *sensorhub_info; + struct timer_list wdt_timer; + struct work_struct work_wdt; + struct delayed_work work_refresh; + + bool shut_down; + bool mcu_dump_mode; + bool time_syncing; + int64_t timestamp; + + int check_status[SSP_SENSOR_MAX]; + + unsigned int com_fail_cnt; + unsigned int reset_cnt; + unsigned int timeout_cnt; + + unsigned int available_sensors; + unsigned int cur_firm_rev; + + char last_resume_state; + char last_ap_state; + + unsigned int sensor_enable; + u32 delay_buf[SSP_SENSOR_MAX]; + s32 batch_latency_buf[SSP_SENSOR_MAX]; + s8 batch_opt_buf[SSP_SENSOR_MAX]; + + int accel_position; + int mag_position; + int fw_dl_state; + + struct mutex comm_lock; + struct mutex pending_lock; + + int mcu_reset_gpio; + int ap_mcu_gpio; + int mcu_ap_gpio; + + struct list_head pending_list; + + struct iio_dev *sensor_devs[SSP_SENSOR_MAX]; + atomic_t enable_refcount; + + __le16 header_buffer[SSP_HEADER_BUFFER_SIZE / sizeof(__le16)] + ____cacheline_aligned; +}; + +void ssp_clean_pending_list(struct ssp_data *data); + +int ssp_command(struct ssp_data *data, char command, int arg); + +int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type, + u8 *send_buf, u8 length); + +int ssp_irq_msg(struct ssp_data *data); + +int ssp_get_chipid(struct ssp_data *data); + +int ssp_set_magnetic_matrix(struct ssp_data *data); + +unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data); + +unsigned int ssp_get_firmware_rev(struct ssp_data *data); + +int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay); + +#endif /* __SSP_SENSORHUB_H__ */ diff --git a/drivers/iio/common/ssp_sensors/ssp_dev.c b/drivers/iio/common/ssp_sensors/ssp_dev.c new file mode 100644 index 000000000000..52d70435f5a1 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_dev.c @@ -0,0 +1,712 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. 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 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/iio/iio.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/of_platform.h> +#include "ssp.h" + +#define SSP_WDT_TIME 10000 +#define SSP_LIMIT_RESET_CNT 20 +#define SSP_LIMIT_TIMEOUT_CNT 3 + +/* It is possible that it is max clk rate for version 1.0 of bootcode */ +#define SSP_BOOT_SPI_HZ 400000 + +/* + * These fields can look enigmatic but this structure is used mainly to flat + * some values and depends on command type. + */ +struct ssp_instruction { + __le32 a; + __le32 b; + u8 c; +} __attribute__((__packed__)); + +static const u8 ssp_magnitude_table[] = {110, 85, 171, 71, 203, 195, 0, 67, + 208, 56, 175, 244, 206, 213, 0, 92, 250, 0, 55, 48, 189, 252, 171, + 243, 13, 45, 250}; + +static const struct ssp_sensorhub_info ssp_rinato_info = { + .fw_name = "ssp_B2.fw", + .fw_crashed_name = "ssp_crashed.fw", + .fw_rev = 14052300, + .mag_table = ssp_magnitude_table, + .mag_length = ARRAY_SIZE(ssp_magnitude_table), +}; + +static const struct ssp_sensorhub_info ssp_thermostat_info = { + .fw_name = "thermostat_B2.fw", + .fw_crashed_name = "ssp_crashed.fw", + .fw_rev = 14080600, + .mag_table = ssp_magnitude_table, + .mag_length = ARRAY_SIZE(ssp_magnitude_table), +}; + +static const struct mfd_cell sensorhub_sensor_devs[] = { + { + .name = "ssp-accelerometer", + }, + { + .name = "ssp-gyroscope", + }, +}; + +static void ssp_toggle_mcu_reset_gpio(struct ssp_data *data) +{ + gpio_set_value(data->mcu_reset_gpio, 0); + usleep_range(1000, 1200); + gpio_set_value(data->mcu_reset_gpio, 1); + msleep(50); +} + +static void ssp_sync_available_sensors(struct ssp_data *data) +{ + int i, ret; + + for (i = 0; i < SSP_SENSOR_MAX; ++i) { + if (data->available_sensors & BIT(i)) { + ret = ssp_enable_sensor(data, i, data->delay_buf[i]); + if (ret < 0) { + dev_err(&data->spi->dev, + "Sync sensor nr: %d fail\n", i); + continue; + } + } + } + + ret = ssp_command(data, SSP_MSG2SSP_AP_MCU_SET_DUMPMODE, + data->mcu_dump_mode); + if (ret < 0) + dev_err(&data->spi->dev, + "SSP_MSG2SSP_AP_MCU_SET_DUMPMODE failed\n"); +} + +static void ssp_enable_mcu(struct ssp_data *data, bool enable) +{ + dev_info(&data->spi->dev, "current shutdown = %d, old = %d\n", enable, + data->shut_down); + + if (enable && data->shut_down) { + data->shut_down = false; + enable_irq(data->spi->irq); + enable_irq_wake(data->spi->irq); + } else if (!enable && !data->shut_down) { + data->shut_down = true; + disable_irq(data->spi->irq); + disable_irq_wake(data->spi->irq); + } else { + dev_warn(&data->spi->dev, "current shutdown = %d, old = %d\n", + enable, data->shut_down); + } +} + +/* + * This function is the first one which communicates with the mcu so it is + * possible that the first attempt will fail + */ +static int ssp_check_fwbl(struct ssp_data *data) +{ + int retries = 0; + + while (retries++ < 5) { + data->cur_firm_rev = ssp_get_firmware_rev(data); + if (data->cur_firm_rev == SSP_INVALID_REVISION || + data->cur_firm_rev == SSP_INVALID_REVISION2) { + dev_warn(&data->spi->dev, + "Invalid revision, trying %d time\n", retries); + } else { + break; + } + } + + if (data->cur_firm_rev == SSP_INVALID_REVISION || + data->cur_firm_rev == SSP_INVALID_REVISION2) { + dev_err(&data->spi->dev, "SSP_INVALID_REVISION\n"); + return SSP_FW_DL_STATE_NEED_TO_SCHEDULE; + } + + dev_info(&data->spi->dev, + "MCU Firm Rev : Old = %8u, New = %8u\n", + data->cur_firm_rev, + data->sensorhub_info->fw_rev); + + if (data->cur_firm_rev != data->sensorhub_info->fw_rev) + return SSP_FW_DL_STATE_NEED_TO_SCHEDULE; + + return SSP_FW_DL_STATE_NONE; +} + +static void ssp_reset_mcu(struct ssp_data *data) +{ + ssp_enable_mcu(data, false); + ssp_clean_pending_list(data); + ssp_toggle_mcu_reset_gpio(data); + ssp_enable_mcu(data, true); +} + +static void ssp_wdt_work_func(struct work_struct *work) +{ + struct ssp_data *data = container_of(work, struct ssp_data, work_wdt); + + dev_err(&data->spi->dev, "%s - Sensor state: 0x%x, RC: %u, CC: %u\n", + __func__, data->available_sensors, data->reset_cnt, + data->com_fail_cnt); + + ssp_reset_mcu(data); + data->com_fail_cnt = 0; + data->timeout_cnt = 0; +} + +static void ssp_wdt_timer_func(unsigned long ptr) +{ + struct ssp_data *data = (struct ssp_data *)ptr; + + switch (data->fw_dl_state) { + case SSP_FW_DL_STATE_FAIL: + case SSP_FW_DL_STATE_DOWNLOADING: + case SSP_FW_DL_STATE_SYNC: + goto _mod; + } + + if (data->timeout_cnt > SSP_LIMIT_TIMEOUT_CNT || + data->com_fail_cnt > SSP_LIMIT_RESET_CNT) + queue_work(system_power_efficient_wq, &data->work_wdt); +_mod: + mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME)); +} + +static void ssp_enable_wdt_timer(struct ssp_data *data) +{ + mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME)); +} + +static void ssp_disable_wdt_timer(struct ssp_data *data) +{ + del_timer_sync(&data->wdt_timer); + cancel_work_sync(&data->work_wdt); +} + +/** + * ssp_get_sensor_delay() - gets sensor data acquisition period + * @data: sensorhub structure + * @type: SSP sensor type + * + * Returns acquisition period in ms + */ +u32 ssp_get_sensor_delay(struct ssp_data *data, enum ssp_sensor_type type) +{ + return data->delay_buf[type]; +} +EXPORT_SYMBOL(ssp_get_sensor_delay); + +/** + * ssp_enable_sensor() - enables data acquisition for sensor + * @data: sensorhub structure + * @type: SSP sensor type + * @delay: delay in ms + * + * Returns 0 or negative value in case of error + */ +int ssp_enable_sensor(struct ssp_data *data, enum ssp_sensor_type type, + u32 delay) +{ + int ret; + struct ssp_instruction to_send; + + to_send.a = cpu_to_le32(delay); + to_send.b = cpu_to_le32(data->batch_latency_buf[type]); + to_send.c = data->batch_opt_buf[type]; + + switch (data->check_status[type]) { + case SSP_INITIALIZATION_STATE: + /* do calibration step, now just enable */ + case SSP_ADD_SENSOR_STATE: + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD, + type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, "Enabling sensor failed\n"); + data->check_status[type] = SSP_NO_SENSOR_STATE; + goto derror; + } + + data->sensor_enable |= BIT(type); + data->check_status[type] = SSP_RUNNING_SENSOR_STATE; + break; + case SSP_RUNNING_SENSOR_STATE: + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_CHANGE_DELAY, type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, + "Changing sensor delay failed\n"); + goto derror; + } + break; + default: + data->check_status[type] = SSP_ADD_SENSOR_STATE; + break; + } + + data->delay_buf[type] = delay; + + if (atomic_inc_return(&data->enable_refcount) == 1) + ssp_enable_wdt_timer(data); + + return 0; + +derror: + return ret; +} +EXPORT_SYMBOL(ssp_enable_sensor); + +/** + * ssp_change_delay() - changes data acquisition for sensor + * @data: sensorhub structure + * @type: SSP sensor type + * @delay: delay in ms + * + * Returns 0 or negative value in case of error + */ +int ssp_change_delay(struct ssp_data *data, enum ssp_sensor_type type, + u32 delay) +{ + int ret; + struct ssp_instruction to_send; + + to_send.a = cpu_to_le32(delay); + to_send.b = cpu_to_le32(data->batch_latency_buf[type]); + to_send.c = data->batch_opt_buf[type]; + + ret = ssp_send_instruction(data, SSP_MSG2SSP_INST_CHANGE_DELAY, type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, "Changing sensor delay failed\n"); + return ret; + } + + data->delay_buf[type] = delay; + + return 0; +} +EXPORT_SYMBOL(ssp_change_delay); + +/** + * ssp_disable_sensor() - disables sensor + * + * @data: sensorhub structure + * @type: SSP sensor type + * + * Returns 0 or negative value in case of error + */ +int ssp_disable_sensor(struct ssp_data *data, enum ssp_sensor_type type) +{ + int ret; + __le32 command; + + if (data->sensor_enable & BIT(type)) { + command = cpu_to_le32(data->delay_buf[type]); + + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_BYPASS_SENSOR_RM, + type, (u8 *)&command, + sizeof(command)); + if (ret < 0) { + dev_err(&data->spi->dev, "Remove sensor fail\n"); + return ret; + } + + data->sensor_enable &= ~BIT(type); + } + + data->check_status[type] = SSP_ADD_SENSOR_STATE; + + if (atomic_dec_and_test(&data->enable_refcount)) + ssp_disable_wdt_timer(data); + + return 0; +} +EXPORT_SYMBOL(ssp_disable_sensor); + +static irqreturn_t ssp_irq_thread_fn(int irq, void *dev_id) +{ + struct ssp_data *data = dev_id; + + /* + * This wrapper is done to preserve error path for ssp_irq_msg, also + * it is defined in different file. + */ + ssp_irq_msg(data); + + return IRQ_HANDLED; +} + +static int ssp_initialize_mcu(struct ssp_data *data) +{ + int ret; + + ssp_clean_pending_list(data); + + ret = ssp_get_chipid(data); + if (ret != SSP_DEVICE_ID) { + dev_err(&data->spi->dev, "%s - MCU %s ret = %d\n", __func__, + ret < 0 ? "is not working" : "identification failed", + ret); + return ret < 0 ? ret : -ENODEV; + } + + dev_info(&data->spi->dev, "MCU device ID = %d\n", ret); + + /* + * needs clarification, for now do not want to export all transfer + * methods to sensors' drivers + */ + ret = ssp_set_magnetic_matrix(data); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s - ssp_set_magnetic_matrix failed\n", __func__); + return ret; + } + + data->available_sensors = ssp_get_sensor_scanning_info(data); + if (data->available_sensors == 0) { + dev_err(&data->spi->dev, + "%s - ssp_get_sensor_scanning_info failed\n", __func__); + return -EIO; + } + + data->cur_firm_rev = ssp_get_firmware_rev(data); + dev_info(&data->spi->dev, "MCU Firm Rev : New = %8u\n", + data->cur_firm_rev); + + return ssp_command(data, SSP_MSG2SSP_AP_MCU_DUMP_CHECK, 0); +} + +/* + * sensorhub can request its reinitialization as some brutal and rare error + * handling. It can be requested from the MCU. + */ +static void ssp_refresh_task(struct work_struct *work) +{ + struct ssp_data *data = container_of((struct delayed_work *)work, + struct ssp_data, work_refresh); + + dev_info(&data->spi->dev, "refreshing\n"); + + data->reset_cnt++; + + if (ssp_initialize_mcu(data) >= 0) { + ssp_sync_available_sensors(data); + if (data->last_ap_state != 0) + ssp_command(data, data->last_ap_state, 0); + + if (data->last_resume_state != 0) + ssp_command(data, data->last_resume_state, 0); + + data->timeout_cnt = 0; + data->com_fail_cnt = 0; + } +} + +int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay) +{ + cancel_delayed_work_sync(&data->work_refresh); + + return queue_delayed_work(system_power_efficient_wq, + &data->work_refresh, + msecs_to_jiffies(delay)); +} + +#ifdef CONFIG_OF +static struct of_device_id ssp_of_match[] = { + { + .compatible = "samsung,sensorhub-rinato", + .data = &ssp_rinato_info, + }, { + .compatible = "samsung,sensorhub-thermostat", + .data = &ssp_thermostat_info, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ssp_of_match); + +static struct ssp_data *ssp_parse_dt(struct device *dev) +{ + int ret; + struct ssp_data *data; + struct device_node *node = dev->of_node; + const struct of_device_id *match; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->mcu_ap_gpio = of_get_named_gpio(node, "mcu-ap-gpios", 0); + if (data->mcu_ap_gpio < 0) + goto err_free_pd; + + data->ap_mcu_gpio = of_get_named_gpio(node, "ap-mcu-gpios", 0); + if (data->ap_mcu_gpio < 0) + goto err_free_pd; + + data->mcu_reset_gpio = of_get_named_gpio(node, "mcu-reset-gpios", 0); + if (data->mcu_reset_gpio < 0) + goto err_free_pd; + + ret = devm_gpio_request_one(dev, data->ap_mcu_gpio, GPIOF_OUT_INIT_HIGH, + "ap-mcu-gpios"); + if (ret) + goto err_free_pd; + + ret = devm_gpio_request_one(dev, data->mcu_reset_gpio, + GPIOF_OUT_INIT_HIGH, "mcu-reset-gpios"); + if (ret) + goto err_ap_mcu; + + match = of_match_node(ssp_of_match, node); + if (!match) + goto err_mcu_reset_gpio; + + data->sensorhub_info = (struct ssp_sensorhub_info *)match->data; + + dev_set_drvdata(dev, data); + + return data; + +err_mcu_reset_gpio: + devm_gpio_free(dev, data->mcu_reset_gpio); +err_ap_mcu: + devm_gpio_free(dev, data->ap_mcu_gpio); +err_free_pd: + devm_kfree(dev, data); + return NULL; +} +#else +static struct ssp_data *ssp_parse_dt(struct device *pdev) +{ + return NULL; +} +#endif + +/** + * ssp_register_consumer() - registers iio consumer in ssp framework + * + * @indio_dev: consumer iio device + * @type: ssp sensor type + */ +void ssp_register_consumer(struct iio_dev *indio_dev, enum ssp_sensor_type type) +{ + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + data->sensor_devs[type] = indio_dev; +} +EXPORT_SYMBOL(ssp_register_consumer); + +static int ssp_probe(struct spi_device *spi) +{ + int ret, i; + struct ssp_data *data; + + data = ssp_parse_dt(&spi->dev); + if (!data) { + dev_err(&spi->dev, "Failed to find platform data\n"); + return -ENODEV; + } + + ret = mfd_add_devices(&spi->dev, -1, sensorhub_sensor_devs, + ARRAY_SIZE(sensorhub_sensor_devs), NULL, 0, NULL); + if (ret < 0) { + dev_err(&spi->dev, "mfd add devices fail\n"); + return ret; + } + + spi->mode = SPI_MODE_1; + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, "Failed to setup spi\n"); + return ret; + } + + data->fw_dl_state = SSP_FW_DL_STATE_NONE; + data->spi = spi; + spi_set_drvdata(spi, data); + + mutex_init(&data->comm_lock); + + for (i = 0; i < SSP_SENSOR_MAX; ++i) { + data->delay_buf[i] = SSP_DEFAULT_POLLING_DELAY; + data->batch_latency_buf[i] = 0; + data->batch_opt_buf[i] = 0; + data->check_status[i] = SSP_INITIALIZATION_STATE; + } + + data->delay_buf[SSP_BIO_HRM_LIB] = 100; + + data->time_syncing = true; + + mutex_init(&data->pending_lock); + INIT_LIST_HEAD(&data->pending_list); + + atomic_set(&data->enable_refcount, 0); + + INIT_WORK(&data->work_wdt, ssp_wdt_work_func); + INIT_DELAYED_WORK(&data->work_refresh, ssp_refresh_task); + + setup_timer(&data->wdt_timer, ssp_wdt_timer_func, (unsigned long)data); + + ret = request_threaded_irq(data->spi->irq, NULL, + ssp_irq_thread_fn, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "SSP_Int", data); + if (ret < 0) { + dev_err(&spi->dev, "Irq request fail\n"); + goto err_setup_irq; + } + + /* Let's start with enabled one so irq balance could be ok */ + data->shut_down = false; + + /* just to avoid unbalanced irq set wake up */ + enable_irq_wake(data->spi->irq); + + data->fw_dl_state = ssp_check_fwbl(data); + if (data->fw_dl_state == SSP_FW_DL_STATE_NONE) { + ret = ssp_initialize_mcu(data); + if (ret < 0) { + dev_err(&spi->dev, "Initialize_mcu failed\n"); + goto err_read_reg; + } + } else { + dev_err(&spi->dev, "Firmware version not supported\n"); + ret = -EPERM; + goto err_read_reg; + } + + return 0; + +err_read_reg: + free_irq(data->spi->irq, data); +err_setup_irq: + mutex_destroy(&data->pending_lock); + mutex_destroy(&data->comm_lock); + + dev_err(&spi->dev, "Probe failed!\n"); + + return ret; +} + +static int ssp_remove(struct spi_device *spi) +{ + struct ssp_data *data = spi_get_drvdata(spi); + + if (ssp_command(data, SSP_MSG2SSP_AP_STATUS_SHUTDOWN, 0) < 0) + dev_err(&data->spi->dev, + "SSP_MSG2SSP_AP_STATUS_SHUTDOWN failed\n"); + + ssp_enable_mcu(data, false); + ssp_disable_wdt_timer(data); + + ssp_clean_pending_list(data); + + free_irq(data->spi->irq, data); + + del_timer_sync(&data->wdt_timer); + cancel_work_sync(&data->work_wdt); + + mutex_destroy(&data->comm_lock); + mutex_destroy(&data->pending_lock); + + mfd_remove_devices(&spi->dev); + + return 0; +} + +static int ssp_suspend(struct device *dev) +{ + int ret; + struct ssp_data *data = spi_get_drvdata(to_spi_device(dev)); + + data->last_resume_state = SSP_MSG2SSP_AP_STATUS_SUSPEND; + + if (atomic_read(&data->enable_refcount) > 0) + ssp_disable_wdt_timer(data); + + ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_SUSPEND, 0); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s SSP_MSG2SSP_AP_STATUS_SUSPEND failed\n", __func__); + + ssp_enable_wdt_timer(data); + return ret; + } + + data->time_syncing = false; + disable_irq(data->spi->irq); + + return 0; +} + +static int ssp_resume(struct device *dev) +{ + int ret; + struct ssp_data *data = spi_get_drvdata(to_spi_device(dev)); + + enable_irq(data->spi->irq); + + if (atomic_read(&data->enable_refcount) > 0) + ssp_enable_wdt_timer(data); + + ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_RESUME, 0); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s SSP_MSG2SSP_AP_STATUS_RESUME failed\n", __func__); + ssp_disable_wdt_timer(data); + return ret; + } + + /* timesyncing is set by MCU */ + data->last_resume_state = SSP_MSG2SSP_AP_STATUS_RESUME; + + return 0; +} + +static const struct dev_pm_ops ssp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ssp_suspend, ssp_resume) +}; + +static struct spi_driver ssp_driver = { + .probe = ssp_probe, + .remove = ssp_remove, + .driver = { + .pm = &ssp_pm_ops, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ssp_of_match), + .name = "sensorhub" + }, +}; + +module_spi_driver(ssp_driver); + +MODULE_DESCRIPTION("ssp sensorhub driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/ssp_sensors/ssp_iio.c b/drivers/iio/common/ssp_sensors/ssp_iio.c new file mode 100644 index 000000000000..a3ae165f8d9f --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_iio.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. 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 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/iio/common/ssp_sensors.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/module.h> +#include <linux/slab.h> +#include "ssp_iio_sensor.h" + +/** + * ssp_common_buffer_postenable() - generic postenable callback for ssp buffer + * + * @indio_dev: iio device + * + * Returns 0 or negative value in case of error + */ +int ssp_common_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ssp_sensor_data *spd = iio_priv(indio_dev); + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + /* the allocation is made in post because scan size is known in this + * moment + * */ + spd->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL | GFP_DMA); + if (!spd->buffer) + return -ENOMEM; + + return ssp_enable_sensor(data, spd->type, + ssp_get_sensor_delay(data, spd->type)); +} +EXPORT_SYMBOL(ssp_common_buffer_postenable); + +/** + * ssp_common_buffer_postdisable() - generic postdisable callback for ssp buffer + * + * @indio_dev: iio device + * + * Returns 0 or negative value in case of error + */ +int ssp_common_buffer_postdisable(struct iio_dev *indio_dev) +{ + int ret; + struct ssp_sensor_data *spd = iio_priv(indio_dev); + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + ret = ssp_disable_sensor(data, spd->type); + if (ret < 0) + return ret; + + kfree(spd->buffer); + + return ret; +} +EXPORT_SYMBOL(ssp_common_buffer_postdisable); + +/** + * ssp_common_process_data() - Common process data callback for ssp sensors + * + * @indio_dev: iio device + * @buf: source buffer + * @len: sensor data length + * @timestamp: system timestamp + * + * Returns 0 or negative value in case of error + */ +int ssp_common_process_data(struct iio_dev *indio_dev, void *buf, + unsigned int len, int64_t timestamp) +{ + __le32 time; + int64_t calculated_time; + struct ssp_sensor_data *spd = iio_priv(indio_dev); + + if (indio_dev->scan_bytes == 0) + return 0; + + /* + * it always sends full set of samples, remember about available masks + */ + memcpy(spd->buffer, buf, len); + + if (indio_dev->scan_timestamp) { + memcpy(&time, &((char *)buf)[len], SSP_TIME_SIZE); + calculated_time = + timestamp + (int64_t)le32_to_cpu(time) * 1000000; + } + + return iio_push_to_buffers_with_timestamp(indio_dev, spd->buffer, + calculated_time); +} +EXPORT_SYMBOL(ssp_common_process_data); + +MODULE_AUTHOR("Karol Wrona <k.wrona@samsung.com>"); +MODULE_DESCRIPTION("Samsung sensorhub commons"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h b/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h new file mode 100644 index 000000000000..541c6590d69c --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h @@ -0,0 +1,71 @@ +#ifndef __SSP_IIO_SENSOR_H__ +#define __SSP_IIO_SENSOR_H__ + +#define SSP_CHANNEL_AG(_type, _mod, _index) \ +{ \ + .type = _type,\ + .modified = 1,\ + .channel2 = _mod,\ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .scan_index = _index,\ + .scan_type = {\ + .sign = 's',\ + .realbits = 16,\ + .storagebits = 16,\ + .shift = 0,\ + .endianness = IIO_LE,\ + },\ +} + +/* It is defined here as it is a mixed timestamp */ +#define SSP_CHAN_TIMESTAMP(_si) { \ + .type = IIO_TIMESTAMP, \ + .channel = -1, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 64, \ + .storagebits = 64, \ + }, \ +} + +#define SSP_MS_PER_S 1000 +#define SSP_INVERTED_SCALING_FACTOR 1000000U + +#define SSP_FACTOR_WITH_MS \ + (SSP_INVERTED_SCALING_FACTOR * SSP_MS_PER_S) + +int ssp_common_buffer_postenable(struct iio_dev *indio_dev); + +int ssp_common_buffer_postdisable(struct iio_dev *indio_dev); + +int ssp_common_process_data(struct iio_dev *indio_dev, void *buf, + unsigned int len, int64_t timestamp); + +/* Converts time in ms to frequency */ +static inline void ssp_convert_to_freq(u32 time, int *integer_part, + int *fractional) +{ + if (time == 0) { + *fractional = 0; + *integer_part = 0; + return; + } + + *integer_part = SSP_FACTOR_WITH_MS / time; + *fractional = *integer_part % SSP_INVERTED_SCALING_FACTOR; + *integer_part = *integer_part / SSP_INVERTED_SCALING_FACTOR; +} + +/* Converts frequency to time in ms */ +static inline int ssp_convert_to_time(int integer_part, int fractional) +{ + u64 value; + + value = (u64)integer_part * SSP_INVERTED_SCALING_FACTOR + fractional; + if (value == 0) + return 0; + + return div64_u64((u64)SSP_FACTOR_WITH_MS, value); +} +#endif /* __SSP_IIO_SENSOR_H__ */ diff --git a/drivers/iio/common/ssp_sensors/ssp_spi.c b/drivers/iio/common/ssp_sensors/ssp_spi.c new file mode 100644 index 000000000000..704284a475ae --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_spi.c @@ -0,0 +1,608 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. 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 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 "ssp.h" + +#define SSP_DEV (&data->spi->dev) +#define SSP_GET_MESSAGE_TYPE(data) (data & (3 << SSP_RW)) + +/* + * SSP -> AP Instruction + * They tell what packet type can be expected. In the future there will + * be less of them. BYPASS means common sensor packets with accel, gyro, + * hrm etc. data. LIBRARY and META are mock-up's for now. + */ +#define SSP_MSG2AP_INST_BYPASS_DATA 0x37 +#define SSP_MSG2AP_INST_LIBRARY_DATA 0x01 +#define SSP_MSG2AP_INST_DEBUG_DATA 0x03 +#define SSP_MSG2AP_INST_BIG_DATA 0x04 +#define SSP_MSG2AP_INST_META_DATA 0x05 +#define SSP_MSG2AP_INST_TIME_SYNC 0x06 +#define SSP_MSG2AP_INST_RESET 0x07 + +#define SSP_UNIMPLEMENTED -1 + +struct ssp_msg_header { + u8 cmd; + __le16 length; + __le16 options; + __le32 data; +} __attribute__((__packed__)); + +struct ssp_msg { + u16 length; + u16 options; + struct list_head list; + struct completion *done; + struct ssp_msg_header *h; + char *buffer; +}; + +static const int ssp_offset_map[SSP_SENSOR_MAX] = { + [SSP_ACCELEROMETER_SENSOR] = SSP_ACCELEROMETER_SIZE + + SSP_TIME_SIZE, + [SSP_GYROSCOPE_SENSOR] = SSP_GYROSCOPE_SIZE + + SSP_TIME_SIZE, + [SSP_GEOMAGNETIC_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GEOMAGNETIC_RAW] = SSP_UNIMPLEMENTED, + [SSP_GEOMAGNETIC_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PRESSURE_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GESTURE_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PROXIMITY_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_TEMPERATURE_HUMIDITY_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_LIGHT_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PROXIMITY_RAW] = SSP_UNIMPLEMENTED, + [SSP_ORIENTATION_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_STEP_DETECTOR] = SSP_UNIMPLEMENTED, + [SSP_SIG_MOTION_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GYRO_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GAME_ROTATION_VECTOR] = SSP_UNIMPLEMENTED, + [SSP_ROTATION_VECTOR] = SSP_UNIMPLEMENTED, + [SSP_STEP_COUNTER] = SSP_UNIMPLEMENTED, + [SSP_BIO_HRM_RAW] = SSP_BIO_HRM_RAW_SIZE + + SSP_TIME_SIZE, + [SSP_BIO_HRM_RAW_FAC] = SSP_BIO_HRM_RAW_FAC_SIZE + + SSP_TIME_SIZE, + [SSP_BIO_HRM_LIB] = SSP_BIO_HRM_LIB_SIZE + + SSP_TIME_SIZE, +}; + +#define SSP_HEADER_SIZE (sizeof(struct ssp_msg_header)) +#define SSP_HEADER_SIZE_ALIGNED (ALIGN(SSP_HEADER_SIZE, 4)) + +static struct ssp_msg *ssp_create_msg(u8 cmd, u16 len, u16 opt, u32 data) +{ + struct ssp_msg_header h; + struct ssp_msg *msg; + + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) + return NULL; + + h.cmd = cmd; + h.length = cpu_to_le16(len); + h.options = cpu_to_le16(opt); + h.data = cpu_to_le32(data); + + msg->buffer = kzalloc(SSP_HEADER_SIZE_ALIGNED + len, + GFP_KERNEL | GFP_DMA); + if (!msg->buffer) { + kfree(msg); + return NULL; + } + + msg->length = len; + msg->options = opt; + + memcpy(msg->buffer, &h, SSP_HEADER_SIZE); + + return msg; +} + +/* + * It is a bit heavy to do it this way but often the function is used to compose + * the message from smaller chunks which are placed on the stack. Often the + * chunks are small so memcpy should be optimalized. + */ +static inline void ssp_fill_buffer(struct ssp_msg *m, unsigned int offset, + const void *src, unsigned int len) +{ + memcpy(&m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], src, len); +} + +static inline void ssp_get_buffer(struct ssp_msg *m, unsigned int offset, + void *dest, unsigned int len) +{ + memcpy(dest, &m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], len); +} + +#define SSP_GET_BUFFER_AT_INDEX(m, index) \ + (m->buffer[SSP_HEADER_SIZE_ALIGNED + index]) +#define SSP_SET_BUFFER_AT_INDEX(m, index, val) \ + (m->buffer[SSP_HEADER_SIZE_ALIGNED + index] = val) + +static void ssp_clean_msg(struct ssp_msg *m) +{ + kfree(m->buffer); + kfree(m); +} + +static int ssp_print_mcu_debug(char *data_frame, int *data_index, + int received_len) +{ + int length = data_frame[(*data_index)++]; + + if (length > received_len - *data_index || length <= 0) { + ssp_dbg("[SSP]: MSG From MCU-invalid debug length(%d/%d)\n", + length, received_len); + return length ? length : -EPROTO; + } + + ssp_dbg("[SSP]: MSG From MCU - %s\n", &data_frame[*data_index]); + + *data_index += length; + + return 0; +} + +/* + * It was designed that way - additional lines to some kind of handshake, + * please do not ask why - only the firmware guy can know it. + */ +static int ssp_check_lines(struct ssp_data *data, bool state) +{ + int delay_cnt = 0; + + gpio_set_value_cansleep(data->ap_mcu_gpio, state); + + while (gpio_get_value_cansleep(data->mcu_ap_gpio) != state) { + usleep_range(3000, 3500); + + if (data->shut_down || delay_cnt++ > 500) { + dev_err(SSP_DEV, "%s:timeout, hw ack wait fail %d\n", + __func__, state); + + if (!state) + gpio_set_value_cansleep(data->ap_mcu_gpio, 1); + + return -ETIMEDOUT; + } + } + + return 0; +} + +static int ssp_do_transfer(struct ssp_data *data, struct ssp_msg *msg, + struct completion *done, int timeout) +{ + int status; + /* + * check if this is a short one way message or the whole transfer has + * second part after an interrupt + */ + const bool use_no_irq = msg->length == 0; + + if (data->shut_down) + return -EPERM; + + msg->done = done; + + mutex_lock(&data->comm_lock); + + status = ssp_check_lines(data, false); + if (status < 0) + goto _error_locked; + + status = spi_write(data->spi, msg->buffer, SSP_HEADER_SIZE); + if (status < 0) { + gpio_set_value_cansleep(data->ap_mcu_gpio, 1); + dev_err(SSP_DEV, "%s spi_write fail\n", __func__); + goto _error_locked; + } + + if (!use_no_irq) { + mutex_lock(&data->pending_lock); + list_add_tail(&msg->list, &data->pending_list); + mutex_unlock(&data->pending_lock); + } + + status = ssp_check_lines(data, true); + if (status < 0) { + if (!use_no_irq) { + mutex_lock(&data->pending_lock); + list_del(&msg->list); + mutex_unlock(&data->pending_lock); + } + goto _error_locked; + } + + mutex_unlock(&data->comm_lock); + + if (!use_no_irq && done) + if (wait_for_completion_timeout(done, + msecs_to_jiffies(timeout)) == + 0) { + mutex_lock(&data->pending_lock); + list_del(&msg->list); + mutex_unlock(&data->pending_lock); + + data->timeout_cnt++; + return -ETIMEDOUT; + } + + return 0; + +_error_locked: + mutex_unlock(&data->comm_lock); + data->timeout_cnt++; + return status; +} + +static inline int ssp_spi_sync_command(struct ssp_data *data, + struct ssp_msg *msg) +{ + return ssp_do_transfer(data, msg, NULL, 0); +} + +static int ssp_spi_sync(struct ssp_data *data, struct ssp_msg *msg, + int timeout) +{ + DECLARE_COMPLETION_ONSTACK(done); + + if (WARN_ON(!msg->length)) + return -EPERM; + + return ssp_do_transfer(data, msg, &done, timeout); +} + +static int ssp_handle_big_data(struct ssp_data *data, char *dataframe, int *idx) +{ + /* mock-up, it will be changed with adding another sensor types */ + *idx += 8; + return 0; +} + +static int ssp_parse_dataframe(struct ssp_data *data, char *dataframe, int len) +{ + int idx, sd; + struct timespec ts; + struct ssp_sensor_data *spd; + struct iio_dev **indio_devs = data->sensor_devs; + + getnstimeofday(&ts); + + for (idx = 0; idx < len;) { + switch (dataframe[idx++]) { + case SSP_MSG2AP_INST_BYPASS_DATA: + sd = dataframe[idx++]; + if (sd < 0 || sd >= SSP_SENSOR_MAX) { + dev_err(SSP_DEV, + "Mcu data frame1 error %d\n", sd); + return -EPROTO; + } + + if (indio_devs[sd]) { + spd = iio_priv(indio_devs[sd]); + if (spd->process_data) + spd->process_data(indio_devs[sd], + &dataframe[idx], + data->timestamp); + } else { + dev_err(SSP_DEV, "no client for frame\n"); + } + + idx += ssp_offset_map[sd]; + break; + case SSP_MSG2AP_INST_DEBUG_DATA: + sd = ssp_print_mcu_debug(dataframe, &idx, len); + if (sd) { + dev_err(SSP_DEV, + "Mcu data frame3 error %d\n", sd); + return sd; + } + break; + case SSP_MSG2AP_INST_LIBRARY_DATA: + idx += len; + break; + case SSP_MSG2AP_INST_BIG_DATA: + ssp_handle_big_data(data, dataframe, &idx); + break; + case SSP_MSG2AP_INST_TIME_SYNC: + data->time_syncing = true; + break; + case SSP_MSG2AP_INST_RESET: + ssp_queue_ssp_refresh_task(data, 0); + break; + } + } + + if (data->time_syncing) + data->timestamp = ts.tv_sec * 1000000000ULL + ts.tv_nsec; + + return 0; +} + +/* threaded irq */ +int ssp_irq_msg(struct ssp_data *data) +{ + bool found = false; + char *buffer; + u8 msg_type; + int ret; + u16 length, msg_options; + struct ssp_msg *msg, *n; + + ret = spi_read(data->spi, data->header_buffer, SSP_HEADER_BUFFER_SIZE); + if (ret < 0) { + dev_err(SSP_DEV, "header read fail\n"); + return ret; + } + + length = le16_to_cpu(data->header_buffer[1]); + msg_options = le16_to_cpu(data->header_buffer[0]); + + if (length == 0) { + dev_err(SSP_DEV, "length received from mcu is 0\n"); + return -EINVAL; + } + + msg_type = SSP_GET_MESSAGE_TYPE(msg_options); + + switch (msg_type) { + case SSP_AP2HUB_READ: + case SSP_AP2HUB_WRITE: + /* + * this is a small list, a few elements - the packets can be + * received with no order + */ + mutex_lock(&data->pending_lock); + list_for_each_entry_safe(msg, n, &data->pending_list, list) { + if (msg->options == msg_options) { + list_del(&msg->list); + found = true; + break; + } + } + + if (!found) { + /* + * here can be implemented dead messages handling + * but the slave should not send such ones - it is to + * check but let's handle this + */ + buffer = kmalloc(length, GFP_KERNEL | GFP_DMA); + if (!buffer) { + ret = -ENOMEM; + goto _unlock; + } + + /* got dead packet so it is always an error */ + ret = spi_read(data->spi, buffer, length); + if (ret >= 0) + ret = -EPROTO; + + kfree(buffer); + + dev_err(SSP_DEV, "No match error %x\n", + msg_options); + + goto _unlock; + } + + if (msg_type == SSP_AP2HUB_READ) + ret = spi_read(data->spi, + &msg->buffer[SSP_HEADER_SIZE_ALIGNED], + msg->length); + + if (msg_type == SSP_AP2HUB_WRITE) { + ret = spi_write(data->spi, + &msg->buffer[SSP_HEADER_SIZE_ALIGNED], + msg->length); + if (msg_options & SSP_AP2HUB_RETURN) { + msg->options = + SSP_AP2HUB_READ | SSP_AP2HUB_RETURN; + msg->length = 1; + + list_add_tail(&msg->list, &data->pending_list); + goto _unlock; + } + } + + if (msg->done) + if (!completion_done(msg->done)) + complete(msg->done); +_unlock: + mutex_unlock(&data->pending_lock); + break; + case SSP_HUB2AP_WRITE: + buffer = kzalloc(length, GFP_KERNEL | GFP_DMA); + if (!buffer) + return -ENOMEM; + + ret = spi_read(data->spi, buffer, length); + if (ret < 0) { + dev_err(SSP_DEV, "spi read fail\n"); + kfree(buffer); + break; + } + + ret = ssp_parse_dataframe(data, buffer, length); + + kfree(buffer); + break; + + default: + dev_err(SSP_DEV, "unknown msg type\n"); + return -EPROTO; + } + + return ret; +} + +void ssp_clean_pending_list(struct ssp_data *data) +{ + struct ssp_msg *msg, *n; + + mutex_lock(&data->pending_lock); + list_for_each_entry_safe(msg, n, &data->pending_list, list) { + list_del(&msg->list); + + if (msg->done) + if (!completion_done(msg->done)) + complete(msg->done); + } + mutex_unlock(&data->pending_lock); +} + +int ssp_command(struct ssp_data *data, char command, int arg) +{ + int ret; + struct ssp_msg *msg; + + msg = ssp_create_msg(command, 0, SSP_AP2HUB_WRITE, arg); + if (!msg) + return -ENOMEM; + + ssp_dbg("%s - command 0x%x %d\n", __func__, command, arg); + + ret = ssp_spi_sync_command(data, msg); + ssp_clean_msg(msg); + + return ret; +} + +int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type, + u8 *send_buf, u8 length) +{ + int ret; + struct ssp_msg *msg; + + if (data->fw_dl_state == SSP_FW_DL_STATE_DOWNLOADING) { + dev_err(SSP_DEV, "%s - Skip Inst! DL state = %d\n", + __func__, data->fw_dl_state); + return -EBUSY; + } else if (!(data->available_sensors & BIT(sensor_type)) && + (inst <= SSP_MSG2SSP_INST_CHANGE_DELAY)) { + dev_err(SSP_DEV, "%s - Bypass Inst Skip! - %u\n", + __func__, sensor_type); + return -EIO; /* just fail */ + } + + msg = ssp_create_msg(inst, length + 2, SSP_AP2HUB_WRITE, 0); + if (!msg) + return -ENOMEM; + + ssp_fill_buffer(msg, 0, &sensor_type, 1); + ssp_fill_buffer(msg, 1, send_buf, length); + + ssp_dbg("%s - Inst = 0x%x, Sensor Type = 0x%x, data = %u\n", + __func__, inst, sensor_type, send_buf[1]); + + ret = ssp_spi_sync(data, msg, 1000); + ssp_clean_msg(msg); + + return ret; +} + +int ssp_get_chipid(struct ssp_data *data) +{ + int ret; + char buffer; + struct ssp_msg *msg; + + msg = ssp_create_msg(SSP_MSG2SSP_AP_WHOAMI, 1, SSP_AP2HUB_READ, 0); + if (!msg) + return -ENOMEM; + + ret = ssp_spi_sync(data, msg, 1000); + + buffer = SSP_GET_BUFFER_AT_INDEX(msg, 0); + + ssp_clean_msg(msg); + + return ret < 0 ? ret : buffer; +} + +int ssp_set_magnetic_matrix(struct ssp_data *data) +{ + int ret; + struct ssp_msg *msg; + + msg = ssp_create_msg(SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX, + data->sensorhub_info->mag_length, SSP_AP2HUB_WRITE, + 0); + if (!msg) + return -ENOMEM; + + ssp_fill_buffer(msg, 0, data->sensorhub_info->mag_table, + data->sensorhub_info->mag_length); + + ret = ssp_spi_sync(data, msg, 1000); + ssp_clean_msg(msg); + + return ret; +} + +unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data) +{ + int ret; + __le32 result; + u32 cpu_result = 0; + + struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_SENSOR_SCANNING, 4, + SSP_AP2HUB_READ, 0); + if (!msg) + return 0; + + ret = ssp_spi_sync(data, msg, 1000); + if (ret < 0) { + dev_err(SSP_DEV, "%s - spi read fail %d\n", __func__, ret); + goto _exit; + } + + ssp_get_buffer(msg, 0, &result, 4); + cpu_result = le32_to_cpu(result); + + dev_info(SSP_DEV, "%s state: 0x%08x\n", __func__, cpu_result); + +_exit: + ssp_clean_msg(msg); + return cpu_result; +} + +unsigned int ssp_get_firmware_rev(struct ssp_data *data) +{ + int ret; + __le32 result; + + struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_FIRMWARE_REV, 4, + SSP_AP2HUB_READ, 0); + if (!msg) + return SSP_INVALID_REVISION; + + ret = ssp_spi_sync(data, msg, 1000); + if (ret < 0) { + dev_err(SSP_DEV, "%s - transfer fail %d\n", __func__, ret); + ret = SSP_INVALID_REVISION; + goto _exit; + } + + ssp_get_buffer(msg, 0, &result, 4); + ret = le32_to_cpu(result); + +_exit: + ssp_clean_msg(msg); + return ret; +} diff --git a/drivers/iio/gyro/Makefile b/drivers/iio/gyro/Makefile index 36a38776f739..f46341b39139 100644 --- a/drivers/iio/gyro/Makefile +++ b/drivers/iio/gyro/Makefile @@ -16,6 +16,8 @@ itg3200-y := itg3200_core.o itg3200-$(CONFIG_IIO_BUFFER) += itg3200_buffer.o obj-$(CONFIG_ITG3200) += itg3200.o +obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_gyro_sensor.o + obj-$(CONFIG_IIO_ST_GYRO_3AXIS) += st_gyro.o st_gyro-y := st_gyro_core.o st_gyro-$(CONFIG_IIO_BUFFER) += st_gyro_buffer.o diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c index a3ea1e8785d7..a3c3e19de527 100644 --- a/drivers/iio/gyro/hid-sensor-gyro-3d.c +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c @@ -111,19 +111,12 @@ static int gyro_3d_read_raw(struct iio_dev *indio_dev, int report_id = -1; u32 address; int ret_type; - s32 poll_value; *val = 0; *val2 = 0; switch (mask) { case 0: - poll_value = hid_sensor_read_poll_value( - &gyro_state->common_attributes); - if (poll_value < 0) - return -EINVAL; - hid_sensor_power_state(&gyro_state->common_attributes, true); - msleep_interruptible(poll_value * 2); report_id = gyro_state->gyro[chan->scan_index].report_id; address = gyro_3d_addresses[chan->scan_index]; if (report_id >= 0) @@ -416,6 +409,7 @@ static struct platform_driver hid_gyro_3d_platform_driver = { .id_table = hid_gyro_3d_ids, .driver = { .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, }, .probe = hid_gyro_3d_probe, .remove = hid_gyro_3d_remove, diff --git a/drivers/iio/gyro/ssp_gyro_sensor.c b/drivers/iio/gyro/ssp_gyro_sensor.c new file mode 100644 index 000000000000..0a8afdd21728 --- /dev/null +++ b/drivers/iio/gyro/ssp_gyro_sensor.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. 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 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/iio/common/ssp_sensors.h> +#include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include "../common/ssp_sensors/ssp_iio_sensor.h" + +#define SSP_CHANNEL_COUNT 3 + +#define SSP_GYROSCOPE_NAME "ssp-gyroscope" +static const char ssp_gyro_name[] = SSP_GYROSCOPE_NAME; + +enum ssp_gyro_3d_channel { + SSP_CHANNEL_SCAN_INDEX_X, + SSP_CHANNEL_SCAN_INDEX_Y, + SSP_CHANNEL_SCAN_INDEX_Z, + SSP_CHANNEL_SCAN_INDEX_TIME, +}; + +static int ssp_gyro_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + u32 t; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + t = ssp_get_sensor_delay(data, SSP_GYROSCOPE_SENSOR); + ssp_convert_to_freq(t, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + + return -EINVAL; +} + +static int ssp_gyro_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + int ret; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ssp_convert_to_time(val, val2); + ret = ssp_change_delay(data, SSP_GYROSCOPE_SENSOR, ret); + if (ret < 0) + dev_err(&indio_dev->dev, "gyro sensor enable fail\n"); + + return ret; + default: + break; + } + + return -EINVAL; +} + +static struct iio_info ssp_gyro_iio_info = { + .read_raw = &ssp_gyro_read_raw, + .write_raw = &ssp_gyro_write_raw, +}; + +static const unsigned long ssp_gyro_scan_mask[] = { 0x07, 0, }; + +static const struct iio_chan_spec ssp_gyro_channels[] = { + SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_X, SSP_CHANNEL_SCAN_INDEX_X), + SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_Y, SSP_CHANNEL_SCAN_INDEX_Y), + SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_Z, SSP_CHANNEL_SCAN_INDEX_Z), + SSP_CHAN_TIMESTAMP(SSP_CHANNEL_SCAN_INDEX_TIME), +}; + +static int ssp_process_gyro_data(struct iio_dev *indio_dev, void *buf, + int64_t timestamp) +{ + return ssp_common_process_data(indio_dev, buf, SSP_GYROSCOPE_SIZE, + timestamp); +} + +static const struct iio_buffer_setup_ops ssp_gyro_buffer_ops = { + .postenable = &ssp_common_buffer_postenable, + .postdisable = &ssp_common_buffer_postdisable, +}; + +static int ssp_gyro_probe(struct platform_device *pdev) +{ + int ret; + struct iio_dev *indio_dev; + struct ssp_sensor_data *spd; + struct iio_buffer *buffer; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*spd)); + if (!indio_dev) + return -ENOMEM; + + spd = iio_priv(indio_dev); + + spd->process_data = ssp_process_gyro_data; + spd->type = SSP_GYROSCOPE_SENSOR; + + indio_dev->name = ssp_gyro_name; + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &ssp_gyro_iio_info; + indio_dev->modes = INDIO_BUFFER_SOFTWARE; + indio_dev->channels = ssp_gyro_channels; + indio_dev->num_channels = ARRAY_SIZE(ssp_gyro_channels); + indio_dev->available_scan_masks = ssp_gyro_scan_mask; + + buffer = devm_iio_kfifo_allocate(&pdev->dev); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + indio_dev->setup_ops = &ssp_gyro_buffer_ops; + + platform_set_drvdata(pdev, indio_dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + return ret; + + /* ssp registering should be done after all iio setup */ + ssp_register_consumer(indio_dev, SSP_GYROSCOPE_SENSOR); + + 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); + +MODULE_AUTHOR("Karol Wrona <k.wrona@samsung.com>"); +MODULE_DESCRIPTION("Samsung sensorhub gyroscopes driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c index 926fccea8de0..844610c3a3a9 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c @@ -116,40 +116,35 @@ int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev) int ret; struct inv_mpu6050_state *st = iio_priv(indio_dev); - st->trig = iio_trigger_alloc("%s-dev%d", - indio_dev->name, - indio_dev->id); - if (st->trig == NULL) { - ret = -ENOMEM; - goto error_ret; - } - ret = request_irq(st->client->irq, &iio_trigger_generic_data_rdy_poll, - IRQF_TRIGGER_RISING, - "inv_mpu", - st->trig); + st->trig = devm_iio_trigger_alloc(&indio_dev->dev, + "%s-dev%d", + indio_dev->name, + indio_dev->id); + if (!st->trig) + return -ENOMEM; + + ret = devm_request_irq(&indio_dev->dev, st->client->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + "inv_mpu", + st->trig); if (ret) - goto error_free_trig; + return ret; + st->trig->dev.parent = &st->client->dev; st->trig->ops = &inv_mpu_trigger_ops; iio_trigger_set_drvdata(st->trig, indio_dev); + ret = iio_trigger_register(st->trig); if (ret) - goto error_free_irq; + return ret; + indio_dev->trig = iio_trigger_get(st->trig); return 0; - -error_free_irq: - free_irq(st->client->irq, st->trig); -error_free_trig: - iio_trigger_free(st->trig); -error_ret: - return ret; } void inv_mpu6050_remove_trigger(struct inv_mpu6050_state *st) { iio_trigger_unregister(st->trig); - free_irq(st->client->irq, st->trig); - iio_trigger_free(st->trig); } diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index 403b72878b0b..71333140d42c 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -639,6 +639,8 @@ static int __iio_update_buffers(struct iio_dev *indio_dev, indio_dev->currentmode = INDIO_BUFFER_TRIGGERED; } else if (indio_dev->modes & INDIO_BUFFER_HARDWARE) { indio_dev->currentmode = INDIO_BUFFER_HARDWARE; + } else if (indio_dev->modes & INDIO_BUFFER_SOFTWARE) { + indio_dev->currentmode = INDIO_BUFFER_SOFTWARE; } else { /* Should never be reached */ ret = -EINVAL; goto error_run_postdisable; diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 69feb912515a..aaba9d3d980e 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -72,6 +72,9 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_HUMIDITYRELATIVE] = "humidityrelative", [IIO_ACTIVITY] = "activity", [IIO_STEPS] = "steps", + [IIO_ENERGY] = "energy", + [IIO_DISTANCE] = "distance", + [IIO_VELOCITY] = "velocity", }; static const char * const iio_modifier_names[] = { @@ -97,6 +100,7 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_JOGGING] = "jogging", [IIO_MOD_WALKING] = "walking", [IIO_MOD_STILL] = "still", + [IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z] = "sqrt(x^2+y^2+z^2)", }; /* relies on pairs of these shared then separate */ @@ -121,6 +125,9 @@ static const char * const iio_chan_info_postfix[] = { [IIO_CHAN_INFO_INT_TIME] = "integration_time", [IIO_CHAN_INFO_ENABLE] = "en", [IIO_CHAN_INFO_CALIBHEIGHT] = "calibheight", + [IIO_CHAN_INFO_CALIBWEIGHT] = "calibweight", + [IIO_CHAN_INFO_DEBOUNCE_COUNT] = "debounce_count", + [IIO_CHAN_INFO_DEBOUNCE_TIME] = "debounce_time", }; /** diff --git a/drivers/iio/industrialio-event.c b/drivers/iio/industrialio-event.c index 3f5cee0295c5..a4b397048f71 100644 --- a/drivers/iio/industrialio-event.c +++ b/drivers/iio/industrialio-event.c @@ -197,7 +197,7 @@ static const char * const iio_ev_type_text[] = { [IIO_EV_TYPE_ROC] = "roc", [IIO_EV_TYPE_THRESH_ADAPTIVE] = "thresh_adaptive", [IIO_EV_TYPE_MAG_ADAPTIVE] = "mag_adaptive", - [IIO_EV_TYPE_INSTANCE] = "instance", + [IIO_EV_TYPE_CHANGE] = "change", }; static const char * const iio_ev_dir_text[] = { @@ -411,7 +411,7 @@ static inline int __iio_add_event_config_attrs(struct iio_dev *indio_dev) { int j, ret, attrcount = 0; - /* Dynically created from the channels array */ + /* Dynamically created from the channels array */ for (j = 0; j < indio_dev->num_channels; j++) { ret = iio_device_add_event_sysfs(indio_dev, &indio_dev->channels[j]); diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 5a3237b2aaa5..ae68c64bdad3 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -48,6 +48,17 @@ config CM32181 To compile this driver as a module, choose M here: the module will be called cm32181. +config CM3232 + depends on I2C + tristate "CM3232 ambient light sensor" + help + Say Y here if you use cm3232. + This option enables ambient light sensor using + Capella Microsystems cm3232 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm3232. + config CM36651 depends on I2C tristate "CM36651 driver" diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 74656c19a899..b12a5160d9e0 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_ADJD_S311) += adjd_s311.o obj-$(CONFIG_AL3320A) += al3320a.o obj-$(CONFIG_APDS9300) += apds9300.o obj-$(CONFIG_CM32181) += cm32181.o +obj-$(CONFIG_CM3232) += cm3232.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/cm32181.c b/drivers/iio/light/cm32181.c index ad36b294e4d5..5d12ae54d088 100644 --- a/drivers/iio/light/cm32181.c +++ b/drivers/iio/light/cm32181.c @@ -169,7 +169,7 @@ static int cm32181_write_als_it(struct cm32181_chip *cm32181, int val) * @cm32181: pointer of struct cm32181. * * Convert sensor raw data to lux. It depends on integration - * time and claibscale variable. + * time and calibscale variable. * * Return: Positive value is lux, otherwise is error code. */ diff --git a/drivers/iio/light/cm3232.c b/drivers/iio/light/cm3232.c new file mode 100644 index 000000000000..90e3519a91de --- /dev/null +++ b/drivers/iio/light/cm3232.c @@ -0,0 +1,403 @@ +/* + * CM3232 Ambient Light Sensor + * + * Copyright (C) 2014-2015 Capella Microsystems Inc. + * Author: Kevin Tsai <ktsai@capellamicro.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. + * + * IIO driver for CM3232 (7-bit I2C slave address 0x10). + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/init.h> + +/* Registers Address */ +#define CM3232_REG_ADDR_CMD 0x00 +#define CM3232_REG_ADDR_ALS 0x50 +#define CM3232_REG_ADDR_ID 0x53 + +#define CM3232_CMD_ALS_DISABLE BIT(0) + +#define CM3232_CMD_ALS_IT_SHIFT 2 +#define CM3232_CMD_ALS_IT_MASK (BIT(2) | BIT(3) | BIT(4)) +#define CM3232_CMD_ALS_IT_DEFAULT (0x01 << CM3232_CMD_ALS_IT_SHIFT) + +#define CM3232_CMD_ALS_RESET BIT(6) + +#define CM3232_CMD_DEFAULT CM3232_CMD_ALS_IT_DEFAULT + +#define CM3232_HW_ID 0x32 +#define CM3232_CALIBSCALE_DEFAULT 100000 +#define CM3232_CALIBSCALE_RESOLUTION 100000 +#define CM3232_MLUX_PER_LUX 1000 + +#define CM3232_MLUX_PER_BIT_DEFAULT 64 +#define CM3232_MLUX_PER_BIT_BASE_IT 100000 + +static const struct { + int val; + int val2; + u8 it; +} cm3232_als_it_scales[] = { + {0, 100000, 0}, /* 0.100000 */ + {0, 200000, 1}, /* 0.200000 */ + {0, 400000, 2}, /* 0.400000 */ + {0, 800000, 3}, /* 0.800000 */ + {1, 600000, 4}, /* 1.600000 */ + {3, 200000, 5}, /* 3.200000 */ +}; + +struct cm3232_als_info { + u8 regs_cmd_default; + u8 hw_id; + int calibscale; + int mlux_per_bit; + int mlux_per_bit_base_it; +}; + +static struct cm3232_als_info cm3232_als_info_default = { + .regs_cmd_default = CM3232_CMD_DEFAULT, + .hw_id = CM3232_HW_ID, + .calibscale = CM3232_CALIBSCALE_DEFAULT, + .mlux_per_bit = CM3232_MLUX_PER_BIT_DEFAULT, + .mlux_per_bit_base_it = CM3232_MLUX_PER_BIT_BASE_IT, +}; + +struct cm3232_chip { + struct i2c_client *client; + struct cm3232_als_info *als_info; + u8 regs_cmd; + u16 regs_als; +}; + +/** + * cm3232_reg_init() - Initialize CM3232 + * @chip: pointer of struct cm3232_chip. + * + * Check and initialize CM3232 ambient light sensor. + * + * Return: 0 for success; otherwise for error code. + */ +static int cm3232_reg_init(struct cm3232_chip *chip) +{ + struct i2c_client *client = chip->client; + s32 ret; + + chip->als_info = &cm3232_als_info_default; + + /* Identify device */ + ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ID); + if (ret < 0) { + dev_err(&chip->client->dev, "Error reading addr_id\n"); + return ret; + } + + if ((ret & 0xFF) != chip->als_info->hw_id) + return -ENODEV; + + /* Disable and reset device */ + chip->regs_cmd = CM3232_CMD_ALS_DISABLE | CM3232_CMD_ALS_RESET; + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + if (ret < 0) { + dev_err(&chip->client->dev, "Error writing reg_cmd\n"); + return ret; + } + + /* Register default value */ + chip->regs_cmd = chip->als_info->regs_cmd_default; + + /* Configure register */ + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + if (ret < 0) + dev_err(&chip->client->dev, "Error writing reg_cmd\n"); + + return 0; +} + +/** + * cm3232_read_als_it() - Get sensor integration time + * @chip: pointer of struct cm3232_chip + * @val: pointer of int to load the integration (sec). + * @val2: pointer of int to load the integration time (microsecond). + * + * Report the current integration time. + * + * Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. + */ +static int cm3232_read_als_it(struct cm3232_chip *chip, int *val, int *val2) +{ + u16 als_it; + int i; + + als_it = chip->regs_cmd; + als_it &= CM3232_CMD_ALS_IT_MASK; + als_it >>= CM3232_CMD_ALS_IT_SHIFT; + for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { + if (als_it == cm3232_als_it_scales[i].it) { + *val = cm3232_als_it_scales[i].val; + *val2 = cm3232_als_it_scales[i].val2; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +/** + * cm3232_write_als_it() - Write sensor integration time + * @chip: pointer of struct cm3232_chip. + * @val: integration time in second. + * @val2: integration time in microsecond. + * + * Convert integration time to sensor value. + * + * Return: i2c_smbus_write_byte_data command return value. + */ +static int cm3232_write_als_it(struct cm3232_chip *chip, int val, int val2) +{ + struct i2c_client *client = chip->client; + u16 als_it, cmd; + int i; + s32 ret; + + for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { + if (val == cm3232_als_it_scales[i].val && + val2 == cm3232_als_it_scales[i].val2) { + + als_it = cm3232_als_it_scales[i].it; + als_it <<= CM3232_CMD_ALS_IT_SHIFT; + + cmd = chip->regs_cmd & ~CM3232_CMD_ALS_IT_MASK; + cmd |= als_it; + ret = i2c_smbus_write_byte_data(client, + CM3232_REG_ADDR_CMD, + cmd); + if (ret < 0) + return ret; + chip->regs_cmd = cmd; + return 0; + } + } + return -EINVAL; +} + +/** + * cm3232_get_lux() - report current lux value + * @chip: pointer of struct cm3232_chip. + * + * Convert sensor data to lux. It depends on integration + * time and calibscale variable. + * + * Return: Zero or positive value is lux, otherwise error code. + */ +static int cm3232_get_lux(struct cm3232_chip *chip) +{ + struct i2c_client *client = chip->client; + struct cm3232_als_info *als_info = chip->als_info; + int ret; + int val, val2; + int als_it; + u64 lux; + + /* Calculate mlux per bit based on als_it */ + ret = cm3232_read_als_it(chip, &val, &val2); + if (ret < 0) + return -EINVAL; + als_it = val * 1000000 + val2; + lux = (__force u64)als_info->mlux_per_bit; + lux *= als_info->mlux_per_bit_base_it; + lux = div_u64(lux, als_it); + + ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ALS); + if (ret < 0) { + dev_err(&client->dev, "Error reading reg_addr_als\n"); + return ret; + } + + chip->regs_als = (u16)ret; + lux *= chip->regs_als; + lux *= als_info->calibscale; + lux = div_u64(lux, CM3232_CALIBSCALE_RESOLUTION); + lux = div_u64(lux, CM3232_MLUX_PER_LUX); + + if (lux > 0xFFFF) + lux = 0xFFFF; + + return (int)lux; +} + +static int cm3232_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm3232_chip *chip = iio_priv(indio_dev); + struct cm3232_als_info *als_info = chip->als_info; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = cm3232_get_lux(chip); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = als_info->calibscale; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + return cm3232_read_als_it(chip, val, val2); + } + + return -EINVAL; +} + +static int cm3232_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cm3232_chip *chip = iio_priv(indio_dev); + struct cm3232_als_info *als_info = chip->als_info; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + als_info->calibscale = val; + return 0; + case IIO_CHAN_INFO_INT_TIME: + return cm3232_write_als_it(chip, val, val2); + } + + return -EINVAL; +} + +/** + * cm3232_get_it_available() - Get available ALS IT value + * @dev: pointer of struct device. + * @attr: pointer of struct device_attribute. + * @buf: pointer of return string buffer. + * + * Display the available integration time in second. + * + * Return: string length. + */ +static ssize_t cm3232_get_it_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len; + + for (i = 0, len = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ", + cm3232_als_it_scales[i].val, + cm3232_als_it_scales[i].val2); + return len + scnprintf(buf + len, PAGE_SIZE - len, "\n"); +} + +static const struct iio_chan_spec cm3232_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, + S_IRUGO, cm3232_get_it_available, NULL, 0); + +static struct attribute *cm3232_attributes[] = { + &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group cm3232_attribute_group = { + .attrs = cm3232_attributes +}; + +static const struct iio_info cm3232_info = { + .driver_module = THIS_MODULE, + .read_raw = &cm3232_read_raw, + .write_raw = &cm3232_write_raw, + .attrs = &cm3232_attribute_group, +}; + +static int cm3232_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cm3232_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + chip->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->channels = cm3232_channels; + indio_dev->num_channels = ARRAY_SIZE(cm3232_channels); + indio_dev->info = &cm3232_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm3232_reg_init(chip); + if (ret) { + dev_err(&client->dev, + "%s: register init failed\n", + __func__); + return ret; + } + + return iio_device_register(indio_dev); +} + +static int cm3232_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + CM3232_CMD_ALS_DISABLE); + + iio_device_unregister(indio_dev); + + return 0; +} + +static const struct i2c_device_id cm3232_id[] = { + {"cm3232", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cm3232_id); + +static const struct of_device_id cm3232_of_match[] = { + {.compatible = "capella,cm3232"}, + {} +}; + +static struct i2c_driver cm3232_driver = { + .driver = { + .name = "cm3232", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(cm3232_of_match), + }, + .id_table = cm3232_id, + .probe = cm3232_probe, + .remove = cm3232_remove, +}; + +module_i2c_driver(cm3232_driver); + +MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>"); +MODULE_DESCRIPTION("CM3232 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c index a5283d75c096..948acfc38b8c 100644 --- a/drivers/iio/light/hid-sensor-als.c +++ b/drivers/iio/light/hid-sensor-als.c @@ -80,7 +80,6 @@ static int als_read_raw(struct iio_dev *indio_dev, int report_id = -1; u32 address; int ret_type; - s32 poll_value; *val = 0; *val2 = 0; @@ -97,15 +96,8 @@ static int als_read_raw(struct iio_dev *indio_dev, break; } if (report_id >= 0) { - poll_value = hid_sensor_read_poll_value( - &als_state->common_attributes); - if (poll_value < 0) - return -EINVAL; - hid_sensor_power_state(&als_state->common_attributes, true); - msleep_interruptible(poll_value * 2); - *val = sensor_hub_input_attr_get_raw_value( als_state->common_attributes.hsdev, HID_USAGE_SENSOR_ALS, address, @@ -381,6 +373,7 @@ static struct platform_driver hid_als_platform_driver = { .id_table = hid_als_ids, .driver = { .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, }, .probe = hid_als_probe, .remove = hid_als_remove, diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c index f5a514698fd8..3ecf79ed08ac 100644 --- a/drivers/iio/light/hid-sensor-prox.c +++ b/drivers/iio/light/hid-sensor-prox.c @@ -75,7 +75,6 @@ static int prox_read_raw(struct iio_dev *indio_dev, int report_id = -1; u32 address; int ret_type; - s32 poll_value; *val = 0; *val2 = 0; @@ -92,16 +91,8 @@ static int prox_read_raw(struct iio_dev *indio_dev, break; } if (report_id >= 0) { - poll_value = hid_sensor_read_poll_value( - &prox_state->common_attributes); - if (poll_value < 0) - return -EINVAL; - hid_sensor_power_state(&prox_state->common_attributes, true); - - msleep_interruptible(poll_value * 2); - *val = sensor_hub_input_attr_get_raw_value( prox_state->common_attributes.hsdev, HID_USAGE_SENSOR_PROX, address, @@ -373,6 +364,7 @@ static struct platform_driver hid_prox_platform_driver = { .id_table = hid_prox_ids, .driver = { .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, }, .probe = hid_prox_probe, .remove = hid_prox_remove, diff --git a/drivers/iio/light/tcs3414.c b/drivers/iio/light/tcs3414.c index a9e449b0be0c..71c2bde275aa 100644 --- a/drivers/iio/light/tcs3414.c +++ b/drivers/iio/light/tcs3414.c @@ -149,8 +149,8 @@ static int tcs3414_read_raw(struct iio_dev *indio_dev, *val = ret; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - i = (data->gain & TCS3414_GAIN_MASK) >> TCS3414_GAIN_SHIFT; - *val = tcs3414_scales[i][0]; + i = (data->gain & TCS3414_GAIN_MASK) >> TCS3414_GAIN_SHIFT; + *val = tcs3414_scales[i][0]; *val2 = tcs3414_scales[i][1]; return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_INT_TIME: diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index 0d10a4baceb6..b13936dacc78 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -813,6 +813,7 @@ static int ak8975_probe(struct i2c_client *client, static const struct i2c_device_id ak8975_id[] = { {"ak8975", AK8975}, {"ak8963", AK8963}, + {"AK8963", AK8963}, {"ak09911", AK09911}, {"ak09912", AK09912}, {} diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c index 6294575d2777..d22993b4066a 100644 --- a/drivers/iio/magnetometer/hid-sensor-magn-3d.c +++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c @@ -157,20 +157,12 @@ static int magn_3d_read_raw(struct iio_dev *indio_dev, int report_id = -1; u32 address; int ret_type; - s32 poll_value; *val = 0; *val2 = 0; switch (mask) { case 0: - poll_value = hid_sensor_read_poll_value( - &magn_state->common_attributes); - if (poll_value < 0) - return -EINVAL; - hid_sensor_power_state(&magn_state->common_attributes, true); - msleep_interruptible(poll_value * 2); - report_id = magn_state->magn[chan->address].report_id; address = magn_3d_addresses[chan->address]; @@ -530,6 +522,7 @@ static struct platform_driver hid_magn_3d_platform_driver = { .id_table = hid_magn_3d_ids, .driver = { .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, }, .probe = hid_magn_3d_probe, .remove = hid_magn_3d_remove, diff --git a/drivers/iio/orientation/hid-sensor-incl-3d.c b/drivers/iio/orientation/hid-sensor-incl-3d.c index 1ff181bbbcef..73854460bb2c 100644 --- a/drivers/iio/orientation/hid-sensor-incl-3d.c +++ b/drivers/iio/orientation/hid-sensor-incl-3d.c @@ -111,20 +111,12 @@ static int incl_3d_read_raw(struct iio_dev *indio_dev, int report_id = -1; u32 address; int ret_type; - s32 poll_value; *val = 0; *val2 = 0; switch (mask) { case IIO_CHAN_INFO_RAW: - poll_value = hid_sensor_read_poll_value( - &incl_state->common_attributes); - if (poll_value < 0) - return -EINVAL; - hid_sensor_power_state(&incl_state->common_attributes, true); - msleep_interruptible(poll_value * 2); - report_id = incl_state->incl[chan->scan_index].report_id; address = incl_3d_addresses[chan->scan_index]; @@ -437,6 +429,7 @@ static struct platform_driver hid_incl_3d_platform_driver = { .id_table = hid_incl_3d_ids, .driver = { .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, }, .probe = hid_incl_3d_probe, .remove = hid_incl_3d_remove, diff --git a/drivers/iio/pressure/hid-sensor-press.c b/drivers/iio/pressure/hid-sensor-press.c index 764928682df2..1af314926ebd 100644 --- a/drivers/iio/pressure/hid-sensor-press.c +++ b/drivers/iio/pressure/hid-sensor-press.c @@ -79,7 +79,6 @@ static int press_read_raw(struct iio_dev *indio_dev, int report_id = -1; u32 address; int ret_type; - s32 poll_value; *val = 0; *val2 = 0; @@ -96,15 +95,8 @@ static int press_read_raw(struct iio_dev *indio_dev, break; } if (report_id >= 0) { - poll_value = hid_sensor_read_poll_value( - &press_state->common_attributes); - if (poll_value < 0) - return -EINVAL; hid_sensor_power_state(&press_state->common_attributes, true); - - msleep_interruptible(poll_value * 2); - *val = sensor_hub_input_attr_get_raw_value( press_state->common_attributes.hsdev, HID_USAGE_SENSOR_PRESSURE, address, @@ -382,6 +374,7 @@ static struct platform_driver hid_press_platform_driver = { .id_table = hid_press_ids, .driver = { .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, }, .probe = hid_press_probe, .remove = hid_press_remove, diff --git a/drivers/iio/proximity/as3935.c b/drivers/iio/proximity/as3935.c index 466aa4314667..bc0d68efd455 100644 --- a/drivers/iio/proximity/as3935.c +++ b/drivers/iio/proximity/as3935.c @@ -273,9 +273,9 @@ static void calibrate_as3935(struct as3935_state *st) } #ifdef CONFIG_PM_SLEEP -static int as3935_suspend(struct spi_device *spi, pm_message_t msg) +static int as3935_suspend(struct device *dev) { - struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct iio_dev *indio_dev = dev_get_drvdata(dev); struct as3935_state *st = iio_priv(indio_dev); int val, ret; @@ -293,9 +293,9 @@ err_suspend: return ret; } -static int as3935_resume(struct spi_device *spi) +static int as3935_resume(struct device *dev) { - struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct iio_dev *indio_dev = dev_get_drvdata(dev); struct as3935_state *st = iio_priv(indio_dev); int val, ret; @@ -311,9 +311,12 @@ err_resume: return ret; } + +static SIMPLE_DEV_PM_OPS(as3935_pm_ops, as3935_suspend, as3935_resume); +#define AS3935_PM_OPS (&as3935_pm_ops) + #else -#define as3935_suspend NULL -#define as3935_resume NULL +#define AS3935_PM_OPS NULL #endif static int as3935_probe(struct spi_device *spi) @@ -441,12 +444,11 @@ static struct spi_driver as3935_driver = { .driver = { .name = "as3935", .owner = THIS_MODULE, + .pm = AS3935_PM_OPS, }, .probe = as3935_probe, .remove = as3935_remove, .id_table = as3935_id, - .suspend = as3935_suspend, - .resume = as3935_resume, }; module_spi_driver(as3935_driver); diff --git a/drivers/staging/iio/Documentation/iio_event_monitor.c b/drivers/staging/iio/Documentation/iio_event_monitor.c index def236abcb3e..72c96aa6e992 100644 --- a/drivers/staging/iio/Documentation/iio_event_monitor.c +++ b/drivers/staging/iio/Documentation/iio_event_monitor.c @@ -59,7 +59,7 @@ static const char * const iio_ev_type_text[] = { [IIO_EV_TYPE_ROC] = "roc", [IIO_EV_TYPE_THRESH_ADAPTIVE] = "thresh_adaptive", [IIO_EV_TYPE_MAG_ADAPTIVE] = "mag_adaptive", - [IIO_EV_TYPE_INSTANCE] = "instance", + [IIO_EV_TYPE_CHANGE] = "change", }; static const char * const iio_ev_dir_text[] = { @@ -178,7 +178,7 @@ static bool event_is_known(struct iio_event_data *event) case IIO_EV_TYPE_ROC: case IIO_EV_TYPE_THRESH_ADAPTIVE: case IIO_EV_TYPE_MAG_ADAPTIVE: - case IIO_EV_TYPE_INSTANCE: + case IIO_EV_TYPE_CHANGE: break; default: return false; diff --git a/drivers/staging/iio/iio_simple_dummy.c b/drivers/staging/iio/iio_simple_dummy.c index 0b8611ac1003..e4520213f627 100644 --- a/drivers/staging/iio/iio_simple_dummy.c +++ b/drivers/staging/iio/iio_simple_dummy.c @@ -73,7 +73,7 @@ static const struct iio_event_spec iio_dummy_event = { * simple step detect event - triggered when a step is detected */ static const struct iio_event_spec step_detect_event = { - .type = IIO_EV_TYPE_INSTANCE, + .type = IIO_EV_TYPE_CHANGE, .dir = IIO_EV_DIR_NONE, .mask_separate = BIT(IIO_EV_INFO_ENABLE), }; diff --git a/drivers/staging/iio/iio_simple_dummy_events.c b/drivers/staging/iio/iio_simple_dummy_events.c index ac15a44ba271..a5cd3bb219fe 100644 --- a/drivers/staging/iio/iio_simple_dummy_events.c +++ b/drivers/staging/iio/iio_simple_dummy_events.c @@ -86,7 +86,7 @@ int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev, } case IIO_STEPS: switch (type) { - case IIO_EV_TYPE_INSTANCE: + case IIO_EV_TYPE_CHANGE: st->event_en = state; break; default: @@ -201,7 +201,7 @@ static irqreturn_t iio_simple_dummy_event_handler(int irq, void *private) iio_push_event(indio_dev, IIO_EVENT_CODE(IIO_STEPS, 0, IIO_NO_MOD, IIO_EV_DIR_NONE, - IIO_EV_TYPE_INSTANCE, 0, 0, 0), + IIO_EV_TYPE_CHANGE, 0, 0, 0), iio_get_time_ns()); break; default: diff --git a/drivers/staging/iio/impedance-analyzer/ad5933.c b/drivers/staging/iio/impedance-analyzer/ad5933.c index e90653ff9159..79194399e040 100644 --- a/drivers/staging/iio/impedance-analyzer/ad5933.c +++ b/drivers/staging/iio/impedance-analyzer/ad5933.c @@ -360,11 +360,11 @@ static ssize_t ad5933_show(struct device *dev, mutex_lock(&indio_dev->mlock); switch ((u32) this_attr->address) { case AD5933_OUT_RANGE: - len = sprintf(buf, "%d\n", + len = sprintf(buf, "%u\n", st->range_avail[(st->ctrl_hb >> 1) & 0x3]); break; case AD5933_OUT_RANGE_AVAIL: - len = sprintf(buf, "%d %d %d %d\n", st->range_avail[0], + len = sprintf(buf, "%u %u %u %u\n", st->range_avail[0], st->range_avail[3], st->range_avail[2], st->range_avail[1]); break; diff --git a/drivers/staging/iio/light/tsl2583.c b/drivers/staging/iio/light/tsl2583.c index cc4ddcce4ff9..8afae8e33d56 100644 --- a/drivers/staging/iio/light/tsl2583.c +++ b/drivers/staging/iio/light/tsl2583.c @@ -692,7 +692,7 @@ static ssize_t taos_luxtable_show(struct device *dev, int offset = 0; for (i = 0; i < ARRAY_SIZE(taos_device_lux); i++) { - offset += sprintf(buf + offset, "%d,%d,%d,", + offset += sprintf(buf + offset, "%u,%u,%u,", taos_device_lux[i].ratio, taos_device_lux[i].ch0, taos_device_lux[i].ch1); diff --git a/drivers/staging/iio/light/tsl2x7x_core.c b/drivers/staging/iio/light/tsl2x7x_core.c index 423f96bdf595..4a5dc26fed4c 100644 --- a/drivers/staging/iio/light/tsl2x7x_core.c +++ b/drivers/staging/iio/light/tsl2x7x_core.c @@ -1147,7 +1147,7 @@ static ssize_t tsl2x7x_luxtable_show(struct device *dev, int offset = 0; while (i < (TSL2X7X_MAX_LUX_TABLE_SIZE * 3)) { - offset += snprintf(buf + offset, PAGE_SIZE, "%d,%d,%d,", + offset += snprintf(buf + offset, PAGE_SIZE, "%u,%u,%u,", chip->tsl2x7x_device_lux[i].ratio, chip->tsl2x7x_device_lux[i].ch0, chip->tsl2x7x_device_lux[i].ch1); |