1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Driver for SMSC USB4604 USB HSIC 4-port 2.0 hub controller driver |
4 | * Based on usb3503 driver |
5 | * |
6 | * Copyright (c) 2012-2013 Dongjin Kim (tobetter@gmail.com) |
7 | * Copyright (c) 2016 Linaro Ltd. |
8 | */ |
9 | |
10 | #include <linux/i2c.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/module.h> |
14 | #include <linux/gpio/consumer.h> |
15 | |
16 | enum usb4604_mode { |
17 | USB4604_MODE_UNKNOWN, |
18 | USB4604_MODE_HUB, |
19 | USB4604_MODE_STANDBY, |
20 | }; |
21 | |
22 | struct usb4604 { |
23 | enum usb4604_mode mode; |
24 | struct device *dev; |
25 | struct gpio_desc *gpio_reset; |
26 | }; |
27 | |
28 | static void usb4604_reset(struct usb4604 *hub, int state) |
29 | { |
30 | gpiod_set_value_cansleep(desc: hub->gpio_reset, value: state); |
31 | |
32 | /* Wait for i2c logic to come up */ |
33 | if (state) |
34 | msleep(msecs: 250); |
35 | } |
36 | |
37 | static int usb4604_connect(struct usb4604 *hub) |
38 | { |
39 | struct device *dev = hub->dev; |
40 | struct i2c_client *client = to_i2c_client(dev); |
41 | int err; |
42 | u8 connect_cmd[] = { 0xaa, 0x55, 0x00 }; |
43 | |
44 | usb4604_reset(hub, state: 1); |
45 | |
46 | err = i2c_master_send(client, buf: connect_cmd, ARRAY_SIZE(connect_cmd)); |
47 | if (err < 0) { |
48 | usb4604_reset(hub, state: 0); |
49 | return err; |
50 | } |
51 | |
52 | hub->mode = USB4604_MODE_HUB; |
53 | dev_dbg(dev, "switched to HUB mode\n" ); |
54 | |
55 | return 0; |
56 | } |
57 | |
58 | static int usb4604_switch_mode(struct usb4604 *hub, enum usb4604_mode mode) |
59 | { |
60 | struct device *dev = hub->dev; |
61 | int err = 0; |
62 | |
63 | switch (mode) { |
64 | case USB4604_MODE_HUB: |
65 | err = usb4604_connect(hub); |
66 | break; |
67 | |
68 | case USB4604_MODE_STANDBY: |
69 | usb4604_reset(hub, state: 0); |
70 | dev_dbg(dev, "switched to STANDBY mode\n" ); |
71 | break; |
72 | |
73 | default: |
74 | dev_err(dev, "unknown mode is requested\n" ); |
75 | err = -EINVAL; |
76 | break; |
77 | } |
78 | |
79 | return err; |
80 | } |
81 | |
82 | static int usb4604_probe(struct usb4604 *hub) |
83 | { |
84 | struct device *dev = hub->dev; |
85 | struct device_node *np = dev->of_node; |
86 | struct gpio_desc *gpio; |
87 | u32 mode = USB4604_MODE_HUB; |
88 | |
89 | gpio = devm_gpiod_get_optional(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
90 | if (IS_ERR(ptr: gpio)) |
91 | return PTR_ERR(ptr: gpio); |
92 | hub->gpio_reset = gpio; |
93 | |
94 | if (of_property_read_u32(np, propname: "initial-mode" , out_value: &hub->mode)) |
95 | hub->mode = mode; |
96 | |
97 | return usb4604_switch_mode(hub, mode: hub->mode); |
98 | } |
99 | |
100 | static int usb4604_i2c_probe(struct i2c_client *i2c) |
101 | { |
102 | struct usb4604 *hub; |
103 | |
104 | hub = devm_kzalloc(dev: &i2c->dev, size: sizeof(*hub), GFP_KERNEL); |
105 | if (!hub) |
106 | return -ENOMEM; |
107 | |
108 | i2c_set_clientdata(client: i2c, data: hub); |
109 | hub->dev = &i2c->dev; |
110 | |
111 | return usb4604_probe(hub); |
112 | } |
113 | |
114 | static int __maybe_unused usb4604_i2c_suspend(struct device *dev) |
115 | { |
116 | struct i2c_client *client = to_i2c_client(dev); |
117 | struct usb4604 *hub = i2c_get_clientdata(client); |
118 | |
119 | usb4604_switch_mode(hub, mode: USB4604_MODE_STANDBY); |
120 | |
121 | return 0; |
122 | } |
123 | |
124 | static int __maybe_unused usb4604_i2c_resume(struct device *dev) |
125 | { |
126 | struct i2c_client *client = to_i2c_client(dev); |
127 | struct usb4604 *hub = i2c_get_clientdata(client); |
128 | |
129 | usb4604_switch_mode(hub, mode: hub->mode); |
130 | |
131 | return 0; |
132 | } |
133 | |
134 | static SIMPLE_DEV_PM_OPS(usb4604_i2c_pm_ops, usb4604_i2c_suspend, |
135 | usb4604_i2c_resume); |
136 | |
137 | static const struct i2c_device_id usb4604_id[] = { |
138 | { "usb4604" , 0 }, |
139 | { } |
140 | }; |
141 | MODULE_DEVICE_TABLE(i2c, usb4604_id); |
142 | |
143 | #ifdef CONFIG_OF |
144 | static const struct of_device_id usb4604_of_match[] = { |
145 | { .compatible = "smsc,usb4604" }, |
146 | {} |
147 | }; |
148 | MODULE_DEVICE_TABLE(of, usb4604_of_match); |
149 | #endif |
150 | |
151 | static struct i2c_driver usb4604_i2c_driver = { |
152 | .driver = { |
153 | .name = "usb4604" , |
154 | .pm = pm_ptr(&usb4604_i2c_pm_ops), |
155 | .of_match_table = of_match_ptr(usb4604_of_match), |
156 | }, |
157 | .probe = usb4604_i2c_probe, |
158 | .id_table = usb4604_id, |
159 | }; |
160 | module_i2c_driver(usb4604_i2c_driver); |
161 | |
162 | MODULE_DESCRIPTION("USB4604 USB HUB driver" ); |
163 | MODULE_LICENSE("GPL v2" ); |
164 | |