From: Jonathan Cameron <jic23@kernel.org>
To: Linus Walleij <linus.walleij@linaro.org>
Cc: linux-iio@vger.kernel.org, "Hartmut Knaack" <knaack.h@gmx.de>,
"Lars-Peter Clausen" <lars@metafoo.de>,
"Peter Meerwald-Stadler" <pmeerw@pmeerw.net>,
linux-input@vger.kernel.org,
"Stephan Gerhold" <stephan@gerhold.net>,
"Minkyu Kang" <mk7.kang@samsung.com>,
"Paweł Chmiel" <pawel.mikolaj.chmiel@gmail.com>,
"Jonathan Bakker" <xc-racer2@live.ca>,
"Oskar Andero" <oskar.andero@gmail.com>,
"Dmitry Torokhov" <dmitry.torokhov@gmail.com>
Subject: Re: [PATCH 2/2 v6] iio: light: Add a driver for Sharp GP2AP002x00F
Date: Sun, 2 Feb 2020 15:27:42 +0000 [thread overview]
Message-ID: <20200202152742.073a4df6@archlinux> (raw)
In-Reply-To: <20200126150548.9168-2-linus.walleij@linaro.org>
On Sun, 26 Jan 2020 16:05:48 +0100
Linus Walleij <linus.walleij@linaro.org> wrote:
> This driver handles two different Sharp sensors that have been
> proposed for merging to the mainline kernel over the years, and
> already has a limited proximity-only driver in the input
> subsystem.
>
> These components are completely different from the confusingly
> similarly named Sharp GP2AP020A00F, for which we have a driver
> in drivers/iio/light/gp2ap020a00f.c
>
> The two components GP2AP002A00F and GP2AP002S00F are
> distinctively different but similar: they share the same set of
> registers but differ slightly in the I2C protocol.
>
> Instead of the approach by the previous input driver, we create
> a combined IIO proximity and light sensor driver.
>
> The plan is to merge this driver and delete the input driver.
>
> The pieces for the driver are picked all over the place after
> researching and grepping through a few different vendor trees
> and driver submissions.
>
> We merge it under the light sensors because:
>
> - It has similarities with the Capella CM3605 light sensor and
> proximity driver which is there.
>
> - It is related to the GP2AP020A00F driver which is also there.
>
> This driver was tested with the Samsung GT-S7710 mobile phone
> which has the GP2AP002S00F proximity sensor mounted.
>
> The suspend/resume cycle will disable the interrupt from the
> sensor. If someone would desire to use this interrupt for
> wakeup, the driver will need modifications.
>
> Cc: Stephan Gerhold <stephan@gerhold.net>
> Cc: Minkyu Kang <mk7.kang@samsung.com>
> Cc: Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>
> Cc: Jonathan Bakker <xc-racer2@live.ca>
> Cc: Oskar Andero <oskar.andero@gmail.com>
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Tested-by: Jonathan Bakker <xc-racer2@live.ca>
> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Hmm. There are a few minor things inline, but in the interests of
saving everyone time I'll just fix them up.
For the missing docs one I'll make something up based on what I think
they are. Please check!
Also it doesn't actually build..
drivers/iio/light/gp2ap002.c:760:26: error: ‘gp2ap002_id’ undeclared here (not in a function); did you mean ‘gp2ap002_info’?
760 | MODULE_DEVICE_TABLE(i2c, gp2ap002_id);
| ^~~~~~~~~~~
./include/linux/module.h:230:15: note: in definition of macro ‘MODULE_DEVICE_TABLE’
230 | extern typeof(name) __mod_##type##__##name##_device_table \
| ^~~~
./include/linux/module.h:230:21: error: ‘__mod_i2c__gp2ap002_id_device_table’ aliased to undefined symbol ‘gp2ap002_id’
230 | extern typeof(name) __mod_##type##__##name##_device_table \
| ^~~~~~
drivers/iio/light/gp2ap002.c:760:1: note: in expansion of macro ‘MODULE_DEVICE_TABLE’
760 | MODULE_DEVICE_TABLE(i2c, gp2ap002_id);
| ^~~~~~~~~~~~~~~~~~~
I'll fix that as well..
> ---
> ChangeLog v5->v6:
> - It turns out that the two chip variants can use the
> same regmap read/write functions, so simplify this and
> use just one regmap variant.
> - Some platforms will get interrupt storms/lockups during
> runtime suspend/resume, so disable the IRQ during suspend,
> re-enable it on resume.
> - Collected Jonathan Bakker's Tested-by.
> ChangeLog v4->v5:
> - Implement runtime PM according to the pattern in
> other drivers such as gyro/mpu3050-core.c, letting
> runtime PM also handle system PM (suspend/resume).
> - Implement the missing .read_event_config() and
> .write_event_config() callbacks.
> - When state 1 is passed to .write_event_config()
> grab runtime PM to power on the sensor and activate
> it, conversely drop runtime PM when state 0 is
> passed.
> ChangeLog v3->v4:
> - Realized I had to fix also the last two users of
> of_property_read_u32(). Replaced with
> device_property_read_u8(). Dropped unsigned byte check
> because it can't be anything else.
> - Change warnings about properties to "failed to obtain.."
> - Return the actual error code instead of -EINVAL
> - Move interpolation message to dev_dbg()
> - Return with error if we fail to enable regulators on
> the resume path.
> ChangeLog v2->v3:
> - Use the device property accessors to read the compatible
> string and drop the dependency on <linux/of.h> making the
> driver reusable with ACPI.
> - Add a paragraph with comments on the hysteresis values.
> ChangeLog v1->v2:
> - Drop ifdef CONFIG_OF and of_match_ptr() to encourage
> reuse with other OF-compliant hardware descriptions such
> as ACPI PRP001
> - Convert success print to dev_dbg()
> - Drop leftover debug read of VDD voltage
> - Create two different regmap buses for the two component variants
> and select the one we want depending on detected component
> - Use if (ret) on all regmap accesses
> - Fix up error handling in interrupt routine for proximity
> - Provide some rationale for the 20-30 ms delay in the irq handler
> - Use GENMASK() where applicable
> - Prefix all defines with GP2AP002_*
> - Drop buffer infrastructure from Kconfig
> - Staticize gp2ap002_illuminance_table
> ---
> MAINTAINERS | 8 +
> drivers/iio/light/Kconfig | 11 +
> drivers/iio/light/Makefile | 1 +
> drivers/iio/light/gp2ap002.c | 783 +++++++++++++++++++++++++++++++++++
> 4 files changed, 803 insertions(+)
> create mode 100644 drivers/iio/light/gp2ap002.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1372b4139ebd..dbd4236fc7f5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14893,6 +14893,14 @@ W: http://www.ibm.com/developerworks/linux/linux390/
> S: Supported
> F: net/smc/
>
> +SHARP GP2AP002A00F/GP2AP002S00F SENSOR DRIVER
> +M: Linus Walleij <linus.walleij@linaro.org>
> +L: linux-iio@vger.kernel.org
> +T: git git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git
> +S: Maintained
> +F: drivers/iio/light/gp2ap002.c
> +F: Documentation/devicetree/bindings/iio/light/sharp,gp2ap002.yaml
> +
> SHARP RJ54N1CB0C SENSOR DRIVER
> M: Jacopo Mondi <jacopo@jmondi.org>
> L: linux-media@vger.kernel.org
> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> index 9968f982fbc7..307412c79c53 100644
> --- a/drivers/iio/light/Kconfig
> +++ b/drivers/iio/light/Kconfig
> @@ -159,6 +159,17 @@ config IIO_CROS_EC_LIGHT_PROX
> To compile this driver as a module, choose M here:
> the module will be called cros_ec_light_prox.
>
> +config GP2AP002
> + tristate "Sharp GP2AP002 Proximity/ALS sensor"
> + depends on I2C
> + select REGMAP
> + help
> + Say Y here if you have a Sharp GP2AP002 proximity/ALS combo-chip
> + hooked to an I2C bus.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called gp2ap002.
> +
> config GP2AP020A00F
> tristate "Sharp GP2AP020A00F Proximity/ALS sensor"
> depends on I2C
> diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> index c98d1cefb861..264a76039006 100644
> --- a/drivers/iio/light/Makefile
> +++ b/drivers/iio/light/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_CM3323) += cm3323.o
> obj-$(CONFIG_CM3605) += cm3605.o
> obj-$(CONFIG_CM36651) += cm36651.o
> obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o
> +obj-$(CONFIG_GP2AP002) += gp2ap002.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/gp2ap002.c b/drivers/iio/light/gp2ap002.c
> new file mode 100644
> index 000000000000..f747a42882e0
> --- /dev/null
> +++ b/drivers/iio/light/gp2ap002.c
> @@ -0,0 +1,783 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * These are the two Sharp GP2AP002 variants supported by this driver:
> + * GP2AP002A00F Ambient Light and Proximity Sensor
> + * GP2AP002S00F Proximity Sensor
> + *
> + * Copyright (C) 2020 Linaro Ltd.
> + * Author: Linus Walleij <linus.walleij@linaro.org>
> + *
> + * Based partly on the code in Sony Ericssons GP2AP00200F driver by
> + * Courtney Cavin and Oskar Andero in drivers/input/misc/gp2ap002a00f.c
> + * Based partly on a Samsung misc driver submitted by
> + * Donggeun Kim & Minkyu Kang in 2011:
> + * https://lore.kernel.org/lkml/1315556546-7445-1-git-send-email-dg77.kim@samsung.com/
> + * Based partly on a submission by
> + * Jonathan Bakker and Paweł Chmiel in january 2019:
> + * https://lore.kernel.org/linux-input/20190125175045.22576-1-pawel.mikolaj.chmiel@gmail.com/
> + * Based partly on code from the Samsung GT-S7710 by <mjchen@sta.samsung.com>
> + * Based partly on the code in LG Electronics GP2AP00200F driver by
> + * Kenobi Lee <sungyoung.lee@lge.com> and EunYoung Cho <ey.cho@lge.com>
> + */
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +#include <linux/regmap.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/iio/events.h>
> +#include <linux/iio/consumer.h> /* To get our ADC channel */
> +#include <linux/iio/types.h> /* To deal with our ADC channel */
> +#include <linux/init.h>
> +#include <linux/delay.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/interrupt.h>
> +#include <linux/bits.h>
> +#include <linux/math64.h>
> +#include <linux/pm.h>
> +
> +#define GP2AP002_PROX_CHANNEL 0
> +#define GP2AP002_ALS_CHANNEL 1
> +
> +/* ------------------------------------------------------------------------ */
> +/* ADDRESS SYMBOL DATA Init R/W */
> +/* D7 D6 D5 D4 D3 D2 D1 D0 */
> +/* ------------------------------------------------------------------------ */
> +/* 0 PROX X X X X X X X VO H'00 R */
> +/* 1 GAIN X X X X LED0 X X X H'00 W */
> +/* 2 HYS HYSD HYSC1 HYSC0 X HYSF3 HYSF2 HYSF1 HYSF0 H'00 W */
> +/* 3 CYCLE X X CYCL2 CYCL1 CYCL0 OSC2 X X H'00 W */
> +/* 4 OPMOD X X X ASD X X VCON SSD H'00 W */
> +/* 6 CON X X X OCON1 OCON0 X X X H'00 W */
> +/* ------------------------------------------------------------------------ */
> +/* VO :Proximity sensing result(0: no detection, 1: detection) */
> +/* LED0 :Select switch for LED driver's On-registence(0:2x higher, 1:normal)*/
> +/* HYSD/HYSF :Adjusts the receiver sensitivity */
> +/* OSC :Select switch internal clocl frequency hoppling(0:effective) */
> +/* CYCL :Determine the detection cycle(typically 8ms, up to 128x) */
> +/* SSD :Software Shutdown function(0:shutdown, 1:operating) */
> +/* VCON :VOUT output method control(0:normal, 1:interrupt) */
> +/* ASD :Select switch for analog sleep function(0:ineffective, 1:effective)*/
> +/* OCON :Select switch for enabling/disabling VOUT (00:enable, 11:disable) */
> +
> +#define GP2AP002_PROX 0x00
> +#define GP2AP002_GAIN 0x01
> +#define GP2AP002_HYS 0x02
> +#define GP2AP002_CYCLE 0x03
> +#define GP2AP002_OPMOD 0x04
> +#define GP2AP002_CON 0x06
> +
> +#define GP2AP002_PROX_VO_DETECT BIT(0)
> +
> +/* Setting this bit to 0 means 2x higher LED resistance */
> +#define GP2AP002_GAIN_LED_NORMAL BIT(3)
> +
> +/*
> + * These bits adjusts the proximity sensitivity, determining characteristics
> + * of the detection distance and its hysteresis.
> + */
> +#define GP2AP002_HYS_HYSD_SHIFT 7
> +#define GP2AP002_HYS_HYSD_MASK BIT(7)
> +#define GP2AP002_HYS_HYSC_SHIFT 5
> +#define GP2AP002_HYS_HYSC_MASK GENMASK(6, 5)
> +#define GP2AP002_HYS_HYSF_SHIFT 0
> +#define GP2AP002_HYS_HYSF_MASK GENMASK(3, 0)
> +#define GP2AP002_HYS_MASK (GP2AP002_HYS_HYSD_MASK | \
> + GP2AP002_HYS_HYSC_MASK | \
> + GP2AP002_HYS_HYSF_MASK)
> +
> +/*
> + * These values determine the detection cycle response time
> + * 0: 8ms, 1: 16ms, 2: 32ms, 3: 64ms, 4: 128ms,
> + * 5: 256ms, 6: 512ms, 7: 1024ms
> + */
> +#define GP2AP002_CYCLE_CYCL_SHIFT 3
> +#define GP2AP002_CYCLE_CYCL_MASK GENMASK(5, 3)
> +
> +/*
> + * Select switch for internal clock frequency hopping
> + * 0: effective,
> + * 1: ineffective
> + */
> +#define GP2AP002_CYCLE_OSC_EFFECTIVE 0
> +#define GP2AP002_CYCLE_OSC_INEFFECTIVE BIT(2)
> +#define GP2AP002_CYCLE_OSC_MASK BIT(2)
> +
> +/* Analog sleep effective */
> +#define GP2AP002_OPMOD_ASD BIT(4)
> +/* Enable chip */
> +#define GP2AP002_OPMOD_SSD_OPERATING BIT(0)
> +/* IRQ mode */
> +#define GP2AP002_OPMOD_VCON_IRQ BIT(1)
> +#define GP2AP002_OPMOD_MASK (BIT(0) | BIT(1) | BIT(4))
> +
> +/*
> + * Select switch for enabling/disabling Vout pin
> + * 0: enable
> + * 2: force to go Low
> + * 3: force to go High
> + */
> +#define GP2AP002_CON_OCON_SHIFT 3
> +#define GP2AP002_CON_OCON_ENABLE (0x0 << GP2AP002_CON_OCON_SHIFT)
> +#define GP2AP002_CON_OCON_LOW (0x2 << GP2AP002_CON_OCON_SHIFT)
> +#define GP2AP002_CON_OCON_HIGH (0x3 << GP2AP002_CON_OCON_SHIFT)
> +#define GP2AP002_CON_OCON_MASK (0x3 << GP2AP002_CON_OCON_SHIFT)
> +
> +/**
> + * struct gp2ap002 - GP2AP002 state
> + * @map: regmap pointer for the i2c regmap
> + * @dev: pointer to parent device
> + * @vdd: regulator controlling VDD
> + * @vio: regulator controlling VIO
> + * @alsout: IIO ADC channel to convert the ALSOUT signal
Grumble.. Incomplete docs. Please send a follow up patch fixing that.
> + * @is_gp2ap002s00f: this is the GP2AP002F variant of the chip
> + * @irq: the IRQ line used by this device
> + * @enabled: we cannot read the status of the hardware so we need to
> + * keep track of whether the event is enabled using this state variable
> + */
> +struct gp2ap002 {
> + struct regmap *map;
> + struct device *dev;
> + struct regulator *vdd;
> + struct regulator *vio;
> + struct iio_channel *alsout;
> + enum iio_event_direction dir;
This one doesn't actually seem to be used so I dropped it rather than
make up the docs ;)
> + u8 hys_far;
> + u8 hys_close;
> + bool is_gp2ap002s00f;
> + int irq;
> + bool enabled;
> +};
> +
> +static irqreturn_t gp2ap002_prox_irq(int irq, void *d)
> +{
> + struct iio_dev *indio_dev = d;
> + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev);
> + u64 ev;
> + int val;
> + int ret;
> +
> + ret = regmap_read(gp2ap002->map, GP2AP002_PROX, &val);
> + if (ret) {
> + dev_err(gp2ap002->dev, "error reading proximity\n");
> + goto err_retrig;
> + }
> +
> + if (val & GP2AP002_PROX_VO_DETECT) {
> + /* Close */
> + dev_dbg(gp2ap002->dev, "close\n");
> + ret = regmap_write(gp2ap002->map, GP2AP002_HYS,
> + gp2ap002->hys_far);
> + if (ret)
> + dev_err(gp2ap002->dev,
> + "error setting up proximity hysteresis\n");
> + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, GP2AP002_PROX_CHANNEL,
> + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING);
> + } else {
> + /* Far */
> + dev_dbg(gp2ap002->dev, "far\n");
> + ret = regmap_write(gp2ap002->map, GP2AP002_HYS,
> + gp2ap002->hys_close);
> + if (ret)
> + dev_err(gp2ap002->dev,
> + "error setting up proximity hysteresis\n");
> + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, GP2AP002_PROX_CHANNEL,
> + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING);
> + }
> + iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev));
> +
> + /*
> + * After changing hysteresis, we need to wait for one detection
> + * cycle to see if anything changed, or we will just trigger the
> + * previous interrupt again. A detection cycle depends on the CYCLE
> + * register, we are hard-coding ~8 ms in probe() so wait some more
> + * than this, 20-30 ms.
> + */
> + usleep_range(20000, 30000);
> +
> +err_retrig:
> + ret = regmap_write(gp2ap002->map, GP2AP002_CON,
> + GP2AP002_CON_OCON_ENABLE);
> + if (ret)
> + dev_err(gp2ap002->dev, "error setting up VOUT control\n");
> +
> + return IRQ_HANDLED;
> +}
> +
> +struct gp2ap002_illuminance {
> + unsigned int curr;
> + unsigned int lux;
> +};
> +
> +/*
> + * This array maps current and lux.
> + *
> + * Ambient light sensing range is 3 to 55000 lux.
> + *
> + * This mapping is based on the following formula.
> + * illuminance = 10 ^ (current / 10)
> + */
> +static const struct gp2ap002_illuminance gp2ap002_illuminance_table[] = {
> + { .curr = 5, .lux = 3 },
> + { .curr = 6, .lux = 4 },
> + { .curr = 7, .lux = 5 },
> + { .curr = 8, .lux = 6 },
> + { .curr = 9, .lux = 8 },
> + { .curr = 10, .lux = 10 },
> + { .curr = 11, .lux = 12 },
> + { .curr = 12, .lux = 16 },
> + { .curr = 13, .lux = 20 },
> + { .curr = 14, .lux = 25 },
> + { .curr = 15, .lux = 32 },
> + { .curr = 16, .lux = 40 },
> + { .curr = 17, .lux = 50 },
> + { .curr = 18, .lux = 63 },
> + { .curr = 19, .lux = 79 },
> + { .curr = 20, .lux = 100 },
> + { .curr = 21, .lux = 126 },
> + { .curr = 22, .lux = 158 },
> + { .curr = 23, .lux = 200 },
> + { .curr = 24, .lux = 251 },
> + { .curr = 25, .lux = 316 },
> + { .curr = 26, .lux = 398 },
> + { .curr = 27, .lux = 501 },
> + { .curr = 28, .lux = 631 },
> + { .curr = 29, .lux = 794 },
> + { .curr = 30, .lux = 1000 },
> + { .curr = 31, .lux = 1259 },
> + { .curr = 32, .lux = 1585 },
> + { .curr = 33, .lux = 1995 },
> + { .curr = 34, .lux = 2512 },
> + { .curr = 35, .lux = 3162 },
> + { .curr = 36, .lux = 3981 },
> + { .curr = 37, .lux = 5012 },
> + { .curr = 38, .lux = 6310 },
> + { .curr = 39, .lux = 7943 },
> + { .curr = 40, .lux = 10000 },
> + { .curr = 41, .lux = 12589 },
> + { .curr = 42, .lux = 15849 },
> + { .curr = 43, .lux = 19953 },
> + { .curr = 44, .lux = 25119 },
> + { .curr = 45, .lux = 31623 },
> + { .curr = 46, .lux = 39811 },
> + { .curr = 47, .lux = 50119 },
> +};
> +
> +static int gp2ap002_get_lux(struct gp2ap002 *gp2ap002)
> +{
> + const struct gp2ap002_illuminance *ill1;
> + const struct gp2ap002_illuminance *ill2;
> + int ret, res;
> + int i;
> +
> + ret = iio_read_channel_processed(gp2ap002->alsout, &res);
> + if (ret < 0)
> + return ret;
> +
> + dev_dbg(gp2ap002->dev, "read %d mA from ADC\n", res);
> +
> + ill1 = &gp2ap002_illuminance_table[0];
> + if (res < ill1->curr) {
> + dev_dbg(gp2ap002->dev, "total darkness\n");
> + return 0;
> + }
> + for (i = 0; i < ARRAY_SIZE(gp2ap002_illuminance_table) - 1; i++) {
> + ill1 = &gp2ap002_illuminance_table[i];
> + ill2 = &gp2ap002_illuminance_table[i + 1];
> +
> + if (res > ill2->curr)
> + continue;
> + if ((res <= ill1->curr) && (res >= ill2->curr))
> + break;
> + }
> + if (res > ill2->curr) {
> + dev_info_once(gp2ap002->dev, "max current overflow\n");
> + return ill2->curr;
> + }
> + /* Interpolate and return */
> + dev_dbg(gp2ap002->dev, "interpolate index %d and %d\n", i, i + 1);
> + /* How many steps along the curve */
> + i = res - ill1->curr; /* x - x0 */
> + /* Linear interpolation */
> + return ill1->lux + i *
> + ((ill2->lux - ill1->lux) / (ill2->curr - ill1->curr));
> +}
> +
> +static int gp2ap002_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val, int *val2, long mask)
> +{
> + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev);
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + switch (chan->type) {
> + case IIO_LIGHT:
> + ret = gp2ap002_get_lux(gp2ap002);
> + if (ret < 0)
> + return ret;
> + *val = ret;
> + return IIO_VAL_INT;
> + default:
> + return -EINVAL;
> + }
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int gp2ap002_init(struct gp2ap002 *gp2ap002)
> +{
> + int ret;
> +
> + /* Set up the IR LED resistance */
> + ret = regmap_write(gp2ap002->map, GP2AP002_GAIN,
> + GP2AP002_GAIN_LED_NORMAL);
> + if (ret) {
> + dev_err(gp2ap002->dev, "error setting up LED gain\n");
> + return ret;
> + }
> + ret = regmap_write(gp2ap002->map, GP2AP002_HYS, gp2ap002->hys_far);
> + if (ret) {
> + dev_err(gp2ap002->dev,
> + "error setting up proximity hysteresis\n");
> + return ret;
> + }
> +
> + /* Disable internal frequency hopping */
> + ret = regmap_write(gp2ap002->map, GP2AP002_CYCLE,
> + GP2AP002_CYCLE_OSC_INEFFECTIVE);
> + if (ret) {
> + dev_err(gp2ap002->dev,
> + "error setting up internal frequency hopping\n");
> + return ret;
> + }
> +
> + /* Enable chip and IRQ, disable analog sleep */
> + ret = regmap_write(gp2ap002->map, GP2AP002_OPMOD,
> + GP2AP002_OPMOD_SSD_OPERATING |
> + GP2AP002_OPMOD_VCON_IRQ);
> + if (ret) {
> + dev_err(gp2ap002->dev, "error setting up operation mode\n");
> + return ret;
> + }
> +
> + /* Interrupt on VOUT enabled */
> + ret = regmap_write(gp2ap002->map, GP2AP002_CON,
> + GP2AP002_CON_OCON_ENABLE);
> + if (ret)
> + dev_err(gp2ap002->dev, "error setting up VOUT control\n");
> +
> + return ret;
> +}
> +
> +static int gp2ap002_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 gp2ap002 *gp2ap002 = iio_priv(indio_dev);
> +
> + /*
> + * We just keep track of this internally, as it is not possible to
> + * query the hardware.
> + */
> + return gp2ap002->enabled;
> +}
> +
> +static int gp2ap002_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 gp2ap002 *gp2ap002 = iio_priv(indio_dev);
> +
> + if (state) {
> + /*
> + * This will bring the regulators up (unless they are on
> + * already) and reintialize the sensor by using runtime_pm
> + * callbacks.
> + */
> + pm_runtime_get_sync(gp2ap002->dev);
> + gp2ap002->enabled = true;
> + } else {
> + pm_runtime_mark_last_busy(gp2ap002->dev);
> + pm_runtime_put_autosuspend(gp2ap002->dev);
> + gp2ap002->enabled = false;
> + }
> +
> + return 0;
> +}
> +
> +static const struct iio_info gp2ap002_info = {
> + .read_raw = gp2ap002_read_raw,
> + .read_event_config = gp2ap002_read_event_config,
> + .write_event_config = gp2ap002_write_event_config,
> +};
> +
> +static const struct iio_event_spec gp2ap002_events[] = {
> + {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_EITHER,
> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> + },
> +};
> +
> +static const struct iio_chan_spec gp2ap002_channels[] = {
> + {
> + .type = IIO_PROXIMITY,
> + .event_spec = gp2ap002_events,
> + .num_event_specs = ARRAY_SIZE(gp2ap002_events),
> + },
> + {
> + .type = IIO_LIGHT,
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> + .channel = GP2AP002_ALS_CHANNEL,
> + },
> +};
> +
> +/*
> + * We need a special regmap because this hardware expects to
> + * write single bytes to registers but read a 16bit word on some
> + * variants and discard the lower 8 bits so combine
> + * i2c_smbus_read_word_data() with i2c_smbus_write_byte_data()
> + * selectively like this.
> + */
> +static int gp2ap002_regmap_i2c_read(void *context, unsigned int reg,
> + unsigned int *val)
> +{
> + struct device *dev = context;
> + struct i2c_client *i2c = to_i2c_client(dev);
> + int ret;
> +
> + ret = i2c_smbus_read_word_data(i2c, reg);
> + if (ret < 0)
> + return ret;
> +
> + *val = (ret >> 8) & 0xFF;
> +
> + return 0;
> +}
> +
> +static int gp2ap002_regmap_i2c_write(void *context, unsigned int reg,
> + unsigned int val)
> +{
> + struct device *dev = context;
> + struct i2c_client *i2c = to_i2c_client(dev);
> +
> + return i2c_smbus_write_byte_data(i2c, reg, val);
> +}
> +
> +static struct regmap_bus gp2ap002_regmap_bus = {
> + .reg_read = gp2ap002_regmap_i2c_read,
> + .reg_write = gp2ap002_regmap_i2c_write,
> +};
> +
> +static int gp2ap002_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct gp2ap002 *gp2ap002;
> + struct iio_dev *indio_dev;
> + struct device *dev = &client->dev;
> + enum iio_chan_type ch_type;
> + static const struct regmap_config config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .max_register = GP2AP002_CON,
> + };
> + struct regmap *regmap;
> + int num_chan;
> + const char *compat;
> + u8 val;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*gp2ap002));
> + if (!indio_dev)
> + return -ENOMEM;
> + i2c_set_clientdata(client, indio_dev);
> +
> + gp2ap002 = iio_priv(indio_dev);
> + gp2ap002->dev = dev;
> +
> + /*
> + * Check the device compatible like this makes it possible to use
> + * ACPI PRP0001 for registering the sensor using device tree
> + * properties.
> + */
> + ret = device_property_read_string(dev, "compatible", &compat);
> + if (ret) {
> + dev_err(dev, "cannot check compatible\n");
> + return ret;
> + }
> + gp2ap002->is_gp2ap002s00f = !strcmp(compat, "sharp,gp2ap002s00f");
> +
> + regmap = devm_regmap_init(dev, &gp2ap002_regmap_bus, dev, &config);
> + if (IS_ERR(regmap)) {
> + dev_err(dev, "Failed to register i2c regmap %d\n",
> + (int)PTR_ERR(regmap));
> + return PTR_ERR(regmap);
> + }
> + gp2ap002->map = regmap;
> +
> + /*
> + * The hysteresis settings are coded into the device tree as values
> + * to be written into the hysteresis register. The datasheet defines
> + * modes "A", "B1" and "B2" with fixed values to be use but vendor
> + * code trees for actual devices are tweaking these values and refer to
> + * modes named things like "B1.5". To be able to support any devices,
> + * we allow passing an arbitrary hysteresis setting for "near" and
> + * "far".
> + */
> +
> + /* Check the device tree for the IR LED hysteresis */
> + ret = device_property_read_u8(dev, "sharp,proximity-far-hysteresis",
> + &val);
> + if (ret) {
> + dev_err(dev, "failed to obtain proximity far setting\n");
> + return ret;
> + }
> + dev_dbg(dev, "proximity far setting %02x\n", val);
> + gp2ap002->hys_far = val;
> +
> + ret = device_property_read_u8(dev, "sharp,proximity-close-hysteresis",
> + &val);
> + if (ret) {
> + dev_err(dev, "failed to obtain proximity close setting\n");
> + return ret;
> + }
> + dev_dbg(dev, "proximity close setting %02x\n", val);
> + gp2ap002->hys_close = val;
> +
> + /* The GP2AP002A00F has a light sensor too */
> + if (!gp2ap002->is_gp2ap002s00f) {
> + gp2ap002->alsout = devm_iio_channel_get(dev, "alsout");
> + if (IS_ERR(gp2ap002->alsout)) {
> + if (PTR_ERR(gp2ap002->alsout) == -ENODEV) {
> + dev_err(dev, "no ADC, deferring...\n");
> + return -EPROBE_DEFER;
> + }
> + dev_err(dev, "failed to get ALSOUT ADC channel\n");
> + return PTR_ERR(gp2ap002->alsout);
> + }
> + ret = iio_get_channel_type(gp2ap002->alsout, &ch_type);
> + if (ret < 0)
> + return ret;
> + if (ch_type != IIO_CURRENT) {
> + dev_err(dev,
> + "wrong type of IIO channel specified for ALSOUT\n");
> + return -EINVAL;
> + }
> + }
> +
> + gp2ap002->vdd = devm_regulator_get(dev, "vdd");
> + if (IS_ERR(gp2ap002->vdd)) {
> + dev_err(dev, "failed to get VDD regulator\n");
> + return PTR_ERR(gp2ap002->vdd);
> + }
> + gp2ap002->vio = devm_regulator_get(dev, "vio");
> + if (IS_ERR(gp2ap002->vio)) {
> + dev_err(dev, "failed to get VIO regulator\n");
> + return PTR_ERR(gp2ap002->vio);
> + }
> +
> + /* Operating voltage 2.4V .. 3.6V according to datasheet */
> + ret = regulator_set_voltage(gp2ap002->vdd, 2400000, 3600000);
> + if (ret) {
> + dev_err(dev, "failed to sett VDD voltage\n");
> + return ret;
> + }
> +
> + /* VIO should be between 1.65V and VDD */
> + ret = regulator_get_voltage(gp2ap002->vdd);
> + if (ret < 0) {
> + dev_err(dev, "failed to get VIO voltage\n");
VDD. I'll fix that up if I don't find anything else.
> + return ret;
> + }
> + ret = regulator_set_voltage(gp2ap002->vio, 1650000, ret);
> + if (ret) {
> + dev_err(dev, "failed to set VIO voltage\n");
> + return ret;
> + }
> +
> + ret = regulator_enable(gp2ap002->vdd);
> + if (ret) {
> + dev_err(dev, "failed to enable VDD regulator\n");
> + return ret;
> + }
> + ret = regulator_enable(gp2ap002->vio);
> + if (ret) {
> + dev_err(dev, "failed to enable VIO regulator\n");
> + goto out_disable_vdd;
> + }
> +
> + msleep(20);
> +
> + /*
> + * Initialize the device and signal to runtime PM that now we are
> + * definately up and using power.
> + */
> + ret = gp2ap002_init(gp2ap002);
> + if (ret) {
> + dev_err(dev, "initialization failed\n");
> + goto out_disable_vio;
> + }
> + pm_runtime_get_noresume(dev);
> + pm_runtime_set_active(dev);
> + pm_runtime_enable(dev);
> + gp2ap002->enabled = false;
> +
> + ret = devm_request_threaded_irq(dev, client->irq, NULL,
> + gp2ap002_prox_irq, IRQF_ONESHOT,
> + "gp2ap002", indio_dev);
> + if (ret) {
> + dev_err(dev, "unable to request IRQ\n");
> + goto out_disable_vio;
> + }
> + gp2ap002->irq = client->irq;
> +
> + /*
> + * As the device takes 20 ms + regulator delay to come up with a fresh
> + * measurement after power-on, do not shut it down unnecessarily.
> + * Set autosuspend to a one second.
> + */
> + pm_runtime_set_autosuspend_delay(dev, 1000);
> + pm_runtime_use_autosuspend(dev);
> + pm_runtime_put(dev);
> +
> + indio_dev->dev.parent = dev;
> + indio_dev->info = &gp2ap002_info;
> + indio_dev->name = "gp2ap002";
> + indio_dev->channels = gp2ap002_channels;
> + /* Skip light channel for the proximity-only sensor */
> + num_chan = ARRAY_SIZE(gp2ap002_channels);
> + if (gp2ap002->is_gp2ap002s00f)
> + num_chan--;
> + indio_dev->num_channels = num_chan;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + ret = iio_device_register(indio_dev);
> + if (ret)
> + goto out_disable_pm;
> + dev_dbg(dev, "Sharp GP2AP002 probed successfully\n");
> +
> + return 0;
> +
> +out_disable_pm:
> + pm_runtime_put_noidle(dev);
> + pm_runtime_disable(dev);
> +out_disable_vio:
> + regulator_disable(gp2ap002->vio);
> +out_disable_vdd:
> + regulator_disable(gp2ap002->vdd);
> + return ret;
> +}
> +
> +static int gp2ap002_remove(struct i2c_client *client)
> +{
> + struct iio_dev *indio_dev = i2c_get_clientdata(client);
> + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev);
> + struct device *dev = &client->dev;
> +
> + pm_runtime_get_sync(dev);
> + pm_runtime_put_noidle(dev);
> + pm_runtime_disable(dev);
> + iio_device_unregister(indio_dev);
> + regulator_disable(gp2ap002->vio);
> + regulator_disable(gp2ap002->vdd);
> +
> + return 0;
> +}
> +
> +static int __maybe_unused gp2ap002_runtime_suspend(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev);
> + int ret;
> +
> + /* Deactivate the IRQ */
> + disable_irq(gp2ap002->irq);
> +
> + /* Disable chip and IRQ, everything off */
> + ret = regmap_write(gp2ap002->map, GP2AP002_OPMOD, 0x00);
> + if (ret) {
> + dev_err(gp2ap002->dev, "error setting up operation mode\n");
> + return ret;
> + }
> + /*
> + * As these regulators may be shared, at least we are now in
> + * sleep even if the regulators aren't really turned off.
> + */
> + regulator_disable(gp2ap002->vio);
> + regulator_disable(gp2ap002->vdd);
> +
> + return 0;
> +}
> +
> +static int __maybe_unused gp2ap002_runtime_resume(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> + struct gp2ap002 *gp2ap002 = iio_priv(indio_dev);
> + int ret;
> +
> + ret = regulator_enable(gp2ap002->vdd);
> + if (ret) {
> + dev_err(dev, "failed to enable VDD regulator in resume path\n");
> + return ret;
> + }
> + ret = regulator_enable(gp2ap002->vio);
> + if (ret) {
> + dev_err(dev, "failed to enable VIO regulator in resume path\n");
> + return ret;
> + }
> +
> + msleep(20);
> +
> + ret = gp2ap002_init(gp2ap002);
> + if (ret) {
> + dev_err(dev, "re-initialization failed\n");
> + return ret;
> + }
> +
> + /* Re-activate the IRQ */
> + enable_irq(gp2ap002->irq);
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops gp2ap002_dev_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> + pm_runtime_force_resume)
> + SET_RUNTIME_PM_OPS(gp2ap002_runtime_suspend,
> + gp2ap002_runtime_resume, NULL)
> +};
> +
> +static const struct i2c_device_id gp2ap002_id_table[] = {
> + { "gp2ap002", 0 },
> + { },
> +};
> +MODULE_DEVICE_TABLE(i2c, gp2ap002_id);
> +
> +static const struct of_device_id gp2ap002_of_match[] = {
> + { .compatible = "sharp,gp2ap002a00f" },
> + { .compatible = "sharp,gp2ap002s00f" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, gp2ap002_of_match);
> +
> +static struct i2c_driver gp2ap002_driver = {
> + .driver = {
> + .name = "gp2ap002",
> + .of_match_table = gp2ap002_of_match,
> + .pm = &gp2ap002_dev_pm_ops,
> + },
> + .probe = gp2ap002_probe,
> + .remove = gp2ap002_remove,
> + .id_table = gp2ap002_id_table,
> +};
> +module_i2c_driver(gp2ap002_driver);
> +
> +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
> +MODULE_DESCRIPTION("GP2AP002 ambient light and proximity sensor driver");
> +MODULE_LICENSE("GPL v2");
next prev parent reply other threads:[~2020-02-02 15:27 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-01-26 15:05 [PATCH 1/2 v6] iio: light: Add DT bindings for GP2AP002 Linus Walleij
2020-01-26 15:05 ` [PATCH 2/2 v6] iio: light: Add a driver for Sharp GP2AP002x00F Linus Walleij
2020-02-02 15:27 ` Jonathan Cameron [this message]
2020-02-08 11:47 ` Linus Walleij
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20200202152742.073a4df6@archlinux \
--to=jic23@kernel.org \
--cc=dmitry.torokhov@gmail.com \
--cc=knaack.h@gmx.de \
--cc=lars@metafoo.de \
--cc=linus.walleij@linaro.org \
--cc=linux-iio@vger.kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=mk7.kang@samsung.com \
--cc=oskar.andero@gmail.com \
--cc=pawel.mikolaj.chmiel@gmail.com \
--cc=pmeerw@pmeerw.net \
--cc=stephan@gerhold.net \
--cc=xc-racer2@live.ca \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).