331 lines
8.0 KiB
C
Executable File
331 lines
8.0 KiB
C
Executable File
/*
|
|
* 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 <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/sec_class.h>
|
|
#include <linux/mcu_ipc.h>
|
|
|
|
#if defined(CONFIG_MUIC_NOTIFIER)
|
|
#include <linux/muic/muic.h>
|
|
#include <linux/muic/muic_notifier.h>
|
|
#elif defined(CONFIG_IFCONN_NOTIFIER)
|
|
#include <linux/ifconn/ifconn_notifier.h>
|
|
#include <linux/ifconn/ifconn_manager.h>
|
|
#include <linux/muic/muic_notifier.h>
|
|
#include <linux/muic/s2mu004-muic.h>
|
|
#endif
|
|
|
|
#if defined(CONFIG_CCIC_NOTIFIER)
|
|
#include <linux/usb/typec/pdic_notifier.h>
|
|
#endif
|
|
|
|
#include <soc/samsung/exynos-pmu.h>
|
|
#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;
|
|
}
|