From 04dd17822334871b23ea2862f7798fb0e0007777 Mon Sep 17 00:00:00 2001 From: hc <hc@nodka.com> Date: Sat, 11 May 2024 08:53:19 +0000 Subject: [PATCH] change otg to host mode --- kernel/drivers/usb/misc/usb251xb.c | 211 +++++++++++++++++++++++++++++++++++++++------------- 1 files changed, 159 insertions(+), 52 deletions(-) diff --git a/kernel/drivers/usb/misc/usb251xb.c b/kernel/drivers/usb/misc/usb251xb.c index 5f7734c..29fe577 100644 --- a/kernel/drivers/usb/misc/usb251xb.c +++ b/kernel/drivers/usb/misc/usb251xb.c @@ -12,10 +12,12 @@ #include <linux/delay.h> #include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> #include <linux/i2c.h> #include <linux/module.h> #include <linux/nls.h> #include <linux/of_device.h> +#include <linux/regulator/consumer.h> #include <linux/slab.h> /* Internal Register Set Addresses & Default Values acc. to DS00001692C */ @@ -25,10 +27,6 @@ #define USB251XB_ADDR_PRODUCT_ID_LSB 0x02 #define USB251XB_ADDR_PRODUCT_ID_MSB 0x03 -#define USB251XB_DEF_PRODUCT_ID_12 0x2512 /* USB2512B/12Bi */ -#define USB251XB_DEF_PRODUCT_ID_13 0x2513 /* USB2513B/13Bi */ -#define USB251XB_DEF_PRODUCT_ID_14 0x2514 /* USB2514B/14Bi */ -#define USB251XB_DEF_PRODUCT_ID_17 0x2517 /* USB2517/17i */ #define USB251XB_ADDR_DEVICE_ID_LSB 0x04 #define USB251XB_ADDR_DEVICE_ID_MSB 0x05 @@ -73,7 +71,6 @@ #define USB251XB_ADDR_PRODUCT_STRING_LEN 0x14 #define USB251XB_ADDR_PRODUCT_STRING 0x54 -#define USB251XB_DEF_PRODUCT_STRING "USB251xB/xBi/7i" #define USB251XB_ADDR_SERIAL_STRING_LEN 0x15 #define USB251XB_ADDR_SERIAL_STRING 0x92 @@ -115,6 +112,7 @@ struct usb251xb { struct device *dev; struct i2c_client *i2c; + struct regulator *vdd; u8 skip_config; struct gpio_desc *gpio_reset; u16 vendor_id; @@ -156,6 +154,14 @@ bool led_support; bool bat_support; char product_str[USB251XB_STRING_BUFSIZE / 2]; /* ASCII string */ +}; + +static const struct usb251xb_data usb2422_data = { + .product_id = 0x2422, + .port_cnt = 2, + .led_support = false, + .bat_support = true, + .product_str = "USB2422", }; static const struct usb251xb_data usb2512b_data = { @@ -222,18 +228,59 @@ .product_str = "USB2517i", }; -static void usb251xb_reset(struct usb251xb *hub, int state) +#ifdef CONFIG_GPIOLIB +static int usb251xb_check_dev_children(struct device *dev, void *child) +{ + if (dev->type == &i2c_adapter_type) { + return device_for_each_child(dev, child, + usb251xb_check_dev_children); + } + + return (dev == child); +} + +static int usb251x_check_gpio_chip(struct usb251xb *hub) +{ + struct gpio_chip *gc = gpiod_to_chip(hub->gpio_reset); + struct i2c_adapter *adap = hub->i2c->adapter; + int ret; + + if (!hub->gpio_reset) + return 0; + + if (!gc) + return -EINVAL; + + ret = usb251xb_check_dev_children(&adap->dev, gc->parent); + if (ret) { + dev_err(hub->dev, "Reset GPIO chip is at the same i2c-bus\n"); + return -EINVAL; + } + + return 0; +} +#else +static int usb251x_check_gpio_chip(struct usb251xb *hub) +{ + return 0; +} +#endif + +static void usb251xb_reset(struct usb251xb *hub) { if (!hub->gpio_reset) return; - gpiod_set_value_cansleep(hub->gpio_reset, state); + i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); + + gpiod_set_value_cansleep(hub->gpio_reset, 1); + usleep_range(1, 10); /* >=1us RESET_N asserted */ + gpiod_set_value_cansleep(hub->gpio_reset, 0); /* wait for hub recovery/stabilization */ - if (!state) - usleep_range(500, 750); /* >=500us at power on */ - else - usleep_range(1, 10); /* >=1us at power down */ + usleep_range(500, 750); /* >=500us after RESET_N deasserted */ + + i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); } static int usb251xb_connect(struct usb251xb *hub) @@ -249,7 +296,7 @@ i2c_wb[0] = 0x01; i2c_wb[1] = USB251XB_STATUS_COMMAND_ATTACH; - usb251xb_reset(hub, 0); + usb251xb_reset(hub); err = i2c_smbus_write_i2c_block_data(hub->i2c, USB251XB_ADDR_STATUS_COMMAND, 2, i2c_wb); @@ -299,7 +346,7 @@ i2c_wb[USB251XB_ADDR_PORT_MAP_7] = hub->port_map7; i2c_wb[USB251XB_ADDR_STATUS_COMMAND] = USB251XB_STATUS_COMMAND_ATTACH; - usb251xb_reset(hub, 0); + usb251xb_reset(hub); /* write registers */ for (i = 0; i < (USB251XB_I2C_REG_SZ / USB251XB_I2C_WRITE_SZ); i++) { @@ -331,14 +378,30 @@ } #ifdef CONFIG_OF +static void usb251xb_get_ports_field(struct usb251xb *hub, + const char *prop_name, u8 port_cnt, + bool ds_only, u8 *fld) +{ + struct device *dev = hub->dev; + struct property *prop; + const __be32 *p; + u32 port; + + of_property_for_each_u32(dev->of_node, prop_name, prop, p, port) { + if ((port >= ds_only ? 1 : 0) && (port <= port_cnt)) + *fld |= BIT(port); + else + dev_warn(dev, "port %u doesn't exist\n", port); + } +} + static int usb251xb_get_ofdata(struct usb251xb *hub, struct usb251xb_data *data) { struct device *dev = hub->dev; struct device_node *np = dev->of_node; - int len, err, i; + int len, err; u32 property_u32 = 0; - const u32 *cproperty_u32; const char *cproperty_char; char str[USB251XB_STRING_BUFSIZE / 2]; @@ -442,46 +505,16 @@ hub->conf_data3 |= BIT(0); hub->non_rem_dev = USB251XB_DEF_NON_REMOVABLE_DEVICES; - cproperty_u32 = of_get_property(np, "non-removable-ports", &len); - if (cproperty_u32 && (len / sizeof(u32)) > 0) { - for (i = 0; i < len / sizeof(u32); i++) { - u32 port = be32_to_cpu(cproperty_u32[i]); - - if ((port >= 1) && (port <= data->port_cnt)) - hub->non_rem_dev |= BIT(port); - else - dev_warn(dev, "NRD port %u doesn't exist\n", - port); - } - } + usb251xb_get_ports_field(hub, "non-removable-ports", data->port_cnt, + true, &hub->non_rem_dev); hub->port_disable_sp = USB251XB_DEF_PORT_DISABLE_SELF; - cproperty_u32 = of_get_property(np, "sp-disabled-ports", &len); - if (cproperty_u32 && (len / sizeof(u32)) > 0) { - for (i = 0; i < len / sizeof(u32); i++) { - u32 port = be32_to_cpu(cproperty_u32[i]); - - if ((port >= 1) && (port <= data->port_cnt)) - hub->port_disable_sp |= BIT(port); - else - dev_warn(dev, "PDS port %u doesn't exist\n", - port); - } - } + usb251xb_get_ports_field(hub, "sp-disabled-ports", data->port_cnt, + true, &hub->port_disable_sp); hub->port_disable_bp = USB251XB_DEF_PORT_DISABLE_BUS; - cproperty_u32 = of_get_property(np, "bp-disabled-ports", &len); - if (cproperty_u32 && (len / sizeof(u32)) > 0) { - for (i = 0; i < len / sizeof(u32); i++) { - u32 port = be32_to_cpu(cproperty_u32[i]); - - if ((port >= 1) && (port <= data->port_cnt)) - hub->port_disable_bp |= BIT(port); - else - dev_warn(dev, "PDB port %u doesn't exist\n", - port); - } - } + usb251xb_get_ports_field(hub, "bp-disabled-ports", data->port_cnt, + true, &hub->port_disable_bp); hub->max_power_sp = USB251XB_DEF_MAX_POWER_SELF; if (!of_property_read_u32(np, "sp-max-total-current-microamp", @@ -539,6 +572,14 @@ (wchar_t *)hub->serial, USB251XB_STRING_BUFSIZE); + /* + * The datasheet documents the register as 'Port Swap' but in real the + * register controls the USB DP/DM signal swapping for each port. + */ + hub->port_swap = USB251XB_DEF_PORT_SWAP; + usb251xb_get_ports_field(hub, "swap-dx-lanes", data->port_cnt, + false, &hub->port_swap); + /* The following parameters are currently not exposed to devicetree, but * may be as soon as needed. */ @@ -546,7 +587,6 @@ hub->boost_up = USB251XB_DEF_BOOST_UP; hub->boost_57 = USB251XB_DEF_BOOST_57; hub->boost_14 = USB251XB_DEF_BOOST_14; - hub->port_swap = USB251XB_DEF_PORT_SWAP; hub->port_map12 = USB251XB_DEF_PORT_MAP_12; hub->port_map34 = USB251XB_DEF_PORT_MAP_34; hub->port_map56 = USB251XB_DEF_PORT_MAP_56; @@ -557,6 +597,9 @@ static const struct of_device_id usb251xb_of_match[] = { { + .compatible = "microchip,usb2422", + .data = &usb2422_data, + }, { .compatible = "microchip,usb2512b", .data = &usb2512b_data, }, { @@ -593,6 +636,13 @@ } #endif /* CONFIG_OF */ +static void usb251xb_regulator_disable_action(void *data) +{ + struct usb251xb *hub = data; + + regulator_disable(hub->vdd); +} + static int usb251xb_probe(struct usb251xb *hub) { struct device *dev = hub->dev; @@ -609,6 +659,38 @@ return err; } } + + /* + * usb251x SMBus-slave SCL lane is muxed with CFG_SEL0 pin. So if anyone + * tries to work with the bus at the moment the hub reset is released, + * it may cause an invalid config being latched by usb251x. Particularly + * one of the config modes makes the hub loading a default registers + * value without SMBus-slave interface activation. If the hub + * accidentally gets this mode, this will cause the driver SMBus- + * functions failure. Normally we could just lock the SMBus-segment the + * hub i2c-interface resides for the device-specific reset timing. But + * the GPIO controller, which is used to handle the hub reset, might be + * placed at the same i2c-bus segment. In this case an error should be + * returned since we can't safely use the GPIO controller to clear the + * reset state (it may affect the hub configuration) and we can't lock + * the i2c-bus segment (it will cause a deadlock). + */ + err = usb251x_check_gpio_chip(hub); + if (err) + return err; + + hub->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(hub->vdd)) + return PTR_ERR(hub->vdd); + + err = regulator_enable(hub->vdd); + if (err) + return err; + + err = devm_add_action_or_reset(dev, + usb251xb_regulator_disable_action, hub); + if (err) + return err; err = usb251xb_connect(hub); if (err) { @@ -637,7 +719,31 @@ return usb251xb_probe(hub); } +static int __maybe_unused usb251xb_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + + return regulator_disable(hub->vdd); +} + +static int __maybe_unused usb251xb_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + int err; + + err = regulator_enable(hub->vdd); + if (err) + return err; + + return usb251xb_connect(hub); +} + +static SIMPLE_DEV_PM_OPS(usb251xb_pm_ops, usb251xb_suspend, usb251xb_resume); + static const struct i2c_device_id usb251xb_id[] = { + { "usb2422", 0 }, { "usb2512b", 0 }, { "usb2512bi", 0 }, { "usb2513b", 0 }, @@ -654,6 +760,7 @@ .driver = { .name = DRIVER_NAME, .of_match_table = of_match_ptr(usb251xb_of_match), + .pm = &usb251xb_pm_ops, }, .probe = usb251xb_i2c_probe, .id_table = usb251xb_id, -- Gitblit v1.6.2