/* * Copyright (c) 2020, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #if IS_ENABLED(CONFIG_SEC_CRASHKEY_LONG) #include #endif #include #include static struct device *sec_ap_pmic_dev; static struct sec_ap_pmic_info *sec_ap_pmic_data; static ssize_t manual_reset_show(struct device *in_dev, struct device_attribute *attr, char *buf) { int ret = 0; ret = sec_get_s2_reset(SEC_PON_KPDPWR_RESIN); pr_info("%s: ret=%d\n", __func__, ret); return sprintf(buf, "%d\n", !ret); } static ssize_t manual_reset_store(struct device *in_dev, struct device_attribute *attr, const char *buf, size_t len) { int onoff = 0; if (kstrtoint(buf, 10, &onoff)) return -EINVAL; pr_info("%s: onoff=%d\n", __func__, onoff); #if IS_ENABLED(CONFIG_SEC_CRASHKEY_LONG) if (onoff) sec_crashkey_long_connect_to_input_evnet(); else sec_crashkey_long_disconnect_from_input_event(); #endif return len; } static DEVICE_ATTR_RW(manual_reset); static ssize_t wake_enabled_show(struct device *in_dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", (sec_get_pm_key_wk_init(SEC_PON_KPDPWR) && sec_get_pm_key_wk_init(SEC_PON_RESIN)) ? 1 : 0); } static ssize_t wake_enabled_store(struct device *in_dev, struct device_attribute *attr, const char *buf, size_t len) { int onoff; int ret; if (kstrtoint(buf, 10, &onoff) < 0) return -EINVAL; pr_info("%s: onoff=%d\n", __func__, onoff); ret = sec_set_pm_key_wk_init(SEC_PON_KPDPWR, onoff); pr_info("%s: PWR ret=%d\n", __func__, ret); ret = sec_set_pm_key_wk_init(SEC_PON_RESIN, onoff); pr_info("%s: RESIN ret=%d\n", __func__, ret); return len; } static DEVICE_ATTR_RW(wake_enabled); #if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) static ssize_t gpio_dump_show(struct device *in_dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", (gpio_dump_enabled) ? 1 : 0); } static ssize_t gpio_dump_store(struct device *in_dev, struct device_attribute *attr, const char *buf, size_t len) { int onoff; if (kstrtoint(buf, 10, &onoff) < 0) return -EINVAL; pr_info("%s: onoff=%d\n", __func__, onoff); gpio_dump_enabled = (onoff) ? true : false; return len; } static DEVICE_ATTR_RW(gpio_dump); #endif /* VDD/IDDQ info */ #define PARAM0_IVALID 1 #define PARAM0_LESS_THAN_0 2 #define DEFAULT_LEN_STR 1023 #define default_scnprintf(buf, offset, fmt, ...) \ do { \ offset += scnprintf(&(buf)[offset], DEFAULT_LEN_STR - (size_t)offset, \ fmt, ##__VA_ARGS__); \ } while (0) static void check_format(char *buf, ssize_t *size, int max_len_str) { int i = 0, cnt = 0, pos = 0; if (!buf || *size <= 0) return; if (*size >= max_len_str) *size = max_len_str - 1; while (i < *size && buf[i]) { if (buf[i] == '"') { cnt++; pos = i; } if ((buf[i] < 0x20) || (buf[i] == 0x5C) || (buf[i] > 0x7E)) buf[i] = ' '; i++; } if (cnt % 2) { if (pos == *size - 1) { buf[*size - 1] = '\0'; } else { buf[*size - 1] = '"'; buf[*size] = '\0'; } } } static int get_param0(const char *name) { struct device_node *np = of_find_node_by_path("/soc/sec_ap_param"); u32 val; int ret; if (!np) { pr_err("No sec_avi_data found\n"); return -PARAM0_IVALID; } ret = of_property_read_u32(np, name, &val); if (ret) { pr_err("failed to get %s from node\n", name); return -PARAM0_LESS_THAN_0; } return val; } #define GET_V(A) ((A) / 1000) #define GET_MV(A) ((A) % 1000) static ssize_t show_ap_info(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t info_size = 0; /* currently, support only for GC_OPV */ default_scnprintf(buf, info_size, "\"GC_OPV_3\":\"%d.%03d\"", GET_V(get_param0("go")), GET_MV(get_param0("go"))); default_scnprintf(buf, info_size, ",\"GC_PRM\":\"%d\"", get_param0("gi")); default_scnprintf(buf, info_size, ",\"DOUR\":\"%d\"", get_param0("dour")); default_scnprintf(buf, info_size, ",\"DOUB\":\"%d\"", get_param0("doub")); check_format(buf, &info_size, DEFAULT_LEN_STR); return info_size; } static DEVICE_ATTR(ap_info, 0440, show_ap_info, NULL); static struct attribute *sec_ap_pmic_attributes[] = { #if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) &dev_attr_gpio_dump.attr, #endif &dev_attr_manual_reset.attr, &dev_attr_wake_enabled.attr, &dev_attr_ap_info.attr, NULL, }; static struct attribute_group sec_ap_pmic_attr_group = { .attrs = sec_ap_pmic_attributes, }; #if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) static void gpio_state_debug_suspend_trace_probe(void *unused, const char *action, int val, bool start) { /* SUSPEND: start(1), val(1), action(machine_suspend) */ if (gpio_dump_enabled && start && val > 0 && !strcmp("machine_suspend", action)) { sec_ap_gpio_debug_print(); sec_pmic_gpio_debug_print(); } } #endif static int sec_ap_pmic_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct sec_ap_pmic_info *info; int err; if (!node) { dev_err(&pdev->dev, "device-tree data is missing\n"); return -ENXIO; } info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) { dev_err(&pdev->dev, "%s: Fail to alloc info\n", __func__); return -ENOMEM; } platform_set_drvdata(pdev, info); info->dev = &pdev->dev; sec_ap_pmic_data = info; #if IS_ENABLED(CONFIG_SEC_CLASS) sec_ap_pmic_dev = sec_device_create(NULL, "ap_pmic"); if (unlikely(IS_ERR(sec_ap_pmic_dev))) { pr_err("%s: Failed to create ap_pmic device\n", __func__); err = PTR_ERR(sec_ap_pmic_dev); goto err_device_create; } err = sysfs_create_group(&sec_ap_pmic_dev->kobj, &sec_ap_pmic_attr_group); if (err < 0) { pr_err("%s: Failed to create sysfs group\n", __func__); goto err_device_create; } #endif #if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) /* Register callback for cheking subsystem stats */ err = register_trace_suspend_resume( gpio_state_debug_suspend_trace_probe, NULL); if (err) { pr_err("%s: Failed to register suspend trace callback, ret=%d\n", __func__, err); } #endif pr_info("%s: ap_pmic successfully inited.\n", __func__); return 0; #if IS_ENABLED(CONFIG_SEC_CLASS) err_device_create: sec_device_destroy(sec_ap_pmic_dev->devt); return err; #endif } static int sec_ap_pmic_remove(struct platform_device *pdev) { #if IS_ENABLED(CONFIG_SEC_GPIO_DUMP) int ret; ret = unregister_trace_suspend_resume( gpio_state_debug_suspend_trace_probe, NULL); #endif #if IS_ENABLED(CONFIG_SEC_CLASS) if (sec_ap_pmic_dev) { sec_device_destroy(sec_ap_pmic_dev->devt); } #endif return 0; } static const struct of_device_id sec_ap_pmic_match_table[] = { { .compatible = "samsung,sec-ap-pmic" }, {} }; static struct platform_driver sec_ap_pmic_driver = { .driver = { .name = "samsung,sec-ap-pmic", .of_match_table = sec_ap_pmic_match_table, }, .probe = sec_ap_pmic_probe, .remove = sec_ap_pmic_remove, }; module_platform_driver(sec_ap_pmic_driver); MODULE_DESCRIPTION("sec_ap_pmic driver"); MODULE_SOFTDEP("pre: sec_class"); MODULE_LICENSE("GPL"); MODULE_SOFTDEP("pre: sec_crashkey_long"); MODULE_SOFTDEP("pre: pm8941-pwrkey"); MODULE_AUTHOR("Jiman Cho