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