| /* Copyright (c) 2017-2018, 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. |
| * |
| */ |
| #define pr_fmt(msg) "bgcom_dev:" msg |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/version.h> |
| #include <linux/fs.h> |
| #include <linux/cdev.h> |
| #include <linux/device.h> |
| #include <linux/errno.h> |
| #include <linux/uaccess.h> |
| #include <linux/slab.h> |
| #include <linux/sysfs.h> |
| #include <linux/kobject.h> |
| #include <soc/qcom/subsystem_restart.h> |
| #include <soc/qcom/subsystem_notif.h> |
| #include "bgcom.h" |
| #include "linux/bgcom_interface.h" |
| #include "bgcom_interface.h" |
| #include <linux/of.h> |
| #include <linux/of_gpio.h> |
| #include <linux/gpio.h> |
| #include <linux/delay.h> |
| #include <linux/platform_device.h> |
| #include <linux/regulator/consumer.h> |
| |
| #define BGCOM "bg_com_dev" |
| |
| #define BGDAEMON_LDO09_LPM_VTG 0 |
| #define BGDAEMON_LDO09_NPM_VTG 10000 |
| |
| #define BGDAEMON_LDO03_LPM_VTG 0 |
| #define BGDAEMON_LDO03_NPM_VTG 10000 |
| |
| #define MPPS_DOWN_EVENT_TO_BG_TIMEOUT 100 |
| |
| enum { |
| SSR_DOMAIN_BG, |
| SSR_DOMAIN_MODEM, |
| SSR_DOMAIN_MAX, |
| }; |
| |
| enum ldo_task { |
| ENABLE_LDO03, |
| ENABLE_LDO09, |
| DISABLE_LDO03, |
| DISABLE_LDO09 |
| }; |
| |
| struct bgdaemon_regulator { |
| struct regulator *regldo03; |
| struct regulator *regldo09; |
| }; |
| |
| struct bgdaemon_priv { |
| struct bgdaemon_regulator rgltr; |
| enum ldo_task ldo_action; |
| }; |
| |
| struct bg_event { |
| enum bg_event_type e_type; |
| }; |
| |
| struct service_info { |
| const char name[32]; |
| int domain_id; |
| void *handle; |
| struct notifier_block *nb; |
| }; |
| |
| static char *ssr_domains[] = { |
| "bg-wear", |
| "modem", |
| }; |
| |
| static struct bgdaemon_priv *dev; |
| static unsigned int bgreset_gpio; |
| static DEFINE_MUTEX(bg_char_mutex); |
| static struct cdev bg_cdev; |
| static struct class *bg_class; |
| struct device *dev_ret; |
| static dev_t bg_dev; |
| static int device_open; |
| static void *handle; |
| static struct bgcom_open_config_type config_type; |
| static DECLARE_COMPLETION(bg_modem_down_wait); |
| |
| /** |
| * send_uevent(): send events to user space |
| * pce : ssr event handle value |
| * Return: 0 on success, standard Linux error code on error |
| * |
| * It adds pce value to event and broadcasts to user space. |
| */ |
| static int send_uevent(struct bg_event *pce) |
| { |
| char event_string[32]; |
| char *envp[2] = { event_string, NULL }; |
| |
| snprintf(event_string, ARRAY_SIZE(event_string), |
| "BG_EVENT=%d", pce->e_type); |
| return kobject_uevent_env(&dev_ret->kobj, KOBJ_CHANGE, envp); |
| } |
| |
| static int bgdaemon_configure_regulators(bool state) |
| { |
| int retval; |
| |
| if (state == true) { |
| retval = regulator_enable(dev->rgltr.regldo03); |
| if (retval) |
| pr_err("Failed to enable LDO-03 regulator:%d\n", |
| retval); |
| retval = regulator_enable(dev->rgltr.regldo09); |
| if (retval) |
| pr_err("Failed to enable LDO-09 regulator:%d\n", |
| retval); |
| } |
| if (state == false) { |
| retval = regulator_disable(dev->rgltr.regldo03); |
| if (retval) |
| pr_err("Failed to disable LDO-03 regulator:%d\n", |
| retval); |
| retval = regulator_disable(dev->rgltr.regldo09); |
| if (retval) |
| pr_err("Failed to disable LDO-09 regulator:%d\n", |
| retval); |
| } |
| return retval; |
| } |
| static int bgdaemon_init_regulators(struct device *pdev) |
| { |
| int rc; |
| struct regulator *reg03; |
| struct regulator *reg09; |
| |
| reg03 = regulator_get(pdev, "ssr-reg1"); |
| if (IS_ERR_OR_NULL(reg03)) { |
| rc = PTR_ERR(reg03); |
| pr_err("Unable to get regulator for LDO-03\n"); |
| goto err_ret; |
| } |
| reg09 = regulator_get(pdev, "ssr-reg2"); |
| if (IS_ERR_OR_NULL(reg09)) { |
| rc = PTR_ERR(reg09); |
| pr_err("Unable to get regulator for LDO-09\n"); |
| goto err_ret; |
| } |
| dev->rgltr.regldo03 = reg03; |
| dev->rgltr.regldo09 = reg09; |
| return 0; |
| err_ret: |
| return rc; |
| } |
| |
| static int bgdaemon_ldowork(enum ldo_task do_action) |
| { |
| int ret; |
| |
| switch (do_action) { |
| case ENABLE_LDO03: |
| ret = regulator_set_load(dev->rgltr.regldo03, |
| BGDAEMON_LDO03_NPM_VTG); |
| if (ret < 0) { |
| pr_err("Failed to request LDO-03 voltage:%d\n", |
| ret); |
| goto err_ret; |
| } |
| break; |
| case ENABLE_LDO09: |
| ret = regulator_set_load(dev->rgltr.regldo09, |
| BGDAEMON_LDO09_NPM_VTG); |
| if (ret < 0) { |
| pr_err("Failed to request LDO-09 voltage:%d\n", |
| ret); |
| goto err_ret; |
| } |
| break; |
| case DISABLE_LDO03: |
| ret = regulator_set_load(dev->rgltr.regldo03, |
| BGDAEMON_LDO03_LPM_VTG); |
| if (ret < 0) { |
| pr_err("Failed to disable LDO-03:%d\n", ret); |
| goto err_ret; |
| } |
| break; |
| case DISABLE_LDO09: |
| ret = regulator_set_load(dev->rgltr.regldo09, |
| BGDAEMON_LDO09_LPM_VTG); |
| if (ret < 0) { |
| pr_err("Failed to disable LDO-09:%d\n", ret); |
| goto err_ret; |
| } |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| |
| err_ret: |
| return ret; |
| } |
| |
| static int bgcom_char_open(struct inode *inode, struct file *file) |
| { |
| int ret; |
| |
| mutex_lock(&bg_char_mutex); |
| if (device_open == 1) { |
| pr_err("device is already open\n"); |
| mutex_unlock(&bg_char_mutex); |
| return -EBUSY; |
| } |
| device_open++; |
| handle = bgcom_open(&config_type); |
| mutex_unlock(&bg_char_mutex); |
| if (IS_ERR(handle)) { |
| device_open = 0; |
| ret = PTR_ERR(handle); |
| handle = NULL; |
| return ret; |
| } |
| return 0; |
| } |
| |
| static int bgchar_read_cmd(struct bg_ui_data *fui_obj_msg, |
| int type) |
| { |
| void *read_buf; |
| int ret; |
| void __user *result = (void *) |
| (uintptr_t)fui_obj_msg->result; |
| |
| read_buf = kmalloc_array(fui_obj_msg->num_of_words, sizeof(uint32_t), |
| GFP_KERNEL); |
| if (read_buf == NULL) |
| return -ENOMEM; |
| switch (type) { |
| case REG_READ: |
| ret = bgcom_reg_read(handle, fui_obj_msg->cmd, |
| fui_obj_msg->num_of_words, |
| read_buf); |
| break; |
| case AHB_READ: |
| ret = bgcom_ahb_read(handle, |
| fui_obj_msg->bg_address, |
| fui_obj_msg->num_of_words, |
| read_buf); |
| break; |
| } |
| if (!ret && copy_to_user(result, read_buf, |
| fui_obj_msg->num_of_words * sizeof(uint32_t))) { |
| pr_err("copy to user failed\n"); |
| ret = -EFAULT; |
| } |
| kfree(read_buf); |
| return ret; |
| } |
| |
| static int bgchar_write_cmd(struct bg_ui_data *fui_obj_msg, int type) |
| { |
| void *write_buf; |
| int ret; |
| void __user *write = (void *) |
| (uintptr_t)fui_obj_msg->write; |
| |
| write_buf = kmalloc_array(fui_obj_msg->num_of_words, sizeof(uint32_t), |
| GFP_KERNEL); |
| if (write_buf == NULL) |
| return -ENOMEM; |
| write_buf = memdup_user(write, |
| fui_obj_msg->num_of_words * sizeof(uint32_t)); |
| if (IS_ERR(write_buf)) { |
| ret = PTR_ERR(write_buf); |
| kfree(write_buf); |
| return ret; |
| } |
| switch (type) { |
| case REG_WRITE: |
| ret = bgcom_reg_write(handle, fui_obj_msg->cmd, |
| fui_obj_msg->num_of_words, |
| write_buf); |
| break; |
| case AHB_WRITE: |
| ret = bgcom_ahb_write(handle, |
| fui_obj_msg->bg_address, |
| fui_obj_msg->num_of_words, |
| write_buf); |
| break; |
| } |
| kfree(write_buf); |
| return ret; |
| } |
| |
| int bg_soft_reset(void) |
| { |
| /*pull down reset gpio */ |
| gpio_direction_output(bgreset_gpio, 0); |
| msleep(50); |
| gpio_set_value(bgreset_gpio, 1); |
| return 0; |
| } |
| EXPORT_SYMBOL(bg_soft_reset); |
| |
| static int modem_down2_bg(void) |
| { |
| complete(&bg_modem_down_wait); |
| return 0; |
| } |
| |
| static long bg_com_ioctl(struct file *filp, |
| unsigned int ui_bgcom_cmd, unsigned long arg) |
| { |
| int ret; |
| struct bg_ui_data ui_obj_msg; |
| |
| switch (ui_bgcom_cmd) { |
| case REG_READ: |
| case AHB_READ: |
| if (copy_from_user(&ui_obj_msg, (void __user *) arg, |
| sizeof(ui_obj_msg))) { |
| pr_err("The copy from user failed\n"); |
| ret = -EFAULT; |
| } |
| ret = bgchar_read_cmd(&ui_obj_msg, |
| ui_bgcom_cmd); |
| if (ret < 0) |
| pr_err("bgchar_read_cmd failed\n"); |
| break; |
| case AHB_WRITE: |
| case REG_WRITE: |
| if (copy_from_user(&ui_obj_msg, (void __user *) arg, |
| sizeof(ui_obj_msg))) { |
| pr_err("The copy from user failed\n"); |
| ret = -EFAULT; |
| } |
| ret = bgchar_write_cmd(&ui_obj_msg, ui_bgcom_cmd); |
| if (ret < 0) |
| pr_err("bgchar_write_cmd failed\n"); |
| break; |
| case SET_SPI_FREE: |
| ret = bgcom_set_spi_state(BGCOM_SPI_FREE); |
| break; |
| case SET_SPI_BUSY: |
| ret = bgcom_set_spi_state(BGCOM_SPI_BUSY); |
| break; |
| case BG_SOFT_RESET: |
| ret = bg_soft_reset(); |
| break; |
| case BG_MODEM_DOWN2_BG_DONE: |
| ret = modem_down2_bg(); |
| break; |
| default: |
| ret = -ENOIOCTLCMD; |
| } |
| return ret; |
| } |
| |
| static int bgcom_char_close(struct inode *inode, struct file *file) |
| { |
| int ret; |
| |
| mutex_lock(&bg_char_mutex); |
| ret = bgcom_close(&handle); |
| device_open = 0; |
| mutex_unlock(&bg_char_mutex); |
| return ret; |
| } |
| |
| static int bg_daemon_probe(struct platform_device *pdev) |
| { |
| struct device_node *node; |
| unsigned int reset_gpio; |
| int ret; |
| |
| node = pdev->dev.of_node; |
| |
| dev = kzalloc(sizeof(struct bgdaemon_priv), GFP_KERNEL); |
| if (!dev) |
| return -ENOMEM; |
| |
| reset_gpio = of_get_named_gpio(node, "qcom,bg-reset-gpio", 0); |
| if (!gpio_is_valid(reset_gpio)) { |
| pr_err("gpio %d found is not valid\n", reset_gpio); |
| goto err_ret; |
| } |
| |
| if (gpio_request(reset_gpio, "bg_reset_gpio")) { |
| pr_err("gpio %d request failed\n", reset_gpio); |
| goto err_ret; |
| } |
| |
| if (gpio_direction_output(reset_gpio, 1)) { |
| pr_err("gpio %d direction not set\n", reset_gpio); |
| goto err_ret; |
| } |
| |
| pr_info("bg-soft-reset gpio successfully requested\n"); |
| bgreset_gpio = reset_gpio; |
| |
| ret = bgdaemon_init_regulators(&pdev->dev); |
| if (ret != 0) { |
| pr_err("Failed to init regulators:%d\n", ret); |
| goto err_device; |
| } |
| ret = bgdaemon_configure_regulators(true); |
| if (ret) { |
| pr_err("Failed to confifigure regulators:%d\n", ret); |
| bgdaemon_configure_regulators(false); |
| goto err_ret; |
| } |
| |
| err_device: |
| return -ENODEV; |
| err_ret: |
| return 0; |
| } |
| |
| static const struct of_device_id bg_daemon_of_match[] = { |
| { .compatible = "qcom,bg-daemon", }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, bg_daemon_of_match); |
| |
| static struct platform_driver bg_daemon_driver = { |
| .probe = bg_daemon_probe, |
| .driver = { |
| .name = "bg-daemon", |
| .of_match_table = bg_daemon_of_match, |
| }, |
| }; |
| |
| static const struct file_operations fops = { |
| .owner = THIS_MODULE, |
| .open = bgcom_char_open, |
| .release = bgcom_char_close, |
| .unlocked_ioctl = bg_com_ioctl, |
| }; |
| |
| static int __init init_bg_com_dev(void) |
| { |
| int ret; |
| |
| ret = alloc_chrdev_region(&bg_dev, 0, 1, BGCOM); |
| if (ret < 0) { |
| pr_err("failed with error %d\n", ret); |
| return ret; |
| } |
| cdev_init(&bg_cdev, &fops); |
| ret = cdev_add(&bg_cdev, bg_dev, 1); |
| if (ret < 0) { |
| unregister_chrdev_region(bg_dev, 1); |
| pr_err("device registration failed\n"); |
| return ret; |
| } |
| bg_class = class_create(THIS_MODULE, BGCOM); |
| if (IS_ERR_OR_NULL(bg_class)) { |
| cdev_del(&bg_cdev); |
| unregister_chrdev_region(bg_dev, 1); |
| pr_err("class creation failed\n"); |
| return PTR_ERR(bg_class); |
| } |
| |
| dev_ret = device_create(bg_class, NULL, bg_dev, NULL, BGCOM); |
| if (IS_ERR_OR_NULL(dev_ret)) { |
| class_destroy(bg_class); |
| cdev_del(&bg_cdev); |
| unregister_chrdev_region(bg_dev, 1); |
| pr_err("device create failed\n"); |
| return PTR_ERR(dev_ret); |
| } |
| |
| if (platform_driver_register(&bg_daemon_driver)) |
| pr_err("%s: failed to register bg-daemon register\n", __func__); |
| |
| return 0; |
| } |
| |
| static void __exit exit_bg_com_dev(void) |
| { |
| device_destroy(bg_class, bg_dev); |
| class_destroy(bg_class); |
| cdev_del(&bg_cdev); |
| unregister_chrdev_region(bg_dev, 1); |
| bgdaemon_configure_regulators(false); |
| platform_driver_unregister(&bg_daemon_driver); |
| } |
| |
| /** |
| *ssr_bg_cb(): callback function is called |
| *by ssr framework when BG goes down, up and during ramdump |
| *collection. It handles BG shutdown and power up events. |
| */ |
| static int ssr_bg_cb(struct notifier_block *this, |
| unsigned long opcode, void *data) |
| { |
| struct bg_event bge; |
| |
| switch (opcode) { |
| case SUBSYS_BEFORE_SHUTDOWN: |
| bge.e_type = BG_BEFORE_POWER_DOWN; |
| bgdaemon_ldowork(ENABLE_LDO03); |
| bgdaemon_ldowork(ENABLE_LDO09); |
| bgcom_bgdown_handler(); |
| bgcom_set_spi_state(BGCOM_SPI_BUSY); |
| send_uevent(&bge); |
| break; |
| case SUBSYS_AFTER_POWERUP: |
| bge.e_type = BG_AFTER_POWER_UP; |
| bgdaemon_ldowork(DISABLE_LDO03); |
| bgdaemon_ldowork(DISABLE_LDO09); |
| bgcom_set_spi_state(BGCOM_SPI_FREE); |
| send_uevent(&bge); |
| break; |
| } |
| return NOTIFY_DONE; |
| } |
| |
| /** |
| *ssr_modem_cb(): callback function is called |
| *by ssr framework when modem goes down, up and during ramdump |
| *collection. It handles modem shutdown and power up events. |
| */ |
| static int ssr_modem_cb(struct notifier_block *this, |
| unsigned long opcode, void *data) |
| { |
| struct bg_event modeme; |
| int ret; |
| |
| switch (opcode) { |
| case SUBSYS_BEFORE_SHUTDOWN: |
| modeme.e_type = MODEM_BEFORE_POWER_DOWN; |
| reinit_completion(&bg_modem_down_wait); |
| send_uevent(&modeme); |
| ret = wait_for_completion_timeout(&bg_modem_down_wait, |
| msecs_to_jiffies(MPPS_DOWN_EVENT_TO_BG_TIMEOUT)); |
| if (!ret) |
| pr_err("Time out on modem down event\n"); |
| break; |
| case SUBSYS_AFTER_POWERUP: |
| modeme.e_type = MODEM_AFTER_POWER_UP; |
| send_uevent(&modeme); |
| break; |
| } |
| return NOTIFY_DONE; |
| } |
| |
| static struct notifier_block ssr_modem_nb = { |
| .notifier_call = ssr_modem_cb, |
| .priority = 0, |
| }; |
| |
| static struct notifier_block ssr_bg_nb = { |
| .notifier_call = ssr_bg_cb, |
| .priority = 0, |
| }; |
| |
| static struct service_info service_data[2] = { |
| { |
| .name = "SSR_BG", |
| .domain_id = SSR_DOMAIN_BG, |
| .nb = &ssr_bg_nb, |
| .handle = NULL, |
| }, |
| { |
| .name = "SSR_MODEM", |
| .domain_id = SSR_DOMAIN_MODEM, |
| .nb = &ssr_modem_nb, |
| .handle = NULL, |
| }, |
| }; |
| |
| /** |
| * ssr_register checks that domain id should be in range and register |
| * SSR framework for value at domain id. |
| */ |
| static int __init ssr_register(void) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(service_data); i++) { |
| if ((service_data[i].domain_id < 0) || |
| (service_data[i].domain_id >= SSR_DOMAIN_MAX)) { |
| pr_err("Invalid service ID = %d\n", |
| service_data[i].domain_id); |
| } else { |
| service_data[i].handle = |
| subsys_notif_register_notifier( |
| ssr_domains[service_data[i].domain_id], |
| service_data[i].nb); |
| if (IS_ERR_OR_NULL(service_data[i].handle)) { |
| pr_err("subsys register failed for id = %d", |
| service_data[i].domain_id); |
| service_data[i].handle = NULL; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| module_init(init_bg_com_dev); |
| late_initcall(ssr_register); |
| module_exit(exit_bg_com_dev); |
| MODULE_LICENSE("GPL v2"); |