/* * Copyright (C) 2014 Samsung Electronics Co.Ltd * http://www.samsung.com * * UART Switch driver * * 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. */ #include #include #include #include #include #if defined(CONFIG_MUIC_NOTIFIER) #include #include #elif defined(CONFIG_IFCONN_NOTIFIER) #include #include #include #include #endif #if defined(CONFIG_CCIC_NOTIFIER) #include #endif #include #include "modem_utils.h" #include "uart_switch.h" #define UART_DIRECTION_BY_BOOTPARAM 1 enum uart_direction_t uart_dir = AP; struct uart_switch_data *switch_data; static void send_uart_noti_to_cp(enum uart_direction_t path) { mif_info("mbox update value %s\n", (path == AP) ? "AP" : "CP"); mbox_update_value(MCU_CP, switch_data->mbx_ap_united_status, path, switch_data->sbi_uart_noti_mask, switch_data->sbi_uart_noti_pos); mbox_set_interrupt(MCU_CP, switch_data->int_uart_noti); } static int set_uart_switch(enum uart_direction_t path) { int ret = 0; u32 reg_val = 0; mif_info("Changing path to %s\n", (path == AP) ? "AP" : "CP"); /* * NOTICE: * Register values are depended on SOC chip * Please set the values by user manual */ if (path == AP) { if (switch_data->use_usb_phy) reg_val = 0x00110001; else reg_val = 0x00120000; } else { if (switch_data->use_usb_phy) reg_val = 0x11001001; else reg_val = 0x11002000; } ret = exynos_pmu_write(0x6200, reg_val); if (ret < 0) { mif_err("ERR(%d) set UART_IO_SHARE_CTRL\n", ret); return ret; } if (switch_data->use_usb_phy) { ret = exynos_pmu_update(0x0704, 1, 0x1 << 1); if (ret < 0) { mif_err("ERR(%d) set USBDEV_PHY_CONTROL\n", ret); return ret; } } return 0; } void cp_recheck_uart_dir(void) { if (uart_dir != CP) { mif_info("uart_dir is not CP\n"); return; } uart_dir = CP; set_uart_switch(CP); send_uart_noti_to_cp(CP); mif_info("Forcely changed to CP uart!!\n"); } #if defined(CONFIG_MUIC_NOTIFIER) && !defined(UART_DIRECTION_BY_BOOTPARAM) static int switch_handle_notification(struct notifier_block *nb, unsigned long action, void *data) { #if defined ( CONFIG_CCIC_NOTIFIER) CC_NOTI_ATTACH_TYPEDEF *p_noti = (CC_NOTI_ATTACH_TYPEDEF *)data; muic_attached_dev_t attached_dev = p_noti->cable_type; #else muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data; #endif mif_info("action=%lu attached_dev=%d\n", action, (int)attached_dev); switch (attached_dev) { case ATTACHED_DEV_JIG_UART_OFF_MUIC: case ATTACHED_DEV_JIG_UART_ON_MUIC: case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: break; default: mif_err("attached device is no JIG\n"); return 0; } switch (action) { case MUIC_NOTIFY_CMD_DETACH: case MUIC_NOTIFY_CMD_LOGICALLY_DETACH: uart_dir = AP; break; case MUIC_NOTIFY_CMD_ATTACH: case MUIC_NOTIFY_CMD_LOGICALLY_ATTACH: uart_dir = CP; set_uart_switch(uart_dir); break; default: mif_err("muic notify cmd error\n"); return -1; } send_uart_noti_to_cp(uart_dir); return 0; } #elif defined(CONFIG_IFCONN_NOTIFIER) static int switch_handle_notification(struct notifier_block *nb, unsigned long action, void *data) { struct ifconn_notifier_template *p_noti = (struct ifconn_notifier_template *)data; muic_attached_dev_t attached_dev = (muic_attached_dev_t)p_noti->event; mif_info("action=%lu attached_dev=%d\n", action, (int)attached_dev); switch (attached_dev) { case ATTACHED_DEV_JIG_UART_OFF_MUIC: case ATTACHED_DEV_JIG_UART_ON_MUIC: case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: break; default: mif_err("attached device is no JIG\n"); return 0; } switch (action) { case IFCONN_NOTIFY_ID_DETACH: uart_dir = AP; break; case IFCONN_NOTIFY_ID_ATTACH: uart_dir = CP; set_uart_switch(uart_dir); break; default: mif_err("ifconn notify id error\n"); return -1; } send_uart_noti_to_cp(uart_dir); return 0; } #endif static ssize_t usb_sel_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "PDA\n"); } static ssize_t usb_sel_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { return count; } static ssize_t uart_sel_show(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%s\n", (uart_dir == AP ? "AP" : "CP")); } static ssize_t uart_sel_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { mif_info("Change UART port path\n"); if (!strncasecmp(buf, "AP", 2)) { uart_dir = AP; } else if (!strncasecmp(buf, "CP", 2)) { uart_dir = CP; } else { mif_err("invalid value\n"); return count; } set_uart_switch(uart_dir); send_uart_noti_to_cp(uart_dir); return count; } static DEVICE_ATTR(usb_sel, 0664, usb_sel_show, usb_sel_store); static DEVICE_ATTR(uart_sel, 0664, uart_sel_show, uart_sel_store); static struct attribute *uart_sel_attributes[] = { &dev_attr_uart_sel.attr, &dev_attr_usb_sel.attr, NULL }; static const struct attribute_group uart_sel_group = { .attrs = uart_sel_attributes, }; static int uart_switch_setup(char *str) { uart_dir = strstr(str, "CP") ? CP : AP; mif_info("uart direction: %s (%d)\n", str, uart_dir); return 0; } __setup("uart_switch=", uart_switch_setup); int uart_switch_init(struct platform_device *pdev) { struct device *dev = &pdev->dev; #if IS_ENABLED(CONFIG_MUIC_SYSFS) struct device *switch_device = NULL; #endif mif_info("uart_switch init start.\n"); switch_data = devm_kzalloc(dev, sizeof(struct uart_switch_data), GFP_KERNEL); if (!switch_data) { mif_err_limited("switch_data failed to alloc memory\n"); return -ENOMEM; } switch_data->dev = dev; if (of_property_read_u32(dev->of_node, "mif,int_ap2cp_uart_noti", &switch_data->int_uart_noti)) { mif_err("int_ap2cp_uart_noti parse error!\n"); goto init_err; } if (of_property_read_u32(dev->of_node, "mbx_ap2cp_united_status", &switch_data->mbx_ap_united_status)) { mif_err("mbox_ap2cp_united_status parse error!\n"); goto init_err; } if (of_property_read_u32(dev->of_node, "sbi_uart_noti_mask", &switch_data->sbi_uart_noti_mask)) { mif_err("sbi_uart_noti_mask parse error!\n"); goto init_err; } if (of_property_read_u32(dev->of_node, "sbi_uart_noti_pos", &switch_data->sbi_uart_noti_pos)) { mif_err("sbi_uart_noti_pos parse error!\n"); goto init_err; } if (of_property_read_u32(dev->of_node, "mif,use_usb_phy", &switch_data->use_usb_phy)) { mif_err("use_usb_phy parse error!\n"); goto init_err; } mif_info("use_usb_phy [%d]\n", switch_data->use_usb_phy); #if defined(CONFIG_MUIC_NOTIFIER) && !defined(UART_DIRECTION_BY_BOOTPARAM) switch_data->uart_notifier.notifier_call = switch_handle_notification; muic_notifier_register(&switch_data->uart_notifier, switch_handle_notification, MUIC_NOTIFY_DEV_MODEM); #elif defined(CONFIG_IFCONN_NOTIFIER) switch_data->uart_notifier.notifier_call = switch_handle_notification; ifconn_notifier_register(&switch_data->uart_notifier, switch_handle_notification, IFCONN_NOTIFY_MODEM, IFCONN_NOTIFY_MUIC); #endif #if IS_ENABLED(CONFIG_MUIC_SYSFS) switch_device = sec_device_find("switch"); #endif /* create sysfs group */ if (sysfs_create_group(&switch_device->kobj, &uart_sel_group)) { mif_err("failed to create modemif sysfs attribute group\n"); goto init_err; } #if defined(UART_DIRECTION_BY_BOOTPARAM) if (get_switch_sel() & SWITCH_SEL_UART_MASK) uart_dir = AP; else uart_dir = CP; set_uart_switch(uart_dir); #endif return 0; init_err: if (switch_data) devm_kfree(dev, switch_data); return -EINVAL; }