blob: 5f8f9f08e5c3326421de7df506d8ba5250e3bec2 [file] [log] [blame]
/*
* 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;
}