15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0+
2740a6a17SStephen Boyd /*
3740a6a17SStephen Boyd * Driver for SMSC USB4604 USB HSIC 4-port 2.0 hub controller driver
4740a6a17SStephen Boyd * Based on usb3503 driver
5740a6a17SStephen Boyd *
6740a6a17SStephen Boyd * Copyright (c) 2012-2013 Dongjin Kim (tobetter@gmail.com)
7740a6a17SStephen Boyd * Copyright (c) 2016 Linaro Ltd.
8740a6a17SStephen Boyd */
9740a6a17SStephen Boyd
10740a6a17SStephen Boyd #include <linux/i2c.h>
11740a6a17SStephen Boyd #include <linux/delay.h>
12740a6a17SStephen Boyd #include <linux/slab.h>
13740a6a17SStephen Boyd #include <linux/module.h>
14740a6a17SStephen Boyd #include <linux/gpio/consumer.h>
15740a6a17SStephen Boyd
16740a6a17SStephen Boyd enum usb4604_mode {
17740a6a17SStephen Boyd USB4604_MODE_UNKNOWN,
18740a6a17SStephen Boyd USB4604_MODE_HUB,
19740a6a17SStephen Boyd USB4604_MODE_STANDBY,
20740a6a17SStephen Boyd };
21740a6a17SStephen Boyd
22740a6a17SStephen Boyd struct usb4604 {
23740a6a17SStephen Boyd enum usb4604_mode mode;
24740a6a17SStephen Boyd struct device *dev;
25740a6a17SStephen Boyd struct gpio_desc *gpio_reset;
26740a6a17SStephen Boyd };
27740a6a17SStephen Boyd
usb4604_reset(struct usb4604 * hub,int state)28740a6a17SStephen Boyd static void usb4604_reset(struct usb4604 *hub, int state)
29740a6a17SStephen Boyd {
30740a6a17SStephen Boyd gpiod_set_value_cansleep(hub->gpio_reset, state);
31740a6a17SStephen Boyd
32740a6a17SStephen Boyd /* Wait for i2c logic to come up */
33740a6a17SStephen Boyd if (state)
34740a6a17SStephen Boyd msleep(250);
35740a6a17SStephen Boyd }
36740a6a17SStephen Boyd
usb4604_connect(struct usb4604 * hub)37740a6a17SStephen Boyd static int usb4604_connect(struct usb4604 *hub)
38740a6a17SStephen Boyd {
39740a6a17SStephen Boyd struct device *dev = hub->dev;
40740a6a17SStephen Boyd struct i2c_client *client = to_i2c_client(dev);
41740a6a17SStephen Boyd int err;
42740a6a17SStephen Boyd u8 connect_cmd[] = { 0xaa, 0x55, 0x00 };
43740a6a17SStephen Boyd
44740a6a17SStephen Boyd usb4604_reset(hub, 1);
45740a6a17SStephen Boyd
46740a6a17SStephen Boyd err = i2c_master_send(client, connect_cmd, ARRAY_SIZE(connect_cmd));
47740a6a17SStephen Boyd if (err < 0) {
48740a6a17SStephen Boyd usb4604_reset(hub, 0);
49740a6a17SStephen Boyd return err;
50740a6a17SStephen Boyd }
51740a6a17SStephen Boyd
52740a6a17SStephen Boyd hub->mode = USB4604_MODE_HUB;
53740a6a17SStephen Boyd dev_dbg(dev, "switched to HUB mode\n");
54740a6a17SStephen Boyd
55740a6a17SStephen Boyd return 0;
56740a6a17SStephen Boyd }
57740a6a17SStephen Boyd
usb4604_switch_mode(struct usb4604 * hub,enum usb4604_mode mode)58740a6a17SStephen Boyd static int usb4604_switch_mode(struct usb4604 *hub, enum usb4604_mode mode)
59740a6a17SStephen Boyd {
60740a6a17SStephen Boyd struct device *dev = hub->dev;
61740a6a17SStephen Boyd int err = 0;
62740a6a17SStephen Boyd
63740a6a17SStephen Boyd switch (mode) {
64740a6a17SStephen Boyd case USB4604_MODE_HUB:
65740a6a17SStephen Boyd err = usb4604_connect(hub);
66740a6a17SStephen Boyd break;
67740a6a17SStephen Boyd
68740a6a17SStephen Boyd case USB4604_MODE_STANDBY:
69740a6a17SStephen Boyd usb4604_reset(hub, 0);
70740a6a17SStephen Boyd dev_dbg(dev, "switched to STANDBY mode\n");
71740a6a17SStephen Boyd break;
72740a6a17SStephen Boyd
73740a6a17SStephen Boyd default:
74740a6a17SStephen Boyd dev_err(dev, "unknown mode is requested\n");
75740a6a17SStephen Boyd err = -EINVAL;
76740a6a17SStephen Boyd break;
77740a6a17SStephen Boyd }
78740a6a17SStephen Boyd
79740a6a17SStephen Boyd return err;
80740a6a17SStephen Boyd }
81740a6a17SStephen Boyd
usb4604_probe(struct usb4604 * hub)82740a6a17SStephen Boyd static int usb4604_probe(struct usb4604 *hub)
83740a6a17SStephen Boyd {
84740a6a17SStephen Boyd struct device *dev = hub->dev;
85740a6a17SStephen Boyd struct device_node *np = dev->of_node;
86740a6a17SStephen Boyd struct gpio_desc *gpio;
87740a6a17SStephen Boyd u32 mode = USB4604_MODE_HUB;
88740a6a17SStephen Boyd
89740a6a17SStephen Boyd gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
90740a6a17SStephen Boyd if (IS_ERR(gpio))
91740a6a17SStephen Boyd return PTR_ERR(gpio);
92740a6a17SStephen Boyd hub->gpio_reset = gpio;
93740a6a17SStephen Boyd
94740a6a17SStephen Boyd if (of_property_read_u32(np, "initial-mode", &hub->mode))
95740a6a17SStephen Boyd hub->mode = mode;
96740a6a17SStephen Boyd
97740a6a17SStephen Boyd return usb4604_switch_mode(hub, hub->mode);
98740a6a17SStephen Boyd }
99740a6a17SStephen Boyd
usb4604_i2c_probe(struct i2c_client * i2c)100d4468280SUwe Kleine-König static int usb4604_i2c_probe(struct i2c_client *i2c)
101740a6a17SStephen Boyd {
102740a6a17SStephen Boyd struct usb4604 *hub;
103740a6a17SStephen Boyd
104740a6a17SStephen Boyd hub = devm_kzalloc(&i2c->dev, sizeof(*hub), GFP_KERNEL);
105740a6a17SStephen Boyd if (!hub)
106740a6a17SStephen Boyd return -ENOMEM;
107740a6a17SStephen Boyd
108740a6a17SStephen Boyd i2c_set_clientdata(i2c, hub);
109740a6a17SStephen Boyd hub->dev = &i2c->dev;
110740a6a17SStephen Boyd
111740a6a17SStephen Boyd return usb4604_probe(hub);
112740a6a17SStephen Boyd }
113740a6a17SStephen Boyd
usb4604_i2c_suspend(struct device * dev)1147aea2a7dSPaul Cercueil static int __maybe_unused usb4604_i2c_suspend(struct device *dev)
115740a6a17SStephen Boyd {
116740a6a17SStephen Boyd struct i2c_client *client = to_i2c_client(dev);
117740a6a17SStephen Boyd struct usb4604 *hub = i2c_get_clientdata(client);
118740a6a17SStephen Boyd
119740a6a17SStephen Boyd usb4604_switch_mode(hub, USB4604_MODE_STANDBY);
120740a6a17SStephen Boyd
121740a6a17SStephen Boyd return 0;
122740a6a17SStephen Boyd }
123740a6a17SStephen Boyd
usb4604_i2c_resume(struct device * dev)1247aea2a7dSPaul Cercueil static int __maybe_unused usb4604_i2c_resume(struct device *dev)
125740a6a17SStephen Boyd {
126740a6a17SStephen Boyd struct i2c_client *client = to_i2c_client(dev);
127740a6a17SStephen Boyd struct usb4604 *hub = i2c_get_clientdata(client);
128740a6a17SStephen Boyd
129740a6a17SStephen Boyd usb4604_switch_mode(hub, hub->mode);
130740a6a17SStephen Boyd
131740a6a17SStephen Boyd return 0;
132740a6a17SStephen Boyd }
133740a6a17SStephen Boyd
134740a6a17SStephen Boyd static SIMPLE_DEV_PM_OPS(usb4604_i2c_pm_ops, usb4604_i2c_suspend,
135740a6a17SStephen Boyd usb4604_i2c_resume);
136740a6a17SStephen Boyd
137740a6a17SStephen Boyd static const struct i2c_device_id usb4604_id[] = {
138740a6a17SStephen Boyd { "usb4604", 0 },
139740a6a17SStephen Boyd { }
140740a6a17SStephen Boyd };
141740a6a17SStephen Boyd MODULE_DEVICE_TABLE(i2c, usb4604_id);
142740a6a17SStephen Boyd
143740a6a17SStephen Boyd #ifdef CONFIG_OF
144740a6a17SStephen Boyd static const struct of_device_id usb4604_of_match[] = {
145740a6a17SStephen Boyd { .compatible = "smsc,usb4604" },
146740a6a17SStephen Boyd {}
147740a6a17SStephen Boyd };
148740a6a17SStephen Boyd MODULE_DEVICE_TABLE(of, usb4604_of_match);
149740a6a17SStephen Boyd #endif
150740a6a17SStephen Boyd
151740a6a17SStephen Boyd static struct i2c_driver usb4604_i2c_driver = {
152740a6a17SStephen Boyd .driver = {
153740a6a17SStephen Boyd .name = "usb4604",
1547aea2a7dSPaul Cercueil .pm = pm_ptr(&usb4604_i2c_pm_ops),
155740a6a17SStephen Boyd .of_match_table = of_match_ptr(usb4604_of_match),
156740a6a17SStephen Boyd },
157*7126a2aeSUwe Kleine-König .probe = usb4604_i2c_probe,
158740a6a17SStephen Boyd .id_table = usb4604_id,
159740a6a17SStephen Boyd };
160740a6a17SStephen Boyd module_i2c_driver(usb4604_i2c_driver);
161740a6a17SStephen Boyd
162740a6a17SStephen Boyd MODULE_DESCRIPTION("USB4604 USB HUB driver");
163740a6a17SStephen Boyd MODULE_LICENSE("GPL v2");
164