162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for SMSC USB4604 USB HSIC 4-port 2.0 hub controller driver
462306a36Sopenharmony_ci * Based on usb3503 driver
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (c) 2012-2013 Dongjin Kim (tobetter@gmail.com)
762306a36Sopenharmony_ci * Copyright (c) 2016 Linaro Ltd.
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/i2c.h>
1162306a36Sopenharmony_ci#include <linux/delay.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cienum usb4604_mode {
1762306a36Sopenharmony_ci	USB4604_MODE_UNKNOWN,
1862306a36Sopenharmony_ci	USB4604_MODE_HUB,
1962306a36Sopenharmony_ci	USB4604_MODE_STANDBY,
2062306a36Sopenharmony_ci};
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistruct usb4604 {
2362306a36Sopenharmony_ci	enum usb4604_mode	mode;
2462306a36Sopenharmony_ci	struct device		*dev;
2562306a36Sopenharmony_ci	struct gpio_desc	*gpio_reset;
2662306a36Sopenharmony_ci};
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic void usb4604_reset(struct usb4604 *hub, int state)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	gpiod_set_value_cansleep(hub->gpio_reset, state);
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	/* Wait for i2c logic to come up */
3362306a36Sopenharmony_ci	if (state)
3462306a36Sopenharmony_ci		msleep(250);
3562306a36Sopenharmony_ci}
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic int usb4604_connect(struct usb4604 *hub)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	struct device *dev = hub->dev;
4062306a36Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
4162306a36Sopenharmony_ci	int err;
4262306a36Sopenharmony_ci	u8 connect_cmd[] = { 0xaa, 0x55, 0x00 };
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	usb4604_reset(hub, 1);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	err = i2c_master_send(client, connect_cmd, ARRAY_SIZE(connect_cmd));
4762306a36Sopenharmony_ci	if (err < 0) {
4862306a36Sopenharmony_ci		usb4604_reset(hub, 0);
4962306a36Sopenharmony_ci		return err;
5062306a36Sopenharmony_ci	}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	hub->mode = USB4604_MODE_HUB;
5362306a36Sopenharmony_ci	dev_dbg(dev, "switched to HUB mode\n");
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	return 0;
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic int usb4604_switch_mode(struct usb4604 *hub, enum usb4604_mode mode)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	struct device *dev = hub->dev;
6162306a36Sopenharmony_ci	int err = 0;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	switch (mode) {
6462306a36Sopenharmony_ci	case USB4604_MODE_HUB:
6562306a36Sopenharmony_ci		err = usb4604_connect(hub);
6662306a36Sopenharmony_ci		break;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	case USB4604_MODE_STANDBY:
6962306a36Sopenharmony_ci		usb4604_reset(hub, 0);
7062306a36Sopenharmony_ci		dev_dbg(dev, "switched to STANDBY mode\n");
7162306a36Sopenharmony_ci		break;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	default:
7462306a36Sopenharmony_ci		dev_err(dev, "unknown mode is requested\n");
7562306a36Sopenharmony_ci		err = -EINVAL;
7662306a36Sopenharmony_ci		break;
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	return err;
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic int usb4604_probe(struct usb4604 *hub)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	struct device *dev = hub->dev;
8562306a36Sopenharmony_ci	struct device_node *np = dev->of_node;
8662306a36Sopenharmony_ci	struct gpio_desc *gpio;
8762306a36Sopenharmony_ci	u32 mode = USB4604_MODE_HUB;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
9062306a36Sopenharmony_ci	if (IS_ERR(gpio))
9162306a36Sopenharmony_ci		return PTR_ERR(gpio);
9262306a36Sopenharmony_ci	hub->gpio_reset = gpio;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	if (of_property_read_u32(np, "initial-mode", &hub->mode))
9562306a36Sopenharmony_ci		hub->mode = mode;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	return usb4604_switch_mode(hub, hub->mode);
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic int usb4604_i2c_probe(struct i2c_client *i2c)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	struct usb4604 *hub;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	hub = devm_kzalloc(&i2c->dev, sizeof(*hub), GFP_KERNEL);
10562306a36Sopenharmony_ci	if (!hub)
10662306a36Sopenharmony_ci		return -ENOMEM;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	i2c_set_clientdata(i2c, hub);
10962306a36Sopenharmony_ci	hub->dev = &i2c->dev;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	return usb4604_probe(hub);
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic int __maybe_unused usb4604_i2c_suspend(struct device *dev)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
11762306a36Sopenharmony_ci	struct usb4604 *hub = i2c_get_clientdata(client);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	usb4604_switch_mode(hub, USB4604_MODE_STANDBY);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	return 0;
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic int __maybe_unused usb4604_i2c_resume(struct device *dev)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
12762306a36Sopenharmony_ci	struct usb4604 *hub = i2c_get_clientdata(client);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	usb4604_switch_mode(hub, hub->mode);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	return 0;
13262306a36Sopenharmony_ci}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(usb4604_i2c_pm_ops, usb4604_i2c_suspend,
13562306a36Sopenharmony_ci		usb4604_i2c_resume);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic const struct i2c_device_id usb4604_id[] = {
13862306a36Sopenharmony_ci	{ "usb4604", 0 },
13962306a36Sopenharmony_ci	{ }
14062306a36Sopenharmony_ci};
14162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, usb4604_id);
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci#ifdef CONFIG_OF
14462306a36Sopenharmony_cistatic const struct of_device_id usb4604_of_match[] = {
14562306a36Sopenharmony_ci	{ .compatible = "smsc,usb4604" },
14662306a36Sopenharmony_ci	{}
14762306a36Sopenharmony_ci};
14862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, usb4604_of_match);
14962306a36Sopenharmony_ci#endif
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic struct i2c_driver usb4604_i2c_driver = {
15262306a36Sopenharmony_ci	.driver = {
15362306a36Sopenharmony_ci		.name = "usb4604",
15462306a36Sopenharmony_ci		.pm = pm_ptr(&usb4604_i2c_pm_ops),
15562306a36Sopenharmony_ci		.of_match_table = of_match_ptr(usb4604_of_match),
15662306a36Sopenharmony_ci	},
15762306a36Sopenharmony_ci	.probe		= usb4604_i2c_probe,
15862306a36Sopenharmony_ci	.id_table	= usb4604_id,
15962306a36Sopenharmony_ci};
16062306a36Sopenharmony_cimodule_i2c_driver(usb4604_i2c_driver);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ciMODULE_DESCRIPTION("USB4604 USB HUB driver");
16362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
164