You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

262 lines
6.2 KiB

/*
* drivers/switch/switch_usb_rt8973.c
* Copyright (C) 2016 Richtek Technology Corp.
*
* USB switch driver
*
* Author: Patrick Chang <patrick_chang@richtek.com>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License 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.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/ctype.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/switch.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#define RT8973_DEVICE_NAME "rt8973"
#define RT8973_DRV_VER "1.0.0_M"
#define RT8973_REG_CTRL 0x02
#define RT8973_REGVAL_MANUAL_CTRL 0x09
#define RT8973_REG_DEV_TYPE 0x0A
#define RT8973_REG_MANUAL_SW 0X13
#define RT8973_REGVAL_SW_CHG 0x6c
#define RT8973_REGVAL_SW_USB 0x24
#define RT8973_REGVAL_DEV_TYPE_SDP (1 << 2)
#define RT8973_REGVAL_DEV_TYPE_CDP (1 << 5)
struct rt8973_usb_switch_data {
struct switch_dev sdev;
struct i2c_client *iic;
};
static const char *const usb_states[] = {
"charger",
"usb",
};
static const uint8_t usb_state_regvals[] = {
RT8973_REGVAL_SW_CHG,
RT8973_REGVAL_SW_USB
};
static ssize_t rt8973_usb_switch_print_state(struct switch_dev *sdev, char *buf)
{
int state;
state = switch_get_state(sdev);
if (state < 0)
return state;
WARN_ON(state >= ARRAY_SIZE(usb_states));
return snprintf(buf, PAGE_SIZE, "%s\n", usb_states[state]);
}
static int rt8973_usb_switch_set_state(struct switch_dev *sdev, int state)
{
struct rt8973_usb_switch_data *switch_data =
container_of(sdev, struct rt8973_usb_switch_data, sdev);
WARN_ON(state >= ARRAY_SIZE(usb_state_regvals));
return i2c_smbus_write_byte_data(switch_data->iic,
RT8973_REG_MANUAL_SW, usb_state_regvals[state]);
}
static int rt8973_usb_switch_parse_dt(struct rt8973_usb_switch_data *switch_data)
{
struct device_node *np = switch_data->iic->dev.of_node;
int retval;
if (!np)
return -EINVAL;
retval = of_property_read_string(np, "usb_switch_name",
(char const **)&switch_data->sdev.name);
if (retval < 0)
switch_data->sdev.name = "usb_switch";
return 0;
}
static int rt8973_usb_switch_init(struct rt8973_usb_switch_data *switch_data)
{
int ret;
bool is_std_or_cdp = false;
ret = i2c_smbus_read_byte_data(switch_data->iic, RT8973_REG_DEV_TYPE);
if (ret < 0)
return ret;
if (ret & (RT8973_REGVAL_DEV_TYPE_CDP | RT8973_REGVAL_DEV_TYPE_SDP)) {
is_std_or_cdp = true;
switch_data->sdev.state = 1;
} else
is_std_or_cdp = false;
ret = i2c_smbus_write_byte_data(switch_data->iic, RT8973_REG_MANUAL_SW,
usb_state_regvals[is_std_or_cdp ? 1 : 0]);
if (ret < 0)
return ret;
ret = i2c_smbus_write_byte_data(switch_data->iic, RT8973_REG_CTRL,
RT8973_REGVAL_MANUAL_CTRL);
return ret;
}
static int rt8973_usbsw_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct rt8973_usb_switch_data *switch_data;
int ret = 0;
switch_data = devm_kzalloc(&client->dev, sizeof(*switch_data), GFP_KERNEL);
if (!switch_data)
return -ENOMEM;
switch_data->sdev.name = "usb_switch";
switch_data->sdev.set_state = rt8973_usb_switch_set_state;
switch_data->sdev.print_state = rt8973_usb_switch_print_state;
switch_data->iic = client;
i2c_set_clientdata(client, switch_data);
rt8973_usb_switch_parse_dt(switch_data);
ret = rt8973_usb_switch_init(switch_data);
if (ret < 0)
goto err_init;
ret = switch_dev_register(&switch_data->sdev);
if (ret < 0)
goto err_switch_dev_register;
return 0;
err_switch_dev_register:
i2c_smbus_write_byte_data(client, 0x1b, 0x01);
err_init:
devm_kfree(&client->dev, switch_data);
return ret;
}
static int rt8973_usbsw_remove(struct i2c_client *client)
{
return 0;
}
static void rt8973_usbsw_shutdown(struct i2c_client *client)
{
/* Reset RT8973 */
i2c_smbus_write_byte_data(client, 0x1b, 0x01);
}
#ifdef CONFIG_PM
static int rt8973_usbsw_suspend(struct device *dev)
{
return 0;
}
static int rt8973_usbsw_resume(struct device *dev)
{
return 0;
}
#ifdef CONFIG_PM_RUNTIME
static int rt8973_usbsw_runtime_suspend(struct device *dev)
{
dev_dbg(dev, "%s\n", __func__);
return 0;
}
static int rt8973_usbsw_runtime_resume(struct device *dev)
{
dev_dbg(dev, "%s\n", __func__);
return 0;
}
static int rt8973_usbsw_runtime_idle(struct device *dev)
{
return 0;
}
#endif /* #ifdef CONFIG_PM_RUNTIME */
static const struct dev_pm_ops rt8973_usbsw_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(rt8973_usbsw_suspend, rt8973_usbsw_resume)
#ifdef CONFIG_PM_RUNTIME
SET_RUNTIME_PM_OPS(rt8973_usbsw_runtime_suspend,
rt8973_usbsw_runtime_resume,
rt8973_usbsw_runtime_idle)
#endif /* #ifdef CONFIG_PM_RUNTIME */
};
#define ptr8973_usbsw_pm_ops (&rt8973_usbsw_pm_ops)
#else
#define ptr8973_usbsw_pm_ops (NULL)
#endif /* #ifdef CONFIG_PM */
static const struct i2c_device_id rt8973_usbsw_id[] = {
{RT8973_DEVICE_NAME, 0},
{}
};
MODULE_DEVICE_TABLE(i2c, rt8973_usbsw_id);
#ifdef CONFIG_OF
static const struct of_device_id rt8973_usbsw_match_table[] = {
{.compatible = "richtek,rt8973-usbsw",},
{},
};
MODULE_DEVICE_TABLE(of, rt8973_usbsw_match_table);
#endif /* #ifdef CONFIG_OF */
static struct i2c_driver rt8973_i2c_driver = {
.driver = {
.name = RT8973_DEVICE_NAME,
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = of_match_ptr(rt8973_usbsw_match_table),
#endif /* #ifdef CONFIG_OF */
.pm = ptr8973_usbsw_pm_ops,
},
.shutdown = rt8973_usbsw_shutdown,
.probe = rt8973_usbsw_probe,
.remove = rt8973_usbsw_remove,
.id_table = rt8973_usbsw_id,
};
static int __init rt8973_usbsw_init(void)
{
int ret = 0;
pr_info("%s\n", __func__);
ret = i2c_add_driver(&rt8973_i2c_driver);
return ret;
}
static void __exit rt8973_usbsw_exit(void)
{
pr_info("%s\n", __func__);
i2c_del_driver(&rt8973_i2c_driver);
}
subsys_initcall_sync(rt8973_usbsw_init);
module_exit(rt8973_usbsw_exit);
MODULE_VERSION(RT8973_DRV_VER);
MODULE_AUTHOR("Patrick Chang <patrick_chang@richtek.com>");
MODULE_DESCRIPTION("USB switch driver");
MODULE_LICENSE("GPL");