From 01ca5c5a9b9836bfe9f88cb9cda0d13748bdc66a Mon Sep 17 00:00:00 2001 From: Olivier Karasangabo Date: Wed, 25 Mar 2020 21:04:58 +0100 Subject: [PATCH] misc: Import SIM_DETECT driver * Sony package version: 52.1.A.0.532 Change-Id: I1537e8c6d1332d72fb7b077ce6ac287fff5efbde Signed-off-by: Sorayukii --- drivers/misc/Kconfig | 9 + drivers/misc/Makefile | 2 + drivers/misc/sim_detect.c | 536 +++++++++++++++++++++++++++++++++++++ include/linux/sim_detect.h | 32 +++ 4 files changed, 579 insertions(+) create mode 100644 drivers/misc/sim_detect.c create mode 100644 include/linux/sim_detect.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index a3fdeec52e1e..e6c2fcc4527b 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -343,6 +343,15 @@ config SGI_GRU_DEBUG This option enables additional debugging code for the SGI GRU driver. If you are unsure, say N. +config SIM_DETECT + tristate "SIM_DETECT driver support" + default n + help + Say Y here + if you want to support SIM_DETECT as detection device. + This driver can be used for sending notifications via + switch_class when sim card is inserted or removed. + config APDS9802ALS tristate "Medfield Avago APDS9802 ALS Sensor module" depends on I2C diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index f432eb02276a..518e7f239180 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -69,6 +69,8 @@ obj-$(CONFIG_VXR200_XR_MISC) += vxr7200.o obj-$(CONFIG_LDO_VIBRATOR) += ldo_vibrator.o +obj-$(CONFIG_SIM_DETECT) += sim_detect.o + lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o lkdtm-$(CONFIG_LKDTM) += lkdtm_heap.o diff --git a/drivers/misc/sim_detect.c b/drivers/misc/sim_detect.c new file mode 100644 index 000000000000..94df1a7f0cf7 --- /dev/null +++ b/drivers/misc/sim_detect.c @@ -0,0 +1,536 @@ +/* misc/sim_detect/sim_detect.c + * + * Copyright (C) 2017 Sony Mobile Communications Inc. + * + * Author: Atsushi Iyogi + * + * 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; either version 2 + * of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIM_DETECT_DEV_NAME "sim_detect" + +struct sim_detect_event_data { + const struct sim_detect_gpio_event *event; + struct timer_list det_timer; + struct work_struct det_work; + unsigned int timer_debounce; + unsigned int irq; +}; + +struct sim_detect_drvdata { + struct device *dev; + struct pinctrl *key_pinctrl; + struct switch_dev sim_detect; + struct mutex lock; + atomic_t detection_in_progress; + unsigned int n_events; + unsigned int current_state; + struct sim_detect_event_data data[0]; +}; + +enum sim_detect_switch_state { + SWITCH_OFF, + SWITCH_ON, +}; + +enum sim_report_state { + NOTHING_HAPPENED = 0, + SIM_REMOVED = 1, + SIM_INSERTED = 2, +}; + +static int sim_detect_gpio_read(struct sim_detect_drvdata *ddata) +{ + int i; + int gpio_state = 0; + + for (i = 0; i < ddata->n_events; i++) + gpio_state += + (gpio_get_value_cansleep(ddata->data[i].event->gpio) + ^ ddata->data[i].event->active_low ? + SWITCH_ON : SWITCH_OFF) << i; + return gpio_state; +} + +static void sim_detect_report_switch_event(struct sim_detect_drvdata *ddata) +{ + int new_state = 0; + int report_state = NOTHING_HAPPENED; + + mutex_lock(&ddata->lock); + + new_state = sim_detect_gpio_read(ddata); + dev_dbg(ddata->dev, "%s: current value(%d) new value(%d)\n", + __func__, ddata->current_state, new_state); + + if (new_state == ddata->current_state) + goto skip_report; + + if (new_state < ddata->current_state) + report_state = SIM_REMOVED; + else if (new_state > ddata->current_state) + report_state = SIM_INSERTED; + + dev_info(ddata->dev, "%s: report (%d)\n", __func__, report_state); + switch_set_state(&ddata->sim_detect, report_state); + ddata->current_state = new_state; + +skip_report: + mutex_unlock(&ddata->lock); +} + +static int sim_detect_get_devtree(struct device *dev, + struct sim_detect_platform_data *pdata) +{ + struct device_node *node, *pp = NULL; + int i = 0; + struct sim_detect_gpio_event *events; + u32 reg; + int gpio; + int ret = -ENODEV; + enum of_gpio_flags flags; + + node = dev->of_node; + if (node == NULL) + goto fail; + + memset(pdata, 0, sizeof(*pdata)); + + pdata->n_events = 0; + pp = NULL; + while ((pp = of_get_next_child(node, pp))) + pdata->n_events++; + + if (pdata->n_events == 0) + goto fail; + + events = kzalloc(pdata->n_events * sizeof(*events), GFP_KERNEL); + if (!events) { + ret = -ENOMEM; + goto fail; + } + + while ((pp = of_get_next_child(node, pp))) { + + if (!of_find_property(pp, "gpios", NULL)) { + pdata->n_events--; + dev_warn(dev, "Found button without gpios\n"); + continue; + } + + gpio = of_get_gpio_flags(pp, 0, &flags); + if (!gpio_is_valid(gpio)) { + dev_err(dev, "%s: invalid gpio %d\n", __func__, gpio); + goto out_fail; + } + events[i].index = i; + events[i].gpio = gpio; + events[i].active_low = flags & OF_GPIO_ACTIVE_LOW; + + events[i].desc = of_get_property(pp, "label", NULL); + + if (of_property_read_u32(pp, "debounce-interval", ®) == 0) + events[i].debounce_interval = reg; + i++; + } + pdata->events = events; + + return 0; + +out_fail: + kfree(events); +fail: + return ret; +} + +static irqreturn_t sim_detect_isr(int irq, void *data) +{ + struct sim_detect_event_data *edata = + (struct sim_detect_event_data *)data; + const struct sim_detect_gpio_event *event = edata->event; + struct sim_detect_drvdata *ddata = container_of(edata, + struct sim_detect_drvdata, + data[event->index]); + + if (edata->timer_debounce) + mod_timer(&edata->det_timer, + jiffies + msecs_to_jiffies(edata->timer_debounce)); + else + schedule_work(&edata->det_work); + + atomic_set(&ddata->detection_in_progress, 1); + + return IRQ_HANDLED; +} + +static void sim_detect_det_tmr_func(unsigned long func_data) +{ + struct sim_detect_event_data *edata = + (struct sim_detect_event_data *)func_data; + + schedule_work(&edata->det_work); +} + +static void sim_detect_det_work(struct work_struct *work) +{ + struct sim_detect_event_data *edata = + container_of(work, struct sim_detect_event_data, det_work); + const struct sim_detect_gpio_event *event = edata->event; + struct sim_detect_drvdata *ddata = container_of(edata, + struct sim_detect_drvdata, + data[event->index]); + + sim_detect_report_switch_event(ddata); + + atomic_set(&ddata->detection_in_progress, 0); +} + +static int sim_detect_pinctrl_configure(struct sim_detect_drvdata *ddata, + bool active) +{ + struct pinctrl_state *set_state; + int retval; + + if (active) { + set_state = + pinctrl_lookup_state(ddata->key_pinctrl, + "tlmm_sim_detect_active"); + if (IS_ERR(set_state)) { + dev_err(ddata->dev, + "cannot get ts pinctrl active state\n"); + goto lookup_err; + } + } else { + set_state = + pinctrl_lookup_state(ddata->key_pinctrl, + "tlmm_sim_detect_suspend"); + if (IS_ERR(set_state)) { + dev_err(ddata->dev, + "cannot get gpiokey pinctrl sleep state\n"); + goto lookup_err; + } + } + retval = pinctrl_select_state(ddata->key_pinctrl, set_state); + if (retval) { + dev_err(ddata->dev, + "cannot set ts pinctrl active state\n"); + goto select_err; + } + + return 0; + +lookup_err: + return PTR_ERR(set_state); +select_err: + return retval; +} + +static int sim_detect_setup_event(struct platform_device *pdev, + struct sim_detect_event_data *edata, + const struct sim_detect_gpio_event *event) +{ + const char *desc = event->desc ? event->desc : SIM_DETECT_DEV_NAME; + struct device *dev = &pdev->dev; + irq_handler_t isr; + unsigned long irqflags; + int irq, error; + + edata->event = event; + + error = gpio_request(event->gpio, desc); + if (error < 0) + dev_warn(dev, "Failed to request GPIO %d, error %d\n", + event->gpio, error); + + error = gpio_direction_input(event->gpio); + if (error < 0) { + dev_err(dev, + "Failed to configure direction for GPIO %d, error %d\n", + event->gpio, error); + goto fail; + } + + edata->timer_debounce = event->debounce_interval; + + irq = gpio_to_irq(event->gpio); + if (irq < 0) { + error = irq; + dev_err(dev, + "Unable to get irq number for GPIO %d, error %d\n", + event->gpio, error); + goto fail; + } + edata->irq = irq; + + isr = sim_detect_isr; + irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; +#ifdef CONFIG_TRAY_SHARED_INTERRUPT_DETECT + irqflags |= IRQF_SHARED; +#endif + + INIT_WORK(&edata->det_work, sim_detect_det_work); + + setup_timer(&edata->det_timer, + sim_detect_det_tmr_func, (unsigned long)edata); + + error = request_any_context_irq(edata->irq, isr, irqflags, desc, edata); + if (error < 0) { + dev_err(dev, "Unable to claim irq %d; error %d\n", + event->irq, error); + goto fail; + } + enable_irq_wake(edata->irq); + + return 0; + +fail: + gpio_free(event->gpio); + return error; +} + +static void sim_detect_remove_event(struct sim_detect_event_data *edata) +{ + free_irq(edata->irq, edata); + if (edata->timer_debounce) + del_timer_sync(&edata->det_timer); + cancel_work_sync(&edata->det_work); + if (gpio_is_valid(edata->event->gpio)) + gpio_free(edata->event->gpio); +} + +static int sim_detect_set_switch_device(struct sim_detect_drvdata *ddata) +{ + int error = 0; + + ddata->sim_detect.name = SIM_DETECT_DEV_NAME; + ddata->sim_detect.state = SWITCH_OFF; + error = switch_dev_register(&ddata->sim_detect); + if (error) { + dev_err(ddata->dev, "%s cannot regist lid(%d)\n", + __func__, error); + } + return error; +} + +static ssize_t sim_attrs_current_state_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sim_detect_drvdata *ddata = dev_get_drvdata(dev); + return snprintf(buf, PAGE_SIZE, "%d\n", ddata->current_state); +} + +static struct device_attribute sim_state_attr[] = { + __ATTR(sim_state, S_IRUGO, sim_attrs_current_state_read, NULL), +}; + +static int sim_detect_probe(struct platform_device *pdev) +{ + struct sim_detect_platform_data *pdata = pdev->dev.platform_data; + struct sim_detect_platform_data alt_pdata; + const struct sim_detect_gpio_event *event; + struct sim_detect_event_data *edata; + struct sim_detect_drvdata *ddata; + int i = 0; + int error = 0; + struct pinctrl_state *set_state; + + if (!pdata) { + error = sim_detect_get_devtree(&pdev->dev, &alt_pdata); + if (error) + goto fail; + pdata = &alt_pdata; + } + + ddata = kzalloc(sizeof(struct sim_detect_drvdata) + + pdata->n_events * sizeof(struct sim_detect_event_data), + GFP_KERNEL); + if (!ddata) { + dev_err(&pdev->dev, "failed to allocate drvdata in probe\n"); + error = -ENOMEM; + goto fail; + } + + ddata->dev = &pdev->dev; + ddata->n_events = pdata->n_events; + ddata->key_pinctrl = devm_pinctrl_get(ddata->dev); + mutex_init(&ddata->lock); + + platform_set_drvdata(pdev, ddata); + dev_set_drvdata(ddata->dev, ddata); + + if (IS_ERR(ddata->key_pinctrl)) { + if (PTR_ERR(ddata->key_pinctrl) == -EPROBE_DEFER) + goto fail_pinctrl; + pr_debug("Target does not use pinctrl\n"); + ddata->key_pinctrl = NULL; + } + + if (ddata->key_pinctrl) { + error = sim_detect_pinctrl_configure(ddata, true); + if (error) { + dev_err(ddata->dev, + "cannot set ts pinctrl active state\n"); + goto fail_pinctrl; + } + } + + error = sim_detect_set_switch_device(ddata); + if (error) { + dev_err(ddata->dev, "%s cannot set switch dev(%d)\n", + __func__, error); + goto fail_set_switch; + } + + error = device_create_file(ddata->dev, sim_state_attr); + if (error) { + dev_err(ddata->dev, "%s: create_file failed %d\n", + __func__, error); + goto fail_device_create_file; + } + + for (i = 0; i < pdata->n_events; i++) { + event = &pdata->events[i]; + edata = &ddata->data[i]; + + error = sim_detect_setup_event(pdev, edata, event); + if (error) { + dev_err(ddata->dev, "%s cannot set event error(%d)\n", + __func__, error); + goto fail_setup_event; + } + } + ddata->current_state = sim_detect_gpio_read(ddata); + + dev_warn(ddata->dev, "sim_detect driver was successful.\n"); + return 0; + +fail_setup_event: + device_remove_file(ddata->dev, sim_state_attr); +fail_device_create_file: + switch_dev_unregister(&ddata->sim_detect); +fail_set_switch: + if (ddata->key_pinctrl) { + set_state = + pinctrl_lookup_state(ddata->key_pinctrl, + "tlmm_sim_detect_suspend"); + if (IS_ERR(set_state)) + dev_err(ddata->dev, "cannot get pinctrl sleep state\n"); + else + pinctrl_select_state(ddata->key_pinctrl, set_state); + } + while (--i >= 0) + sim_detect_remove_event(&ddata->data[i]); + platform_set_drvdata(pdev, NULL); +fail_pinctrl: + mutex_destroy(&ddata->lock); + kzfree(ddata); +fail: + return error; +} + +static int sim_detect_remove(struct platform_device *pdev) +{ + int i; + struct sim_detect_drvdata *ddata = platform_get_drvdata(pdev); + + device_remove_file(ddata->dev, sim_state_attr); + switch_dev_unregister(&ddata->sim_detect); + mutex_destroy(&ddata->lock); + for (i = 0; i < ddata->n_events; i++) + sim_detect_remove_event(&ddata->data[i]); + if (!pdev->dev.platform_data) + kfree(ddata->data[0].event); + kzfree(ddata); + + return 0; +} + +static int sim_detect_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct sim_detect_drvdata *ddata = platform_get_drvdata(pdev); + int ret = 0; + + if (atomic_read(&ddata->detection_in_progress)) { + dev_dbg(&pdev->dev, "detection in progress. (%s)\n", + __func__); + ret = -EAGAIN; + goto out; + } + + if (ddata->key_pinctrl) { + ret = sim_detect_pinctrl_configure(ddata, false); + if (ret) + dev_err(&pdev->dev, "failed to put the pin\n"); + } + +out: + return ret; +} + +static int sim_detect_resume(struct platform_device *pdev) +{ + struct sim_detect_drvdata *ddata = platform_get_drvdata(pdev); + int ret = 0; + + if (ddata->key_pinctrl) { + ret = sim_detect_pinctrl_configure(ddata, true); + if (ret) + dev_err(&pdev->dev, "failed to put the pin\n"); + } + + return ret; +} + +static struct of_device_id sim_detect_match_table[] = { + { .compatible = "sim-detect", + }, + {} +}; + +static struct platform_driver sim_detect_driver = { + .probe = sim_detect_probe, + .remove = sim_detect_remove, + .suspend = sim_detect_suspend, + .resume = sim_detect_resume, + .driver = { + .name = SIM_DETECT_DEV_NAME, + .owner = THIS_MODULE, + .of_match_table = sim_detect_match_table, + }, +}; + +static int __init sim_detect_init(void) +{ + return platform_driver_register(&sim_detect_driver); +} + +static void __exit sim_detect_exit(void) +{ + platform_driver_unregister(&sim_detect_driver); +} + +late_initcall(sim_detect_init); +module_exit(sim_detect_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/sim_detect.h b/include/linux/sim_detect.h new file mode 100644 index 000000000000..93b98bd24b12 --- /dev/null +++ b/include/linux/sim_detect.h @@ -0,0 +1,32 @@ +/* misc/sim_detect/sim_detect.h + * + * Copyright (C) 2017 Sony Mobile Communications Inc. + * + * Author: Atsushi Iyogi + * + * 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; either version 2 + * of the License, or (at your option) any later version. + */ + +#ifndef _LINUX_SIM_DETECT_H +#define _LINUX_SIM_DETECT_H + +struct device; + +struct sim_detect_gpio_event { + int index; + int gpio; + int active_low; + const char *desc; + int debounce_interval; + unsigned int irq; +}; + +struct sim_detect_platform_data { + int n_events; + struct sim_detect_gpio_event *events; +}; + +#endif