Re: [PATCH V2 1/1] iio: light: Added CM36672 Proximity Sensor Driver.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




On Thu, Jun 09, 2016 at 02:00:45AM -0700, Kevin Tsai wrote:
> Added Vishay Capella CM36672 Proximity Sensor IIO driver.  Support both
> ACPI and Device Tree.
> 
> Signed-off-by: Kevin Tsai <capellamicro@xxxxxxxxx>
> ---
> V2:
> Thanks commends from Peter Meerwald-Stadler, Jonathan Cameron, and Linux
> Walleij.  Updated for the following:
>  - Remove unused defines.
>  - Rewrite event registration.
>  - Remove direct register values in the device tree.
>  - Rewrite by regmap API.
>  - Remove irq module parameter.
>  - Correct cm36672_remove().
> 
>  .../devicetree/bindings/iio/light/cm36672.txt      |  37 +
>  MAINTAINERS                                        |   4 +-
>  drivers/iio/light/Kconfig                          |  11 +
>  drivers/iio/light/Makefile                         |   1 +
>  drivers/iio/light/cm36672.c                        | 820 +++++++++++++++++++++
>  5 files changed, 871 insertions(+), 2 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/iio/light/cm36672.txt
>  create mode 100644 drivers/iio/light/cm36672.c
> 
> diff --git a/Documentation/devicetree/bindings/iio/light/cm36672.txt b/Documentation/devicetree/bindings/iio/light/cm36672.txt
> new file mode 100644
> index 0000000..9681d2c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/light/cm36672.txt
> @@ -0,0 +1,37 @@
> +* Vishay Capella CM36672 I2C Proximity sensor
> +
> +Required properties:
> +- compatible: must be capella,cm36672"
> +- reg: the I2C slave address, usually 0x60
> +- interrupts: interrupt mapping for GPIO IRQ
> +
> +Optional properties:
> +- interrupt-parent: interrupt controller
> +- interrupts: interrupt mapping for GPIO IRQ
> +- cm36672,prx_led_current: LED current, must be one of the following values:

Correct prefix is capella, not cm36672. Use '-' not '_'.

Is this a standard property of prox sensors? If so define a common 
property.

