Commit | Line | Data |
---|---|---|
1c48c759 BD |
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/module.h> | |
14 | #include <linux/regmap.h> | |
15 | #include <linux/slab.h> | |
16 | #include <linux/usb/typec.h> | |
17 | #include <linux/delay.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 typec_capability typec_cap; | |
42 | }; | |
43 | ||
44 | static int hd3ss3220_set_source_pref(struct hd3ss3220 *hd3ss3220, int src_pref) | |
45 | { | |
46 | return regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, | |
47 | HD3SS3220_REG_GEN_CTRL_SRC_PREF_MASK, | |
48 | src_pref); | |
49 | } | |
50 | ||
51 | static enum usb_role hd3ss3220_get_attached_state(struct hd3ss3220 *hd3ss3220) | |
52 | { | |
53 | unsigned int reg_val; | |
54 | enum usb_role attached_state; | |
55 | int ret; | |
56 | ||
57 | ret = regmap_read(hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, | |
58 | ®_val); | |
59 | if (ret < 0) | |
60 | return ret; | |
61 | ||
62 | switch (reg_val & HD3SS3220_REG_CN_STAT_CTRL_ATTACHED_STATE_MASK) { | |
63 | case HD3SS3220_REG_CN_STAT_CTRL_AS_DFP: | |
64 | attached_state = USB_ROLE_HOST; | |
65 | break; | |
66 | case HD3SS3220_REG_CN_STAT_CTRL_AS_UFP: | |
67 | attached_state = USB_ROLE_DEVICE; | |
68 | break; | |
69 | default: | |
70 | attached_state = USB_ROLE_NONE; | |
71 | break; | |
72 | } | |
73 | ||
74 | return attached_state; | |
75 | } | |
76 | ||
77 | static int hd3ss3220_dr_set(const struct typec_capability *cap, | |
78 | enum typec_data_role role) | |
79 | { | |
80 | struct hd3ss3220 *hd3ss3220 = container_of(cap, struct hd3ss3220, | |
81 | typec_cap); | |
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, pref); | |
94 | usleep_range(10, 100); | |
95 | ||
96 | usb_role_switch_set_role(hd3ss3220->role_sw, role_val); | |
97 | typec_set_data_role(hd3ss3220->port, role); | |
98 | ||
99 | return ret; | |
100 | } | |
101 | ||
102 | static void hd3ss3220_set_role(struct hd3ss3220 *hd3ss3220) | |
103 | { | |
104 | enum usb_role role_state = hd3ss3220_get_attached_state(hd3ss3220); | |
105 | ||
106 | usb_role_switch_set_role(hd3ss3220->role_sw, role_state); | |
107 | if (role_state == USB_ROLE_NONE) | |
108 | hd3ss3220_set_source_pref(hd3ss3220, | |
109 | HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT); | |
110 | ||
111 | switch (role_state) { | |
112 | case USB_ROLE_HOST: | |
113 | typec_set_data_role(hd3ss3220->port, TYPEC_HOST); | |
114 | break; | |
115 | case USB_ROLE_DEVICE: | |
116 | typec_set_data_role(hd3ss3220->port, TYPEC_DEVICE); | |
117 | break; | |
118 | default: | |
119 | break; | |
120 | } | |
121 | } | |
122 | ||
dd3fd317 | 123 | static irqreturn_t hd3ss3220_irq(struct hd3ss3220 *hd3ss3220) |
1c48c759 BD |
124 | { |
125 | int err; | |
126 | ||
127 | hd3ss3220_set_role(hd3ss3220); | |
128 | err = regmap_update_bits_base(hd3ss3220->regmap, | |
129 | HD3SS3220_REG_CN_STAT_CTRL, | |
130 | HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS, | |
131 | HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS, | |
132 | NULL, false, true); | |
133 | if (err < 0) | |
134 | return IRQ_NONE; | |
135 | ||
136 | return IRQ_HANDLED; | |
137 | } | |
138 | ||
139 | static irqreturn_t hd3ss3220_irq_handler(int irq, void *data) | |
140 | { | |
141 | struct i2c_client *client = to_i2c_client(data); | |
142 | struct hd3ss3220 *hd3ss3220 = i2c_get_clientdata(client); | |
143 | ||
144 | return hd3ss3220_irq(hd3ss3220); | |
145 | } | |
146 | ||
147 | static const struct regmap_config config = { | |
148 | .reg_bits = 8, | |
149 | .val_bits = 8, | |
150 | .max_register = 0x0A, | |
151 | }; | |
152 | ||
153 | static int hd3ss3220_probe(struct i2c_client *client, | |
154 | const struct i2c_device_id *id) | |
155 | { | |
156 | struct hd3ss3220 *hd3ss3220; | |
157 | struct fwnode_handle *connector; | |
158 | int ret; | |
159 | unsigned int data; | |
160 | ||
161 | hd3ss3220 = devm_kzalloc(&client->dev, sizeof(struct hd3ss3220), | |
162 | GFP_KERNEL); | |
163 | if (!hd3ss3220) | |
164 | return -ENOMEM; | |
165 | ||
166 | i2c_set_clientdata(client, hd3ss3220); | |
167 | ||
168 | hd3ss3220->dev = &client->dev; | |
169 | hd3ss3220->regmap = devm_regmap_init_i2c(client, &config); | |
170 | if (IS_ERR(hd3ss3220->regmap)) | |
171 | return PTR_ERR(hd3ss3220->regmap); | |
172 | ||
173 | hd3ss3220_set_source_pref(hd3ss3220, | |
174 | HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT); | |
175 | connector = device_get_named_child_node(hd3ss3220->dev, "connector"); | |
176 | if (IS_ERR(connector)) | |
177 | return PTR_ERR(connector); | |
178 | ||
179 | hd3ss3220->role_sw = fwnode_usb_role_switch_get(connector); | |
180 | fwnode_handle_put(connector); | |
181 | if (IS_ERR_OR_NULL(hd3ss3220->role_sw)) | |
182 | return PTR_ERR(hd3ss3220->role_sw); | |
183 | ||
184 | hd3ss3220->typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; | |
185 | hd3ss3220->typec_cap.dr_set = hd3ss3220_dr_set; | |
186 | hd3ss3220->typec_cap.type = TYPEC_PORT_DRP; | |
187 | hd3ss3220->typec_cap.data = TYPEC_PORT_DRD; | |
188 | ||
189 | hd3ss3220->port = typec_register_port(&client->dev, | |
190 | &hd3ss3220->typec_cap); | |
191 | if (IS_ERR(hd3ss3220->port)) | |
192 | return PTR_ERR(hd3ss3220->port); | |
193 | ||
194 | hd3ss3220_set_role(hd3ss3220); | |
195 | ret = regmap_read(hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, &data); | |
196 | if (ret < 0) | |
197 | goto error; | |
198 | ||
199 | if (data & HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS) { | |
200 | ret = regmap_write(hd3ss3220->regmap, | |
201 | HD3SS3220_REG_CN_STAT_CTRL, | |
202 | data | HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS); | |
203 | if (ret < 0) | |
204 | goto error; | |
205 | } | |
206 | ||
207 | if (client->irq > 0) { | |
208 | ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, | |
209 | hd3ss3220_irq_handler, | |
210 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, | |
211 | "hd3ss3220", &client->dev); | |
212 | if (ret) | |
213 | goto error; | |
214 | } | |
215 | ||
216 | ret = i2c_smbus_read_byte_data(client, HD3SS3220_REG_DEV_REV); | |
217 | if (ret < 0) | |
218 | goto error; | |
219 | ||
220 | dev_info(&client->dev, "probed revision=0x%x\n", ret); | |
221 | ||
222 | return 0; | |
223 | error: | |
224 | typec_unregister_port(hd3ss3220->port); | |
225 | usb_role_switch_put(hd3ss3220->role_sw); | |
226 | ||
227 | return ret; | |
228 | } | |
229 | ||
230 | static int hd3ss3220_remove(struct i2c_client *client) | |
231 | { | |
232 | struct hd3ss3220 *hd3ss3220 = i2c_get_clientdata(client); | |
233 | ||
234 | typec_unregister_port(hd3ss3220->port); | |
235 | usb_role_switch_put(hd3ss3220->role_sw); | |
236 | ||
237 | return 0; | |
238 | } | |
239 | ||
240 | static const struct of_device_id dev_ids[] = { | |
241 | { .compatible = "ti,hd3ss3220"}, | |
242 | {} | |
243 | }; | |
244 | MODULE_DEVICE_TABLE(of, dev_ids); | |
245 | ||
246 | static struct i2c_driver hd3ss3220_driver = { | |
247 | .driver = { | |
248 | .name = "hd3ss3220", | |
249 | .of_match_table = of_match_ptr(dev_ids), | |
250 | }, | |
251 | .probe = hd3ss3220_probe, | |
252 | .remove = hd3ss3220_remove, | |
253 | }; | |
254 | ||
255 | module_i2c_driver(hd3ss3220_driver); | |
256 | ||
257 | MODULE_AUTHOR("Biju Das <biju.das@bp.renesas.com>"); | |
258 | MODULE_DESCRIPTION("TI HD3SS3220 DRP Port Controller Driver"); | |
259 | MODULE_LICENSE("GPL"); |