android_kernel_samsung_on5x.../drivers/misc/uart_sel/uart_sel.c
2018-06-19 23:16:04 +02:00

390 lines
9.9 KiB
C

/*
* 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/interrupt.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/delay.h>
#include <soc/samsung/pmu-cp.h>
#include <soc/samsung/exynos-pmu.h>
#ifdef CONFIG_SEC_SYSFS
#include <linux/sec_sysfs.h>
#endif
#include <linux/muic/muic.h>
#ifdef CONFIG_MUIC_NOTIFIER
#include <linux/muic/muic_notifier.h>
#ifndef CONFIG_SOC_EXYNOS7570
#include <linux/muic/s2mu005-muic.h>
#endif
#endif
#include <linux/mcu_ipc.h>
#include <linux/uart_sel.h>
struct uart_sel_data *switch_data = NULL;
#ifdef CONFIG_SOC_EXYNOS7570
void config_pmu_sel(int path)
{
unsigned int func_iso_en, sel_uart_dbg_gpio, usbdev_phy_enable;
int ret = 0;
pr_info("%s: Changing path to %s\n", __func__, path ? "CP" : "AP");
if (path == AP) { /* AP */
func_iso_en = 0;
sel_uart_dbg_gpio = SEL_UART_DBG_GPIO;
usbdev_phy_enable = 0;
} else { /* CP */
func_iso_en = FUNC_ISO_EN;
sel_uart_dbg_gpio = 0;
usbdev_phy_enable = USBDEV_PHY_ENABLE;
}
ret = exynos_pmu_update(EXYNOS_PMU_UART_IO_SHARE_CTRL,
FUNC_ISO_EN, func_iso_en);
if (ret < 0)
pr_err("%s: ERR(%d) set FUNC_ISO_EN\n", __func__, ret);
ret = exynos_pmu_update(EXYNOS_PMU_UART_IO_SHARE_CTRL,
SEL_UART_DBG_GPIO, sel_uart_dbg_gpio);
if (ret < 0)
pr_err("%s: ERR(%d) set SEL_UART_DBG_GPIO\n", __func__, ret);
ret = exynos_pmu_update(PMU_USBDEV_PHY_CONTROL,
USBDEV_PHY_ENABLE, usbdev_phy_enable);
if (ret < 0)
pr_err("%s: ERR(%d) set USBDEV_PHY_ENABLE\n", __func__, ret);
}
#endif
static void uart_dir_work(void)
{
u32 info_value = 0;
if (switch_data == NULL)
return;
info_value = (switch_data->uart_connect && switch_data->uart_switch_sel);
#ifdef CONFIG_SOC_EXYNOS7570
if (switch_data->uart_connect)
{
if (switch_data->uart_switch_sel) {
pr_info("%s: selection CP uart path\n", __func__);
config_pmu_sel(CP);
#if defined(CONFIG_MUIC_RT8973) || defined(CONFIG_MUIC_SM5504) \
|| defined(CONFIG_MUIC_UNIVERSAL_SM5504)
if (muic_pdata.muic_set_path)
muic_pdata.muic_set_path(switch_device->driver_data, USB);
else
pr_info("%s: fail to set path of muic\n", __func__);
#endif
} else {
pr_info("%s: selection AP uart path\n", __func__);
config_pmu_sel(AP);
#if defined(CONFIG_MUIC_RT8973) || defined(CONFIG_MUIC_SM5504) \
|| defined(CONFIG_MUIC_UNIVERSAL_SM5504)
if (muic_pdata.muic_set_path)
muic_pdata.muic_set_path(switch_device->driver_data, UART);
else
pr_info("%s: fail to set path of muic\n", __func__);
#endif
}
}
#endif
if (info_value != mbox_extract_value(MCU_CP, switch_data->mbx_ap_united_status,
switch_data->sbi_uart_noti_mask, switch_data->sbi_uart_noti_pos)) {
pr_err("%s: change uart state to %s\n", __func__,
info_value ? "CP" : "AP");
mbox_update_value(MCU_CP, switch_data->mbx_ap_united_status, info_value,
switch_data->sbi_uart_noti_mask, switch_data->sbi_uart_noti_pos);
if (mbox_extract_value(MCU_CP, switch_data->mbx_ap_united_status,
switch_data->sbi_uart_noti_mask, switch_data->sbi_uart_noti_pos))
mbox_set_interrupt(MCU_CP, switch_data->int_uart_noti);
}
}
void cp_recheck_uart_dir(void)
{
u32 mbx_uart_noti;
mbx_uart_noti = mbox_extract_value(MCU_CP, switch_data->mbx_ap_united_status,
switch_data->sbi_uart_noti_mask, switch_data->sbi_uart_noti_pos);
if (switch_data->uart_switch_sel != mbx_uart_noti)
pr_err("Uart notifier data is not matched with mbox!\n");
uart_dir_work();
}
EXPORT_SYMBOL_GPL(cp_recheck_uart_dir);
#ifdef CONFIG_MUIC_NOTIFIER
static int switch_handle_notification(struct notifier_block *nb,
unsigned long action, void *data)
{
muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data;
pr_err("%s: action=%lu attached_dev=%d\n", __func__, action, (int)attached_dev);
if ((attached_dev == ATTACHED_DEV_JIG_UART_OFF_MUIC) ||
(attached_dev == ATTACHED_DEV_JIG_UART_ON_MUIC) ||
(attached_dev == ATTACHED_DEV_JIG_UART_OFF_VB_MUIC)) {
switch (action) {
case MUIC_NOTIFY_CMD_DETACH:
case MUIC_NOTIFY_CMD_LOGICALLY_DETACH:
switch_data->uart_connect = false;
uart_dir_work();
break;
case MUIC_NOTIFY_CMD_ATTACH:
case MUIC_NOTIFY_CMD_LOGICALLY_ATTACH:
switch_data->uart_connect = true;
uart_dir_work();
break;
}
}
return 0;
}
#endif
static int set_uart_sel(void)
{
int ret;
if (switch_data->uart_switch_sel == CP) {
pr_err("Change Uart to CP\n");
#ifdef CONFIG_SOC_EXYNOS7570
/* There is bit[8] converting in JAVA platform only */
ret = exynos_pmu_update(EXYNOS_PMU_UART_IO_SHARE_CTRL,
SEL_CP_UART_DBG, 0);
#else
ret = exynos_pmu_update(EXYNOS_PMU_UART_IO_SHARE_CTRL,
SEL_CP_UART_DBG, SEL_CP_UART_DBG);
#endif
if (ret < 0)
pr_err("%s: ERR! write Fail: %d\n", __func__, ret);
} else {
pr_err("Change UART to AP\n");
#ifdef CONFIG_SOC_EXYNOS7570
ret = exynos_pmu_update(EXYNOS_PMU_UART_IO_SHARE_CTRL,
SEL_CP_UART_DBG, SEL_CP_UART_DBG);
#else
ret = exynos_pmu_update(EXYNOS_PMU_UART_IO_SHARE_CTRL,
SEL_CP_UART_DBG, 0);
#endif
if (ret < 0)
pr_err("%s: ERR! write Fail: %d\n", __func__, ret);
}
uart_dir_work();
return 0;
}
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)
{
u32 uart_ctrl;
ssize_t ret = 0;
if (ret < PAGE_SIZE - 1) {
exynos_pmu_read(EXYNOS_PMU_UART_IO_SHARE_CTRL, &uart_ctrl);
#ifdef CONFIG_SOC_EXYNOS7570
ret += snprintf(buf + ret, PAGE_SIZE - ret, (uart_ctrl & SEL_CP_UART_DBG) ?
"AP\n":"CP\n");
#else
ret += snprintf(buf + ret, PAGE_SIZE - ret, (uart_ctrl & SEL_CP_UART_DBG) ?
"CP\n":"AP\n");
#endif
} else {
buf[PAGE_SIZE-2] = '\n';
buf[PAGE_SIZE-1] = '\0';
ret = PAGE_SIZE-1;
}
return ret;
}
static ssize_t
uart_sel_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
pr_err("%s Change UART port path\n", __func__);
if (!strncasecmp(buf, "AP", 2)) {
switch_data->uart_switch_sel = AP;
set_uart_sel();
} else if (!strncasecmp(buf, "CP", 2)) {
switch_data->uart_switch_sel = CP;
set_uart_sel();
} else {
pr_err("%s invalid value\n", __func__);
}
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 *modemif_sysfs_attributes[] = {
&dev_attr_uart_sel.attr,
&dev_attr_usb_sel.attr,
NULL
};
static const struct attribute_group uart_sel_sysfs_group = {
.attrs = modemif_sysfs_attributes,
};
static int uart_sel_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int err = 0;
pr_err("%s: uart_sel probe start.\n", __func__);
switch_data = devm_kzalloc(dev, sizeof(struct uart_sel_data), GFP_KERNEL);
err = of_property_read_u32(dev->of_node, "int_ap2cp_uart_noti",
&switch_data->int_uart_noti);
if (err) {
pr_err("SWITCH_SEL parse error! [id]\n");
return err;
}
err = of_property_read_u32(dev->of_node, "mbx_ap2cp_united_status",
&switch_data->mbx_ap_united_status);
err = of_property_read_u32(dev->of_node, "sbi_uart_noti_mask",
&switch_data->sbi_uart_noti_mask);
err = of_property_read_u32(dev->of_node, "sbi_uart_noti_pos",
&switch_data->sbi_uart_noti_pos);
if (err) {
pr_err("SWITCH_SEL parse error! [id]\n");
return err;
}
err = device_create_file(dev, &dev_attr_uart_sel);
if (err)
pr_err("can't create modem_ctrl!!!\n");
#if defined(CONFIG_MUIC_RT8973) || defined(CONFIG_MUIC_SM5504) \
|| defined(CONFIG_MUIC_UNIVERSAL_SM5504)
if ((get_switch_sel() & 0xff) != 0) {
if ((get_switch_sel() & SWITCH_SEL_UART_MASK))
switch_data->uart_switch_sel = AP;
else
switch_data->uart_switch_sel = CP;
} else {
switch_data->uart_switch_sel = AP;
}
#else
switch_data->uart_switch_sel = AP;
#endif
switch_data->uart_connect = false;
set_uart_sel();
#if defined(CONFIG_MUIC_NOTIFIER)
switch_data->uart_notifier.notifier_call = switch_handle_notification;
muic_notifier_register(&switch_data->uart_notifier, switch_handle_notification,
MUIC_NOTIFY_DEV_USB);
#endif
#ifdef CONFIG_SEC_SYSFS
if (IS_ERR(switch_device)) {
pr_err("%s Failed to create device(switch)!\n", __func__);
return -ENODEV;
}
/* create sysfs group */
err = sysfs_create_group(&switch_device->kobj, &uart_sel_sysfs_group);
if (err) {
pr_err("%s: failed to create modemif sysfs attribute group\n",
__func__);
return -ENODEV;
}
#endif
return 0;
}
static int __exit uart_sel_remove(struct platform_device *pdev)
{
/* TODO */
return 0;
}
#ifdef CONFIG_PM
static int uart_sel_suspend(struct device *dev)
{
/* TODO */
return 0;
}
static int uart_sel_resume(struct device *dev)
{
/* TODO */
return 0;
}
#else
#define uart_sel_suspend NULL
#define uart_sel_resume NULL
#endif
static const struct dev_pm_ops uart_sel_pm_ops = {
.suspend = uart_sel_suspend,
.resume = uart_sel_resume,
};
static const struct of_device_id exynos_uart_sel_dt_match[] = {
{ .compatible = "samsung,exynos-uart-sel", },
{},
};
MODULE_DEVICE_TABLE(of, exynos_uart_sel_dt_match);
static struct platform_driver uart_sel_driver = {
.probe = uart_sel_probe,
.remove = uart_sel_remove,
.driver = {
.name = "uart_sel",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(exynos_uart_sel_dt_match),
.pm = &uart_sel_pm_ops,
},
};
module_platform_driver(uart_sel_driver);
MODULE_DESCRIPTION("UART SEL driver");
MODULE_AUTHOR("<hy50.seo@samsung.com>");
MODULE_LICENSE("GPL");