109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
28afa505cSLinus Walleij /*
38afa505cSLinus Walleij * CM3605 Ambient Light and Proximity Sensor
48afa505cSLinus Walleij *
58afa505cSLinus Walleij * Copyright (C) 2016 Linaro Ltd.
68afa505cSLinus Walleij * Author: Linus Walleij <linus.walleij@linaro.org>
78afa505cSLinus Walleij *
88afa505cSLinus Walleij * This hardware was found in the very first Nexus One handset from Google/HTC
98afa505cSLinus Walleij * and an early endavour into mobile light and proximity sensors.
108afa505cSLinus Walleij */
118afa505cSLinus Walleij
128afa505cSLinus Walleij #include <linux/module.h>
13fdb726c4SJonathan Cameron #include <linux/mod_devicetable.h>
148afa505cSLinus Walleij #include <linux/iio/iio.h>
158afa505cSLinus Walleij #include <linux/iio/sysfs.h>
168afa505cSLinus Walleij #include <linux/iio/events.h>
178afa505cSLinus Walleij #include <linux/iio/consumer.h> /* To get our ADC channel */
188afa505cSLinus Walleij #include <linux/iio/types.h> /* To deal with our ADC channel */
198afa505cSLinus Walleij #include <linux/init.h>
208afa505cSLinus Walleij #include <linux/leds.h>
218afa505cSLinus Walleij #include <linux/platform_device.h>
22fdb726c4SJonathan Cameron #include <linux/property.h>
238afa505cSLinus Walleij #include <linux/regulator/consumer.h>
248afa505cSLinus Walleij #include <linux/gpio/consumer.h>
258afa505cSLinus Walleij #include <linux/interrupt.h>
268afa505cSLinus Walleij #include <linux/math64.h>
278afa505cSLinus Walleij #include <linux/pm.h>
288afa505cSLinus Walleij
298afa505cSLinus Walleij #define CM3605_PROX_CHANNEL 0
308afa505cSLinus Walleij #define CM3605_ALS_CHANNEL 1
318afa505cSLinus Walleij #define CM3605_AOUT_TYP_MAX_MV 1550
328afa505cSLinus Walleij /* It should not go above 1.650V according to the data sheet */
338afa505cSLinus Walleij #define CM3605_AOUT_MAX_MV 1650
348afa505cSLinus Walleij
358afa505cSLinus Walleij /**
368afa505cSLinus Walleij * struct cm3605 - CM3605 state
378afa505cSLinus Walleij * @dev: pointer to parent device
388afa505cSLinus Walleij * @vdd: regulator controlling VDD
398afa505cSLinus Walleij * @aset: sleep enable GPIO, high = sleep
408afa505cSLinus Walleij * @aout: IIO ADC channel to convert the AOUT signal
418afa505cSLinus Walleij * @als_max: maximum LUX detection (depends on RSET)
428afa505cSLinus Walleij * @dir: proximity direction: start as FALLING
438afa505cSLinus Walleij * @led: trigger for the infrared LED used by the proximity sensor
448afa505cSLinus Walleij */
458afa505cSLinus Walleij struct cm3605 {
468afa505cSLinus Walleij struct device *dev;
478afa505cSLinus Walleij struct regulator *vdd;
488afa505cSLinus Walleij struct gpio_desc *aset;
498afa505cSLinus Walleij struct iio_channel *aout;
508afa505cSLinus Walleij s32 als_max;
518afa505cSLinus Walleij enum iio_event_direction dir;
528afa505cSLinus Walleij struct led_trigger *led;
538afa505cSLinus Walleij };
548afa505cSLinus Walleij
cm3605_prox_irq(int irq,void * d)558afa505cSLinus Walleij static irqreturn_t cm3605_prox_irq(int irq, void *d)
568afa505cSLinus Walleij {
578afa505cSLinus Walleij struct iio_dev *indio_dev = d;
588afa505cSLinus Walleij struct cm3605 *cm3605 = iio_priv(indio_dev);
598afa505cSLinus Walleij u64 ev;
608afa505cSLinus Walleij
618afa505cSLinus Walleij ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, CM3605_PROX_CHANNEL,
628afa505cSLinus Walleij IIO_EV_TYPE_THRESH, cm3605->dir);
638afa505cSLinus Walleij iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev));
648afa505cSLinus Walleij
658afa505cSLinus Walleij /* Invert the edge for each event */
668afa505cSLinus Walleij if (cm3605->dir == IIO_EV_DIR_RISING)
678afa505cSLinus Walleij cm3605->dir = IIO_EV_DIR_FALLING;
688afa505cSLinus Walleij else
698afa505cSLinus Walleij cm3605->dir = IIO_EV_DIR_RISING;
708afa505cSLinus Walleij
718afa505cSLinus Walleij return IRQ_HANDLED;
728afa505cSLinus Walleij }
738afa505cSLinus Walleij
cm3605_get_lux(struct cm3605 * cm3605)748afa505cSLinus Walleij static int cm3605_get_lux(struct cm3605 *cm3605)
758afa505cSLinus Walleij {
768afa505cSLinus Walleij int ret, res;
778afa505cSLinus Walleij s64 lux;
788afa505cSLinus Walleij
798afa505cSLinus Walleij ret = iio_read_channel_processed(cm3605->aout, &res);
808afa505cSLinus Walleij if (ret < 0)
818afa505cSLinus Walleij return ret;
828afa505cSLinus Walleij
838afa505cSLinus Walleij dev_dbg(cm3605->dev, "read %d mV from ADC\n", res);
848afa505cSLinus Walleij
858afa505cSLinus Walleij /*
868afa505cSLinus Walleij * AOUT has an offset of ~30mV then linear at dark
878afa505cSLinus Walleij * then goes from 2.54 up to 650 LUX yielding 1.55V
888afa505cSLinus Walleij * (1550 mV) so scale the returned value to this interval
898afa505cSLinus Walleij * using simple linear interpolation.
908afa505cSLinus Walleij */
918afa505cSLinus Walleij if (res < 30)
928afa505cSLinus Walleij return 0;
938afa505cSLinus Walleij if (res > CM3605_AOUT_MAX_MV)
948afa505cSLinus Walleij dev_err(cm3605->dev, "device out of range\n");
958afa505cSLinus Walleij
968afa505cSLinus Walleij /* Remove bias */
978afa505cSLinus Walleij lux = res - 30;
988afa505cSLinus Walleij
998afa505cSLinus Walleij /* Linear interpolation between 0 and ALS typ max */
1008afa505cSLinus Walleij lux *= cm3605->als_max;
1018afa505cSLinus Walleij lux = div64_s64(lux, CM3605_AOUT_TYP_MAX_MV);
1028afa505cSLinus Walleij
1038afa505cSLinus Walleij return lux;
1048afa505cSLinus Walleij }
1058afa505cSLinus Walleij
cm3605_read_raw(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,int * val,int * val2,long mask)1068afa505cSLinus Walleij static int cm3605_read_raw(struct iio_dev *indio_dev,
1078afa505cSLinus Walleij struct iio_chan_spec const *chan,
1088afa505cSLinus Walleij int *val, int *val2, long mask)
1098afa505cSLinus Walleij {
1108afa505cSLinus Walleij struct cm3605 *cm3605 = iio_priv(indio_dev);
1118afa505cSLinus Walleij int ret;
1128afa505cSLinus Walleij
1138afa505cSLinus Walleij switch (mask) {
1148afa505cSLinus Walleij case IIO_CHAN_INFO_RAW:
1158afa505cSLinus Walleij switch (chan->type) {
1168afa505cSLinus Walleij case IIO_LIGHT:
1178afa505cSLinus Walleij ret = cm3605_get_lux(cm3605);
1188afa505cSLinus Walleij if (ret < 0)
1198afa505cSLinus Walleij return ret;
1208afa505cSLinus Walleij *val = ret;
1218afa505cSLinus Walleij return IIO_VAL_INT;
1228afa505cSLinus Walleij default:
1238afa505cSLinus Walleij return -EINVAL;
1248afa505cSLinus Walleij }
1258afa505cSLinus Walleij default:
1268afa505cSLinus Walleij return -EINVAL;
1278afa505cSLinus Walleij }
1288afa505cSLinus Walleij }
1298afa505cSLinus Walleij
1308afa505cSLinus Walleij static const struct iio_info cm3605_info = {
1318afa505cSLinus Walleij .read_raw = cm3605_read_raw,
1328afa505cSLinus Walleij };
1338afa505cSLinus Walleij
1348afa505cSLinus Walleij static const struct iio_event_spec cm3605_events[] = {
1358afa505cSLinus Walleij {
1368afa505cSLinus Walleij .type = IIO_EV_TYPE_THRESH,
1378afa505cSLinus Walleij .dir = IIO_EV_DIR_EITHER,
1388afa505cSLinus Walleij .mask_separate = BIT(IIO_EV_INFO_ENABLE),
1398afa505cSLinus Walleij },
1408afa505cSLinus Walleij };
1418afa505cSLinus Walleij
1428afa505cSLinus Walleij static const struct iio_chan_spec cm3605_channels[] = {
1438afa505cSLinus Walleij {
1448afa505cSLinus Walleij .type = IIO_PROXIMITY,
1458afa505cSLinus Walleij .event_spec = cm3605_events,
1468afa505cSLinus Walleij .num_event_specs = ARRAY_SIZE(cm3605_events),
1478afa505cSLinus Walleij },
1488afa505cSLinus Walleij {
1498afa505cSLinus Walleij .type = IIO_LIGHT,
1508afa505cSLinus Walleij .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
1518afa505cSLinus Walleij .channel = CM3605_ALS_CHANNEL,
1528afa505cSLinus Walleij },
1538afa505cSLinus Walleij };
1548afa505cSLinus Walleij
cm3605_probe(struct platform_device * pdev)1558afa505cSLinus Walleij static int cm3605_probe(struct platform_device *pdev)
1568afa505cSLinus Walleij {
1578afa505cSLinus Walleij struct cm3605 *cm3605;
1588afa505cSLinus Walleij struct iio_dev *indio_dev;
1598afa505cSLinus Walleij struct device *dev = &pdev->dev;
1608afa505cSLinus Walleij enum iio_chan_type ch_type;
1618afa505cSLinus Walleij u32 rset;
1620d31d91eSCai Huoqing int irq;
1638afa505cSLinus Walleij int ret;
1648afa505cSLinus Walleij
1658afa505cSLinus Walleij indio_dev = devm_iio_device_alloc(dev, sizeof(*cm3605));
1668afa505cSLinus Walleij if (!indio_dev)
1678afa505cSLinus Walleij return -ENOMEM;
1688afa505cSLinus Walleij platform_set_drvdata(pdev, indio_dev);
1698afa505cSLinus Walleij
1708afa505cSLinus Walleij cm3605 = iio_priv(indio_dev);
1718afa505cSLinus Walleij cm3605->dev = dev;
1728afa505cSLinus Walleij cm3605->dir = IIO_EV_DIR_FALLING;
1738afa505cSLinus Walleij
174fdb726c4SJonathan Cameron ret = device_property_read_u32(dev, "capella,aset-resistance-ohms", &rset);
1758afa505cSLinus Walleij if (ret) {
1768afa505cSLinus Walleij dev_info(dev, "no RSET specified, assuming 100K\n");
1778afa505cSLinus Walleij rset = 100000;
1788afa505cSLinus Walleij }
1798afa505cSLinus Walleij switch (rset) {
1808afa505cSLinus Walleij case 50000:
1818afa505cSLinus Walleij cm3605->als_max = 650;
1828afa505cSLinus Walleij break;
1838afa505cSLinus Walleij case 100000:
1848afa505cSLinus Walleij cm3605->als_max = 300;
1858afa505cSLinus Walleij break;
1868afa505cSLinus Walleij case 300000:
1878afa505cSLinus Walleij cm3605->als_max = 100;
1888afa505cSLinus Walleij break;
1898afa505cSLinus Walleij case 600000:
1908afa505cSLinus Walleij cm3605->als_max = 50;
1918afa505cSLinus Walleij break;
1928afa505cSLinus Walleij default:
1938afa505cSLinus Walleij dev_info(dev, "non-standard resistance\n");
1948afa505cSLinus Walleij return -EINVAL;
1958afa505cSLinus Walleij }
1968afa505cSLinus Walleij
1978afa505cSLinus Walleij cm3605->aout = devm_iio_channel_get(dev, "aout");
1988afa505cSLinus Walleij if (IS_ERR(cm3605->aout)) {
1990d31d91eSCai Huoqing ret = PTR_ERR(cm3605->aout);
2000d31d91eSCai Huoqing ret = (ret == -ENODEV) ? -EPROBE_DEFER : ret;
2010d31d91eSCai Huoqing return dev_err_probe(dev, ret, "failed to get AOUT ADC channel\n");
2028afa505cSLinus Walleij }
2038afa505cSLinus Walleij ret = iio_get_channel_type(cm3605->aout, &ch_type);
2048afa505cSLinus Walleij if (ret < 0)
2058afa505cSLinus Walleij return ret;
2068afa505cSLinus Walleij if (ch_type != IIO_VOLTAGE) {
2078afa505cSLinus Walleij dev_err(dev, "wrong type of IIO channel specified for AOUT\n");
2088afa505cSLinus Walleij return -EINVAL;
2098afa505cSLinus Walleij }
2108afa505cSLinus Walleij
2118afa505cSLinus Walleij cm3605->vdd = devm_regulator_get(dev, "vdd");
2120d31d91eSCai Huoqing if (IS_ERR(cm3605->vdd))
2130d31d91eSCai Huoqing return dev_err_probe(dev, PTR_ERR(cm3605->vdd),
2140d31d91eSCai Huoqing "failed to get VDD regulator\n");
2150d31d91eSCai Huoqing
2168afa505cSLinus Walleij ret = regulator_enable(cm3605->vdd);
2178afa505cSLinus Walleij if (ret) {
2188afa505cSLinus Walleij dev_err(dev, "failed to enable VDD regulator\n");
2198afa505cSLinus Walleij return ret;
2208afa505cSLinus Walleij }
2218afa505cSLinus Walleij
2228afa505cSLinus Walleij cm3605->aset = devm_gpiod_get(dev, "aset", GPIOD_OUT_HIGH);
2238afa505cSLinus Walleij if (IS_ERR(cm3605->aset)) {
2240d31d91eSCai Huoqing ret = dev_err_probe(dev, PTR_ERR(cm3605->aset), "no ASET GPIO\n");
2258afa505cSLinus Walleij goto out_disable_vdd;
2268afa505cSLinus Walleij }
2278afa505cSLinus Walleij
2280d31d91eSCai Huoqing irq = platform_get_irq(pdev, 0);
22916090554SChristophe JAILLET if (irq < 0) {
230*089c1e11SRuan Jinjie ret = irq;
23116090554SChristophe JAILLET goto out_disable_aset;
23216090554SChristophe JAILLET }
2330d31d91eSCai Huoqing
2340d31d91eSCai Huoqing ret = devm_request_threaded_irq(dev, irq, cm3605_prox_irq,
2350d31d91eSCai Huoqing NULL, 0, "cm3605", indio_dev);
2368afa505cSLinus Walleij if (ret) {
2378afa505cSLinus Walleij dev_err(dev, "unable to request IRQ\n");
2388afa505cSLinus Walleij goto out_disable_aset;
2398afa505cSLinus Walleij }
2408afa505cSLinus Walleij
2418afa505cSLinus Walleij /* Just name the trigger the same as the driver */
2428afa505cSLinus Walleij led_trigger_register_simple("cm3605", &cm3605->led);
2438afa505cSLinus Walleij led_trigger_event(cm3605->led, LED_FULL);
2448afa505cSLinus Walleij
2458afa505cSLinus Walleij indio_dev->info = &cm3605_info;
2468afa505cSLinus Walleij indio_dev->name = "cm3605";
2478afa505cSLinus Walleij indio_dev->channels = cm3605_channels;
2488afa505cSLinus Walleij indio_dev->num_channels = ARRAY_SIZE(cm3605_channels);
2498afa505cSLinus Walleij indio_dev->modes = INDIO_DIRECT_MODE;
2508afa505cSLinus Walleij
2518afa505cSLinus Walleij ret = iio_device_register(indio_dev);
2528afa505cSLinus Walleij if (ret)
2538afa505cSLinus Walleij goto out_remove_trigger;
2548afa505cSLinus Walleij dev_info(dev, "Capella Microsystems CM3605 enabled range 0..%d LUX\n",
2558afa505cSLinus Walleij cm3605->als_max);
2568afa505cSLinus Walleij
2578afa505cSLinus Walleij return 0;
2588afa505cSLinus Walleij
2598afa505cSLinus Walleij out_remove_trigger:
2608afa505cSLinus Walleij led_trigger_event(cm3605->led, LED_OFF);
2618afa505cSLinus Walleij led_trigger_unregister_simple(cm3605->led);
2628afa505cSLinus Walleij out_disable_aset:
2638afa505cSLinus Walleij gpiod_set_value_cansleep(cm3605->aset, 0);
2648afa505cSLinus Walleij out_disable_vdd:
2658afa505cSLinus Walleij regulator_disable(cm3605->vdd);
2668afa505cSLinus Walleij return ret;
2678afa505cSLinus Walleij }
2688afa505cSLinus Walleij
cm3605_remove(struct platform_device * pdev)2698afa505cSLinus Walleij static int cm3605_remove(struct platform_device *pdev)
2708afa505cSLinus Walleij {
2718afa505cSLinus Walleij struct iio_dev *indio_dev = platform_get_drvdata(pdev);
2728afa505cSLinus Walleij struct cm3605 *cm3605 = iio_priv(indio_dev);
2738afa505cSLinus Walleij
2748afa505cSLinus Walleij led_trigger_event(cm3605->led, LED_OFF);
2758afa505cSLinus Walleij led_trigger_unregister_simple(cm3605->led);
2768afa505cSLinus Walleij gpiod_set_value_cansleep(cm3605->aset, 0);
2778afa505cSLinus Walleij iio_device_unregister(indio_dev);
2788afa505cSLinus Walleij regulator_disable(cm3605->vdd);
2798afa505cSLinus Walleij
2808afa505cSLinus Walleij return 0;
2818afa505cSLinus Walleij }
2828afa505cSLinus Walleij
cm3605_pm_suspend(struct device * dev)283dc0258e3SJonathan Cameron static int cm3605_pm_suspend(struct device *dev)
2848afa505cSLinus Walleij {
2858afa505cSLinus Walleij struct iio_dev *indio_dev = dev_get_drvdata(dev);
2868afa505cSLinus Walleij struct cm3605 *cm3605 = iio_priv(indio_dev);
2878afa505cSLinus Walleij
2888afa505cSLinus Walleij led_trigger_event(cm3605->led, LED_OFF);
2898afa505cSLinus Walleij regulator_disable(cm3605->vdd);
2908afa505cSLinus Walleij
2918afa505cSLinus Walleij return 0;
2928afa505cSLinus Walleij }
2938afa505cSLinus Walleij
cm3605_pm_resume(struct device * dev)294dc0258e3SJonathan Cameron static int cm3605_pm_resume(struct device *dev)
2958afa505cSLinus Walleij {
2968afa505cSLinus Walleij struct iio_dev *indio_dev = dev_get_drvdata(dev);
2978afa505cSLinus Walleij struct cm3605 *cm3605 = iio_priv(indio_dev);
2988afa505cSLinus Walleij int ret;
2998afa505cSLinus Walleij
3008afa505cSLinus Walleij ret = regulator_enable(cm3605->vdd);
3018afa505cSLinus Walleij if (ret)
3028afa505cSLinus Walleij dev_err(dev, "failed to enable regulator in resume path\n");
3038afa505cSLinus Walleij led_trigger_event(cm3605->led, LED_FULL);
3048afa505cSLinus Walleij
3058afa505cSLinus Walleij return 0;
3068afa505cSLinus Walleij }
307dc0258e3SJonathan Cameron static DEFINE_SIMPLE_DEV_PM_OPS(cm3605_dev_pm_ops, cm3605_pm_suspend,
308dc0258e3SJonathan Cameron cm3605_pm_resume);
3098afa505cSLinus Walleij
3108afa505cSLinus Walleij static const struct of_device_id cm3605_of_match[] = {
3118afa505cSLinus Walleij {.compatible = "capella,cm3605"},
3128afa505cSLinus Walleij { },
3138afa505cSLinus Walleij };
3148afa505cSLinus Walleij MODULE_DEVICE_TABLE(of, cm3605_of_match);
3158afa505cSLinus Walleij
3168afa505cSLinus Walleij static struct platform_driver cm3605_driver = {
3178afa505cSLinus Walleij .driver = {
3188afa505cSLinus Walleij .name = "cm3605",
3198afa505cSLinus Walleij .of_match_table = cm3605_of_match,
320dc0258e3SJonathan Cameron .pm = pm_sleep_ptr(&cm3605_dev_pm_ops),
3218afa505cSLinus Walleij },
3228afa505cSLinus Walleij .probe = cm3605_probe,
3238afa505cSLinus Walleij .remove = cm3605_remove,
3248afa505cSLinus Walleij };
3258afa505cSLinus Walleij module_platform_driver(cm3605_driver);
3268afa505cSLinus Walleij
3278afa505cSLinus Walleij MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
3288afa505cSLinus Walleij MODULE_DESCRIPTION("CM3605 ambient light and proximity sensor driver");
3298afa505cSLinus Walleij MODULE_LICENSE("GPL");
330