1
2
3
4
5
6
7
8#include <linux/i2c.h>
9#include <linux/kernel.h>
10#include <linux/module.h>
11#include <linux/mutex.h>
12#include <linux/usb/typec_dp.h>
13#include <linux/usb/typec_mux.h>
14
15#define PI3USB30532_CONF 0x00
16
17#define PI3USB30532_CONF_OPEN 0x00
18#define PI3USB30532_CONF_SWAP 0x01
19#define PI3USB30532_CONF_4LANE_DP 0x02
20#define PI3USB30532_CONF_USB3 0x04
21#define PI3USB30532_CONF_USB3_AND_2LANE_DP 0x06
22
23struct pi3usb30532 {
24 struct i2c_client *client;
25 struct mutex lock;
26 struct typec_switch sw;
27 struct typec_mux mux;
28 u8 conf;
29};
30
31static int pi3usb30532_set_conf(struct pi3usb30532 *pi, u8 new_conf)
32{
33 int ret = 0;
34
35 if (pi->conf == new_conf)
36 return 0;
37
38 ret = i2c_smbus_write_byte_data(pi->client, PI3USB30532_CONF, new_conf);
39 if (ret) {
40 dev_err(&pi->client->dev, "Error writing conf: %d\n", ret);
41 return ret;
42 }
43
44 pi->conf = new_conf;
45 return 0;
46}
47
48static int pi3usb30532_sw_set(struct typec_switch *sw,
49 enum typec_orientation orientation)
50{
51 struct pi3usb30532 *pi = container_of(sw, struct pi3usb30532, sw);
52 u8 new_conf;
53 int ret;
54
55 mutex_lock(&pi->lock);
56 new_conf = pi->conf;
57
58 switch (orientation) {
59 case TYPEC_ORIENTATION_NONE:
60 new_conf = PI3USB30532_CONF_OPEN;
61 break;
62 case TYPEC_ORIENTATION_NORMAL:
63 new_conf &= ~PI3USB30532_CONF_SWAP;
64 break;
65 case TYPEC_ORIENTATION_REVERSE:
66 new_conf |= PI3USB30532_CONF_SWAP;
67 break;
68 }
69
70 ret = pi3usb30532_set_conf(pi, new_conf);
71 mutex_unlock(&pi->lock);
72
73 return ret;
74}
75
76static int pi3usb30532_mux_set(struct typec_mux *mux, int state)
77{
78 struct pi3usb30532 *pi = container_of(mux, struct pi3usb30532, mux);
79 u8 new_conf;
80 int ret;
81
82 mutex_lock(&pi->lock);
83 new_conf = pi->conf;
84
85 switch (state) {
86 case TYPEC_STATE_SAFE:
87 new_conf = PI3USB30532_CONF_OPEN;
88 break;
89 case TYPEC_STATE_USB:
90 new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
91 PI3USB30532_CONF_USB3;
92 break;
93 case TYPEC_DP_STATE_C:
94 case TYPEC_DP_STATE_E:
95 new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
96 PI3USB30532_CONF_4LANE_DP;
97 break;
98 case TYPEC_DP_STATE_D:
99 new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
100 PI3USB30532_CONF_USB3_AND_2LANE_DP;
101 break;
102 default:
103 break;
104 }
105
106 ret = pi3usb30532_set_conf(pi, new_conf);
107 mutex_unlock(&pi->lock);
108
109 return ret;
110}
111
112static int pi3usb30532_probe(struct i2c_client *client)
113{
114 struct device *dev = &client->dev;
115 struct pi3usb30532 *pi;
116 int ret;
117
118 pi = devm_kzalloc(dev, sizeof(*pi), GFP_KERNEL);
119 if (!pi)
120 return -ENOMEM;
121
122 pi->client = client;
123 pi->sw.dev = dev;
124 pi->sw.set = pi3usb30532_sw_set;
125 pi->mux.dev = dev;
126 pi->mux.set = pi3usb30532_mux_set;
127 mutex_init(&pi->lock);
128
129 ret = i2c_smbus_read_byte_data(client, PI3USB30532_CONF);
130 if (ret < 0) {
131 dev_err(dev, "Error reading config register %d\n", ret);
132 return ret;
133 }
134 pi->conf = ret;
135
136 ret = typec_switch_register(&pi->sw);
137 if (ret) {
138 dev_err(dev, "Error registering typec switch: %d\n", ret);
139 return ret;
140 }
141
142 ret = typec_mux_register(&pi->mux);
143 if (ret) {
144 typec_switch_unregister(&pi->sw);
145 dev_err(dev, "Error registering typec mux: %d\n", ret);
146 return ret;
147 }
148
149 i2c_set_clientdata(client, pi);
150 return 0;
151}
152
153static int pi3usb30532_remove(struct i2c_client *client)
154{
155 struct pi3usb30532 *pi = i2c_get_clientdata(client);
156
157 typec_mux_unregister(&pi->mux);
158 typec_switch_unregister(&pi->sw);
159 return 0;
160}
161
162static const struct i2c_device_id pi3usb30532_table[] = {
163 { "pi3usb30532" },
164 { }
165};
166MODULE_DEVICE_TABLE(i2c, pi3usb30532_table);
167
168static struct i2c_driver pi3usb30532_driver = {
169 .driver = {
170 .name = "pi3usb30532",
171 },
172 .probe_new = pi3usb30532_probe,
173 .remove = pi3usb30532_remove,
174 .id_table = pi3usb30532_table,
175};
176
177module_i2c_driver(pi3usb30532_driver);
178
179MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
180MODULE_DESCRIPTION("Pericom PI3USB30532 Type-C mux driver");
181MODULE_LICENSE("GPL");
182