diff options
Diffstat (limited to 'common/usb_onboard_hub.c')
-rw-r--r-- | common/usb_onboard_hub.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/common/usb_onboard_hub.c b/common/usb_onboard_hub.c new file mode 100644 index 00000000000..805b2ccbc00 --- /dev/null +++ b/common/usb_onboard_hub.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for onboard USB hubs + * + * Copyright (C) 2022, STMicroelectronics - All Rights Reserved + * + * Mostly inspired by Linux kernel v6.1 onboard_usb_hub driver + */ + +#include <asm/gpio.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <dm/uclass-internal.h> +#include <i2c.h> +#include <linux/delay.h> +#include <power/regulator.h> + +#define USB5744_COMMAND_ATTACH 0x0056 +#define USB5744_COMMAND_ATTACH_LSB 0xAA +#define USB5744_CONFIG_REG_ACCESS 0x0037 +#define USB5744_CONFIG_REG_ACCESS_LSB 0x99 + +#define MAX_SUPPLIES 2 + +struct onboard_hub { + struct udevice *vdd[MAX_SUPPLIES]; + struct gpio_desc *reset_gpio; +}; + +struct onboard_hub_data { + unsigned long reset_us; + unsigned long power_on_delay_us; + unsigned int num_supplies; + const char * const supply_names[MAX_SUPPLIES]; + int (*init)(struct udevice *dev); +}; + +static int usb5744_i2c_init(struct udevice *dev) +{ + /* + * Prevent the MCU from the putting the HUB in suspend mode through register write. + * The BYPASS_UDC_SUSPEND bit (Bit 3) of the RuntimeFlags2 register at address + * 0x411D controls this aspect of the hub. + * Format to write to hub registers via SMBus- 2D 00 00 05 00 01 41 1D 08 + * Byte 0: Address of slave 2D + * Byte 1: Memory address 00 + * Byte 2: Memory address 00 + * Byte 3: Number of bytes to write to memory + * Byte 4: Write configuration register (00) + * Byte 5: Write the number of data bytes (01- 1 data byte) + * Byte 6: LSB of register address 0x41 + * Byte 7: MSB of register address 0x1D + * Byte 8: value to be written to the register + */ + u8 data_buf[8] = {0x0, 0x5, 0x0, 0x1, 0x41, 0x1D, 0x08}; + u8 config_reg_access_buf = USB5744_CONFIG_REG_ACCESS; + struct udevice *i2c_bus = NULL, *i2c_dev; + struct ofnode_phandle_args phandle; + u8 buf = USB5744_COMMAND_ATTACH; + struct dm_i2c_chip *i2c_chip; + int ret, slave_addr; + + ret = dev_read_phandle_with_args(dev, "i2c-bus", NULL, 0, 0, &phandle); + if (ret) { + dev_err(dev, "i2c-bus not specified\n"); + return ret; + } + + ret = device_get_global_by_ofnode(ofnode_get_parent(phandle.node), &i2c_bus); + if (ret) { + dev_err(dev, "Failed to get i2c node, err: %d\n", ret); + return ret; + } + + ret = ofnode_read_u32(phandle.node, "reg", &slave_addr); + if (ret) + return ret; + + ret = i2c_get_chip(i2c_bus, slave_addr, 1, &i2c_dev); + if (ret) { + dev_err(dev, "%s: can't find i2c chip device for addr 0x%x\n", __func__, + slave_addr); + return ret; + } + + i2c_chip = dev_get_parent_plat(i2c_dev); + if (!i2c_chip) { + dev_err(dev, "parent platform data not found\n"); + return -EINVAL; + } + + i2c_chip->flags &= ~DM_I2C_CHIP_WR_ADDRESS; + /* SMBus write command */ + ret = dm_i2c_write(i2c_dev, 0, (uint8_t *)&data_buf, 8); + if (ret) { + dev_err(dev, "data_buf i2c_write failed, err:%d\n", ret); + return ret; + } + + /* Configuration register access command */ + ret = dm_i2c_write(i2c_dev, USB5744_CONFIG_REG_ACCESS_LSB, + &config_reg_access_buf, 2); + if (ret) { + dev_err(dev, "config_reg_access i2c_write failed, err: %d\n", ret); + return ret; + } + + /* USB Attach with SMBus */ + ret = dm_i2c_write(i2c_dev, USB5744_COMMAND_ATTACH_LSB, &buf, 2); + if (ret) { + dev_err(dev, "usb_attach i2c_write failed, err: %d\n", ret); + return ret; + } + + return 0; +} + +int usb_onboard_hub_reset(struct udevice *dev) +{ + struct onboard_hub_data *data = + (struct onboard_hub_data *)dev_get_driver_data(dev); + struct onboard_hub *hub = dev_get_priv(dev); + int ret; + + hub->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_IS_OUT); + + /* property is optional, don't return error! */ + if (!hub->reset_gpio) + return 0; + + ret = dm_gpio_set_value(hub->reset_gpio, 1); + if (ret) + return ret; + + udelay(data->reset_us); + + ret = dm_gpio_set_value(hub->reset_gpio, 0); + if (ret) + return ret; + + udelay(data->power_on_delay_us); + + return 0; +} + +static int usb_onboard_hub_power_off(struct udevice *dev) +{ + struct onboard_hub_data *data = + (struct onboard_hub_data *)dev_get_driver_data(dev); + struct onboard_hub *hub = dev_get_priv(dev); + int ret = 0, ret2; + unsigned int i; + + for (i = data->num_supplies; i > 0; i--) { + if (hub->vdd[i-1]) { + ret2 = regulator_set_enable_if_allowed(hub->vdd[i-1], false); + if (ret2 && ret2 != -ENOSYS) { + dev_err(dev, "can't disable %s: %d\n", data->supply_names[i-1], ret2); + ret |= ret2; + } + } + } + + return ret; +} + +static int usb_onboard_hub_probe(struct udevice *dev) +{ + struct onboard_hub_data *data = + (struct onboard_hub_data *)dev_get_driver_data(dev); + struct onboard_hub *hub = dev_get_priv(dev); + unsigned int i; + int ret; + + if (data->num_supplies > MAX_SUPPLIES) { + dev_err(dev, "invalid supplies number, max supported: %d\n", MAX_SUPPLIES); + return -EINVAL; + } + + for (i = 0; i < data->num_supplies; i++) { + ret = device_get_supply_regulator(dev, data->supply_names[i], &hub->vdd[i]); + if (ret && ret != -ENOENT && ret != -ENOSYS) { + dev_err(dev, "can't get %s: %d\n", data->supply_names[i], ret); + goto err_supply; + } + + if (hub->vdd[i]) { + ret = regulator_set_enable_if_allowed(hub->vdd[i], true); + if (ret && ret != -ENOSYS) { + dev_err(dev, "can't enable %s: %d\n", data->supply_names[i], ret); + goto err_supply; + } + } + } + + ret = usb_onboard_hub_reset(dev); + if (ret) + goto err_supply; + + if (data->init) { + ret = data->init(dev); + if (ret) { + dev_err(dev, "onboard i2c init failed: %d\n", ret); + goto err; + } + } + return 0; +err: + dm_gpio_set_value(hub->reset_gpio, 0); +err_supply: + usb_onboard_hub_power_off(dev); + return ret; +} + +static int usb_onboard_hub_bind(struct udevice *dev) +{ + struct ofnode_phandle_args phandle; + struct udevice *peerdev; + int ret; + + ret = dev_read_phandle_with_args(dev, "peer-hub", NULL, 0, 0, &phandle); + if (ret == -ENOENT) { + dev_dbg(dev, "peer-hub property not present\n"); + return 0; + } + + if (ret) { + dev_err(dev, "peer-hub not specified\n"); + return ret; + } + + ret = uclass_find_device_by_ofnode(UCLASS_USB_HUB, phandle.node, &peerdev); + if (ret) { + dev_dbg(dev, "binding before peer-hub %s\n", + ofnode_get_name(phandle.node)); + return 0; + } + + dev_dbg(dev, "peer-hub %s has been bound\n", peerdev->name); + + return -ENODEV; +} + +static int usb_onboard_hub_remove(struct udevice *dev) +{ + struct onboard_hub *hub = dev_get_priv(dev); + int ret = 0; + + if (hub->reset_gpio) { + ret = dm_gpio_set_value(hub->reset_gpio, 1); + if (ret) + dev_err(dev, "can't set gpio %s: %d\n", hub->reset_gpio->dev->name, + ret); + } + + ret |= usb_onboard_hub_power_off(dev); + return ret; +} + +static const struct onboard_hub_data usb2514_data = { + .power_on_delay_us = 500, + .reset_us = 1, + .num_supplies = 1, + .supply_names = { "vdd-supply" }, +}; + +static const struct onboard_hub_data usb5744_data = { + .init = usb5744_i2c_init, + .power_on_delay_us = 1000, + .reset_us = 5, + .num_supplies = 1, + .supply_names = { "vdd-supply" }, +}; + +static const struct onboard_hub_data usbhx3_data = { + .reset_us = 10000, + .num_supplies = 2, + .supply_names = { "vdd-supply", "vdd2-supply" }, +}; + +static const struct udevice_id usb_onboard_hub_ids[] = { + /* Use generic usbVID,PID dt-bindings (usb-device.yaml) */ + { .compatible = "usb424,2514", /* USB2514B USB 2.0 */ + .data = (ulong)&usb2514_data, + }, { + .compatible = "usb424,2744", /* USB2744 USB 2.0 */ + .data = (ulong)&usb5744_data, + }, { + .compatible = "usb424,5744", /* USB5744 USB 3.0 */ + .data = (ulong)&usb5744_data, + }, { + .compatible = "usb4b4,6504", /* Cypress HX3 USB 3.0 */ + .data = (ulong)&usbhx3_data, + }, { + .compatible = "usb4b4,6506", /* Cypress HX3 USB 2.0 */ + .data = (ulong)&usbhx3_data, + }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(usb_onboard_hub) = { + .name = "usb_onboard_hub", + .id = UCLASS_USB_HUB, + .bind = usb_onboard_hub_bind, + .probe = usb_onboard_hub_probe, + .remove = usb_onboard_hub_remove, + .of_match = usb_onboard_hub_ids, + .priv_auto = sizeof(struct onboard_hub), +}; |