> +    - 0: 50mA
> +    - 1: 75mA
> +    - 2: 100mA
> +    - 3: 120mA
> +    - 4: 140mA
> +    - 5: 160mA
> +    - 6: 180mA
> +    - 7: 200mA
> +- cm36672,prx_hd: width of proximity sensor output data, must be one of the
> +  following values:
> +    - 0: 12-bit
> +    - 1: 16-bit
> +
> +Example:
> +
> +    cm36672@60 {
> +        compatible = "capella,cm36672";
> +        reg = <0x60>;
> +        interrupt-parent = <&gpio0>;
> +        interrupts = <30 0>;
> +
> +        /* cm36672-specific properties */
> +        cm36672,prx_led_current = <2>;
> +        cm36672,prx_hd = <1>;
> +    };
> +
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ed42cb6..9618af4 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2822,10 +2822,10 @@ F:	security/commoncap.c
>  F:	kernel/capability.c
>  
>  CAPELLA MICROSYSTEMS LIGHT SENSOR DRIVER
> -M:	Kevin Tsai <ktsai@xxxxxxxxxxxxxxxx>
> +M:	Kevin Tsai <capellamicro@xxxxxxxxx>
>  S:	Maintained
>  F:	drivers/iio/light/cm*
> -F:	Documentation/devicetree/bindings/i2c/trivial-devices.txt
> +F:	Documentation/devicetree/bindings/iio/light/cm*
>  
>  CAVIUM LIQUIDIO NETWORK DRIVER
>  M:     Derek Chickles <derek.chickles@xxxxxxxxxxxxxxxxxx>
> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> index 7c566f5..cb5052a 100644
> --- a/drivers/iio/light/Kconfig
> +++ b/drivers/iio/light/Kconfig
> @@ -127,6 +127,17 @@ config CM36651
>  	 To compile this driver as a module, choose M here:
>  	 the module will be called cm36651.
>  
> +config CM36672
> +	depends on I2C
> +	tristate "CM36672 driver"
> +	help
> +	 Say Y here if you use cm36672.
> +	 This option enables proximity sensors using Vishay
> +	 Capella cm36672 device driver.
> +
> +	 To compile this driver as a module, choose M here:
> +	 the module will be called cm36672.
> +
>  config GP2AP020A00F
>  	tristate "Sharp GP2AP020A00F Proximity/ALS sensor"
>  	depends on I2C
> diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> index 6f2a3c6..b155425 100644
> --- a/drivers/iio/light/Makefile
> +++ b/drivers/iio/light/Makefile
> @@ -14,6 +14,7 @@ obj-$(CONFIG_CM32181)		+= cm32181.o
>  obj-$(CONFIG_CM3232)		+= cm3232.o
>  obj-$(CONFIG_CM3323)		+= cm3323.o
>  obj-$(CONFIG_CM36651)		+= cm36651.o
> +obj-$(CONFIG_CM36672)		+= cm36672.o
>  obj-$(CONFIG_GP2AP020A00F)	+= gp2ap020a00f.o
>  obj-$(CONFIG_HID_SENSOR_ALS)	+= hid-sensor-als.o
>  obj-$(CONFIG_HID_SENSOR_PROX)	+= hid-sensor-prox.o
> diff --git a/drivers/iio/light/cm36672.c b/drivers/iio/light/cm36672.c
> new file mode 100644
> index 0000000..975629f
> --- /dev/null
> +++ b/drivers/iio/light/cm36672.c
> @@ -0,0 +1,820 @@
> +/*
> + * CM36672 Proximity Sensor
> + *
> + * Copyright (C) 2014-2016 Vishay Capella
> + * Author: Kevin Tsai <capellamicro@xxxxxxxxx>
> + *
> + * 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 CM36672 (7-bit I2C slave address 0x60).
> + */
> +#include <linux/kernel.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/regmap.h>
> +#include <linux/gpio.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/iio/events.h>
> +
> +#ifdef CONFIG_ACPI
> +#include <linux/acpi.h>
> +#endif
> +
> +#define CM36672_DRIVER_NAME		"cm36672"
> +#define CM36672_REGMAP_NAME		"cm36672_regmap"
> +
> +/* Sensor registers */
> +#define CM36672_ADDR_PRX_CONF		0x03
> +#define CM36672_ADDR_PRX_CONF3		0x04
> +#define CM36672_ADDR_PRX_THDL		0x06
> +#define CM36672_ADDR_PRX_THDH		0x07
> +#define CM36672_REGS_NUM		0x08
> +/* Read only registers */
> +#define CM36672_ADDR_PRX		0x08
> +#define CM36672_ADDR_STATUS		0x0B
> +
> +/* PRX_CONF */
> +#define CM36672_PRX_HD_SHIFT		11
> +#define CM36672_PRX_HD			(1 << CM36672_PRX_HD_SHIFT)
> +
> +/* PRX_CONF: interrupt */
> +#define CM36672_PRX_INT_THDH		BIT(8)
> +#define CM36672_PRX_INT_THDL		BIT(9)
> +#define CM36672_PRX_INT_MASK		(CM36672_PRX_INT_THDH |	\
> +					CM36672_PRX_INT_THDL)
> +
> +/* PRX_CONF: persistence */
> +#define CM36672_PRX_PERS_MASK		(BIT(4) | BIT(5))
> +#define CM36672_PRX_PERS_SHIFT		4
> +#define CM36672_PRX_PERS_DISABLE	0
> +#define CM36672_PRX_PERS_2		(1 << CM36672_PRX_PERS_SHIFT)
> +#define CM36672_PRX_PERS_3		(2 << CM36672_PRX_PERS_SHIFT)
> +#define CM36672_PRX_PERS_4		(3 << CM36672_PRX_PERS_SHIFT)
> +
> +/* PRX_CONF: integration time */
> +#define CM36672_PRX_IT_MASK		(BIT(1) | BIT(2) | BIT(3))
> +#define CM36672_PRX_IT_SHIFT		1
> +#define CM36672_PRX_IT_1T		0
> +#define CM36672_PRX_IT_1_5T		(1 << CM36672_PRX_IT_SHIFT)
> +#define CM36672_PRX_IT_2T		(2 << CM36672_PRX_IT_SHIFT)
> +#define CM36672_PRX_IT_2_5T		(3 << CM36672_PRX_IT_SHIFT)
> +#define CM36672_PRX_IT_3T		(4 << CM36672_PRX_IT_SHIFT)
> +#define CM36672_PRX_IT_3_5T		(5 << CM36672_PRX_IT_SHIFT)
> +#define CM36672_PRX_IT_4T		(6 << CM36672_PRX_IT_SHIFT)
> +#define CM36672_PRX_IT_8T		(7 << CM36672_PRX_IT_SHIFT)
> +
> +/* PRX_CONF3 */
> +#define CM36672_PRX_LED_I_MASK		(BIT(8) | BIT(9) | BIT(10))
> +#define CM36672_PRX_LED_I_SHIFT		8
> +#define CM36672_PRX_LED_I_50MA		0
> +#define CM36672_PRX_LED_I_75MA		(1 << CM36672_PRX_LED_I_SHIFT)
> +#define CM36672_PRX_LED_I_100MA		(2 << CM36672_PRX_LED_I_SHIFT)
> +#define CM36672_PRX_LED_I_120MA		(3 << CM36672_PRX_LED_I_SHIFT)
> +#define CM36672_PRX_LED_I_140MA		(4 << CM36672_PRX_LED_I_SHIFT)
> +#define CM36672_PRX_LED_I_160MA		(5 << CM36672_PRX_LED_I_SHIFT)
> +#define CM36672_PRX_LED_I_180MA		(6 << CM36672_PRX_LED_I_SHIFT)
> +#define CM36672_PRX_LED_I_200MA		(7 << CM36672_PRX_LED_I_SHIFT)
> +
> +/* INT_FLAG */
> +#define CM36672_INT_PRX_CLOSE		BIT(9)
> +#define CM36672_INT_PRX_AWAY		BIT(8)
> +
> +struct cm36672_it_scale {
> +	u8 it;
> +	int val;
> +	int val2;
> +};
> +
> +static const struct cm36672_it_scale cm36672_prx_it_scales[] = {
> +	{0, 0, 100},	/* 0.00010 */
> +	{1, 0, 150},	/* 0.00015 */
> +	{2, 0, 200},	/* 0.00020 */
> +	{3, 0, 250},	/* 0.00025 */
> +	{4, 0, 300},	/* 0.00030 */
> +	{5, 0, 350},	/* 0.00035 */
> +	{6, 0, 400},	/* 0.00040 */
> +	{7, 0, 800},	/* 0.00080 */
> +};
> +
> +#define CM36672_PRX_INT_TIME_AVAIL			\
> +	"0.000100 0.000150 0.000200 0.000250 "		\
> +	"0.000300 0.000350 0.000400 0.000800"
> +
> +static const u16 cm36672_regs_default[] = {
> +	0x0001,
> +	0x0000,
> +	0x0000,
> +	CM36672_PRX_INT_THDH | CM36672_PRX_INT_THDL |
> +	CM36672_PRX_IT_2T | CM36672_PRX_PERS_3,
> +	CM36672_PRX_LED_I_100MA,
> +	0x0000,
> +	0x0005,
> +	0x000A,
> +};
> +
> +struct cm36672_chip {
> +	const struct cm36672_platform_data *pdata;
> +	struct i2c_client *client;
> +	struct mutex lock;
> +
> +	/* regmap fields */
> +	struct regmap *regmap;
> +	struct regmap_field *reg_prx_int_hi;
> +	struct regmap_field *reg_prx_int_lo;
> +	struct regmap_field *reg_prx_it;
> +
> +	u16 regs[CM36672_REGS_NUM];
> +};
> +
> +static const struct reg_field cm36672_reg_field_prx_int_hi =
> +				REG_FIELD(CM36672_ADDR_PRX_CONF, 8, 8);
> +static const struct reg_field cm36672_reg_field_prx_int_lo =
> +				REG_FIELD(CM36672_ADDR_PRX_CONF, 9, 9);
> +static const struct reg_field cm36672_reg_field_prx_it =
> +				REG_FIELD(CM36672_ADDR_PRX_CONF, 1, 3);
> +
> +#ifdef CONFIG_OF
> +static void cm36672_mod_u16(u16 *reg, u16 mask, u8 shift, u16 val)
> +{
> +	*reg &= ~mask;
> +	*reg |= (u16)(val << shift);
> +}
> +
> +static void cm36672_parse_dt(struct cm36672_chip *chip)
> +{
> +	struct device_node *dn = chip->client->dev.of_node;
> +	u32 temp_val;
> +
> +	if (!of_property_read_u32(dn, "cm36672,prx_led_current",
> +				&temp_val))
> +		cm36672_mod_u16(&chip->regs[CM36672_ADDR_PRX_CONF3],
> +				CM36672_PRX_LED_I_MASK,
> +				CM36672_PRX_LED_I_SHIFT,
> +				(u16)temp_val);
> +
> +	if (!of_property_read_u32(dn, "cm36672,prx_hd", &temp_val))
> +		cm36672_mod_u16(&chip->regs[CM36672_ADDR_PRX_CONF],
> +				CM36672_PRX_HD, CM36672_PRX_HD_SHIFT,
> +				(u16)temp_val);
> +}
> +#endif
> +
> +#ifdef CONFIG_ACPI
> +/**
> + * cm36672_acpi_get_cpm_info() - Get CPM object from ACPI
> + * @client:	pointer of struct i2c_client.
> + * @obj_name:	pointer of ACPI object name.
> + * @count:	maximum size of return array.
> + * @vals:	pointer of array for return elements.
> + *
> + * Convert ACPI CPM table to array. Special thanks to Srinivas Pandruvada's
> + * help to implement this routine.
> + *
> + * Return: -ENODEV for fail. Otherwise the number of elements.
> + */
> +static int cm36672_acpi_get_cpm_info(struct i2c_client *client, char *obj_name,
> +							int count, u64 *vals)
> +{
> +	acpi_handle handle;
> +	struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
> +	int i;
> +	acpi_status status;
> +	union acpi_object *cpm;
> +
> +	handle = ACPI_HANDLE(&client->dev);
> +	if (!handle)
> +		return -ENODEV;
> +
> +	status = acpi_evaluate_object(handle, obj_name, NULL, &buffer);
> +	if (ACPI_FAILURE(status)) {
> +		dev_err(&client->dev, "object %s not found\n", obj_name);
> +		return -ENODEV;
> +	}
> +
> +	cpm = buffer.pointer;
> +	for (i = 0; i < cpm->package.count && i < count; ++i) {
> +		union acpi_object *elem;
> +
> +		elem = &(cpm->package.elements[i]);
> +		vals[i] = elem->integer.value;
> +	}
> +
> +	kfree(buffer.pointer);
> +
> +	return i;
> +}
> +
> +static void cm36672_parse_acpi(struct cm36672_chip *chip)
> +{
> +	struct i2c_client *client = chip->client;
> +	int cpm_elem_count, i;
> +	u64 cpm_elems[20];
> +
> +	cpm_elem_count = cm36672_acpi_get_cpm_info(client, "CPM0",
> +				ARRAY_SIZE(cpm_elems), cpm_elems);
> +
> +	if (cpm_elem_count > 0) {
> +		int header_num = 3;
> +		int regs_bmp = cpm_elems[2];
> +		int reg_num = cpm_elem_count - header_num;
> +
> +		if (reg_num > CM36672_REGS_NUM)
> +			reg_num = CM36672_REGS_NUM;
> +		for (i = 0; i < reg_num; i++)
> +			if (regs_bmp & (1 << i))
> +				chip->regs[i] = cpm_elems[header_num + i];
> +	}
> +}
> +
> +#endif
> +
> +static int cm36672_regfield_init(struct cm36672_chip *chip)
> +{
> +	struct device *dev = &chip->client->dev;
> +	struct regmap *regmap = chip->regmap;
> +
> +	chip->reg_prx_int_lo = devm_regmap_field_alloc(dev, regmap,
> +					cm36672_reg_field_prx_int_lo);
> +	if (IS_ERR(chip->reg_prx_int_lo)) {
> +		dev_err(dev, "%s: reg_prx_int_lo init failed\n", __func__);
> +		return PTR_ERR(chip->reg_prx_int_lo);
> +	}
> +
> +	chip->reg_prx_int_hi = devm_regmap_field_alloc(dev, regmap,
> +					cm36672_reg_field_prx_int_hi);
> +	if (IS_ERR(chip->reg_prx_int_hi)) {
> +		dev_err(dev, "%s: reg_prx_int_hi init failed\n", __func__);
> +		return PTR_ERR(chip->reg_prx_int_hi);
> +	}
> +
> +	chip->reg_prx_it = devm_regmap_field_alloc(dev, regmap,
> +					cm36672_reg_field_prx_it);
> +	if (IS_ERR(chip->reg_prx_it)) {
> +		dev_err(dev, "%s: reg_prx_it init failed\n", __func__);
> +		return PTR_ERR(chip->reg_prx_it);
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * cm36672_setup_reg() - Initialize sensor to default values.
> + * @chip:	pointer of struct cm36672_chip
> + *
> + * Return: 0 for success; otherwise an error code.
> + */
> +static int cm36672_setup_reg(struct cm36672_chip *chip)
> +{
> +	int i, reg, ret;
> +	u16 prx_conf;
> +
> +	memcpy((char *)&chip->regs, (char *)&cm36672_regs_default,
> +		sizeof(cm36672_regs_default));
> +
> +#ifdef CONFIG_OF
> +	if (chip->client->dev.of_node)
> +		cm36672_parse_dt(chip);
> +#endif
> +
> +#ifdef CONFIG_ACPI
> +	if (ACPI_HANDLE(&chip->client->dev))
> +		cm36672_parse_acpi(chip);
> +#endif
> +
> +	/* Store regs[CM36672_ADDR_PRX_CONF] */
> +	prx_conf = chip->regs[CM36672_ADDR_PRX_CONF];
> +
> +	/* Disable INT when initialize registers */
> +	chip->regs[CM36672_ADDR_PRX_CONF] &= ~CM36672_PRX_INT_THDH;
> +	chip->regs[CM36672_ADDR_PRX_CONF] &= ~CM36672_PRX_INT_THDL;
> +
> +	for (i = 0; i < CM36672_REGS_NUM; i++) {
> +		ret = regmap_write(chip->regmap, i, chip->regs[i]);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	/* Restore regs[CM36672_ADDR_PRX_CONF] */
> +	chip->regs[CM36672_ADDR_PRX_CONF] = prx_conf;
> +
> +	/* Force to clear flags */
> +	ret = regmap_read(chip->regmap, CM36672_ADDR_STATUS, &reg);
> +	if (ret < 0) {
> +		dev_err(&chip->client->dev,
> +			"%s: Failed to read Status register, err= %d\n",
> +			__func__, ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * cm36672_irq_handler() - Interrupt handling routine.
> + * @irq:	irq number
> + * @private:	pointer of void
> + *
> + * Return: IRQ_HANDLED.
> + */
> +static irqreturn_t cm36672_irq_handler(int irq, void *private)
> +{
> +	struct iio_dev *indio_dev = private;
> +	struct cm36672_chip *chip = iio_priv(indio_dev);
> +	s64 timestamp = iio_get_time_ns();
> +	int ret, status;
> +
> +	ret = regmap_read(chip->regmap, CM36672_ADDR_STATUS, &status);
> +	if (ret < 0)
> +		return IRQ_HANDLED;
> +
> +	if (status & CM36672_INT_PRX_CLOSE)
> +		iio_push_event(indio_dev,
> +				IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
> +						IIO_EV_TYPE_THRESH,
> +						IIO_EV_DIR_RISING),
> +				timestamp);
> +
> +	if (status & CM36672_INT_PRX_AWAY)
> +		iio_push_event(indio_dev,
> +				IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
> +						IIO_EV_TYPE_THRESH,
> +						IIO_EV_DIR_FALLING),
> +				timestamp);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +/**
> + * cm36672_read_prx_it() - Get the current proximity integration time.
> + * @chip:	point of struct cm36672_chip
> + * @val:	point of the integer part of integration time
> + * @val2:	point of the micro part of integration time
> + *
> + * Return: IIO_VAL_INT_PLUS_MICRO for success; otherwise an error code.
> + */
> +static int cm36672_read_prx_it(struct cm36672_chip *chip, int *val, int *val2)
> +{
> +	int ret, index;
> +
> +	ret = regmap_field_read(chip->reg_prx_it, &index);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (index < 0 || index >= ARRAY_SIZE(cm36672_prx_it_scales))
> +		return -EINVAL;
> +
> +	*val = cm36672_prx_it_scales[index].val;
> +	*val2 = cm36672_prx_it_scales[index].val2;
> +
> +	return IIO_VAL_INT_PLUS_MICRO;
> +}
> +
> +/**
> + * cm36672_write_prx_it() - Set the proximity integration time.
> + * @chip:	point of struct cm36672_chip
> + * @val:	the integer part of integration time
> + * @val2:	the micro part of integration time
> + *
> + * Return: 0 for success; otherwise an error code.
> + */
> +static int cm36672_write_prx_it(struct cm36672_chip *chip, int val, int val2)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(cm36672_prx_it_scales); i++)
> +		if (val == cm36672_prx_it_scales[i].val &&
> +				val2 == cm36672_prx_it_scales[i].val2)
> +			return regmap_field_write(chip->reg_prx_it, i);
> +
> +	return -EINVAL;
> +}
> +
> +/**
> + * cm36672_prx_read() - Read from proximity sensor.
> + * @indio_dev:	point of struct iio_dev
> + * @chan:	point of struct iio_chan_spec
> + * @val:	point of integer
> + * @val2:	point of integer
> + * @mask:	iio_chan_info_enum
> + *
> + * Return: IIO_VAL type for success; otherwise an error code.
> + */
> +static int cm36672_prx_read(struct iio_dev *indio_dev,
> +			struct iio_chan_spec const *chan,
> +			int *val, int *val2, long mask)
> +{
> +	struct cm36672_chip *chip = iio_priv(indio_dev);
> +	int ret;
> +
> +	if (chan->type != IIO_PROXIMITY)
> +		return -EINVAL;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_INT_TIME:
> +		return cm36672_read_prx_it(chip, val, val2);
> +	case IIO_CHAN_INFO_RAW:
> +		ret = regmap_read(chip->regmap, CM36672_ADDR_PRX, val);
> +		if (ret < 0)
> +			return ret;
> +		return IIO_VAL_INT;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +/**
> + * cm36672_prx_write() - Write to proximity sensor.
> + * @indio_dev:	point of struct iio_dev
> + * @chan:	point of struct iio_chan_spec
> + * @val:	integer for integer part
> + * @val2:	integer for micro part
> + * @mask:	iio_chan_info_enum
> + *
> + * Return: Equal or great than 0 for success; otherwise an error code.
> + */
> +static int cm36672_prx_write(struct iio_dev *indio_dev,
> +			struct iio_chan_spec const *chan,
> +			int val, int val2, long mask)
> +{
> +	struct cm36672_chip *chip = iio_priv(indio_dev);
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_INT_TIME:
> +		return cm36672_write_prx_it(chip, val, val2);
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int cm36672_read_event(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 cm36672_chip *chip = iio_priv(indio_dev);
> +	int ret = -EINVAL;
> +
> +	if (info != IIO_EV_INFO_VALUE)
> +		return -EINVAL;
> +
> +	if (chan->type == IIO_PROXIMITY) {
> +		*val2 = 0;
> +		if (dir == IIO_EV_DIR_RISING)
> +			ret = regmap_read(chip->regmap, CM36672_ADDR_PRX_THDH,
> +					val);
> +		else if (dir == IIO_EV_DIR_FALLING)
> +			ret = regmap_read(chip->regmap, CM36672_ADDR_PRX_THDL,
> +					val);
> +	}
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	return IIO_VAL_INT;
> +}
> +
> +static int cm36672_write_event(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)
> +{
> +	int reg, ret = 0;
> +	int value, max;
> +	struct cm36672_chip *chip = iio_priv(indio_dev);
> +
> +	if (info != IIO_EV_INFO_VALUE)
> +		return -EINVAL;
> +
> +	if (chan->type == IIO_PROXIMITY) {
> +		ret = regmap_read(chip->regmap, CM36672_ADDR_PRX_CONF, &value);
> +		max = (value & CM36672_PRX_HD) ? 65535:4095;
> +		if (val < 0 || val > max)
> +			return -EINVAL;
> +		if (dir == IIO_EV_DIR_RISING)
> +			reg = CM36672_ADDR_PRX_THDH;
> +		else if (dir == IIO_EV_DIR_FALLING)
> +			reg = CM36672_ADDR_PRX_THDL;
> +		else
> +			return -EINVAL;
> +
> +		ret = regmap_write(chip->regmap, reg, val);
> +		if (ret < 0)
> +			return ret;
> +	} else
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static int cm36672_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 cm36672_chip *chip = iio_priv(indio_dev);
> +	int ret, state;
> +
> +	if (chan->type == IIO_PROXIMITY) {
> +		if (dir == IIO_EV_DIR_RISING)
> +			ret = regmap_field_read(chip->reg_prx_int_hi, &state);
> +		else if (dir == IIO_EV_DIR_FALLING)
> +			ret = regmap_field_read(chip->reg_prx_int_lo, &state);
> +		else
> +			return -EINVAL;
> +
> +		if (ret)
> +			return ret;
> +
> +		return state;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int cm36672_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 cm36672_chip *chip = iio_priv(indio_dev);
> +	int ret;
> +
> +	if (chan->type == IIO_PROXIMITY) {
> +		if (dir == IIO_EV_DIR_RISING)
> +			ret = regmap_field_write(chip->reg_prx_int_hi, state);
> +		else if (dir == IIO_EV_DIR_FALLING)
> +			ret = regmap_field_write(chip->reg_prx_int_lo, state);
> +		else
> +			return -EINVAL;
> +
> +		if (ret)
> +			return ret;
> +
> +		return 0;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static const struct iio_event_spec cm36672_prx_event_spec[] = {
> +	{
> +		.type = IIO_EV_TYPE_THRESH,
> +		.dir = IIO_EV_DIR_RISING,
> +		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
> +				BIT(IIO_EV_INFO_ENABLE),
> +	},
> +	{
> +		.type = IIO_EV_TYPE_THRESH,
> +		.dir = IIO_EV_DIR_FALLING,
> +		.mask_separate = BIT(IIO_EV_INFO_VALUE) |
> +				BIT(IIO_EV_INFO_ENABLE),
> +	},
> +};
> +
> +static const struct iio_chan_spec cm36672_channels[] = {
> +	{
> +		.type = IIO_PROXIMITY,
> +		.info_mask_separate =
> +			BIT(IIO_CHAN_INFO_RAW),
> +			BIT(IIO_CHAN_INFO_INT_TIME),
> +		.channel = 0,
> +		.indexed = 0,
> +		.scan_index = -1,
> +		.event_spec = cm36672_prx_event_spec,
> +		.num_event_specs = ARRAY_SIZE(cm36672_prx_event_spec),
> +	},
> +};
> +
> +static bool cm36672_is_writeable_reg(struct device *dev, unsigned int reg)
> +{
> +	if (reg <= CM36672_ADDR_PRX_THDH)
> +		return true;
> +	return false;
> +}
> +
> +static IIO_CONST_ATTR(in_proximity_integration_time_available,
> +		CM36672_PRX_INT_TIME_AVAIL);
> +
> +static struct attribute *cm36672_attributes[] = {
> +	&iio_const_attr_in_proximity_integration_time_available.dev_attr.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group cm36672_attribute_group = {
> +	.attrs = cm36672_attributes
> +};
> +
> +static const struct iio_info cm36672_info = {
> +	.driver_module		= THIS_MODULE,
> +	.read_raw		= &cm36672_prx_read,
> +	.write_raw		= &cm36672_prx_write,
> +	.attrs			= &cm36672_attribute_group,
> +	.read_event_value	= cm36672_read_event,
> +	.write_event_value	= cm36672_write_event,
> +	.read_event_config	= cm36672_read_event_config,
> +	.write_event_config	= cm36672_write_event_config,
> +};
> +
> +static const struct iio_info cm36672_info_no_irq = {
> +	.driver_module		= THIS_MODULE,
> +	.read_raw		= &cm36672_prx_read,
> +	.write_raw		= &cm36672_prx_write,
> +	.attrs			= &cm36672_attribute_group,
> +};
> +
> +static const struct regmap_config cm36672_regmap_config = {
> +	.name = CM36672_REGMAP_NAME,
> +	.reg_bits = 8,
> +	.val_bits = 16,
> +	.writeable_reg = cm36672_is_writeable_reg,
> +	.use_single_rw = true,
> +	.val_format_endian = REGMAP_ENDIAN_LITTLE,
> +};
> +
> +static int cm36672_probe(struct i2c_client *client,
> +			const struct i2c_device_id *id)
> +{
> +	struct cm36672_chip *chip;
> +	struct iio_dev *indio_dev;
> +	struct regmap *regmap;
> +	int ret;
> +
> +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	regmap = devm_regmap_init_i2c(client, &cm36672_regmap_config);
> +	if (IS_ERR(regmap)) {
> +		dev_err(&client->dev, "%s: regmap initialize failed\n",
> +			__func__);
> +		return PTR_ERR(regmap);
> +	}
> +
> +	chip = iio_priv(indio_dev);
> +	i2c_set_clientdata(client, indio_dev);
> +	chip->client = client;
> +	chip->regmap = regmap;
> +
> +	mutex_init(&chip->lock);
> +	indio_dev->dev.parent = &client->dev;
> +	indio_dev->channels = cm36672_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(cm36672_channels);
> +	indio_dev->name = CM36672_DRIVER_NAME;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	ret = cm36672_regfield_init(chip);
> +	if (ret) {
> +		dev_err(&client->dev, "%s: regfield init failed\n", __func__);
> +		return ret;
> +	}
> +
> +	ret = cm36672_setup_reg(chip);
> +	if (ret) {
> +		dev_err(&client->dev, "%s: register setup failed\n", __func__);
> +		return ret;
> +	}
> +
> +	if (client->irq) {
> +		indio_dev->info = &cm36672_info;
> +
> +		ret = request_threaded_irq(client->irq, NULL,
> +					cm36672_irq_handler,
> +					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> +					"cm36672", indio_dev);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"%s: request irq failed\n",
> +				__func__);
> +			return ret;
> +		}
> +
> +		/* Enable interrupt if default request*/
> +		if (chip->regs[CM36672_ADDR_PRX_CONF] & CM36672_PRX_INT_MASK) {
> +			ret = regmap_write(chip->regmap, CM36672_ADDR_PRX_CONF,
> +					chip->regs[CM36672_ADDR_PRX_CONF]);
> +			if (ret) {
> +				dev_err(&client->dev,
> +					"%s: enable interrupt failed\n",
> +					__func__);
> +				return ret;
> +			}
> +		}
> +	} else
> +		indio_dev->info = &cm36672_info_no_irq;
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret) {
> +		dev_err(&client->dev,
> +			"%s: registering device failed\n",
> +			__func__);
> +		if (client->irq)
> +			free_irq(client->irq, indio_dev);
> +	}
> +
> +	return 0;
> +}
> +
> +static int cm36672_remove(struct i2c_client *client)
> +{
> +	struct iio_dev *indio_dev = i2c_get_clientdata(client);
> +	struct cm36672_chip *chip = iio_priv(indio_dev);
> +
> +	iio_device_unregister(indio_dev);
> +	regmap_update_bits(chip->regmap, CM36672_ADDR_PRX_CONF, 1, 1);
> +	if (client->irq)
> +		free_irq(client->irq, indio_dev);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id cm36672_id[] = {
> +	{ "cm36672", 0 },
> +	{ }
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, cm36672_id);
> +
> +static const struct of_device_id cm36672_of_match[] = {
> +	{ .compatible = "capella,cm36672" },
> +	{ }
> +};
> +
> +#ifdef CONFIG_ACPI
> +static const struct acpi_device_id cm36672_acpi_match[] = {
> +	{ "CPLM6672", 0},
> +	{},
> +};
> +
> +MODULE_DEVICE_TABLE(acpi, cm36672_acpi_match);
> +#endif
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int cm36672_suspend(struct device *dev)
> +{
> +	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
> +	struct cm36672_chip *chip = iio_priv(indio_dev);
> +	struct i2c_client *client = chip->client;
> +	int i, ret;
> +
> +	for (i = 0; i < CM36672_REGS_NUM; i++) {
> +		ret = i2c_smbus_read_word_data(client, i);
> +		if (ret < 0)
> +			return ret;
> +		chip->regs[i] = ret;
> +	}
> +
> +	return regmap_update_bits(chip->regmap, CM36672_ADDR_PRX_CONF, 1, 1);
> +}
> +
> +static int cm36672_resume(struct device *dev)
> +{
> +	struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
> +	struct cm36672_chip *chip = iio_priv(indio_dev);
> +	struct i2c_client *client = chip->client;
> +	int i, ret;
> +
> +	for (i = 0; i < CM36672_REGS_NUM; i++) {
> +		ret = i2c_smbus_write_word_data(client, i, chip->regs[i]);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	return regmap_update_bits(chip->regmap, CM36672_ADDR_PRX_CONF, 1, 0);
> +}
> +
> +static const struct dev_pm_ops cm36672_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(cm36672_suspend, cm36672_resume)};
> +#endif
> +
> +static struct i2c_driver cm36672_driver = {
> +	.driver = {
> +		.name	= CM36672_DRIVER_NAME,
> +		.owner	= THIS_MODULE,
> +		.of_match_table = cm36672_of_match,
> +#ifdef CONFIG_ACPI
> +		.acpi_match_table = ACPI_PTR(cm36672_acpi_match),
> +#endif
> +#ifdef CONFIG_PM_SLEEP
> +		.pm	= &cm36672_pm_ops,
> +#endif
> +	},
> +	.id_table	= cm36672_id,
> +	.probe		= cm36672_probe,
> +	.remove		= cm36672_remove,
> +};
> +
> +module_i2c_driver(cm36672_driver);
> +
> +MODULE_AUTHOR("Kevin Tsai <capellamicro@xxxxxxxxx>");
> +MODULE_DESCRIPTION("CM36672 proximity sensor driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 1.8.3.1
> 
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux