2018-03-20 12:57:11 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
/*
|
|
|
|
* Pericom PI3USB30532 Type-C cross switch / mux driver
|
|
|
|
*
|
|
|
|
* Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/mutex.h>
|
2018-06-27 15:19:52 +00:00
|
|
|
#include <linux/usb/typec_dp.h>
|
2018-03-20 12:57:11 +00:00
|
|
|
#include <linux/usb/typec_mux.h>
|
|
|
|
|
|
|
|
#define PI3USB30532_CONF 0x00
|
|
|
|
|
|
|
|
#define PI3USB30532_CONF_OPEN 0x00
|
|
|
|
#define PI3USB30532_CONF_SWAP 0x01
|
|
|
|
#define PI3USB30532_CONF_4LANE_DP 0x02
|
|
|
|
#define PI3USB30532_CONF_USB3 0x04
|
|
|
|
#define PI3USB30532_CONF_USB3_AND_2LANE_DP 0x06
|
|
|
|
|
|
|
|
struct pi3usb30532 {
|
|
|
|
struct i2c_client *client;
|
|
|
|
struct mutex lock; /* protects the cached conf register */
|
2019-05-31 14:15:41 +00:00
|
|
|
struct typec_switch *sw;
|
|
|
|
struct typec_mux *mux;
|
2018-03-20 12:57:11 +00:00
|
|
|
u8 conf;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int pi3usb30532_set_conf(struct pi3usb30532 *pi, u8 new_conf)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (pi->conf == new_conf)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ret = i2c_smbus_write_byte_data(pi->client, PI3USB30532_CONF, new_conf);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(&pi->client->dev, "Error writing conf: %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
pi->conf = new_conf;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pi3usb30532_sw_set(struct typec_switch *sw,
|
|
|
|
enum typec_orientation orientation)
|
|
|
|
{
|
2019-05-31 14:15:41 +00:00
|
|
|
struct pi3usb30532 *pi = typec_switch_get_drvdata(sw);
|
2018-03-20 12:57:11 +00:00
|
|
|
u8 new_conf;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
mutex_lock(&pi->lock);
|
|
|
|
new_conf = pi->conf;
|
|
|
|
|
|
|
|
switch (orientation) {
|
|
|
|
case TYPEC_ORIENTATION_NONE:
|
|
|
|
new_conf = PI3USB30532_CONF_OPEN;
|
|
|
|
break;
|
|
|
|
case TYPEC_ORIENTATION_NORMAL:
|
|
|
|
new_conf &= ~PI3USB30532_CONF_SWAP;
|
|
|
|
break;
|
|
|
|
case TYPEC_ORIENTATION_REVERSE:
|
|
|
|
new_conf |= PI3USB30532_CONF_SWAP;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = pi3usb30532_set_conf(pi, new_conf);
|
|
|
|
mutex_unlock(&pi->lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-12-30 14:26:08 +00:00
|
|
|
static int
|
|
|
|
pi3usb30532_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
|
2018-03-20 12:57:11 +00:00
|
|
|
{
|
2019-05-31 14:15:41 +00:00
|
|
|
struct pi3usb30532 *pi = typec_mux_get_drvdata(mux);
|
2018-03-20 12:57:11 +00:00
|
|
|
u8 new_conf;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
mutex_lock(&pi->lock);
|
|
|
|
new_conf = pi->conf;
|
|
|
|
|
2019-12-30 14:26:08 +00:00
|
|
|
switch (state->mode) {
|
2018-06-27 15:19:52 +00:00
|
|
|
case TYPEC_STATE_SAFE:
|
2019-02-22 19:22:39 +00:00
|
|
|
new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
|
|
|
|
PI3USB30532_CONF_OPEN;
|
2018-03-20 12:57:11 +00:00
|
|
|
break;
|
2018-06-27 15:19:52 +00:00
|
|
|
case TYPEC_STATE_USB:
|
2018-03-20 12:57:11 +00:00
|
|
|
new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
|
|
|
|
PI3USB30532_CONF_USB3;
|
|
|
|
break;
|
2018-06-27 15:19:52 +00:00
|
|
|
case TYPEC_DP_STATE_C:
|
|
|
|
case TYPEC_DP_STATE_E:
|
2018-03-20 12:57:11 +00:00
|
|
|
new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
|
|
|
|
PI3USB30532_CONF_4LANE_DP;
|
|
|
|
break;
|
2018-06-27 15:19:52 +00:00
|
|
|
case TYPEC_DP_STATE_D:
|
2018-03-20 12:57:11 +00:00
|
|
|
new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
|
|
|
|
PI3USB30532_CONF_USB3_AND_2LANE_DP;
|
|
|
|
break;
|
2018-06-27 15:19:52 +00:00
|
|
|
default:
|
|
|
|
break;
|
2018-03-20 12:57:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = pi3usb30532_set_conf(pi, new_conf);
|
|
|
|
mutex_unlock(&pi->lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pi3usb30532_probe(struct i2c_client *client)
|
|
|
|
{
|
|
|
|
struct device *dev = &client->dev;
|
2020-04-14 13:33:13 +00:00
|
|
|
struct typec_switch_desc sw_desc = { };
|
|
|
|
struct typec_mux_desc mux_desc = { };
|
2018-03-20 12:57:11 +00:00
|
|
|
struct pi3usb30532 *pi;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
pi = devm_kzalloc(dev, sizeof(*pi), GFP_KERNEL);
|
|
|
|
if (!pi)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
pi->client = client;
|
|
|
|
mutex_init(&pi->lock);
|
|
|
|
|
|
|
|
ret = i2c_smbus_read_byte_data(client, PI3USB30532_CONF);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "Error reading config register %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
pi->conf = ret;
|
|
|
|
|
2019-05-31 14:15:41 +00:00
|
|
|
sw_desc.drvdata = pi;
|
|
|
|
sw_desc.fwnode = dev->fwnode;
|
|
|
|
sw_desc.set = pi3usb30532_sw_set;
|
|
|
|
|
|
|
|
pi->sw = typec_switch_register(dev, &sw_desc);
|
|
|
|
if (IS_ERR(pi->sw)) {
|
|
|
|
dev_err(dev, "Error registering typec switch: %ld\n",
|
|
|
|
PTR_ERR(pi->sw));
|
|
|
|
return PTR_ERR(pi->sw);
|
2018-03-20 12:57:11 +00:00
|
|
|
}
|
|
|
|
|
2019-05-31 14:15:41 +00:00
|
|
|
mux_desc.drvdata = pi;
|
|
|
|
mux_desc.fwnode = dev->fwnode;
|
|
|
|
mux_desc.set = pi3usb30532_mux_set;
|
|
|
|
|
|
|
|
pi->mux = typec_mux_register(dev, &mux_desc);
|
|
|
|
if (IS_ERR(pi->mux)) {
|
|
|
|
typec_switch_unregister(pi->sw);
|
|
|
|
dev_err(dev, "Error registering typec mux: %ld\n",
|
|
|
|
PTR_ERR(pi->mux));
|
|
|
|
return PTR_ERR(pi->mux);
|
2018-03-20 12:57:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
i2c_set_clientdata(client, pi);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int pi3usb30532_remove(struct i2c_client *client)
|
|
|
|
{
|
|
|
|
struct pi3usb30532 *pi = i2c_get_clientdata(client);
|
|
|
|
|
2019-05-31 14:15:41 +00:00
|
|
|
typec_mux_unregister(pi->mux);
|
|
|
|
typec_switch_unregister(pi->sw);
|
2018-03-20 12:57:11 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct i2c_device_id pi3usb30532_table[] = {
|
|
|
|
{ "pi3usb30532" },
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, pi3usb30532_table);
|
|
|
|
|
|
|
|
static struct i2c_driver pi3usb30532_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "pi3usb30532",
|
|
|
|
},
|
|
|
|
.probe_new = pi3usb30532_probe,
|
|
|
|
.remove = pi3usb30532_remove,
|
|
|
|
.id_table = pi3usb30532_table,
|
|
|
|
};
|
|
|
|
|
|
|
|
module_i2c_driver(pi3usb30532_driver);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
|
|
|
MODULE_DESCRIPTION("Pericom PI3USB30532 Type-C mux driver");
|
|
|
|
MODULE_LICENSE("GPL");
|