/** * sec-detect-conn.c * * Copyright (C) 2017 Samsung Electronics * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #define DEBUG_FOR_SECDETECT #include static int detect_conn_enabled; struct detect_conn_info *gpinfo; #define SEC_CONN_PRINT(format, ...) pr_info("[SEC_Detect_Conn] " format, ##__VA_ARGS__) #if defined(CONFIG_SEC_FACTORY) #ifdef CONFIG_OF static const struct of_device_id sec_detect_conn_dt_match[] = { { .compatible = "samsung,sec_detect_conn" }, { } }; #endif static int sec_detect_conn_pm_suspend(struct device *dev) { return 0; } static int sec_detect_conn_pm_resume(struct device *dev) { return 0; } static int sec_detect_conn_remove(struct platform_device *pdev) { return 0; } static const struct dev_pm_ops sec_detect_conn_pm = { .suspend = sec_detect_conn_pm_suspend, .resume = sec_detect_conn_pm_resume, }; /** * Send uevent from irq handler. */ void send_uevent_irq(int irq, struct detect_conn_info *pinfo, int type) { char *uevent_conn_str[3] = {"", "", NULL}; char uevent_dev_str[UEVENT_CONN_MAX_DEV_NAME]; char uevent_dev_type_str[UEVENT_CONN_MAX_DEV_NAME]; int i; /*Send Uevent Data*/ for (i = 0; i < pinfo->pdata->gpio_cnt; i++) { if (irq == pinfo->pdata->irq_number[i]) { if (gpio_get_value(pinfo->pdata->irq_gpio[i])) { SEC_CONN_PRINT("%s status changed.\n", pinfo->pdata->name[i]); sprintf(uevent_dev_str, "CONNECTOR_NAME=%s", pinfo->pdata->name[i]); if (type == IRQ_TYPE_EDGE_RISING) { sprintf(uevent_dev_type_str, "CONNECTOR_TYPE=RISING_EDGE"); SEC_CONN_PRINT("send uevent irq[%d]:CONNECTOR_NAME=%s,CONNECTOR_TYPE=RISING_EDGE.\n" , irq, pinfo->pdata->name[i]); } else if (type == IRQ_TYPE_EDGE_FALLING) { sprintf(uevent_dev_type_str, "CONNECTOR_TYPE=FALLING_EDGE"); SEC_CONN_PRINT("send uevent irq[%d]:CONNECTOR_NAME=%s,CONNECTOR_TYPE=FALLING_EDGE.\n" , irq, pinfo->pdata->name[i]); } else if (type == IRQ_TYPE_EDGE_BOTH) { sprintf(uevent_dev_type_str, "CONNECTOR_TYPE=EDGE_BOTH"); SEC_CONN_PRINT("send uevent irq[%d]:CONNECTOR_NAME=%s,CONNECTOR_TYPE=ALL_EDGE.\n" , irq, pinfo->pdata->name[i]); } else { SEC_CONN_PRINT("Err:Unknown type irq : irq[%d]:CONNECTOR_NAME=%s,CONNECTOR_TYPE=%d.\n" , irq, pinfo->pdata->name[i], type); return; } uevent_conn_str[0] = uevent_dev_str; uevent_conn_str[1] = uevent_dev_type_str; kobject_uevent_env(&pinfo->dev->kobj, KOBJ_CHANGE, uevent_conn_str); } } } } /** * Send an uevent about given gpio pin number. */ void send_uevent_by_num(int num, struct detect_conn_info *pinfo, int level) { char *uevent_conn_str[3] = {"", "", NULL}; char uevent_dev_str[UEVENT_CONN_MAX_DEV_NAME]; char uevent_dev_type_str[UEVENT_CONN_MAX_DEV_NAME]; /*Send Uevent Data*/ sprintf(uevent_dev_str, "CONNECTOR_NAME=%s", pinfo->pdata->name[num]); uevent_conn_str[0] = uevent_dev_str; if (level == 1) sprintf(uevent_dev_type_str, "CONNECTOR_TYPE=HIGH_LEVEL"); else if (level == 0) sprintf(uevent_dev_type_str, "CONNECTOR_TYPE=LOW_LEVEL"); uevent_conn_str[1] = uevent_dev_type_str; kobject_uevent_env(&pinfo->dev->kobj, KOBJ_CHANGE, uevent_conn_str); if (level == 1) SEC_CONN_PRINT("send uevent pin[%d]:CONNECTOR_NAME=%s,CONNECTOR_TYPE=HIGH_LEVEL.\n" , num, pinfo->pdata->name[num]); else if (level == 0) SEC_CONN_PRINT("send uevent pin[%d]:CONNECTOR_NAME=%s,CONNECTOR_TYPE=LOW_LEVEL.\n" , num, pinfo->pdata->name[num]); } /** * Called when the connector pin state changes. */ static irqreturn_t detect_conn_interrupt_handler(int irq, void *handle) { int type; struct detect_conn_info *pinfo = handle; if (detect_conn_enabled != 0) { SEC_CONN_PRINT("%s\n", __func__); type = irq_get_trigger_type(irq); send_uevent_irq(irq, pinfo, type); } return IRQ_HANDLED; } /** * Enable all gpio pin IRQ which is from Device Tree. */ int detect_conn_irq_enable(struct detect_conn_info *pinfo, bool enable, int pin) { int retval = 0; int i; if (enable) { /*enable IRQ*/ enable_irq(pinfo->pdata->irq_number[pin]); pinfo->irq_enabled[pin] = true; } else { for (i = 0; i < pinfo->pdata->gpio_cnt; i++) { if (pinfo->irq_enabled[i]) { disable_irq(pinfo->pdata->irq_number[i]); pinfo->irq_enabled[i] = false; } } } return retval; } /** * Triggered when "enabled" node is set. * When enabling this node, check and send an uevent if the pin level is high. * And then gpio pin interrupt is enabled. */ static ssize_t store_detect_conn_enabled(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct detect_conn_info *pinfo; struct sec_det_conn_p_data *pdata; int ret; int i; int bufLength; int pinNameLength; if (gpinfo == 0) return -1; pinfo = gpinfo; pdata = pinfo->pdata; bufLength = strlen(buf); #if defined(DEBUG_FOR_SECDETECT) SEC_CONN_PRINT("buf = %s\n", buf); SEC_CONN_PRINT("bufLength = %d\n", bufLength); #endif /*Disable irq when "enabled" value set to 0*/ if (!strncmp(buf, "0", 1)) { SEC_CONN_PRINT("SEC Detect connector driver disable.\n"); detect_conn_enabled = 0; ret = detect_conn_irq_enable(pinfo, false, 0); if (ret) { SEC_CONN_PRINT("Interrupt not disabled.\n"); return ret; } } else { for (i = 0; i < pdata->gpio_cnt; i++) { pinNameLength = strlen(pdata->name[i]); #if defined(DEBUG_FOR_SECDETECT) SEC_CONN_PRINT("pinName = %s\n", pdata->name[i]); SEC_CONN_PRINT("pinNameLength = %d\n", pinNameLength); #endif if (pinNameLength == bufLength) { if (!strncmp(buf, pdata->name[i], bufLength)) { SEC_CONN_PRINT("%s driver enabled.\n", buf); detect_conn_enabled |= (1 << i); #if defined(DEBUG_FOR_SECDETECT) SEC_CONN_PRINT("gpio level [%d] = %d\n", pdata->irq_gpio[i], gpio_get_value(pdata->irq_gpio[i])); #endif /*get level value of the gpio pin.*/ /*if there's gpio low pin, send uevent*/ if (gpio_get_value(pdata->irq_gpio[i])) send_uevent_by_num(i, pinfo, 1); else send_uevent_by_num(i, pinfo, 0); /*Enable interrupt.*/ ret = detect_conn_irq_enable(pinfo, true, i); if (ret < 0) { SEC_CONN_PRINT("%s Interrupt not enabled.\n", buf); return ret; } } } /* For ALL_CONNECT input, enable all nodes except already enabled node. */ if (bufLength == 11) { if (!strncmp(buf, "ALL_CONNECT", bufLength)) { if (!(detect_conn_enabled & (1 << i))) { SEC_CONN_PRINT("%s driver enabled.\n", buf); detect_conn_enabled |= (1 << i); #if defined(DEBUG_FOR_SECDETECT) SEC_CONN_PRINT("gpio level [%d] = %d\n", pdata->irq_gpio[i], gpio_get_value(pdata->irq_gpio[i])); #endif /*get level value of the gpio pin.*/ /*if there's gpio low pin, send uevent*/ if (gpio_get_value(pdata->irq_gpio[i])) send_uevent_by_num(i, pinfo, 1); else send_uevent_by_num(i, pinfo, 0); /*Enable interrupt.*/ ret = detect_conn_irq_enable(pinfo, true, i); if (ret < 0) { SEC_CONN_PRINT("%s Interrupt not enabled.\n", buf); return ret; } } } } } } return count; } static ssize_t show_detect_conn_enabled(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", detect_conn_enabled); } static DEVICE_ATTR(enabled, 0644, show_detect_conn_enabled, store_detect_conn_enabled); static ssize_t show_detect_conn_available_pins(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%s\n", sec_detect_available_pins_string); } static DEVICE_ATTR(available_pins, 0444, show_detect_conn_available_pins, NULL); #ifdef CONFIG_OF /** * Parse the device tree and get gpio number, irq type. * Request gpio */ static int detect_conn_parse_dt(struct device *dev) { struct sec_det_conn_p_data *pdata = dev->platform_data; struct device_node *np = dev->of_node; int i; pdata->gpio_cnt = of_gpio_named_count(np, "sec,det_conn_gpios"); for (i = 0; i < pdata->gpio_cnt; i++) { /*Get connector name*/ of_property_read_string_index(np, "sec,det_conn_name", i, &(pdata->name[i])); /*Get connector gpio number*/ pdata->irq_gpio[i] = of_get_named_gpio(np, "sec,det_conn_gpios", i); if (gpio_is_valid(pdata->irq_gpio[i])) { #if defined(DEBUG_FOR_SECDETECT) SEC_CONN_PRINT("i = [%d] gpio level [%d] = %d\n", i, pdata->irq_gpio[i], gpio_get_value(pdata->irq_gpio[i])); SEC_CONN_PRINT("gpio irq gpio = [%d], irq = [%d]\n", pdata->irq_gpio[i], gpio_to_irq(pdata->irq_gpio[i])); #endif /*Filling the irq_number from this gpio.*/ pdata->irq_number[i] = gpio_to_irq(pdata->irq_gpio[i]); } else { dev_err(dev, "%s: Failed to get irq gpio.\n", __func__); return -EINVAL; } } /*Get type of gpio irq*/ if (of_property_read_u32_array(np, "sec,det_conn_irq_type", pdata->irq_type, pdata->gpio_cnt)) { dev_err(dev, "%s, Failed to get irq_type property.\n", __func__); return -EINVAL; } return 0; } #endif static int detect_conn_init_irq(void) { struct detect_conn_info *pinfo; struct sec_det_conn_p_data *pdata; int retval = 0; int i; if (gpinfo == 0) return -1; pinfo = gpinfo; pdata = pinfo->pdata; for (i = 0; i < pinfo->pdata->gpio_cnt; i++) { retval = request_threaded_irq(pinfo->pdata->irq_number[i], NULL, detect_conn_interrupt_handler, pinfo->pdata->irq_type[i] | IRQF_ONESHOT, pinfo->pdata->name[i], pinfo); if (retval) { SEC_CONN_PRINT("%s: Failed to request threaded irq %d.\n", __func__, retval); return retval; } #if defined(DEBUG_FOR_SECDETECT) SEC_CONN_PRINT("%s: Succeeded to request threaded irq %d: irq_num[%d], type[%x],name[%s].\n", __func__, retval, pinfo->pdata->irq_number[i], pinfo->pdata->irq_type[i], pinfo->pdata->name[i]); #endif /*disable irq init*/ disable_irq(pinfo->pdata->irq_number[i]); } return 0; } static int sec_detect_conn_item_make(void) { struct detect_conn_info *pinfo; struct sec_det_conn_p_data *pdata; int i = 0; pinfo = gpinfo; pdata = pinfo->pdata; for (i = 0; i < pdata->gpio_cnt; i++) { strcat(sec_detect_available_pins_string,pdata->name[i]); strcat(sec_detect_available_pins_string, "/"); } sec_detect_available_pins_string[strlen(sec_detect_available_pins_string)-1] = '\0'; return 0; } static int sec_detect_conn_probe(struct platform_device *pdev) { struct sec_det_conn_p_data *pdata; struct detect_conn_info *pinfo; int ret; SEC_CONN_PRINT("%s\n", __func__); /* First Get the GPIO pins; if it fails, we'll defer the probe. */ if (pdev->dev.of_node) { pdata = devm_kzalloc(&pdev->dev, sizeof(struct sec_det_conn_p_data), GFP_KERNEL); if (!pdata) { dev_err(&pdev->dev, "Failed to allocate platform data.\n"); return -ENOMEM; } pdev->dev.platform_data = pdata; #if CONFIG_OF ret = detect_conn_parse_dt(&pdev->dev); #else ret = 0; #endif if (ret) { dev_err(&pdev->dev, "Failed to parse dt data.\n"); kfree(pdata); return ret; } pr_info("%s: parse dt done.\n", __func__); } else { pdata = pdev->dev.platform_data; } if (!pdata) { dev_err(&pdev->dev, "There are no platform data.\n"); return -EINVAL; } pinfo = devm_kzalloc(&pdev->dev, sizeof(struct detect_conn_info), GFP_KERNEL); if (!pinfo) { SEC_CONN_PRINT("pinfo : failed to allocate pinfo.\n"); kfree(pdata); return -ENOMEM; } /* Create sys device /sys/class/sec/sec_detect_conn */ pinfo->dev = sec_device_create(pinfo, "sec_detect_conn"); if (unlikely(IS_ERR(pinfo->dev))) { pr_err("%s Failed to create device(sec_detect_conn).\n", __func__); ret = -ENODEV; goto out; } /* Create sys node /sys/class/sec/sec_detect_conn/enabled */ ret = device_create_file(pinfo->dev, &dev_attr_enabled); if (ret) { dev_err(&pdev->dev, "%s: Failed to create device file.\n", __func__); goto err_create_detect_conn_sysfs; } /* Create sys node /sys/class/sec/sec_detect_conn/available_pins */ ret = device_create_file(pinfo->dev, &dev_attr_available_pins); if (ret) { dev_err(&pdev->dev, "%s: Failed to create device file.\n", __func__); goto err_create_detect_conn_sysfs; } /*save pinfo data to pdata to interrupt enable*/ pdata->pinfo = pinfo; /*save pdata data to pinfo for enable node*/ pinfo->pdata = pdata; /* save pinfo to gpinfo to enabled node*/ gpinfo = pinfo; /* detect_conn_init_irq thread create*/ ret = detect_conn_init_irq(); /* make sec_detect_conn item*/ ret = sec_detect_conn_item_make(); return ret; err_create_detect_conn_sysfs: sec_device_destroy(pinfo->dev->devt); out: gpinfo = 0; kfree(pinfo); kfree(pdata); return ret; } static struct platform_driver sec_detect_conn_driver = { .probe = sec_detect_conn_probe, .remove = sec_detect_conn_remove, .driver = { .name = "sec_detect_conn", .owner = THIS_MODULE, #if defined(CONFIG_PM) .pm = &sec_detect_conn_pm, #endif #if CONFIG_OF .of_match_table = of_match_ptr(sec_detect_conn_dt_match), #endif }, }; #endif static int __init sec_detect_conn_init(void) { #if defined(CONFIG_SEC_FACTORY) SEC_CONN_PRINT("%s\n", __func__); return platform_driver_register(&sec_detect_conn_driver); #else SEC_CONN_PRINT("Not support Sec_Detect_Conn.\n"); return 0; #endif } static void __exit sec_detect_conn_exit(void) { #if defined(CONFIG_SEC_FACTORY) return platform_driver_unregister(&sec_detect_conn_driver); #endif } module_init(sec_detect_conn_init); module_exit(sec_detect_conn_exit); MODULE_DESCRIPTION("Samsung Detecting Connector Driver"); MODULE_AUTHOR("Samsung Electronics"); MODULE_LICENSE("GPL");