1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Pericom PI3USB30532 Type-C cross switch / mux driver |
4 | * |
5 | * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com> |
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 | |
23 | struct pi3usb30532 { |
24 | struct i2c_client *client; |
25 | struct mutex lock; /* protects the cached conf register */ |
26 | struct typec_switch_dev *sw; |
27 | struct typec_mux_dev *mux; |
28 | u8 conf; |
29 | }; |
30 | |
31 | static 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(client: pi->client, PI3USB30532_CONF, value: 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 | |
48 | static int pi3usb30532_sw_set(struct typec_switch_dev *sw, |
49 | enum typec_orientation orientation) |
50 | { |
51 | struct pi3usb30532 *pi = typec_switch_get_drvdata(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(lock: &pi->lock); |
72 | |
73 | return ret; |
74 | } |
75 | |
76 | static int |
77 | pi3usb30532_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) |
78 | { |
79 | struct pi3usb30532 *pi = typec_mux_get_drvdata(mux); |
80 | u8 new_conf; |
81 | int ret; |
82 | |
83 | mutex_lock(&pi->lock); |
84 | new_conf = pi->conf; |
85 | |
86 | switch (state->mode) { |
87 | case TYPEC_STATE_SAFE: |
88 | new_conf = (new_conf & PI3USB30532_CONF_SWAP) | |
89 | PI3USB30532_CONF_OPEN; |
90 | break; |
91 | case TYPEC_STATE_USB: |
92 | new_conf = (new_conf & PI3USB30532_CONF_SWAP) | |
93 | PI3USB30532_CONF_USB3; |
94 | break; |
95 | case TYPEC_DP_STATE_C: |
96 | case TYPEC_DP_STATE_E: |
97 | new_conf = (new_conf & PI3USB30532_CONF_SWAP) | |
98 | PI3USB30532_CONF_4LANE_DP; |
99 | break; |
100 | case TYPEC_DP_STATE_D: |
101 | new_conf = (new_conf & PI3USB30532_CONF_SWAP) | |
102 | PI3USB30532_CONF_USB3_AND_2LANE_DP; |
103 | break; |
104 | default: |
105 | break; |
106 | } |
107 | |
108 | ret = pi3usb30532_set_conf(pi, new_conf); |
109 | mutex_unlock(lock: &pi->lock); |
110 | |
111 | return ret; |
112 | } |
113 | |
114 | static int pi3usb30532_probe(struct i2c_client *client) |
115 | { |
116 | struct device *dev = &client->dev; |
117 | struct typec_switch_desc sw_desc = { }; |
118 | struct typec_mux_desc mux_desc = { }; |
119 | struct pi3usb30532 *pi; |
120 | int ret; |
121 | |
122 | pi = devm_kzalloc(dev, size: sizeof(*pi), GFP_KERNEL); |
123 | if (!pi) |
124 | return -ENOMEM; |
125 | |
126 | pi->client = client; |
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 | sw_desc.drvdata = pi; |
137 | sw_desc.fwnode = dev->fwnode; |
138 | sw_desc.set = pi3usb30532_sw_set; |
139 | |
140 | pi->sw = typec_switch_register(parent: dev, desc: &sw_desc); |
141 | if (IS_ERR(ptr: pi->sw)) { |
142 | dev_err(dev, "Error registering typec switch: %ld\n" , |
143 | PTR_ERR(pi->sw)); |
144 | return PTR_ERR(ptr: pi->sw); |
145 | } |
146 | |
147 | mux_desc.drvdata = pi; |
148 | mux_desc.fwnode = dev->fwnode; |
149 | mux_desc.set = pi3usb30532_mux_set; |
150 | |
151 | pi->mux = typec_mux_register(parent: dev, desc: &mux_desc); |
152 | if (IS_ERR(ptr: pi->mux)) { |
153 | typec_switch_unregister(sw: pi->sw); |
154 | dev_err(dev, "Error registering typec mux: %ld\n" , |
155 | PTR_ERR(pi->mux)); |
156 | return PTR_ERR(ptr: pi->mux); |
157 | } |
158 | |
159 | i2c_set_clientdata(client, data: pi); |
160 | return 0; |
161 | } |
162 | |
163 | static void pi3usb30532_remove(struct i2c_client *client) |
164 | { |
165 | struct pi3usb30532 *pi = i2c_get_clientdata(client); |
166 | |
167 | typec_mux_unregister(mux: pi->mux); |
168 | typec_switch_unregister(sw: pi->sw); |
169 | } |
170 | |
171 | static const struct i2c_device_id pi3usb30532_table[] = { |
172 | { "pi3usb30532" }, |
173 | { } |
174 | }; |
175 | MODULE_DEVICE_TABLE(i2c, pi3usb30532_table); |
176 | |
177 | static struct i2c_driver pi3usb30532_driver = { |
178 | .driver = { |
179 | .name = "pi3usb30532" , |
180 | }, |
181 | .probe = pi3usb30532_probe, |
182 | .remove = pi3usb30532_remove, |
183 | .id_table = pi3usb30532_table, |
184 | }; |
185 | |
186 | module_i2c_driver(pi3usb30532_driver); |
187 | |
188 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>" ); |
189 | MODULE_DESCRIPTION("Pericom PI3USB30532 Type-C mux driver" ); |
190 | MODULE_LICENSE("GPL" ); |
191 | |