1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * TI HD3SS3220 Type-C DRP Port Controller Driver |
4 | * |
5 | * Copyright (C) 2019 Renesas Electronics Corp. |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/i2c.h> |
10 | #include <linux/usb/role.h> |
11 | #include <linux/irqreturn.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/usb/typec.h> |
16 | #include <linux/delay.h> |
17 | #include <linux/workqueue.h> |
18 | |
19 | #define HD3SS3220_REG_CN_STAT_CTRL 0x09 |
20 | #define HD3SS3220_REG_GEN_CTRL 0x0A |
21 | #define HD3SS3220_REG_DEV_REV 0xA0 |
22 | |
23 | /* Register HD3SS3220_REG_CN_STAT_CTRL*/ |
24 | #define HD3SS3220_REG_CN_STAT_CTRL_ATTACHED_STATE_MASK (BIT(7) | BIT(6)) |
25 | #define HD3SS3220_REG_CN_STAT_CTRL_AS_DFP BIT(6) |
26 | #define HD3SS3220_REG_CN_STAT_CTRL_AS_UFP BIT(7) |
27 | #define HD3SS3220_REG_CN_STAT_CTRL_TO_ACCESSORY (BIT(7) | BIT(6)) |
28 | #define HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS BIT(4) |
29 | |
30 | /* Register HD3SS3220_REG_GEN_CTRL*/ |
31 | #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_MASK (BIT(2) | BIT(1)) |
32 | #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT 0x00 |
33 | #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SNK BIT(1) |
34 | #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SRC (BIT(2) | BIT(1)) |
35 | |
36 | struct hd3ss3220 { |
37 | struct device *dev; |
38 | struct regmap *regmap; |
39 | struct usb_role_switch *role_sw; |
40 | struct typec_port *port; |
41 | struct delayed_work output_poll_work; |
42 | enum usb_role role_state; |
43 | bool poll; |
44 | }; |
45 | |
46 | static int hd3ss3220_set_source_pref(struct hd3ss3220 *hd3ss3220, int src_pref) |
47 | { |
48 | return regmap_update_bits(map: hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, |
49 | HD3SS3220_REG_GEN_CTRL_SRC_PREF_MASK, |
50 | val: src_pref); |
51 | } |
52 | |
53 | static enum usb_role hd3ss3220_get_attached_state(struct hd3ss3220 *hd3ss3220) |
54 | { |
55 | unsigned int reg_val; |
56 | enum usb_role attached_state; |
57 | int ret; |
58 | |
59 | ret = regmap_read(map: hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, |
60 | val: ®_val); |
61 | if (ret < 0) |
62 | return ret; |
63 | |
64 | switch (reg_val & HD3SS3220_REG_CN_STAT_CTRL_ATTACHED_STATE_MASK) { |
65 | case HD3SS3220_REG_CN_STAT_CTRL_AS_DFP: |
66 | attached_state = USB_ROLE_HOST; |
67 | break; |
68 | case HD3SS3220_REG_CN_STAT_CTRL_AS_UFP: |
69 | attached_state = USB_ROLE_DEVICE; |
70 | break; |
71 | default: |
72 | attached_state = USB_ROLE_NONE; |
73 | break; |
74 | } |
75 | |
76 | return attached_state; |
77 | } |
78 | |
79 | static int hd3ss3220_dr_set(struct typec_port *port, enum typec_data_role role) |
80 | { |
81 | struct hd3ss3220 *hd3ss3220 = typec_get_drvdata(port); |
82 | enum usb_role role_val; |
83 | int pref, ret = 0; |
84 | |
85 | if (role == TYPEC_HOST) { |
86 | role_val = USB_ROLE_HOST; |
87 | pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SRC; |
88 | } else { |
89 | role_val = USB_ROLE_DEVICE; |
90 | pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SNK; |
91 | } |
92 | |
93 | ret = hd3ss3220_set_source_pref(hd3ss3220, src_pref: pref); |
94 | usleep_range(min: 10, max: 100); |
95 | |
96 | usb_role_switch_set_role(sw: hd3ss3220->role_sw, role: role_val); |
97 | typec_set_data_role(port: hd3ss3220->port, role); |
98 | |
99 | return ret; |
100 | } |
101 | |
102 | static const struct typec_operations hd3ss3220_ops = { |
103 | .dr_set = hd3ss3220_dr_set |
104 | }; |
105 | |
106 | static void hd3ss3220_set_role(struct hd3ss3220 *hd3ss3220) |
107 | { |
108 | enum usb_role role_state = hd3ss3220_get_attached_state(hd3ss3220); |
109 | |
110 | usb_role_switch_set_role(sw: hd3ss3220->role_sw, role: role_state); |
111 | if (role_state == USB_ROLE_NONE) |
112 | hd3ss3220_set_source_pref(hd3ss3220, |
113 | HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT); |
114 | |
115 | switch (role_state) { |
116 | case USB_ROLE_HOST: |
117 | typec_set_data_role(port: hd3ss3220->port, role: TYPEC_HOST); |
118 | break; |
119 | case USB_ROLE_DEVICE: |
120 | typec_set_data_role(port: hd3ss3220->port, role: TYPEC_DEVICE); |
121 | break; |
122 | default: |
123 | break; |
124 | } |
125 | |
126 | hd3ss3220->role_state = role_state; |
127 | } |
128 | |
129 | static void output_poll_execute(struct work_struct *work) |
130 | { |
131 | struct delayed_work *delayed_work = to_delayed_work(work); |
132 | struct hd3ss3220 *hd3ss3220 = container_of(delayed_work, |
133 | struct hd3ss3220, |
134 | output_poll_work); |
135 | enum usb_role role_state = hd3ss3220_get_attached_state(hd3ss3220); |
136 | |
137 | if (hd3ss3220->role_state != role_state) |
138 | hd3ss3220_set_role(hd3ss3220); |
139 | |
140 | schedule_delayed_work(dwork: &hd3ss3220->output_poll_work, HZ); |
141 | } |
142 | |
143 | static irqreturn_t hd3ss3220_irq(struct hd3ss3220 *hd3ss3220) |
144 | { |
145 | int err; |
146 | |
147 | hd3ss3220_set_role(hd3ss3220); |
148 | err = regmap_write_bits(map: hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, |
149 | HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS, |
150 | HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS); |
151 | if (err < 0) |
152 | return IRQ_NONE; |
153 | |
154 | return IRQ_HANDLED; |
155 | } |
156 | |
157 | static irqreturn_t hd3ss3220_irq_handler(int irq, void *data) |
158 | { |
159 | struct i2c_client *client = to_i2c_client(data); |
160 | struct hd3ss3220 *hd3ss3220 = i2c_get_clientdata(client); |
161 | |
162 | return hd3ss3220_irq(hd3ss3220); |
163 | } |
164 | |
165 | static const struct regmap_config config = { |
166 | .reg_bits = 8, |
167 | .val_bits = 8, |
168 | .max_register = 0x0A, |
169 | }; |
170 | |
171 | static int hd3ss3220_probe(struct i2c_client *client) |
172 | { |
173 | struct typec_capability typec_cap = { }; |
174 | struct hd3ss3220 *hd3ss3220; |
175 | struct fwnode_handle *connector, *ep; |
176 | int ret; |
177 | unsigned int data; |
178 | |
179 | hd3ss3220 = devm_kzalloc(dev: &client->dev, size: sizeof(struct hd3ss3220), |
180 | GFP_KERNEL); |
181 | if (!hd3ss3220) |
182 | return -ENOMEM; |
183 | |
184 | i2c_set_clientdata(client, data: hd3ss3220); |
185 | |
186 | hd3ss3220->dev = &client->dev; |
187 | hd3ss3220->regmap = devm_regmap_init_i2c(client, &config); |
188 | if (IS_ERR(ptr: hd3ss3220->regmap)) |
189 | return PTR_ERR(ptr: hd3ss3220->regmap); |
190 | |
191 | hd3ss3220_set_source_pref(hd3ss3220, |
192 | HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT); |
193 | /* For backward compatibility check the connector child node first */ |
194 | connector = device_get_named_child_node(dev: hd3ss3220->dev, childname: "connector" ); |
195 | if (connector) { |
196 | hd3ss3220->role_sw = fwnode_usb_role_switch_get(node: connector); |
197 | } else { |
198 | ep = fwnode_graph_get_next_endpoint(dev_fwnode(hd3ss3220->dev), NULL); |
199 | if (!ep) |
200 | return -ENODEV; |
201 | connector = fwnode_graph_get_remote_port_parent(fwnode: ep); |
202 | fwnode_handle_put(fwnode: ep); |
203 | if (!connector) |
204 | return -ENODEV; |
205 | hd3ss3220->role_sw = usb_role_switch_get(dev: hd3ss3220->dev); |
206 | } |
207 | |
208 | if (IS_ERR(ptr: hd3ss3220->role_sw)) { |
209 | ret = PTR_ERR(ptr: hd3ss3220->role_sw); |
210 | goto err_put_fwnode; |
211 | } |
212 | |
213 | typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; |
214 | typec_cap.driver_data = hd3ss3220; |
215 | typec_cap.type = TYPEC_PORT_DRP; |
216 | typec_cap.data = TYPEC_PORT_DRD; |
217 | typec_cap.ops = &hd3ss3220_ops; |
218 | typec_cap.fwnode = connector; |
219 | |
220 | hd3ss3220->port = typec_register_port(parent: &client->dev, cap: &typec_cap); |
221 | if (IS_ERR(ptr: hd3ss3220->port)) { |
222 | ret = PTR_ERR(ptr: hd3ss3220->port); |
223 | goto err_put_role; |
224 | } |
225 | |
226 | hd3ss3220_set_role(hd3ss3220); |
227 | ret = regmap_read(map: hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, val: &data); |
228 | if (ret < 0) |
229 | goto err_unreg_port; |
230 | |
231 | if (data & HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS) { |
232 | ret = regmap_write(map: hd3ss3220->regmap, |
233 | HD3SS3220_REG_CN_STAT_CTRL, |
234 | val: data | HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS); |
235 | if (ret < 0) |
236 | goto err_unreg_port; |
237 | } |
238 | |
239 | if (client->irq > 0) { |
240 | ret = devm_request_threaded_irq(dev: &client->dev, irq: client->irq, NULL, |
241 | thread_fn: hd3ss3220_irq_handler, |
242 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
243 | devname: "hd3ss3220" , dev_id: &client->dev); |
244 | if (ret) |
245 | goto err_unreg_port; |
246 | } else { |
247 | INIT_DELAYED_WORK(&hd3ss3220->output_poll_work, output_poll_execute); |
248 | hd3ss3220->poll = true; |
249 | } |
250 | |
251 | ret = i2c_smbus_read_byte_data(client, HD3SS3220_REG_DEV_REV); |
252 | if (ret < 0) |
253 | goto err_unreg_port; |
254 | |
255 | fwnode_handle_put(fwnode: connector); |
256 | |
257 | if (hd3ss3220->poll) |
258 | schedule_delayed_work(dwork: &hd3ss3220->output_poll_work, HZ); |
259 | |
260 | dev_info(&client->dev, "probed revision=0x%x\n" , ret); |
261 | |
262 | return 0; |
263 | err_unreg_port: |
264 | typec_unregister_port(port: hd3ss3220->port); |
265 | err_put_role: |
266 | usb_role_switch_put(sw: hd3ss3220->role_sw); |
267 | err_put_fwnode: |
268 | fwnode_handle_put(fwnode: connector); |
269 | |
270 | return ret; |
271 | } |
272 | |
273 | static void hd3ss3220_remove(struct i2c_client *client) |
274 | { |
275 | struct hd3ss3220 *hd3ss3220 = i2c_get_clientdata(client); |
276 | |
277 | if (hd3ss3220->poll) |
278 | cancel_delayed_work_sync(dwork: &hd3ss3220->output_poll_work); |
279 | |
280 | typec_unregister_port(port: hd3ss3220->port); |
281 | usb_role_switch_put(sw: hd3ss3220->role_sw); |
282 | } |
283 | |
284 | static const struct of_device_id dev_ids[] = { |
285 | { .compatible = "ti,hd3ss3220" }, |
286 | {} |
287 | }; |
288 | MODULE_DEVICE_TABLE(of, dev_ids); |
289 | |
290 | static struct i2c_driver hd3ss3220_driver = { |
291 | .driver = { |
292 | .name = "hd3ss3220" , |
293 | .of_match_table = dev_ids, |
294 | }, |
295 | .probe = hd3ss3220_probe, |
296 | .remove = hd3ss3220_remove, |
297 | }; |
298 | |
299 | module_i2c_driver(hd3ss3220_driver); |
300 | |
301 | MODULE_AUTHOR("Biju Das <biju.das@bp.renesas.com>" ); |
302 | MODULE_DESCRIPTION("TI HD3SS3220 DRP Port Controller Driver" ); |
303 | MODULE_LICENSE("GPL" ); |
304 | |