linux-usb.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] Add two new configuration drivers for Microchip USB hubs
@ 2020-07-22 18:38 Christian Eggers
  2020-07-22 18:38 ` [PATCH 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support Christian Eggers
                   ` (3 more replies)
  0 siblings, 4 replies; 16+ messages in thread
From: Christian Eggers @ 2020-07-22 18:38 UTC (permalink / raw)
  To: linux-usb, devicetree
  Cc: Greg Kroah-Hartman, Rob Herring, Richard Leitner, linux-kernel

Most USB hub chips from Microchip can be configured via I2C/SMBus
interface. The series adds two new drivers for configuration of the
following USB hub series:

- USB253x
- USB3x13
- USB46x4
- USB47xx
- USB49xx



^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support
  2020-07-22 18:38 [PATCH] Add two new configuration drivers for Microchip USB hubs Christian Eggers
@ 2020-07-22 18:38 ` Christian Eggers
  2020-07-22 18:38 ` [PATCH 2/4] usb: misc: Add USB253x/xi Hi-Speed Hub Controller Driver Christian Eggers
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 16+ messages in thread
From: Christian Eggers @ 2020-07-22 18:38 UTC (permalink / raw)
  To: linux-usb, devicetree
  Cc: Greg Kroah-Hartman, Rob Herring, Richard Leitner, linux-kernel,
	Christian Eggers

Add DT bindings for Microchip USB253x/USB3x13/USB46x4 driver.

Signed-off-by: Christian Eggers <ceggers@arri.de>
---
 .../devicetree/bindings/usb/usb253x.yaml      | 234 ++++++++++++++++++
 1 file changed, 234 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/usb253x.yaml

diff --git a/Documentation/devicetree/bindings/usb/usb253x.yaml b/Documentation/devicetree/bindings/usb/usb253x.yaml
new file mode 100644
index 000000000000..88ea744147b6
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/usb253x.yaml
@@ -0,0 +1,234 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/usb253x.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip USB253x/USB3x13/USB46x4 USB 2.0 Hi-Speed Hub Controller
+
+maintainers:
+  - Christian Eggers <ceggers@arri.de>
+
+description: |
+  http://ww1.microchip.com/downloads/en/AppNotes/00001801C.pdf
+
+properties:
+  compatible:
+    enum:
+      - microchip,usb2532
+      - microchip,usb2532i
+      - microchip,usb2533
+      - microchip,usb2533i
+      - microchip,usb2534
+      - microchip,usb2534i
+      - microchip,usb3613
+      - microchip,usb3613i
+      - microchip,usb3813
+      - microchip,usb3813i
+      - microchip,usb4604
+      - microchip,usb4604i
+      - microchip,usb4624
+      - microchip,usb4624i
+
+  reg:
+    maxItems: 1
+    description:
+      I2C address on the selected bus (usually <0x2D>).
+
+  reset-gpios:
+    maxItems: 1
+    description:
+      Specify the gpio for hub reset.
+
+  vdd-supply:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Specify the regulator supplying vdd.
+
+  skip-config:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Skip Hub configuration, but only send the USB-Attach command.
+
+  vendor-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Vendor ID of the hub.
+
+  product-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Product ID of the hub.
+
+  device-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Device ID of the hub.
+
+  language-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Language ID.
+
+  manufacturer:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Manufacturer string (max. a total of 93 characters for
+      manufacturer, product and serial).
+
+  product:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Product string (max. a total of 93 characters for
+      manufacturer, product and serial).
+
+  serial:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Serial string (max. a total of 93 characters for
+      manufacturer, product and serial).
+
+  bus-powered:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects bus powered operation.
+
+  self-powered:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects self powered operation (default).
+
+  disable-hi-speed:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Disable USB Hi-Speed support.
+
+  multi-tt:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects multi-transaction-translator (default).
+
+  single-tt:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects single-transaction-translator.
+
+  disable-eop:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Disable End of Packet generation in full-speed mode.
+
+  ganged-sensing:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select ganged over-current sense type in self-powered mode.
+
+  individual-sensing:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select individual over-current sense type in self-powered mode (default).
+
+  ganged-port-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select ganged port power switching mode.
+
+  individual-port-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select individual port power switching mode (default).
+
+  dynamic-power-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Enable auto-switching from self- to bus-powered operation if the local
+      power source is removed or unavailable.
+
+  oc-delay-us:
+    enum:
+      - 100
+      - 4000
+      - 8000
+      - 16000
+    default: 8000
+    description:
+      Delay time (in microseconds) for filtering the over-current sense inputs.
+      If an invalid value is given, the default is used instead.
+
+  compound-device:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Indicate the hub is part of a compound device.
+
+  port-mapping-mode:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Enable port mapping mode.
+
+  non-removable-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Should specify the ports which have a non-removable device connected.
+
+  sp-disabled-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Specifies the ports which will be self-power disabled.
+
+  bp-disabled-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Specifies the ports which will be bus-power disabled.
+
+  power-on-time-ms:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    default: 100
+    minimum: 0
+    maximum: 510
+    description:
+      Specifies the time (in milliseconds) it takes from the time the host
+      initiates the power-on sequence to a port until the port has adequate
+      power.
+
+  hub-controller-port:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Hub port where the internal hub controller shall be connected. Usually
+      <number of ports>+1.
+
+additionalProperties: false
+
+required:
+  - compatible
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    i2c0 {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      usb2534i@2d {
+        compatible = "microchip,usb2534i";
+        reg = <0x2d>;
+        pinctrl-names = "default";
+        pinctrl-0 = <&pinctrl_usb_hub>;
+        /* usb253x.c already assumes low-active, don't negate twice */
+        reset-gpios = <&gpio3 19 GPIO_ACTIVE_HIGH>;
+        /*skip-config;*/
+        /* T_ON,max = 4 ms for NCP380 */
+        power-on-time-ms = <4>;
+        manufacturer = "Foo";
+        product = "Foo-Bar";
+        /* port 2 is connected to an internal SD-Card reader */
+        non-removable-ports = <2>;
+        /* hub controller mapped to logical port 5 */
+        hub-controller-port = <5>;
+      };
+    };
+
+...
-- 
Christian Eggers
Embedded software developer

Arnold & Richter Cine Technik GmbH & Co. Betriebs KG
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRA 57918
Persoenlich haftender Gesellschafter: Arnold & Richter Cine Technik GmbH
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRB 54477
Geschaeftsfuehrer: Dr. Michael Neuhaeuser; Stephan Schenk; Walter Trauninger; Markus Zeiler


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 2/4] usb: misc: Add USB253x/xi Hi-Speed Hub Controller Driver
  2020-07-22 18:38 [PATCH] Add two new configuration drivers for Microchip USB hubs Christian Eggers
  2020-07-22 18:38 ` [PATCH 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support Christian Eggers
@ 2020-07-22 18:38 ` Christian Eggers
  2020-07-22 18:38 ` [PATCH 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support Christian Eggers
  2020-07-22 18:38 ` [PATCH 4/4] usb: misc: Add USB49xx/xi Hi-Speed Hub Controller Driver Christian Eggers
  3 siblings, 0 replies; 16+ messages in thread
From: Christian Eggers @ 2020-07-22 18:38 UTC (permalink / raw)
  To: linux-usb, devicetree
  Cc: Greg Kroah-Hartman, Rob Herring, Richard Leitner, linux-kernel,
	Christian Eggers

This patch adds a driver for configuration of the Microchip USB253x/xi
USB 2.0 hub controller series with USB 2.0 upstream connectivity, SMBus
configuration interface and two to four USB 2.0 downstream ports.

Furthermore add myself as a maintainer for this driver.

The datasheet can be found at the manufacturers website, see [1]. All
device-tree exposed configuration features have been tested on a i.MX6
platform with a USB2534i hub.

[1] http://ww1.microchip.com/downloads/en/AppNotes/00001801C.pdf

Signed-off-by: Christian Eggers <ceggers@arri.de>
---
 MAINTAINERS                |   7 +
 drivers/usb/misc/Kconfig   |  10 +
 drivers/usb/misc/Makefile  |   1 +
 drivers/usb/misc/usb253x.c | 834 +++++++++++++++++++++++++++++++++++++
 4 files changed, 852 insertions(+)
 create mode 100644 drivers/usb/misc/usb253x.c

diff --git a/MAINTAINERS b/MAINTAINERS
index d53db30d1365..fe1108ba6ce7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11356,6 +11356,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/usb/usb251xb.txt
 F:	drivers/usb/misc/usb251xb.c
 
+MICROCHIP USB253X DRIVER
+M:	Christian Eggers <ceggers@arri.de>
+L:	linux-usb@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/usb/usb253x.yaml
+F:	drivers/usb/misc/usb253x.c
+
 MICROCHIP USBA UDC DRIVER
 M:	Cristian Birsan <cristian.birsan@microchip.com>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 4e48f8eed168..7a7b854e803a 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -242,6 +242,16 @@ config USB_HUB_USB251XB
 	  parameters may be set in devicetree or platform data.
 	  Say Y or M here if you need to configure such a device via SMBus.
 
+config USB_HUB_USB253X
+	tristate "USB253X Hub Controller Configuration Driver"
+	depends on I2C
+	select REGMAP
+	help
+	  This option enables support for configuration via I2C bus of the
+	  Microchip USB253x/xi USB 2.0 Hub Controller series.
+	  Configuration parameters may be set in devicetree or platform data.
+	  Say Y or M here if you need to configure such a device via I2C.
+
 config USB_HSIC_USB3503
 	tristate "USB3503 HSIC to USB20 Driver"
 	depends on I2C
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index da39bddb0604..6fd8c53841e4 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_USB_USS720)		+= uss720.o
 obj-$(CONFIG_USB_SEVSEG)		+= usbsevseg.o
 obj-$(CONFIG_USB_YUREX)			+= yurex.o
 obj-$(CONFIG_USB_HUB_USB251XB)		+= usb251xb.o
+obj-$(CONFIG_USB_HUB_USB253X)		+= usb253x.o
 obj-$(CONFIG_USB_HSIC_USB3503)		+= usb3503.o
 obj-$(CONFIG_USB_HSIC_USB4604)		+= usb4604.o
 obj-$(CONFIG_USB_CHAOSKEY)		+= chaoskey.o
diff --git a/drivers/usb/misc/usb253x.c b/drivers/usb/misc/usb253x.c
new file mode 100644
index 000000000000..ffd72e14c3c3
--- /dev/null
+++ b/drivers/usb/misc/usb253x.c
@@ -0,0 +1,834 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Microchip USB253x USB 2.0 Hi-Speed Hub Controller
+ * Configuration via I2C.
+ *
+ * Copyright (c) 2020 ARRI Lighting
+ *
+ * This work is based on the USB251xb driver by Richard Leitner.
+ *
+ */
+
+#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/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+/* Internal Register Set Addresses acc. to DS00001801C */
+#define USB253X_ADDR_VENDOR_ID                0x3000
+#define USB253X_ADDR_PRODUCT_ID               0x3002
+#define USB253X_ADDR_DEVICE_ID                0x3004
+#define USB253X_ADDR_HUB_CFG                  0x3006
+#define USB253X_HUB_CFG3_PRTMAP_EN            BIT(3)
+#define USB253X_HUB_CFG3_STRING_EN            BIT(0)
+#define USB253X_ADDR_NON_REMOVABLE_DEVICES    0x3009
+#define USB253X_ADDR_PORT_DISABLE_SELF        0x300A
+#define USB253X_ADDR_PORT_DISABLE_BUS         0x300B
+#define USB253X_ADDR_POWER_ON_TIME            0x3010
+#define USB253X_ADDR_LANGUAGE_ID              0x3011
+#define USB253X_ADDR_MANUFACTURER_STRING_LEN  0x3013
+#define USB253X_ADDR_PRODUCT_STRING_LEN       0x3014
+#define USB253X_ADDR_SERIAL_STRING_LEN        0x3015
+#define USB253X_ADDR_STRINGS                  0x3016
+#define USB253X_STRINGS_BUFSIZE              93  /* chars (UTF16) */
+#define USB253X_ADDR_HUB_CTRL_REMAP           0x30FD
+#define USB253X_ADDR_USB2_HUB_CTRL            0x3104
+#define USB253X_USB2_HUB_CTRL_LPM_DISABLE     BIT(1)
+#define USB253X_ADDR_INTERNAL_PORT            0x4130
+#define USB253X_INTERNAL_PORT_PORT_ENUM_DEFAULT        0b00
+#define USB253X_INTERNAL_PORT_PORT_ENUM_ALWAYS_ENABLE  0b01
+#define USB253X_INTERNAL_PORT_PORT_ENUM_ALWAYS_DISABLE 0b10
+
+#define DRIVER_NAME "usb253x"
+
+
+struct usb253x {
+	struct device *dev;
+	struct i2c_client *i2c;
+	struct regulator *vdd;
+	struct regmap *regmap;
+	u8 skip_config;
+	struct gpio_desc *gpio_reset;
+};
+
+#ifdef CONFIG_GPIOLIB
+static int usb253x_check_dev_children(struct device *dev, void *child)
+{
+	if (dev->type == &i2c_adapter_type) {
+		return device_for_each_child(dev, child,
+					     usb253x_check_dev_children);
+	}
+
+	return (dev == child);
+}
+
+static int usb253x_check_gpio_chip(struct usb253x *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 = usb253x_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 usb253x_check_gpio_chip(struct usb253x *hub)
+{
+	return 0;
+}
+#endif
+
+static void usb253x_reset(struct usb253x *hub, int state)
+{
+	if (state) {
+		/* During rising edge of reset, there must be no traffic on the i2c bus */
+		i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+		gpiod_set_value_cansleep(hub->gpio_reset, 1);
+		/* Wait for i2c logic to come up (hub seems to require ~210 ms if
+		 * RESET_N pulse was longer than 25 ms)
+		 */
+		msleep(250);
+		i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+	} else {
+		gpiod_set_value_cansleep(hub->gpio_reset, 0);
+		/* minimum reset pulse width from data sheet is 5 us */
+		usleep_range(5, 10);
+	}
+}
+
+static void usb253x_get_ports_field(struct usb253x *hub,
+				    const char *prop_name, u8 port_cnt,
+				    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 >= 1) && (port <= port_cnt))
+			*fld |= BIT(port);
+		else
+			dev_warn(dev, "port %u doesn't exist\n", port);
+	}
+}
+
+static int usb253x_connect(struct usb253x *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+	int err;
+	char attach_buffer[3];
+
+	usb253x_reset(hub, 1);
+
+	if (hub->skip_config) {
+		dev_info(dev, "Skip hub configuration, only attach.\n");
+	} else {
+		u16 vendor_id, product_id, device_id, lang_id;
+		u8 hub_cfg[3];
+		u32 property_u32;
+		char const *manufacturer;
+		char const *product;
+		char const *serial;
+		u8 non_rem_dev = 0;
+		u8 port_disable_sp = 0;
+		u8 port_disable_bp = 0;
+		wchar_t strings[USB253X_STRINGS_BUFSIZE];
+		int strings_used = 0;
+		u32 hub_controller_port = 0;
+
+		memset(strings, 0, sizeof(strings));
+
+		if (!of_property_read_u16_array(np, "vendor-id", &vendor_id, 1)) {
+			char buffer[] = { vendor_id >> 8, vendor_id };
+
+			err = regmap_bulk_write(hub->regmap, USB253X_ADDR_VENDOR_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting vendor id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "product-id", &product_id, 1)) {
+			char buffer[] = { product_id >> 8, product_id };
+
+			err = regmap_bulk_write(hub->regmap, USB253X_ADDR_PRODUCT_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting product id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "device-id", &device_id, 1)) {
+			char buffer[] = { device_id >> 8, device_id };
+
+			err = regmap_bulk_write(hub->regmap, USB253X_ADDR_DEVICE_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting device id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		err = regmap_bulk_read(hub->regmap, USB253X_ADDR_HUB_CFG,
+				hub_cfg, ARRAY_SIZE(hub_cfg));
+		if (err) {
+			dev_err(dev, "Reading hub cfg failed: %d\n", err);
+			return err;
+		}
+
+		if (of_get_property(np, "self-powered", NULL)) {
+			hub_cfg[0] |= BIT(7);
+
+			/* Configure Over-Current sens when self-powered */
+			hub_cfg[0] &= ~BIT(2);
+			if (of_get_property(np, "ganged-sensing", NULL))
+				hub_cfg[0] &= ~BIT(1);
+			else if (of_get_property(np, "individual-sensing", NULL))
+				hub_cfg[0] |= BIT(1);
+		} else if (of_get_property(np, "bus-powered", NULL)) {
+			hub_cfg[0] &= ~BIT(7);
+
+			/* Disable Over-Current sense when bus-powered */
+			hub_cfg[0] |= BIT(2);
+		}
+
+		if (of_get_property(np, "disable-hi-speed", NULL))
+			hub_cfg[0] |= BIT(5);
+
+		if (of_get_property(np, "multi-tt", NULL))
+			hub_cfg[0] |= BIT(4);
+		else if (of_get_property(np, "single-tt", NULL))
+			hub_cfg[0] &= ~BIT(4);
+
+		if (of_get_property(np, "disable-eop", NULL))
+			hub_cfg[0] |= BIT(3);
+
+		if (of_get_property(np, "individual-port-switching", NULL))
+			hub_cfg[0] |= BIT(0);
+		else if (of_get_property(np, "ganged-port-switching", NULL))
+			hub_cfg[0] &= ~BIT(0);
+
+		if (of_get_property(np, "dynamic-power-switching", NULL))
+			hub_cfg[1] |= BIT(7);
+
+		if (!of_property_read_u32(np, "oc-delay-us", &property_u32)) {
+			if (property_u32 == 100) {
+				/* 100 us*/
+				hub_cfg[1] &= ~BIT(5);
+				hub_cfg[1] &= ~BIT(4);
+			} else if (property_u32 == 4000) {
+				/* 4 ms */
+				hub_cfg[1] &= ~BIT(5);
+				hub_cfg[1] |= BIT(4);
+			} else if (property_u32 == 16000) {
+				/* 16 ms */
+				hub_cfg[1] |= BIT(5);
+				hub_cfg[1] |= BIT(4);
+			} else {
+				/* 8 ms (DEFAULT) */
+				hub_cfg[1] |= BIT(5);
+				hub_cfg[1] &= ~BIT(4);
+			}
+		}
+
+		if (of_get_property(np, "compound-device", NULL))
+			hub_cfg[1] |= BIT(3);
+
+		if (of_get_property(np, "port-mapping-mode", NULL))
+			hub_cfg[2] |= USB253X_HUB_CFG3_PRTMAP_EN;
+
+		/* hub controller will only work when port mapping is enabled and
+		 * controller port is mapped
+		 */
+		if (!of_property_read_u32(np, "hub-controller-port",
+				&hub_controller_port))
+			hub_cfg[2] |= USB253X_HUB_CFG3_PRTMAP_EN;
+
+		/* port mapping requires that Link Power Management is disabled */
+		if (hub_cfg[2] & USB253X_HUB_CFG3_PRTMAP_EN) {
+			unsigned int usb2_hub_ctrl;
+
+			/* Disable Link Power Management */
+			err = regmap_read(hub->regmap, USB253X_ADDR_USB2_HUB_CTRL,
+					&usb2_hub_ctrl);
+			if (err) {
+				dev_err(dev, "Reading USB2 Hub Control failed: %d\n", err);
+				return err;
+			}
+
+			usb2_hub_ctrl |= USB253X_USB2_HUB_CTRL_LPM_DISABLE;
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_USB2_HUB_CTRL,
+					usb2_hub_ctrl);
+			if (err) {
+				dev_err(dev, "Writing USB2 Hub Control failed: %d\n", err);
+				return err;
+			}
+		}
+
+		manufacturer = of_get_property(np, "manufacturer", NULL);
+		product = of_get_property(np, "product", NULL);
+		serial = of_get_property(np, "serial", NULL);
+
+		if (manufacturer || product || serial)
+			hub_cfg[2] |= USB253X_HUB_CFG3_STRING_EN;
+
+		err = regmap_bulk_write(hub->regmap, USB253X_ADDR_HUB_CFG,
+				hub_cfg, ARRAY_SIZE(hub_cfg));
+		if (err) {
+			dev_err(dev, "Setting hub cfg failed: %d\n", err);
+			return err;
+		}
+
+		usb253x_get_ports_field(hub, "non-removable-ports", 4, &non_rem_dev);
+		err = regmap_write(hub->regmap, USB253X_ADDR_NON_REMOVABLE_DEVICES,
+				non_rem_dev);
+		if (err) {
+			dev_err(dev, "Setting non removable devices failed: %d\n", err);
+			return err;
+		}
+
+		usb253x_get_ports_field(hub, "sp-disabled-ports", 4, &port_disable_sp);
+		err = regmap_write(hub->regmap, USB253X_ADDR_PORT_DISABLE_SELF,
+				port_disable_sp);
+		if (err) {
+			dev_err(dev, "Setting port disable self powered failed: %d\n", err);
+			return err;
+		}
+
+		usb253x_get_ports_field(hub, "bp-disabled-ports", 4, &port_disable_bp);
+		err = regmap_write(hub->regmap, USB253X_ADDR_PORT_DISABLE_BUS,
+				port_disable_bp);
+		if (err) {
+			dev_err(dev, "Setting port disable powered powered failed: %d\n", err);
+			return err;
+		}
+
+		if (!of_property_read_u32(np, "power-on-time-ms", &property_u32)) {
+			u8 power_on_time = min_t(u8, property_u32 / 2, 255);
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_POWER_ON_TIME,
+					power_on_time);
+			if (err) {
+				dev_err(dev, "Setting port disable powered powered failed: %d\n",
+						err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "language-id", &lang_id, 1)) {
+			char buffer[] = { lang_id >> 8, lang_id };
+
+			err = regmap_bulk_write(hub->regmap, USB253X_ADDR_LANGUAGE_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting language id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (manufacturer) {
+			u8 manufacturer_len = utf8s_to_utf16s(manufacturer, strlen(manufacturer),
+					UTF16_LITTLE_ENDIAN,
+					&strings[strings_used],
+					USB253X_STRINGS_BUFSIZE - strings_used);
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_MANUFACTURER_STRING_LEN,
+					manufacturer_len);
+			if (err) {
+				dev_err(dev, "Setting manufacturer string length failed: %d\n",
+						err);
+				return err;
+			}
+
+			strings_used += manufacturer_len;
+		}
+
+		if (product) {
+			u8 product_len = utf8s_to_utf16s(product, strlen(product),
+					UTF16_LITTLE_ENDIAN,
+					&strings[strings_used],
+					USB253X_STRINGS_BUFSIZE - strings_used);
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_PRODUCT_STRING_LEN,
+					product_len);
+			if (err) {
+				dev_err(dev, "Setting product string length failed: %d\n", err);
+				return err;
+			}
+
+			strings_used += product_len;
+		}
+
+		if (serial) {
+			u8 serial_len = utf8s_to_utf16s(serial, strlen(serial),
+					UTF16_LITTLE_ENDIAN,
+					&strings[strings_used],
+					USB253X_STRINGS_BUFSIZE - strings_used);
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_SERIAL_STRING_LEN,
+					serial_len);
+			if (err) {
+				dev_err(dev, "Setting serial string length failed: %d\n", err);
+				return err;
+			}
+
+			strings_used += serial_len;
+		}
+
+		err = regmap_bulk_write(hub->regmap, USB253X_ADDR_STRINGS,
+				strings, sizeof(strings));
+		if (err) {
+			dev_err(dev, "Setting strings failed: %d\n", err);
+			return err;
+		}
+
+		if (hub_controller_port != 0) {
+			/* Writes to this register are disabled unless PRTMAP_EN bit in
+			 * HUB_CFG_3 is set.
+			 */
+			err = regmap_write(hub->regmap, USB253X_ADDR_HUB_CTRL_REMAP,
+					hub_controller_port);
+			if (err) {
+				dev_err(dev, "Setting hub controller remap failed: %d\n", err);
+				return err;
+			}
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_INTERNAL_PORT,
+					USB253X_INTERNAL_PORT_PORT_ENUM_ALWAYS_ENABLE);
+			if (err) {
+				dev_err(dev, "Setting internal port failed: %d\n", err);
+				return err;
+			}
+		}
+	}
+
+#ifdef DEBUG
+	{
+		char *buffer = kzalloc(0x5000, GFP_KERNEL);
+
+		err = regmap_bulk_read(hub->regmap, 0, buffer, 0x5000);
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buffer, 0x5000);
+		kfree(buffer);
+	}
+#endif
+
+	/*
+	 *  attach
+	 */
+	attach_buffer[0] = 0xAA;   /* USB attach (high byte) */
+	attach_buffer[1] = 0x55;   /* USB attach (low byte) */
+	attach_buffer[2] = 0x00;   /* terminator */
+
+	err = i2c_master_send(hub->i2c, attach_buffer, 3);
+	if (err != 3) {
+		dev_err(dev, "attaching hub failed: %d\n", err);
+		return err;
+	}
+
+	dev_info(dev, "Hub configuration was successful.\n");
+	return 0;
+
+}
+
+#ifdef CONFIG_OF
+static int usb253x_get_ofdata(struct usb253x *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+
+	if (!np) {
+		dev_err(dev, "failed to get ofdata\n");
+		return -ENODEV;
+	}
+
+	if (of_get_property(np, "skip-config", NULL))
+		hub->skip_config = 1;
+	else
+		hub->skip_config = 0;
+
+	hub->gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(hub->gpio_reset))
+		return PTR_ERR(hub->gpio_reset);
+
+	return 0;
+}
+
+static const struct of_device_id usb253x_of_match[] = {
+	{ .compatible = "microchip,usb2532",  },
+	{ .compatible = "microchip,usb2532i", },
+	{ .compatible = "microchip,usb2533",  },
+	{ .compatible = "microchip,usb2533i", },
+	{ .compatible = "microchip,usb2534",  },
+	{ .compatible = "microchip,usb2534i", },
+	{ .compatible = "microchip,usb3613",  },
+	{ .compatible = "microchip,usb3613i", },
+	{ .compatible = "microchip,usb3813",  },
+	{ .compatible = "microchip,usb3813i", },
+	{ .compatible = "microchip,usb4604",  },
+	{ .compatible = "microchip,usb4604i", },
+	{ .compatible = "microchip,usb4624",  },
+	{ .compatible = "microchip,usb4624i", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, usb253x_of_match);
+#else /* CONFIG_OF */
+static int usb253x_get_ofdata(struct usb253x *hub)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static void usb253x_regulator_disable_action(void *data)
+{
+	struct usb253x *hub = data;
+
+	regulator_disable(hub->vdd);
+}
+
+static int usb253x_probe(struct usb253x *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+	int err;
+
+	if (np) {
+		err = usb253x_get_ofdata(hub);
+		if (err) {
+			dev_err(dev, "failed to get ofdata: %d\n", err);
+			return err;
+		}
+	}
+
+	/*
+	 * usb253x 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 usb253x. 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 = usb253x_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,
+				       usb253x_regulator_disable_action, hub);
+	if (err)
+		return err;
+
+	err = usb253x_connect(hub);
+	if (err) {
+		dev_err(dev, "Failed to connect hub (%d)\n", err);
+		return err;
+	}
+
+	dev_info(dev, "Hub probed successfully\n");
+
+	return 0;
+}
+
+static int usb253x_i2c_gather_write(void *context,
+		const void *reg, size_t reg_len,
+		const void *val, size_t val_len)
+{
+	struct i2c_client *i2c = (struct i2c_client *)context;
+	size_t frame_len = val_len + 7;
+	char *buffer;
+	int err;
+
+	if (WARN_ON(reg_len != 2))
+		return -EINVAL;
+
+	buffer = kmalloc(frame_len, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	buffer[0] = 0x00;                 /* Memory address (high byte) */
+	buffer[1] = 0x00;                 /* Memory address (low byte) */
+	buffer[2] = val_len + 4;          /* Number of bytes to write to memory */
+	buffer[3] = 0x00;                 /* Register Write */
+	buffer[4] = val_len;              /* Write val_len data bytes */
+	buffer[5] = *((const char *)reg + 0);   /* Register address (high byte) */
+	buffer[6] = *((const char *)reg + 1);   /* Register address (low byte) */
+	memcpy(&buffer[7], val, val_len);
+
+	err = i2c_master_send(i2c, buffer, frame_len);
+	if (err != (int)frame_len) {
+		dev_err(&i2c->dev, "Writing to hub memory failed: %d\n", err);
+		goto error_kfree;
+	}
+
+	buffer[0] = 0x99;   /* Command (high byte) */
+	buffer[1] = 0x37;   /* Command (low byte) */
+	buffer[2] = 0x00;   /* Termination */
+	frame_len = 3;
+
+	err = i2c_master_send(i2c, buffer, frame_len);
+	if (err != (int)frame_len) {
+		dev_err(&i2c->dev, "Accessing configuration data failed: %d\n", err);
+		goto error_kfree;
+	}
+
+	err = 0;
+
+error_kfree:
+	kfree(buffer);
+	return err;
+}
+
+/* regmap puts address into data[0] and data[1]; value is in data[2] */
+static int usb253x_i2c_write(void *context, const void *data, size_t count)
+{
+	if (WARN_ON(count != 3))
+		return -EINVAL;
+
+	return usb253x_i2c_gather_write(context, data, 2, data + 2, 1);
+}
+
+static int usb253x_i2c_read(void *context, const void *reg_buf, size_t reg_size,
+		void *val_buf, size_t val_size)
+{
+	struct i2c_client *i2c = (struct i2c_client *)context;
+	char *tx, *rx;
+	int err;
+
+	if (WARN_ON(reg_size != 2)) {
+		err =  -EINVAL;
+		goto error_return;
+	}
+
+	tx = kmalloc(7, GFP_KERNEL);
+	if (!tx) {
+		err = -ENOMEM;
+		goto error_return;
+	}
+
+	rx = kmalloc(1 + val_size, GFP_KERNEL);
+	if (!rx) {
+		err = -ENOMEM;
+		goto error_free_tx;
+	}
+
+	tx[0] = 0x00;                 /* Memory address (high byte) */
+	tx[1] = 0x00;                 /* Memory address (low byte) */
+	tx[2] = 4;                    /* Number of bytes to write to memory */
+	tx[3] = 0x01;                 /* Register Read */
+	tx[4] = val_size;             /* Read val_size data bytes */
+	tx[5] = *((const char *)reg_buf + 0);   /* Register address (high byte) */
+	tx[6] = *((const char *)reg_buf + 1);   /* Register address (low byte) */
+
+	err = i2c_master_send(i2c, tx, 7);
+	if (err != 7) {
+		dev_err(&i2c->dev, "Writing to hub memory failed: %d\n", err);
+		goto error_free_rx;
+	}
+
+	tx[0] = 0x99;   /* Command (high byte) */
+	tx[1] = 0x37;   /* Command (low byte) */
+	tx[2] = 0x00;   /* Termination */
+
+	err = i2c_master_send(i2c, tx, 3);
+	if (err != 3) {
+		dev_err(&i2c->dev, "Accessing configuration data failed: %d\n", err);
+		goto error_free_rx;
+	}
+
+	/* Read from hub memory address 4 (according to application note) */
+	tx[0] = 0x00;    /* Memory address (high byte) */
+	tx[1] = 0x04;    /* Memory address (low byte) */
+
+	{
+		struct i2c_msg msgs[] = {
+			{
+				.addr = i2c->addr, .flags = 0,
+				.len  =         2, .buf = tx
+			},
+			{
+				.addr = i2c->addr, .flags = I2C_M_RD,
+				.len = 1 + val_size, .buf = rx
+			},
+		};
+
+		err = i2c_transfer(i2c->adapter, msgs, ARRAY_SIZE(msgs));
+		if (err < 0) {
+			dev_err(&i2c->dev, "Reading from hub memory failed: %d\n", err);
+			goto error_free_rx;
+		}
+	}
+
+	WARN_ON(rx[0] < val_size);
+
+	memcpy(val_buf, &rx[1], val_size);
+
+	err = 0;
+
+error_free_rx:
+	kfree(rx);
+error_free_tx:
+	kfree(tx);
+error_return:
+	return err;
+}
+
+/*
+ * The reason we need this custom regmap_bus instead of using regmap_init_i2c()
+ * is that accesses to the USB253x configuration memory have to be wrapped into
+ * a special protocol.
+ */
+static const struct regmap_bus usb253x_i2c_regmap = {
+	.write         = usb253x_i2c_write,
+	.gather_write  = usb253x_i2c_gather_write,
+	.read          = usb253x_i2c_read,
+	.max_raw_read  = 128,
+	.max_raw_write = 128,
+};
+
+/* Avoid reading bigger dead ranges inside hubs memory.  Same table for
+ * read and write.
+ */
+static const struct regmap_range usb253x_regmap_yes_ranges[] = {
+	{ .range_min = 0x0800, .range_max = 0x09ff },
+	{ .range_min = 0x3000, .range_max = 0x31ff },
+	{ .range_min = 0x3C00, .range_max = 0x3cff },
+	{ .range_min = 0x4100, .range_max = 0x42ff },
+	{ .range_min = 0x6000, .range_max = 0x72ff },
+};
+
+static const struct regmap_access_table usb253x_regmap_access_table = {
+	.yes_ranges   = usb253x_regmap_yes_ranges,
+	.n_yes_ranges = ARRAY_SIZE(usb253x_regmap_yes_ranges),
+};
+
+static int usb253x_i2c_probe(struct i2c_client *i2c,
+			      const struct i2c_device_id *id __always_unused)
+{
+	struct usb253x *hub;
+	struct regmap_config regmap_cfg = {
+		.name            = "usb253x_config_mem",
+		.reg_bits        = 16,
+		.val_bits        = 8,
+		.max_register    = 0x7fff,
+		.wr_table        = &usb253x_regmap_access_table,
+		.rd_table        = &usb253x_regmap_access_table,
+		.can_multi_write = 1,
+	};
+
+	hub = devm_kzalloc(&i2c->dev, sizeof(struct usb253x), GFP_KERNEL);
+	if (!hub)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, hub);
+	hub->dev = &i2c->dev;
+	hub->i2c = i2c;
+	hub->regmap = devm_regmap_init(&i2c->dev, &usb253x_i2c_regmap, i2c,
+			&regmap_cfg);
+	if (IS_ERR(hub->regmap))
+		return PTR_ERR(hub->regmap);
+
+	return usb253x_probe(hub);
+}
+
+static int usb253x_i2c_remove(struct i2c_client *i2c)
+{
+	struct usb253x *hub = i2c_get_clientdata(i2c);
+
+	usb253x_reset(hub, 0);
+
+	return 0;
+};
+
+static int __maybe_unused usb253x_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct usb253x *hub = i2c_get_clientdata(client);
+
+	return regulator_disable(hub->vdd);
+}
+
+static int __maybe_unused usb253x_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct usb253x *hub = i2c_get_clientdata(client);
+	int err;
+
+	err = regulator_enable(hub->vdd);
+	if (err)
+		return err;
+
+	return usb253x_connect(hub);
+}
+
+static SIMPLE_DEV_PM_OPS(usb253x_pm_ops, usb253x_suspend, usb253x_resume);
+
+static const struct i2c_device_id usb253x_id[] = {
+	{ "usb2532", 0 },
+	{ "usb2532i", 0 },
+	{ "usb2533", 0 },
+	{ "usb2533i", 0 },
+	{ "usb2534", 0 },
+	{ "usb2534i", 0 },
+	{ "usb3613", 0 },
+	{ "usb3613i", 0 },
+	{ "usb3813", 0 },
+	{ "usb3813i", 0 },
+	{ "usb4604", 0 },
+	{ "usb4604i", 0 },
+	{ "usb4624", 0 },
+	{ "usb4624i", 0 },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, usb253x_id);
+
+static struct i2c_driver usb253x_i2c_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.of_match_table = of_match_ptr(usb253x_of_match),
+		.pm = &usb253x_pm_ops,
+	},
+	.probe    = usb253x_i2c_probe,
+	.remove   = usb253x_i2c_remove,
+	.id_table = usb253x_id,
+};
+
+module_i2c_driver(usb253x_i2c_driver);
+
+MODULE_AUTHOR("Christian Eggers <ceggers@arri.de>");
+MODULE_DESCRIPTION("USB253x/xi USB 2.0 Hub Controller Driver");
+MODULE_LICENSE("GPL");
-- 
Christian Eggers
Embedded software developer

Arnold & Richter Cine Technik GmbH & Co. Betriebs KG
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRA 57918
Persoenlich haftender Gesellschafter: Arnold & Richter Cine Technik GmbH
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRB 54477
Geschaeftsfuehrer: Dr. Michael Neuhaeuser; Stephan Schenk; Walter Trauninger; Markus Zeiler


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support
  2020-07-22 18:38 [PATCH] Add two new configuration drivers for Microchip USB hubs Christian Eggers
  2020-07-22 18:38 ` [PATCH 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support Christian Eggers
  2020-07-22 18:38 ` [PATCH 2/4] usb: misc: Add USB253x/xi Hi-Speed Hub Controller Driver Christian Eggers
@ 2020-07-22 18:38 ` Christian Eggers
  2020-07-23 15:35   ` Rob Herring
  2020-07-22 18:38 ` [PATCH 4/4] usb: misc: Add USB49xx/xi Hi-Speed Hub Controller Driver Christian Eggers
  3 siblings, 1 reply; 16+ messages in thread
From: Christian Eggers @ 2020-07-22 18:38 UTC (permalink / raw)
  To: linux-usb, devicetree
  Cc: Greg Kroah-Hartman, Rob Herring, Richard Leitner, linux-kernel,
	Christian Eggers

Add DT bindings for Microchip USB47xx/USB49xx driver.

Signed-off-by: Christian Eggers <ceggers@arri.de>
---
 .../devicetree/bindings/usb/usb49xx.yaml      | 230 ++++++++++++++++++
 1 file changed, 230 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/usb49xx.yaml

diff --git a/Documentation/devicetree/bindings/usb/usb49xx.yaml b/Documentation/devicetree/bindings/usb/usb49xx.yaml
new file mode 100644
index 000000000000..20c7b64c6b7d
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/usb49xx.yaml
@@ -0,0 +1,230 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/usb49xx.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip USB47xx/USB49xx USB 2.0 Hi-Speed Hub Controller
+
+maintainers:
+  - Christian Eggers <ceggers@arri.de>
+
+description: |
+  http://ww1.microchip.com/downloads/en/Appnotes/AN2651-Configuration-of-Microchip-USB47xx-USB49xx-Application-Note-00002651B.pdf
+
+properties:
+  compatible:
+    enum:
+      - microchip,usb4712
+      - microchip,usb4712i
+      - microchip,usb4715
+      - microchip,usb4715i
+      - microchip,usb4912
+      - microchip,usb4912i
+      - microchip,usb4914
+      - microchip,usb4914i
+      - microchip,usb4916
+      - microchip,usb4916i
+      - microchip,usb4925
+      - microchip,usb4925i
+      - microchip,usb4927
+      - microchip,usb4927i
+
+  reg:
+    maxItems: 1
+    description:
+      I2C address on the selected bus (usually <0x2D>).
+
+  reset-gpios:
+    maxItems: 1
+    description:
+      Specify the gpio for hub reset.
+
+  vdd-supply:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Specify the regulator supplying vdd.
+
+  skip-config:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Skip Hub configuration, but only send the USB-Attach command.
+
+  vendor-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Vendor ID of the hub.
+
+  product-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Product ID of the hub.
+
+  device-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Device ID of the hub.
+
+  language-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Language ID.
+
+  manufacturer:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Manufacturer string (max. 62 characters long).
+
+  product:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Product string (max. 62 characters long).
+
+  bus-powered:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects bus powered operation.
+
+  self-powered:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects self powered operation (default).
+
+  disable-hi-speed:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Disable USB Hi-Speed support.
+
+  multi-tt:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects multi-transaction-translator (default).
+
+  single-tt:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects single-transaction-translator.
+
+  disable-eop:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Disable End of Packet generation in full-speed mode.
+
+  ganged-sensing:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select ganged over-current sense type in self-powered mode.
+
+  individual-sensing:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select individual over-current sense type in self-powered mode (default).
+
+  ganged-port-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select ganged port power switching mode.
+
+  individual-port-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select individual port power switching mode (default).
+
+  dynamic-power-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Enable auto-switching from self- to bus-powered operation if the local
+      power source is removed or unavailable.
+
+  oc-delay-us:
+    enum:
+      - 50
+      - 100
+      - 200
+      - 400
+    default: 200
+    description:
+      Delay time (in microseconds) for filtering the over-current sense inputs.
+      If an invalid value is given, the default is used instead.
+
+  compound-device:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Indicate the hub is part of a compound device.
+
+  port-mapping-mode:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Enable port mapping mode.
+
+  non-removable-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Should specify the ports which have a non-removable device connected.
+
+  sp-disabled-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Specifies the ports which will be self-power disabled.
+
+  bp-disabled-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Specifies the ports which will be bus-power disabled.
+
+  power-on-time-ms:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    default: 100
+    minimum: 0
+    maximum: 510
+    description:
+      Specifies the time (in milliseconds) it takes from the time the host
+      initiates the power-on sequence to a port until the port has adequate
+      power.
+
+  hub-controller-port:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Hub port where the internal hub controller shall be connected. Usually
+      <number of ports>+1.
+
+additionalProperties: false
+
+required:
+  - compatible
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    i2c0 {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      usb4916i@2d {
+        compatible = "microchip,usb4916i";
+        reg = <0x2d>;
+        pinctrl-names = "default";
+        pinctrl-0 = <&pinctrl_usb_hub>;
+        /* usb49xx.c already assumes low-active, don't negate twice */
+        reset-gpios = <&gpio3 19 GPIO_ACTIVE_HIGH>;
+        //skip-config;
+        //self-powered;         /* power on default */
+        //individual-sensing;   /* power on default */
+        //multi-tt;             /* power on default */
+        //disable-eop;          /* power on default */
+        //individual-port-switching;  /* power on default */
+        //oc-delay-ns = <200>;  /* power on default */
+        power-on-time-ms = <4>; /* T_ON,max = 4 ms for NCP380 */
+        ocs-min-width-ms = <0>; /* MIC2005 only outputs 2us FAULT pulses */
+        manufacturer = "Foo";
+        product = "Foo-Bar";
+        /* port 5 is connected to an internal SD-Card reader */
+        non-removable-ports = <5>;
+      };
+    };
+
+...
-- 
Christian Eggers
Embedded software developer

Arnold & Richter Cine Technik GmbH & Co. Betriebs KG
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRA 57918
Persoenlich haftender Gesellschafter: Arnold & Richter Cine Technik GmbH
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRB 54477
Geschaeftsfuehrer: Dr. Michael Neuhaeuser; Stephan Schenk; Walter Trauninger; Markus Zeiler


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 4/4] usb: misc: Add USB49xx/xi Hi-Speed Hub Controller Driver
  2020-07-22 18:38 [PATCH] Add two new configuration drivers for Microchip USB hubs Christian Eggers
                   ` (2 preceding siblings ...)
  2020-07-22 18:38 ` [PATCH 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support Christian Eggers
@ 2020-07-22 18:38 ` Christian Eggers
  3 siblings, 0 replies; 16+ messages in thread
From: Christian Eggers @ 2020-07-22 18:38 UTC (permalink / raw)
  To: linux-usb, devicetree
  Cc: Greg Kroah-Hartman, Rob Herring, Richard Leitner, linux-kernel,
	Christian Eggers

This patch adds a driver for configuration of the Microchip USB49xx/xi
USB 2.0 hub controller series with USB 2.0 upstream connectivity, SMBus
configuration interface and two to four USB 2.0 downstream ports.

Furthermore add myself as a maintainer for this driver.

The datasheet can be found at the manufacturers website, see [1]. All
device-tree exposed configuration features have been tested on a i.MX6
platform with a USB4916 hub.

[1] http://ww1.microchip.com/downloads/en/Appnotes/AN2651-Configuration-of-Microchip-USB47xx-USB49xx-Application-Note-00002651B.pdf

Signed-off-by: Christian Eggers <ceggers@arri.de>
---
 MAINTAINERS                |   7 +
 drivers/usb/misc/Kconfig   |  10 +
 drivers/usb/misc/Makefile  |   1 +
 drivers/usb/misc/usb49xx.c | 825 +++++++++++++++++++++++++++++++++++++
 4 files changed, 843 insertions(+)
 create mode 100644 drivers/usb/misc/usb49xx.c

diff --git a/MAINTAINERS b/MAINTAINERS
index fe1108ba6ce7..068d6e94122b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11363,6 +11363,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/usb/usb253x.yaml
 F:	drivers/usb/misc/usb253x.c
 
+MICROCHIP USB49XX DRIVER
+M:	Christian Eggers <ceggers@arri.de>
+L:	linux-usb@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/usb/usb49xx.yaml
+F:	drivers/usb/misc/usb49xx.c
+
 MICROCHIP USBA UDC DRIVER
 M:	Cristian Birsan <cristian.birsan@microchip.com>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 7a7b854e803a..14bea87c0f81 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -252,6 +252,16 @@ config USB_HUB_USB253X
 	  Configuration parameters may be set in devicetree or platform data.
 	  Say Y or M here if you need to configure such a device via I2C.
 
+config USB_HUB_USB49XX
+	tristate "USB47XX/USB49XX Hub Controller Configuration Driver"
+	depends on I2C
+	select REGMAP
+	help
+	  This option enables support for configuration via I2C bus of the
+	  Microchip USB47xx/49xx USB 2.0 Hub Controller series.
+	  Configuration parameters may be set in devicetree or platform data.
+	  Say Y or M here if you need to configure such a device via I2C.
+
 config USB_HSIC_USB3503
 	tristate "USB3503 HSIC to USB20 Driver"
 	depends on I2C
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 6fd8c53841e4..3832858aba1e 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_USB_SEVSEG)		+= usbsevseg.o
 obj-$(CONFIG_USB_YUREX)			+= yurex.o
 obj-$(CONFIG_USB_HUB_USB251XB)		+= usb251xb.o
 obj-$(CONFIG_USB_HUB_USB253X)		+= usb253x.o
+obj-$(CONFIG_USB_HUB_USB49XX)		+= usb49xx.o
 obj-$(CONFIG_USB_HSIC_USB3503)		+= usb3503.o
 obj-$(CONFIG_USB_HSIC_USB4604)		+= usb4604.o
 obj-$(CONFIG_USB_CHAOSKEY)		+= chaoskey.o
diff --git a/drivers/usb/misc/usb49xx.c b/drivers/usb/misc/usb49xx.c
new file mode 100644
index 000000000000..b713ef4557c2
--- /dev/null
+++ b/drivers/usb/misc/usb49xx.c
@@ -0,0 +1,825 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Microchip USB47xx and USB49xx USB 2.0 Hi-Speed Hub Controller
+ * Configuration via I2C.
+ *
+ * Copyright (c) 2020 ARRI Lighting
+ *
+ * This work is based on the USB251xb driver by Richard Leitner.
+ *
+ */
+
+#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/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+/* Internal Register Set Addresses acc. to DS00002651B */
+#define USB49XX_ADDR_VENDOR_ID                0xBF803000
+#define USB49XX_ADDR_PRODUCT_ID               0xBF803002
+#define USB49XX_ADDR_DEVICE_ID                0xBF803004
+#define USB49XX_ADDR_HUB_CFG                  0xBF803006
+#define USB49XX_HUB_CFG3_PRTMAP_EN            BIT(3)
+#define USB49XX_HUB_CFG3_STRING_EN            BIT(0)
+#define USB49XX_ADDR_NON_REMOVABLE_DEVICES    0xBF803009
+#define USB49XX_ADDR_PORT_DISABLE_SELF        0xBF80300A
+#define USB49XX_ADDR_PORT_DISABLE_BUS         0xBF80300B
+#define USB49XX_ADDR_POWER_ON_TIME            0xBF803010
+#define USB49XX_ADDR_OCS_MIN_WIDTH            0xBF8030EA
+#define USB49XX_ADDR_LANGUAGE_ID              0xBF803202
+#define USB49XX_STRING_BUFSIZE                62
+#define USB49XX_ADDR_MANUFACTURER_STRING_DESC 0xBFD23204
+#define USB49XX_ADDR_PRODUCT_STRING_DESC      0xBFD23244
+#define USB49XX_ADDR_MANUFACTURER_STRING_LEN  0xBFD2346A
+#define USB49XX_ADDR_PRODUCT_STRING_LEN       0xBFD23472
+#define USB49XX_ADDR_USB2_HUB_CTRL            0xBF803104
+#define USB49XX_USB2_HUB_CTRL_LPM_DISABLE     BIT(1)
+
+#define DRIVER_NAME "usb49xx"
+
+
+struct usb49xx {
+	struct device *dev;
+	struct i2c_client *i2c;
+	struct regulator *vdd;
+	struct regmap *regmap;
+	u8 skip_config;
+	struct gpio_desc *gpio_reset;
+};
+
+#ifdef CONFIG_GPIOLIB
+static int usb49xx_check_dev_children(struct device *dev, void *child)
+{
+	if (dev->type == &i2c_adapter_type) {
+		return device_for_each_child(dev, child,
+					     usb49xx_check_dev_children);
+	}
+
+	return (dev == child);
+}
+
+static int usb49xx_check_gpio_chip(struct usb49xx *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 = usb49xx_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 usb49xx_check_gpio_chip(struct usb49xx *hub)
+{
+	return 0;
+}
+#endif
+
+static void usb49xx_reset(struct usb49xx *hub, int state)
+{
+	if (state) {
+		/* During rising edge of reset, there must be no traffic on the i2c bus */
+		i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+		gpiod_set_value_cansleep(hub->gpio_reset, 1);
+		/* Wait for i2c logic to come up (hub seems to require ~10 ms)*/
+		msleep(20);
+		i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+	} else {
+		gpiod_set_value_cansleep(hub->gpio_reset, 0);
+		/* minimum reset pulse width is 5 us */
+		usleep_range(5, 10);
+	}
+}
+
+struct usb49xx_string_desc {
+	u8 length;   /* length of string in bytes + 2 */
+	u8 type;     /* always 0x03 */
+	wchar_t string[USB49XX_STRING_BUFSIZE/sizeof(wchar_t)];
+};
+
+static void usb49xx_get_ports_field(struct usb49xx *hub,
+				    const char *prop_name, u8 port_cnt,
+				    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 >= 1) && (port <= port_cnt))
+			*fld |= BIT(port);
+		else
+			dev_warn(dev, "port %u doesn't exist\n", port);
+	}
+}
+
+static int usb49xx_connect(struct usb49xx *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+	int err;
+	char attach_buffer[3];
+
+	usb49xx_reset(hub, 1);
+
+	if (hub->skip_config) {
+		dev_info(dev, "Skip hub configuration, only attach.\n");
+	} else {
+		u16 vendor_id, product_id, device_id, lang_id;
+		u8 hub_cfg[3];
+		u32 property_u32;
+		char const *manufacturer;
+		char const *product;
+		u8 non_rem_dev = 0;
+		u8 port_disable_sp = 0;
+		u8 port_disable_bp = 0;
+
+		if (!of_property_read_u16_array(np, "vendor-id", &vendor_id, 1)) {
+			char buffer[] = { vendor_id >> 8, vendor_id };
+
+			err = regmap_bulk_write(hub->regmap, USB49XX_ADDR_VENDOR_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting vendor id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "product-id", &product_id, 1)) {
+			char buffer[] = { product_id >> 8, product_id };
+
+			err = regmap_bulk_write(hub->regmap, USB49XX_ADDR_PRODUCT_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting product id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "device-id", &device_id, 1)) {
+			char buffer[] = { device_id >> 8, device_id };
+
+			err = regmap_bulk_write(hub->regmap, USB49XX_ADDR_DEVICE_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting device id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		err = regmap_bulk_read(hub->regmap, USB49XX_ADDR_HUB_CFG,
+				hub_cfg, ARRAY_SIZE(hub_cfg));
+		if (err) {
+			dev_err(dev, "Reading hub cfg failed: %d\n", err);
+			return err;
+		}
+
+		if (of_get_property(np, "self-powered", NULL)) {
+			hub_cfg[0] |= BIT(7);
+
+			/* Configure Over-Current sens when self-powered */
+			hub_cfg[0] &= ~BIT(2);
+			if (of_get_property(np, "ganged-sensing", NULL))
+				hub_cfg[0] &= ~BIT(1);
+			else if (of_get_property(np, "individual-sensing", NULL))
+				hub_cfg[0] |= BIT(1);
+		} else if (of_get_property(np, "bus-powered", NULL)) {
+			hub_cfg[0] &= ~BIT(7);
+
+			/* Disable Over-Current sense when bus-powered */
+			hub_cfg[0] |= BIT(2);
+		}
+
+		if (of_get_property(np, "disable-hi-speed", NULL))
+			hub_cfg[0] |= BIT(5);
+
+		if (of_get_property(np, "multi-tt", NULL))
+			hub_cfg[0] |= BIT(4);
+		else if (of_get_property(np, "single-tt", NULL))
+			hub_cfg[0] &= ~BIT(4);
+
+		if (of_get_property(np, "disable-eop", NULL))
+			hub_cfg[0] |= BIT(3);
+
+		if (of_get_property(np, "individual-port-switching", NULL))
+			hub_cfg[0] |= BIT(0);
+		else if (of_get_property(np, "ganged-port-switching", NULL))
+			hub_cfg[0] &= ~BIT(0);
+
+		if (of_get_property(np, "dynamic-power-switching", NULL))
+			hub_cfg[1] |= BIT(7);
+
+		if (!of_property_read_u32(np, "oc-delay-ns", &property_u32)) {
+			if (property_u32 == 50) {
+				/* 50 ns*/
+				hub_cfg[1] &= ~BIT(5);
+				hub_cfg[1] &= ~BIT(4);
+			} else if (property_u32 == 100) {
+				/* 100 ns */
+				hub_cfg[1] &= ~BIT(5);
+				hub_cfg[1] |= BIT(4);
+			} else if (property_u32 == 200) {
+				/* 400 ns */
+				hub_cfg[1] |= BIT(5);
+				hub_cfg[1] |= BIT(4);
+			} else {
+				/* 200 ns (DEFAULT) */
+				hub_cfg[1] |= BIT(5);
+				hub_cfg[1] &= ~BIT(4);
+			}
+		}
+
+		if (of_get_property(np, "compound-device", NULL))
+			hub_cfg[1] |= BIT(3);
+
+		if (of_get_property(np, "port-mapping-mode", NULL))
+			hub_cfg[2] |= USB49XX_HUB_CFG3_PRTMAP_EN;
+
+		/* port mapping requires that Link Power Management is disabled */
+		if (hub_cfg[2] & USB49XX_HUB_CFG3_PRTMAP_EN) {
+			unsigned int usb2_hub_ctrl;
+
+			/* Disable Link Power Management */
+			err = regmap_read(hub->regmap, USB49XX_ADDR_USB2_HUB_CTRL,
+					&usb2_hub_ctrl);
+			if (err) {
+				dev_err(dev, "Reading USB2 Hub Control failed: %d\n", err);
+				return err;
+			}
+
+			usb2_hub_ctrl |= USB49XX_USB2_HUB_CTRL_LPM_DISABLE;
+
+			err = regmap_write(hub->regmap, USB49XX_ADDR_USB2_HUB_CTRL,
+					usb2_hub_ctrl);
+			if (err) {
+				dev_err(dev, "Writing USB2 Hub Control failed: %d\n", err);
+				return err;
+			}
+		}
+
+		manufacturer = of_get_property(np, "manufacturer", NULL);
+		product = of_get_property(np, "product", NULL);
+
+		if (manufacturer || product)
+			hub_cfg[2] |= USB49XX_HUB_CFG3_STRING_EN;
+
+		err = regmap_bulk_write(hub->regmap, USB49XX_ADDR_HUB_CFG,
+				hub_cfg, ARRAY_SIZE(hub_cfg));
+		if (err) {
+			dev_err(dev, "Setting hub cfg failed: %d\n", err);
+			return err;
+		}
+
+		usb49xx_get_ports_field(hub, "non-removable-ports", 5, &non_rem_dev);
+		err = regmap_write(hub->regmap, USB49XX_ADDR_NON_REMOVABLE_DEVICES,
+				non_rem_dev);
+		if (err) {
+			dev_err(dev, "Setting non removable devices failed: %d\n", err);
+			return err;
+		}
+
+		usb49xx_get_ports_field(hub, "sp-disabled-ports", 7, &port_disable_sp);
+		err = regmap_write(hub->regmap, USB49XX_ADDR_PORT_DISABLE_SELF,
+				port_disable_sp);
+		if (err) {
+			dev_err(dev, "Setting port disable self powered failed: %d\n", err);
+			return err;
+		}
+
+		usb49xx_get_ports_field(hub, "bp-disabled-ports", 7, &port_disable_bp);
+		err = regmap_write(hub->regmap, USB49XX_ADDR_PORT_DISABLE_BUS,
+				port_disable_bp);
+		if (err) {
+			dev_err(dev, "Setting port disable powered powered failed: %d\n", err);
+			return err;
+		}
+
+		if (!of_property_read_u32(np, "power-on-time-ms", &property_u32)) {
+			u8 power_on_time = min_t(u8, property_u32 / 2, 255);
+
+			err = regmap_write(hub->regmap, USB49XX_ADDR_POWER_ON_TIME,
+					power_on_time);
+			if (err) {
+				dev_err(dev, "Setting power on time failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u32(np, "ocs-min-width-ms", &property_u32)) {
+			u8 ocs_min_width = min_t(u8, property_u32, 5);
+
+			err = regmap_write(hub->regmap, USB49XX_ADDR_OCS_MIN_WIDTH,
+					ocs_min_width);
+			if (err) {
+				dev_err(dev, "Setting ocs min width failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "language-id", &lang_id, 1)) {
+			char buffer[] = { lang_id >> 8, lang_id };
+
+			err = regmap_bulk_write(hub->regmap, USB49XX_ADDR_LANGUAGE_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting language id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (manufacturer) {
+			struct usb49xx_string_desc manufacturer_desc;
+
+			memset(&manufacturer_desc, 0, sizeof(manufacturer_desc));
+			manufacturer_desc.type = 0x3;
+			manufacturer_desc.length =
+					utf8s_to_utf16s(manufacturer, strlen(manufacturer),
+					UTF16_LITTLE_ENDIAN,
+					manufacturer_desc.string, USB49XX_STRING_BUFSIZE)
+					* sizeof(wchar_t) + 2;
+
+			err = regmap_bulk_write(hub->regmap,
+					USB49XX_ADDR_MANUFACTURER_STRING_DESC,
+					&manufacturer_desc, sizeof(manufacturer_desc));
+			if (err) {
+				dev_err(dev, "Setting manufacturer string descriptor failed: %d\n",
+						err);
+				return err;
+			}
+
+			err = regmap_write(hub->regmap, USB49XX_ADDR_MANUFACTURER_STRING_LEN,
+					manufacturer_desc.length);
+			if (err) {
+				dev_err(dev, "Setting manufacturer string length failed: %d\n",
+						err);
+				return err;
+			}
+		}
+
+		if (product) {
+			struct usb49xx_string_desc product_desc;
+
+			memset(&product_desc, 0, sizeof(product_desc));
+			product_desc.type = 0x3;
+			product_desc.length = utf8s_to_utf16s(product, strlen(product),
+					UTF16_LITTLE_ENDIAN,
+					product_desc.string, USB49XX_STRING_BUFSIZE)
+					* sizeof(wchar_t) + 2;
+
+			err = regmap_bulk_write(hub->regmap,
+					USB49XX_ADDR_PRODUCT_STRING_DESC,
+					&product_desc, sizeof(product_desc));
+			if (err) {
+				dev_err(dev, "Setting product string descriptor failed: %d\n", err);
+				return err;
+			}
+
+			err = regmap_write(hub->regmap, USB49XX_ADDR_PRODUCT_STRING_LEN,
+					product_desc.length);
+			if (err) {
+				dev_err(dev, "SSetting product string length failed: %d\n", err);
+				return err;
+			}
+		}
+	}
+
+#ifdef DEBUG
+	{
+		char *buffer = kzalloc(0x5000, GFP_KERNEL);
+
+		err = regmap_bulk_read(hub->regmap, 0xBF800000, buffer, 0x4);
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buffer, 0x4);
+		err = regmap_bulk_read(hub->regmap, 0xBF803000, buffer, 0x200);
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buffer, 0x200);
+		err = regmap_bulk_read(hub->regmap, 0xBFD23200, buffer, 0x200);
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buffer, 0x200);
+		kfree(buffer);
+	}
+#endif
+
+	/*
+	 *  attach
+	 */
+	/* n.b. Could also use 0xAA56 (attach with SMBus runtime access) */
+	attach_buffer[0] = 0xAA;   /* USB attach (high byte) */
+	attach_buffer[1] = 0x56;   /* USB attach (low byte) */
+	attach_buffer[2] = 0x00;   /* terminator */
+
+	err = i2c_master_send(hub->i2c, attach_buffer, 3);
+	if (err != 3) {
+		dev_err(dev, "attaching hub failed: %d\n", err);
+		return err;
+	}
+
+	dev_info(dev, "Hub configuration was successful.\n");
+	return 0;
+
+}
+
+#ifdef CONFIG_OF
+static int usb49xx_get_ofdata(struct usb49xx *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+
+	if (!np) {
+		dev_err(dev, "failed to get ofdata\n");
+		return -ENODEV;
+	}
+
+	if (of_get_property(np, "skip-config", NULL))
+		hub->skip_config = 1;
+	else
+		hub->skip_config = 0;
+
+	hub->gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(hub->gpio_reset))
+		return PTR_ERR(hub->gpio_reset);
+
+	return 0;
+}
+
+static const struct of_device_id usb49xx_of_match[] = {
+	{ .compatible = "microchip,usb4712",  },
+	{ .compatible = "microchip,usb4712i", },
+	{ .compatible = "microchip,usb4715",  },
+	{ .compatible = "microchip,usb4715i", },
+	{ .compatible = "microchip,usb4912",  },
+	{ .compatible = "microchip,usb4912i", },
+	{ .compatible = "microchip,usb4914",  },
+	{ .compatible = "microchip,usb4914i", },
+	{ .compatible = "microchip,usb4916",  },
+	{ .compatible = "microchip,usb4916i", },
+	{ .compatible = "microchip,usb4925",  },
+	{ .compatible = "microchip,usb4925i", },
+	{ .compatible = "microchip,usb4927",  },
+	{ .compatible = "microchip,usb4927i", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, usb49xx_of_match);
+#else /* CONFIG_OF */
+static int usb49xx_get_ofdata(struct usb49xx *hub)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static void usb49xx_regulator_disable_action(void *data)
+{
+	struct usb49xx *hub = data;
+
+	regulator_disable(hub->vdd);
+}
+
+static int usb49xx_probe(struct usb49xx *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+	int err;
+
+	if (np) {
+		err = usb49xx_get_ofdata(hub);
+		if (err) {
+			dev_err(dev, "failed to get ofdata: %d\n", err);
+			return err;
+		}
+	}
+
+	/*
+	 * The usb49xx performs a check on the SMBus during the SMBUS_CHECK state.
+	 * In order to successful, SCL and SDA must be stable at this moment.
+	 * 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 = usb49xx_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,
+				       usb49xx_regulator_disable_action, hub);
+	if (err)
+		return err;
+
+	err = usb49xx_connect(hub);
+	if (err) {
+		dev_err(dev, "Failed to connect hub (%d)\n", err);
+		return err;
+	}
+
+	dev_info(dev, "Hub probed successfully\n");
+
+	return 0;
+}
+
+static int usb49xx_i2c_gather_write(void *context,
+		const void *reg, size_t reg_len,
+		const void *val, size_t val_len)
+{
+	struct i2c_client *i2c = (struct i2c_client *)context;
+	size_t frame_len = val_len + 9;
+	char *buffer;
+	int err;
+
+	if (WARN_ON(reg_len != 4))
+		return -EINVAL;
+
+	buffer = kmalloc(frame_len, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	buffer[0] = 0x00;                 /* Memory address (high byte) */
+	buffer[1] = 0x00;                 /* Memory address (low byte) */
+	buffer[2] = val_len + 6;          /* Number of bytes to write to memory */
+	buffer[3] = 0x00;                 /* Register Write */
+	buffer[4] = val_len;              /* Write val_len data bytes */
+	buffer[5] = *((const char *)reg + 0);   /* Register address (MSB) */
+	buffer[6] = *((const char *)reg + 1);   /* Register address */
+	buffer[7] = *((const char *)reg + 2);   /* Register address */
+	buffer[8] = *((const char *)reg + 3);   /* Register address (LSB) */
+	memcpy(&buffer[9], val, val_len);
+
+	err = i2c_master_send(i2c, buffer, frame_len);
+	if (err != (int)frame_len) {
+		dev_err(&i2c->dev, "Writing to hub memory failed: %d\n", err);
+		goto error_kfree;
+	}
+
+	buffer[0] = 0x99;   /* Command (high byte) */
+	buffer[1] = 0x37;   /* Command (low byte) */
+	buffer[2] = 0x00;   /* Termination */
+	frame_len = 3;
+
+	err = i2c_master_send(i2c, buffer, frame_len);
+	if (err != (int)frame_len) {
+		dev_err(&i2c->dev, "Accessing configuration data failed: %d\n", err);
+		goto error_kfree;
+	}
+
+	err = 0;
+
+error_kfree:
+	kfree(buffer);
+	return err;
+}
+
+/* regmap puts address into data[0] .. data[3]; value is in data[4] */
+static int usb49xx_i2c_write(void *context, const void *data, size_t count)
+{
+	if (WARN_ON(count != 5))
+		return -EINVAL;
+
+	return usb49xx_i2c_gather_write(context, data, 4, data + 4, 1);
+}
+
+static int usb49xx_i2c_read(void *context, const void *reg_buf, size_t reg_size,
+		void *val_buf, size_t val_size)
+{
+	struct i2c_client *i2c = (struct i2c_client *)context;
+	char *tx, *rx;
+	int err;
+
+	if (WARN_ON(reg_size != 4)) {
+		err =  -EINVAL;
+		goto error_return;
+	}
+
+	tx = kmalloc(9, GFP_KERNEL);
+	if (!tx) {
+		err = -ENOMEM;
+		goto error_return;
+	}
+
+	rx = kmalloc(1 + val_size, GFP_KERNEL);
+	if (!rx) {
+		err = -ENOMEM;
+		goto error_free_tx;
+	}
+
+	tx[0] = 0x00;                 /* Memory address (high byte) */
+	tx[1] = 0x00;                 /* Memory address (low byte) */
+	tx[2] = 6;                    /* Number of bytes to write to memory */
+	tx[3] = 0x01;                 /* Register Read */
+	tx[4] = val_size;             /* Read val_size data bytes */
+	tx[5] = *((const char *)reg_buf + 0);   /* Register address (MSB) */
+	tx[6] = *((const char *)reg_buf + 1);   /* Register address */
+	tx[7] = *((const char *)reg_buf + 2);   /* Register address */
+	tx[8] = *((const char *)reg_buf + 3);   /* Register address (LSB) */
+
+	err = i2c_master_send(i2c, tx, 9);
+	if (err != 9) {
+		dev_err(&i2c->dev, "Writing to hub memory failed: %d\n", err);
+		goto error_free_rx;
+	}
+
+	tx[0] = 0x99;   /* Command (high byte) */
+	tx[1] = 0x37;   /* Command (low byte) */
+	tx[2] = 0x00;   /* Termination */
+
+	err = i2c_master_send(i2c, tx, 3);
+	if (err != 3) {
+		dev_err(&i2c->dev, "Accessing configuration data failed: %d\n", err);
+		goto error_free_rx;
+	}
+
+	/* Read from hub memory address 6 (according to application note) */
+	tx[0] = 0x00;    /* Memory address (high byte) */
+	tx[1] = 0x06;    /* Memory address (low byte) */
+
+	{
+		struct i2c_msg msgs[] = {
+			{
+				.addr = i2c->addr, .flags = 0,
+				.len  =         2, .buf = tx
+			},
+			{
+				.addr = i2c->addr, .flags = I2C_M_RD,
+				.len = 1 + val_size, .buf = rx
+			},
+		};
+
+		err = i2c_transfer(i2c->adapter, msgs, ARRAY_SIZE(msgs));
+		if (err < 0) {
+			dev_err(&i2c->dev, "Reading from hub memory failed: %d\n", err);
+			goto error_free_rx;
+		}
+	}
+
+	WARN_ON(rx[0] < val_size);
+
+	memcpy(val_buf, &rx[1], val_size);
+
+	err = 0;
+
+error_free_rx:
+	kfree(rx);
+error_free_tx:
+	kfree(tx);
+error_return:
+	return err;
+}
+
+/*
+ * The reason we need this custom regmap_bus instead of using regmap_init_i2c()
+ * is that accesses to the USB49xx configuration memory have to be wrapped into
+ * a special protocol.
+ */
+static const struct regmap_bus usb49xx_i2c_regmap = {
+	.write         = usb49xx_i2c_write,
+	.gather_write  = usb49xx_i2c_gather_write,
+	.read          = usb49xx_i2c_read,
+	.max_raw_read  = 8,
+	.max_raw_write = 128,
+};
+
+/* Avoid reading bigger dead ranges inside hubs memory. Same table for
+ * read and write.
+ */
+static const struct regmap_range usb49xx_regmap_yes_ranges[] = {
+	{ .range_min = 0xBF800000, .range_max = 0xBF800003 },
+	{ .range_min = 0xBF800900, .range_max = 0xBF8009FF },
+	{ .range_min = 0xBF803000, .range_max = 0xBF803015 },
+	{ .range_min = 0xBF8030D0, .range_max = 0xBF803197 },
+	{ .range_min = 0xBF80398C, .range_max = 0xBF80398D },
+	{ .range_min = 0xBF803C00, .range_max = 0xBF803C3C },
+	{ .range_min = 0xBF8060CA, .range_max = 0xBF8060CC },
+	{ .range_min = 0xBF8064CA, .range_max = 0xBF8064CC },
+	{ .range_min = 0xBF8068CA, .range_max = 0xBF8068CC },
+	{ .range_min = 0xBF806CCA, .range_max = 0xBF806CCC },
+	{ .range_min = 0xBF8070CA, .range_max = 0xBF8070CC },
+	{ .range_min = 0xBF8074CA, .range_max = 0xBF8074CC },
+	{ .range_min = 0xBF8078CA, .range_max = 0xBF8078CC },
+
+	{ .range_min = 0xBFD22856, .range_max = 0xBFD22857 },
+	{ .range_min = 0xBFD23202, .range_max = 0xBFD23472 },
+};
+
+static const struct regmap_access_table usb49xx_regmap_access_table = {
+	.yes_ranges   = usb49xx_regmap_yes_ranges,
+	.n_yes_ranges = ARRAY_SIZE(usb49xx_regmap_yes_ranges),
+};
+
+static int usb49xx_i2c_probe(struct i2c_client *i2c,
+			      const struct i2c_device_id *id __always_unused)
+{
+	struct usb49xx *hub;
+	struct regmap_config regmap_cfg = {
+		.name            = "usb49xx_config_mem",
+		.reg_bits        = 32,
+		.val_bits        = 8,
+		.wr_table        = &usb49xx_regmap_access_table,
+		.rd_table        = &usb49xx_regmap_access_table,
+		.can_multi_write = 1,
+	};
+
+	hub = devm_kzalloc(&i2c->dev, sizeof(struct usb49xx), GFP_KERNEL);
+	if (!hub)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, hub);
+	hub->dev = &i2c->dev;
+	hub->i2c = i2c;
+	hub->regmap = devm_regmap_init(&i2c->dev, &usb49xx_i2c_regmap, i2c,
+			&regmap_cfg);
+	if (IS_ERR(hub->regmap))
+		return PTR_ERR(hub->regmap);
+
+	return usb49xx_probe(hub);
+}
+
+static int usb49xx_i2c_remove(struct i2c_client *i2c)
+{
+	struct usb49xx *hub = i2c_get_clientdata(i2c);
+
+	usb49xx_reset(hub, 0);
+
+	return 0;
+};
+
+static int __maybe_unused usb49xx_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct usb49xx *hub = i2c_get_clientdata(client);
+
+	return regulator_disable(hub->vdd);
+}
+
+static int __maybe_unused usb49xx_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct usb49xx *hub = i2c_get_clientdata(client);
+	int err;
+
+	err = regulator_enable(hub->vdd);
+	if (err)
+		return err;
+
+	return usb49xx_connect(hub);
+}
+
+static SIMPLE_DEV_PM_OPS(usb49xx_pm_ops, usb49xx_suspend, usb49xx_resume);
+
+static const struct i2c_device_id usb49xx_id[] = {
+	{ "usb4712", 0 },
+	{ "usb4712i", 0 },
+	{ "usb4715", 0 },
+	{ "usb4715i", 0 },
+	{ "usb4912", 0 },
+	{ "usb4912i", 0 },
+	{ "usb4914", 0 },
+	{ "usb4914i", 0 },
+	{ "usb4916", 0 },
+	{ "usb4916	i", 0 },
+	{ "usb4925", 0 },
+	{ "usb4925i", 0 },
+	{ "usb4927", 0 },
+	{ "usb4927i", 0 },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, usb49xx_id);
+
+static struct i2c_driver usb49xx_i2c_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.of_match_table = of_match_ptr(usb49xx_of_match),
+		.pm = &usb49xx_pm_ops,
+	},
+	.probe    = usb49xx_i2c_probe,
+	.remove   = usb49xx_i2c_remove,
+	.id_table = usb49xx_id,
+};
+
+module_i2c_driver(usb49xx_i2c_driver);
+
+MODULE_AUTHOR("Christian Eggers <ceggers@arri.de>");
+MODULE_DESCRIPTION("USB47xx/USB49xx USB 2.0 Hub Controller Driver");
+MODULE_LICENSE("GPL");
-- 
Christian Eggers
Embedded software developer

Arnold & Richter Cine Technik GmbH & Co. Betriebs KG
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRA 57918
Persoenlich haftender Gesellschafter: Arnold & Richter Cine Technik GmbH
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRB 54477
Geschaeftsfuehrer: Dr. Michael Neuhaeuser; Stephan Schenk; Walter Trauninger; Markus Zeiler


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support
  2020-07-22 18:38 ` [PATCH 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support Christian Eggers
@ 2020-07-23 15:35   ` Rob Herring
  2020-07-23 19:29     ` [PATCH v2 " Christian Eggers
  0 siblings, 1 reply; 16+ messages in thread
From: Rob Herring @ 2020-07-23 15:35 UTC (permalink / raw)
  To: Christian Eggers
  Cc: Rob Herring, Richard Leitner, devicetree, Greg Kroah-Hartman,
	linux-usb, linux-kernel

On Wed, 22 Jul 2020 20:38:58 +0200, Christian Eggers wrote:
> Add DT bindings for Microchip USB47xx/USB49xx driver.
> 
> Signed-off-by: Christian Eggers <ceggers@arri.de>
> ---
>  .../devicetree/bindings/usb/usb49xx.yaml      | 230 ++++++++++++++++++
>  1 file changed, 230 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/usb/usb49xx.yaml
> 


My bot found errors running 'make dt_binding_check' on your patch:

/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/usb/usb49xx.example.dt.yaml: usb4916i@2d: 'ocs-min-width-ms' does not match any of the regexes: 'pinctrl-[0-9]+'


See https://patchwork.ozlabs.org/patch/1334134

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure dt-schema is up to date:

pip3 install git+https://github.com/devicetree-org/dt-schema.git@master --upgrade

Please check and re-submit.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v2 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support
  2020-07-23 15:35   ` Rob Herring
@ 2020-07-23 19:29     ` Christian Eggers
  2020-07-26  8:41       ` Greg Kroah-Hartman
  0 siblings, 1 reply; 16+ messages in thread
From: Christian Eggers @ 2020-07-23 19:29 UTC (permalink / raw)
  To: Rob Hering
  Cc: Rob Herring, Richard Leitner, devicetree, Greg Kroah-Hartman,
	linux-usb, linux-kernel, Christian Eggers

Add DT bindings for Microchip USB47xx/USB49xx driver.

Signed-off-by: Christian Eggers <ceggers@arri.de>
---
> My bot found errors running 'make dt_binding_check' on your patch:

> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/usb/usb49xx.example.dt.yaml: usb4916i@2d: 'ocs-min-width-ms' does not match any of the regexes: 'pinctrl-[0-9]+'
> If you already ran 'make dt_binding_check' and didn't see the above
> error(s), then make sure dt-schema is up to date:
The mistake was sitting in front of the computer. I simply overlooked this message.

Changes in v2:
- added property description for ocs-min-width-ms
- fixed property description for oc-delay-ns

 .../devicetree/bindings/usb/usb49xx.yaml      | 238 ++++++++++++++++++
 1 file changed, 238 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/usb49xx.yaml

diff --git a/Documentation/devicetree/bindings/usb/usb49xx.yaml b/Documentation/devicetree/bindings/usb/usb49xx.yaml
new file mode 100644
index 000000000000..a4843f2cbefa
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/usb49xx.yaml
@@ -0,0 +1,238 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/usb49xx.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip USB47xx/USB49xx USB 2.0 Hi-Speed Hub Controller
+
+maintainers:
+  - Christian Eggers <ceggers@arri.de>
+
+description: |
+  http://ww1.microchip.com/downloads/en/Appnotes/AN2651-Configuration-of-Microchip-USB47xx-USB49xx-Application-Note-00002651B.pdf
+
+properties:
+  compatible:
+    enum:
+      - microchip,usb4712
+      - microchip,usb4712i
+      - microchip,usb4715
+      - microchip,usb4715i
+      - microchip,usb4912
+      - microchip,usb4912i
+      - microchip,usb4914
+      - microchip,usb4914i
+      - microchip,usb4916
+      - microchip,usb4916i
+      - microchip,usb4925
+      - microchip,usb4925i
+      - microchip,usb4927
+      - microchip,usb4927i
+
+  reg:
+    maxItems: 1
+    description:
+      I2C address on the selected bus (usually <0x2D>).
+
+  reset-gpios:
+    maxItems: 1
+    description:
+      Specify the gpio for hub reset.
+
+  vdd-supply:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Specify the regulator supplying vdd.
+
+  skip-config:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Skip Hub configuration, but only send the USB-Attach command.
+
+  vendor-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Vendor ID of the hub.
+
+  product-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Product ID of the hub.
+
+  device-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Device ID of the hub.
+
+  language-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Language ID.
+
+  manufacturer:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Manufacturer string (max. 62 characters long).
+
+  product:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Product string (max. 62 characters long).
+
+  bus-powered:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects bus powered operation.
+
+  self-powered:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects self powered operation (default).
+
+  disable-hi-speed:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Disable USB Hi-Speed support.
+
+  multi-tt:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects multi-transaction-translator (default).
+
+  single-tt:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects single-transaction-translator.
+
+  disable-eop:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Disable End of Packet generation in full-speed mode.
+
+  ganged-sensing:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select ganged over-current sense type in self-powered mode.
+
+  individual-sensing:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select individual over-current sense type in self-powered mode (default).
+
+  ganged-port-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select ganged port power switching mode.
+
+  individual-port-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select individual port power switching mode (default).
+
+  dynamic-power-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Enable auto-switching from self- to bus-powered operation if the local
+      power source is removed or unavailable.
+
+  oc-delay-ns:
+    enum:
+      - 50
+      - 100
+      - 200
+      - 400
+    default: 200
+    description:
+      Delay time (in nanoseconds) for filtering the over-current sense inputs.
+      If an invalid value is given, the default is used instead.
+
+  compound-device:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Indicate the hub is part of a compound device.
+
+  port-mapping-mode:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Enable port mapping mode.
+
+  non-removable-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Should specify the ports which have a non-removable device connected.
+
+  sp-disabled-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Specifies the ports which will be self-power disabled.
+
+  bp-disabled-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Specifies the ports which will be bus-power disabled.
+
+  power-on-time-ms:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    default: 100
+    minimum: 0
+    maximum: 510
+    description:
+      Specifies the time (in milliseconds) it takes from the time the host
+      initiates the power-on sequence to a port until the port has adequate
+      power.
+
+  ocs-min-width-ms:
+    default: 5
+    minimum: 0
+    maximum: 5
+    description:
+      Minimum OCS pulse width (in milliseconds) required to detect an OCS
+      event.
+
+  hub-controller-port:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Hub port where the internal hub controller shall be connected. Usually
+      <number of ports>+1.
+
+additionalProperties: false
+
+required:
+  - compatible
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    i2c0 {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      usb4916i@2d {
+        compatible = "microchip,usb4916i";
+        reg = <0x2d>;
+        pinctrl-names = "default";
+        pinctrl-0 = <&pinctrl_usb_hub>;
+        /* usb49xx.c already assumes low-active, don't negate twice */
+        reset-gpios = <&gpio3 19 GPIO_ACTIVE_HIGH>;
+        //skip-config;
+        //self-powered;         /* power on default */
+        //individual-sensing;   /* power on default */
+        //multi-tt;             /* power on default */
+        //disable-eop;          /* power on default */
+        //individual-port-switching;  /* power on default */
+        //oc-delay-ns = <200>;  /* power on default */
+        power-on-time-ms = <4>; /* T_ON,max = 4 ms for NCP380 */
+        ocs-min-width-ms = <0>; /* MIC2005 only outputs 2us FAULT pulses */
+        manufacturer = "Foo";
+        product = "Foo-Bar";
+        /* port 5 is connected to an internal SD-Card reader */
+        non-removable-ports = <5>;
+      };
+    };
+
+...
-- 
Christian Eggers
Embedded software developer

Arnold & Richter Cine Technik GmbH & Co. Betriebs KG
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRA 57918
Persoenlich haftender Gesellschafter: Arnold & Richter Cine Technik GmbH
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRB 54477
Geschaeftsfuehrer: Dr. Michael Neuhaeuser; Stephan Schenk; Walter Trauninger; Markus Zeiler


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support
  2020-07-23 19:29     ` [PATCH v2 " Christian Eggers
@ 2020-07-26  8:41       ` Greg Kroah-Hartman
  2020-07-27  8:33         ` [PATCH v3] Add two new configuration drivers for Microchip USB hubs Christian Eggers
  0 siblings, 1 reply; 16+ messages in thread
From: Greg Kroah-Hartman @ 2020-07-26  8:41 UTC (permalink / raw)
  To: Christian Eggers
  Cc: Rob Hering, Rob Herring, Richard Leitner, devicetree, linux-usb,
	linux-kernel

On Thu, Jul 23, 2020 at 09:29:01PM +0200, Christian Eggers wrote:
> Add DT bindings for Microchip USB47xx/USB49xx driver.
> 
> Signed-off-by: Christian Eggers <ceggers@arri.de>
> ---
> > My bot found errors running 'make dt_binding_check' on your patch:
> 
> > /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/usb/usb49xx.example.dt.yaml: usb4916i@2d: 'ocs-min-width-ms' does not match any of the regexes: 'pinctrl-[0-9]+'
> > If you already ran 'make dt_binding_check' and didn't see the above
> > error(s), then make sure dt-schema is up to date:
> The mistake was sitting in front of the computer. I simply overlooked this message.
> 
> Changes in v2:
> - added property description for ocs-min-width-ms
> - fixed property description for oc-delay-ns

Please resend the whole series, not just a single patch, as it makes it
very difficult to pick the "correct" patches to be applied...

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v3] Add two new configuration drivers for Microchip USB hubs
  2020-07-26  8:41       ` Greg Kroah-Hartman
@ 2020-07-27  8:33         ` Christian Eggers
  2020-07-27  8:33           ` [PATCH v3 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support Christian Eggers
                             ` (4 more replies)
  0 siblings, 5 replies; 16+ messages in thread
From: Christian Eggers @ 2020-07-27  8:33 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Rob Hering, Rob Herring, Richard Leitner, devicetree, linux-usb,
	linux-kernel, ceggers

On Sonday, greg k-h wrote:
> Please resend the whole series, not just a single patch, as it makes it
> very difficult to pick the "correct" patches to be applied...

Changes in v3:
 - none (only resend the whole series)

Changes in v2:
 - added property description for ocs-min-width-ms
 - fixed property description for oc-delay-ns




^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v3 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support
  2020-07-27  8:33         ` [PATCH v3] Add two new configuration drivers for Microchip USB hubs Christian Eggers
@ 2020-07-27  8:33           ` Christian Eggers
  2020-07-31 20:38             ` Rob Herring
  2020-07-27  8:33           ` [PATCH v3 2/4] usb: misc: Add USB253x/xi Hi-Speed Hub Controller Driver Christian Eggers
                             ` (3 subsequent siblings)
  4 siblings, 1 reply; 16+ messages in thread
From: Christian Eggers @ 2020-07-27  8:33 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Rob Hering, Rob Herring, Richard Leitner, devicetree, linux-usb,
	linux-kernel, ceggers

Add DT bindings for Microchip USB253x/USB3x13/USB46x4 driver.

Signed-off-by: Christian Eggers <ceggers@arri.de>
---
 .../devicetree/bindings/usb/usb253x.yaml      | 234 ++++++++++++++++++
 1 file changed, 234 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/usb253x.yaml

diff --git a/Documentation/devicetree/bindings/usb/usb253x.yaml b/Documentation/devicetree/bindings/usb/usb253x.yaml
new file mode 100644
index 000000000000..88ea744147b6
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/usb253x.yaml
@@ -0,0 +1,234 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/usb253x.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip USB253x/USB3x13/USB46x4 USB 2.0 Hi-Speed Hub Controller
+
+maintainers:
+  - Christian Eggers <ceggers@arri.de>
+
+description: |
+  http://ww1.microchip.com/downloads/en/AppNotes/00001801C.pdf
+
+properties:
+  compatible:
+    enum:
+      - microchip,usb2532
+      - microchip,usb2532i
+      - microchip,usb2533
+      - microchip,usb2533i
+      - microchip,usb2534
+      - microchip,usb2534i
+      - microchip,usb3613
+      - microchip,usb3613i
+      - microchip,usb3813
+      - microchip,usb3813i
+      - microchip,usb4604
+      - microchip,usb4604i
+      - microchip,usb4624
+      - microchip,usb4624i
+
+  reg:
+    maxItems: 1
+    description:
+      I2C address on the selected bus (usually <0x2D>).
+
+  reset-gpios:
+    maxItems: 1
+    description:
+      Specify the gpio for hub reset.
+
+  vdd-supply:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Specify the regulator supplying vdd.
+
+  skip-config:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Skip Hub configuration, but only send the USB-Attach command.
+
+  vendor-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Vendor ID of the hub.
+
+  product-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Product ID of the hub.
+
+  device-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Device ID of the hub.
+
+  language-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Language ID.
+
+  manufacturer:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Manufacturer string (max. a total of 93 characters for
+      manufacturer, product and serial).
+
+  product:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Product string (max. a total of 93 characters for
+      manufacturer, product and serial).
+
+  serial:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Serial string (max. a total of 93 characters for
+      manufacturer, product and serial).
+
+  bus-powered:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects bus powered operation.
+
+  self-powered:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects self powered operation (default).
+
+  disable-hi-speed:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Disable USB Hi-Speed support.
+
+  multi-tt:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects multi-transaction-translator (default).
+
+  single-tt:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects single-transaction-translator.
+
+  disable-eop:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Disable End of Packet generation in full-speed mode.
+
+  ganged-sensing:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select ganged over-current sense type in self-powered mode.
+
+  individual-sensing:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select individual over-current sense type in self-powered mode (default).
+
+  ganged-port-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select ganged port power switching mode.
+
+  individual-port-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select individual port power switching mode (default).
+
+  dynamic-power-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Enable auto-switching from self- to bus-powered operation if the local
+      power source is removed or unavailable.
+
+  oc-delay-us:
+    enum:
+      - 100
+      - 4000
+      - 8000
+      - 16000
+    default: 8000
+    description:
+      Delay time (in microseconds) for filtering the over-current sense inputs.
+      If an invalid value is given, the default is used instead.
+
+  compound-device:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Indicate the hub is part of a compound device.
+
+  port-mapping-mode:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Enable port mapping mode.
+
+  non-removable-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Should specify the ports which have a non-removable device connected.
+
+  sp-disabled-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Specifies the ports which will be self-power disabled.
+
+  bp-disabled-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Specifies the ports which will be bus-power disabled.
+
+  power-on-time-ms:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    default: 100
+    minimum: 0
+    maximum: 510
+    description:
+      Specifies the time (in milliseconds) it takes from the time the host
+      initiates the power-on sequence to a port until the port has adequate
+      power.
+
+  hub-controller-port:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Hub port where the internal hub controller shall be connected. Usually
+      <number of ports>+1.
+
+additionalProperties: false
+
+required:
+  - compatible
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    i2c0 {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      usb2534i@2d {
+        compatible = "microchip,usb2534i";
+        reg = <0x2d>;
+        pinctrl-names = "default";
+        pinctrl-0 = <&pinctrl_usb_hub>;
+        /* usb253x.c already assumes low-active, don't negate twice */
+        reset-gpios = <&gpio3 19 GPIO_ACTIVE_HIGH>;
+        /*skip-config;*/
+        /* T_ON,max = 4 ms for NCP380 */
+        power-on-time-ms = <4>;
+        manufacturer = "Foo";
+        product = "Foo-Bar";
+        /* port 2 is connected to an internal SD-Card reader */
+        non-removable-ports = <2>;
+        /* hub controller mapped to logical port 5 */
+        hub-controller-port = <5>;
+      };
+    };
+
+...
-- 
Christian Eggers
Embedded software developer

Arnold & Richter Cine Technik GmbH & Co. Betriebs KG
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRA 57918
Persoenlich haftender Gesellschafter: Arnold & Richter Cine Technik GmbH
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRB 54477
Geschaeftsfuehrer: Dr. Michael Neuhaeuser; Stephan Schenk; Walter Trauninger; Markus Zeiler


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v3 2/4] usb: misc: Add USB253x/xi Hi-Speed Hub Controller Driver
  2020-07-27  8:33         ` [PATCH v3] Add two new configuration drivers for Microchip USB hubs Christian Eggers
  2020-07-27  8:33           ` [PATCH v3 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support Christian Eggers
@ 2020-07-27  8:33           ` Christian Eggers
  2020-07-27  8:33           ` [PATCH v3 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support Christian Eggers
                             ` (2 subsequent siblings)
  4 siblings, 0 replies; 16+ messages in thread
From: Christian Eggers @ 2020-07-27  8:33 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Rob Hering, Rob Herring, Richard Leitner, devicetree, linux-usb,
	linux-kernel, ceggers

This patch adds a driver for configuration of the Microchip USB253x/xi
USB 2.0 hub controller series with USB 2.0 upstream connectivity, SMBus
configuration interface and two to four USB 2.0 downstream ports.

Furthermore add myself as a maintainer for this driver.

The datasheet can be found at the manufacturers website, see [1]. All
device-tree exposed configuration features have been tested on a i.MX6
platform with a USB2534i hub.

[1] http://ww1.microchip.com/downloads/en/AppNotes/00001801C.pdf

Signed-off-by: Christian Eggers <ceggers@arri.de>
---
 MAINTAINERS                |   7 +
 drivers/usb/misc/Kconfig   |  10 +
 drivers/usb/misc/Makefile  |   1 +
 drivers/usb/misc/usb253x.c | 834 +++++++++++++++++++++++++++++++++++++
 4 files changed, 852 insertions(+)
 create mode 100644 drivers/usb/misc/usb253x.c

diff --git a/MAINTAINERS b/MAINTAINERS
index d53db30d1365..fe1108ba6ce7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11356,6 +11356,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/usb/usb251xb.txt
 F:	drivers/usb/misc/usb251xb.c
 
+MICROCHIP USB253X DRIVER
+M:	Christian Eggers <ceggers@arri.de>
+L:	linux-usb@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/usb/usb253x.yaml
+F:	drivers/usb/misc/usb253x.c
+
 MICROCHIP USBA UDC DRIVER
 M:	Cristian Birsan <cristian.birsan@microchip.com>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 4e48f8eed168..7a7b854e803a 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -242,6 +242,16 @@ config USB_HUB_USB251XB
 	  parameters may be set in devicetree or platform data.
 	  Say Y or M here if you need to configure such a device via SMBus.
 
+config USB_HUB_USB253X
+	tristate "USB253X Hub Controller Configuration Driver"
+	depends on I2C
+	select REGMAP
+	help
+	  This option enables support for configuration via I2C bus of the
+	  Microchip USB253x/xi USB 2.0 Hub Controller series.
+	  Configuration parameters may be set in devicetree or platform data.
+	  Say Y or M here if you need to configure such a device via I2C.
+
 config USB_HSIC_USB3503
 	tristate "USB3503 HSIC to USB20 Driver"
 	depends on I2C
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index da39bddb0604..6fd8c53841e4 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_USB_USS720)		+= uss720.o
 obj-$(CONFIG_USB_SEVSEG)		+= usbsevseg.o
 obj-$(CONFIG_USB_YUREX)			+= yurex.o
 obj-$(CONFIG_USB_HUB_USB251XB)		+= usb251xb.o
+obj-$(CONFIG_USB_HUB_USB253X)		+= usb253x.o
 obj-$(CONFIG_USB_HSIC_USB3503)		+= usb3503.o
 obj-$(CONFIG_USB_HSIC_USB4604)		+= usb4604.o
 obj-$(CONFIG_USB_CHAOSKEY)		+= chaoskey.o
diff --git a/drivers/usb/misc/usb253x.c b/drivers/usb/misc/usb253x.c
new file mode 100644
index 000000000000..ffd72e14c3c3
--- /dev/null
+++ b/drivers/usb/misc/usb253x.c
@@ -0,0 +1,834 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Microchip USB253x USB 2.0 Hi-Speed Hub Controller
+ * Configuration via I2C.
+ *
+ * Copyright (c) 2020 ARRI Lighting
+ *
+ * This work is based on the USB251xb driver by Richard Leitner.
+ *
+ */
+
+#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/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+/* Internal Register Set Addresses acc. to DS00001801C */
+#define USB253X_ADDR_VENDOR_ID                0x3000
+#define USB253X_ADDR_PRODUCT_ID               0x3002
+#define USB253X_ADDR_DEVICE_ID                0x3004
+#define USB253X_ADDR_HUB_CFG                  0x3006
+#define USB253X_HUB_CFG3_PRTMAP_EN            BIT(3)
+#define USB253X_HUB_CFG3_STRING_EN            BIT(0)
+#define USB253X_ADDR_NON_REMOVABLE_DEVICES    0x3009
+#define USB253X_ADDR_PORT_DISABLE_SELF        0x300A
+#define USB253X_ADDR_PORT_DISABLE_BUS         0x300B
+#define USB253X_ADDR_POWER_ON_TIME            0x3010
+#define USB253X_ADDR_LANGUAGE_ID              0x3011
+#define USB253X_ADDR_MANUFACTURER_STRING_LEN  0x3013
+#define USB253X_ADDR_PRODUCT_STRING_LEN       0x3014
+#define USB253X_ADDR_SERIAL_STRING_LEN        0x3015
+#define USB253X_ADDR_STRINGS                  0x3016
+#define USB253X_STRINGS_BUFSIZE              93  /* chars (UTF16) */
+#define USB253X_ADDR_HUB_CTRL_REMAP           0x30FD
+#define USB253X_ADDR_USB2_HUB_CTRL            0x3104
+#define USB253X_USB2_HUB_CTRL_LPM_DISABLE     BIT(1)
+#define USB253X_ADDR_INTERNAL_PORT            0x4130
+#define USB253X_INTERNAL_PORT_PORT_ENUM_DEFAULT        0b00
+#define USB253X_INTERNAL_PORT_PORT_ENUM_ALWAYS_ENABLE  0b01
+#define USB253X_INTERNAL_PORT_PORT_ENUM_ALWAYS_DISABLE 0b10
+
+#define DRIVER_NAME "usb253x"
+
+
+struct usb253x {
+	struct device *dev;
+	struct i2c_client *i2c;
+	struct regulator *vdd;
+	struct regmap *regmap;
+	u8 skip_config;
+	struct gpio_desc *gpio_reset;
+};
+
+#ifdef CONFIG_GPIOLIB
+static int usb253x_check_dev_children(struct device *dev, void *child)
+{
+	if (dev->type == &i2c_adapter_type) {
+		return device_for_each_child(dev, child,
+					     usb253x_check_dev_children);
+	}
+
+	return (dev == child);
+}
+
+static int usb253x_check_gpio_chip(struct usb253x *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 = usb253x_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 usb253x_check_gpio_chip(struct usb253x *hub)
+{
+	return 0;
+}
+#endif
+
+static void usb253x_reset(struct usb253x *hub, int state)
+{
+	if (state) {
+		/* During rising edge of reset, there must be no traffic on the i2c bus */
+		i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+		gpiod_set_value_cansleep(hub->gpio_reset, 1);
+		/* Wait for i2c logic to come up (hub seems to require ~210 ms if
+		 * RESET_N pulse was longer than 25 ms)
+		 */
+		msleep(250);
+		i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+	} else {
+		gpiod_set_value_cansleep(hub->gpio_reset, 0);
+		/* minimum reset pulse width from data sheet is 5 us */
+		usleep_range(5, 10);
+	}
+}
+
+static void usb253x_get_ports_field(struct usb253x *hub,
+				    const char *prop_name, u8 port_cnt,
+				    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 >= 1) && (port <= port_cnt))
+			*fld |= BIT(port);
+		else
+			dev_warn(dev, "port %u doesn't exist\n", port);
+	}
+}
+
+static int usb253x_connect(struct usb253x *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+	int err;
+	char attach_buffer[3];
+
+	usb253x_reset(hub, 1);
+
+	if (hub->skip_config) {
+		dev_info(dev, "Skip hub configuration, only attach.\n");
+	} else {
+		u16 vendor_id, product_id, device_id, lang_id;
+		u8 hub_cfg[3];
+		u32 property_u32;
+		char const *manufacturer;
+		char const *product;
+		char const *serial;
+		u8 non_rem_dev = 0;
+		u8 port_disable_sp = 0;
+		u8 port_disable_bp = 0;
+		wchar_t strings[USB253X_STRINGS_BUFSIZE];
+		int strings_used = 0;
+		u32 hub_controller_port = 0;
+
+		memset(strings, 0, sizeof(strings));
+
+		if (!of_property_read_u16_array(np, "vendor-id", &vendor_id, 1)) {
+			char buffer[] = { vendor_id >> 8, vendor_id };
+
+			err = regmap_bulk_write(hub->regmap, USB253X_ADDR_VENDOR_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting vendor id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "product-id", &product_id, 1)) {
+			char buffer[] = { product_id >> 8, product_id };
+
+			err = regmap_bulk_write(hub->regmap, USB253X_ADDR_PRODUCT_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting product id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "device-id", &device_id, 1)) {
+			char buffer[] = { device_id >> 8, device_id };
+
+			err = regmap_bulk_write(hub->regmap, USB253X_ADDR_DEVICE_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting device id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		err = regmap_bulk_read(hub->regmap, USB253X_ADDR_HUB_CFG,
+				hub_cfg, ARRAY_SIZE(hub_cfg));
+		if (err) {
+			dev_err(dev, "Reading hub cfg failed: %d\n", err);
+			return err;
+		}
+
+		if (of_get_property(np, "self-powered", NULL)) {
+			hub_cfg[0] |= BIT(7);
+
+			/* Configure Over-Current sens when self-powered */
+			hub_cfg[0] &= ~BIT(2);
+			if (of_get_property(np, "ganged-sensing", NULL))
+				hub_cfg[0] &= ~BIT(1);
+			else if (of_get_property(np, "individual-sensing", NULL))
+				hub_cfg[0] |= BIT(1);
+		} else if (of_get_property(np, "bus-powered", NULL)) {
+			hub_cfg[0] &= ~BIT(7);
+
+			/* Disable Over-Current sense when bus-powered */
+			hub_cfg[0] |= BIT(2);
+		}
+
+		if (of_get_property(np, "disable-hi-speed", NULL))
+			hub_cfg[0] |= BIT(5);
+
+		if (of_get_property(np, "multi-tt", NULL))
+			hub_cfg[0] |= BIT(4);
+		else if (of_get_property(np, "single-tt", NULL))
+			hub_cfg[0] &= ~BIT(4);
+
+		if (of_get_property(np, "disable-eop", NULL))
+			hub_cfg[0] |= BIT(3);
+
+		if (of_get_property(np, "individual-port-switching", NULL))
+			hub_cfg[0] |= BIT(0);
+		else if (of_get_property(np, "ganged-port-switching", NULL))
+			hub_cfg[0] &= ~BIT(0);
+
+		if (of_get_property(np, "dynamic-power-switching", NULL))
+			hub_cfg[1] |= BIT(7);
+
+		if (!of_property_read_u32(np, "oc-delay-us", &property_u32)) {
+			if (property_u32 == 100) {
+				/* 100 us*/
+				hub_cfg[1] &= ~BIT(5);
+				hub_cfg[1] &= ~BIT(4);
+			} else if (property_u32 == 4000) {
+				/* 4 ms */
+				hub_cfg[1] &= ~BIT(5);
+				hub_cfg[1] |= BIT(4);
+			} else if (property_u32 == 16000) {
+				/* 16 ms */
+				hub_cfg[1] |= BIT(5);
+				hub_cfg[1] |= BIT(4);
+			} else {
+				/* 8 ms (DEFAULT) */
+				hub_cfg[1] |= BIT(5);
+				hub_cfg[1] &= ~BIT(4);
+			}
+		}
+
+		if (of_get_property(np, "compound-device", NULL))
+			hub_cfg[1] |= BIT(3);
+
+		if (of_get_property(np, "port-mapping-mode", NULL))
+			hub_cfg[2] |= USB253X_HUB_CFG3_PRTMAP_EN;
+
+		/* hub controller will only work when port mapping is enabled and
+		 * controller port is mapped
+		 */
+		if (!of_property_read_u32(np, "hub-controller-port",
+				&hub_controller_port))
+			hub_cfg[2] |= USB253X_HUB_CFG3_PRTMAP_EN;
+
+		/* port mapping requires that Link Power Management is disabled */
+		if (hub_cfg[2] & USB253X_HUB_CFG3_PRTMAP_EN) {
+			unsigned int usb2_hub_ctrl;
+
+			/* Disable Link Power Management */
+			err = regmap_read(hub->regmap, USB253X_ADDR_USB2_HUB_CTRL,
+					&usb2_hub_ctrl);
+			if (err) {
+				dev_err(dev, "Reading USB2 Hub Control failed: %d\n", err);
+				return err;
+			}
+
+			usb2_hub_ctrl |= USB253X_USB2_HUB_CTRL_LPM_DISABLE;
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_USB2_HUB_CTRL,
+					usb2_hub_ctrl);
+			if (err) {
+				dev_err(dev, "Writing USB2 Hub Control failed: %d\n", err);
+				return err;
+			}
+		}
+
+		manufacturer = of_get_property(np, "manufacturer", NULL);
+		product = of_get_property(np, "product", NULL);
+		serial = of_get_property(np, "serial", NULL);
+
+		if (manufacturer || product || serial)
+			hub_cfg[2] |= USB253X_HUB_CFG3_STRING_EN;
+
+		err = regmap_bulk_write(hub->regmap, USB253X_ADDR_HUB_CFG,
+				hub_cfg, ARRAY_SIZE(hub_cfg));
+		if (err) {
+			dev_err(dev, "Setting hub cfg failed: %d\n", err);
+			return err;
+		}
+
+		usb253x_get_ports_field(hub, "non-removable-ports", 4, &non_rem_dev);
+		err = regmap_write(hub->regmap, USB253X_ADDR_NON_REMOVABLE_DEVICES,
+				non_rem_dev);
+		if (err) {
+			dev_err(dev, "Setting non removable devices failed: %d\n", err);
+			return err;
+		}
+
+		usb253x_get_ports_field(hub, "sp-disabled-ports", 4, &port_disable_sp);
+		err = regmap_write(hub->regmap, USB253X_ADDR_PORT_DISABLE_SELF,
+				port_disable_sp);
+		if (err) {
+			dev_err(dev, "Setting port disable self powered failed: %d\n", err);
+			return err;
+		}
+
+		usb253x_get_ports_field(hub, "bp-disabled-ports", 4, &port_disable_bp);
+		err = regmap_write(hub->regmap, USB253X_ADDR_PORT_DISABLE_BUS,
+				port_disable_bp);
+		if (err) {
+			dev_err(dev, "Setting port disable powered powered failed: %d\n", err);
+			return err;
+		}
+
+		if (!of_property_read_u32(np, "power-on-time-ms", &property_u32)) {
+			u8 power_on_time = min_t(u8, property_u32 / 2, 255);
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_POWER_ON_TIME,
+					power_on_time);
+			if (err) {
+				dev_err(dev, "Setting port disable powered powered failed: %d\n",
+						err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "language-id", &lang_id, 1)) {
+			char buffer[] = { lang_id >> 8, lang_id };
+
+			err = regmap_bulk_write(hub->regmap, USB253X_ADDR_LANGUAGE_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting language id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (manufacturer) {
+			u8 manufacturer_len = utf8s_to_utf16s(manufacturer, strlen(manufacturer),
+					UTF16_LITTLE_ENDIAN,
+					&strings[strings_used],
+					USB253X_STRINGS_BUFSIZE - strings_used);
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_MANUFACTURER_STRING_LEN,
+					manufacturer_len);
+			if (err) {
+				dev_err(dev, "Setting manufacturer string length failed: %d\n",
+						err);
+				return err;
+			}
+
+			strings_used += manufacturer_len;
+		}
+
+		if (product) {
+			u8 product_len = utf8s_to_utf16s(product, strlen(product),
+					UTF16_LITTLE_ENDIAN,
+					&strings[strings_used],
+					USB253X_STRINGS_BUFSIZE - strings_used);
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_PRODUCT_STRING_LEN,
+					product_len);
+			if (err) {
+				dev_err(dev, "Setting product string length failed: %d\n", err);
+				return err;
+			}
+
+			strings_used += product_len;
+		}
+
+		if (serial) {
+			u8 serial_len = utf8s_to_utf16s(serial, strlen(serial),
+					UTF16_LITTLE_ENDIAN,
+					&strings[strings_used],
+					USB253X_STRINGS_BUFSIZE - strings_used);
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_SERIAL_STRING_LEN,
+					serial_len);
+			if (err) {
+				dev_err(dev, "Setting serial string length failed: %d\n", err);
+				return err;
+			}
+
+			strings_used += serial_len;
+		}
+
+		err = regmap_bulk_write(hub->regmap, USB253X_ADDR_STRINGS,
+				strings, sizeof(strings));
+		if (err) {
+			dev_err(dev, "Setting strings failed: %d\n", err);
+			return err;
+		}
+
+		if (hub_controller_port != 0) {
+			/* Writes to this register are disabled unless PRTMAP_EN bit in
+			 * HUB_CFG_3 is set.
+			 */
+			err = regmap_write(hub->regmap, USB253X_ADDR_HUB_CTRL_REMAP,
+					hub_controller_port);
+			if (err) {
+				dev_err(dev, "Setting hub controller remap failed: %d\n", err);
+				return err;
+			}
+
+			err = regmap_write(hub->regmap, USB253X_ADDR_INTERNAL_PORT,
+					USB253X_INTERNAL_PORT_PORT_ENUM_ALWAYS_ENABLE);
+			if (err) {
+				dev_err(dev, "Setting internal port failed: %d\n", err);
+				return err;
+			}
+		}
+	}
+
+#ifdef DEBUG
+	{
+		char *buffer = kzalloc(0x5000, GFP_KERNEL);
+
+		err = regmap_bulk_read(hub->regmap, 0, buffer, 0x5000);
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buffer, 0x5000);
+		kfree(buffer);
+	}
+#endif
+
+	/*
+	 *  attach
+	 */
+	attach_buffer[0] = 0xAA;   /* USB attach (high byte) */
+	attach_buffer[1] = 0x55;   /* USB attach (low byte) */
+	attach_buffer[2] = 0x00;   /* terminator */
+
+	err = i2c_master_send(hub->i2c, attach_buffer, 3);
+	if (err != 3) {
+		dev_err(dev, "attaching hub failed: %d\n", err);
+		return err;
+	}
+
+	dev_info(dev, "Hub configuration was successful.\n");
+	return 0;
+
+}
+
+#ifdef CONFIG_OF
+static int usb253x_get_ofdata(struct usb253x *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+
+	if (!np) {
+		dev_err(dev, "failed to get ofdata\n");
+		return -ENODEV;
+	}
+
+	if (of_get_property(np, "skip-config", NULL))
+		hub->skip_config = 1;
+	else
+		hub->skip_config = 0;
+
+	hub->gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(hub->gpio_reset))
+		return PTR_ERR(hub->gpio_reset);
+
+	return 0;
+}
+
+static const struct of_device_id usb253x_of_match[] = {
+	{ .compatible = "microchip,usb2532",  },
+	{ .compatible = "microchip,usb2532i", },
+	{ .compatible = "microchip,usb2533",  },
+	{ .compatible = "microchip,usb2533i", },
+	{ .compatible = "microchip,usb2534",  },
+	{ .compatible = "microchip,usb2534i", },
+	{ .compatible = "microchip,usb3613",  },
+	{ .compatible = "microchip,usb3613i", },
+	{ .compatible = "microchip,usb3813",  },
+	{ .compatible = "microchip,usb3813i", },
+	{ .compatible = "microchip,usb4604",  },
+	{ .compatible = "microchip,usb4604i", },
+	{ .compatible = "microchip,usb4624",  },
+	{ .compatible = "microchip,usb4624i", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, usb253x_of_match);
+#else /* CONFIG_OF */
+static int usb253x_get_ofdata(struct usb253x *hub)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static void usb253x_regulator_disable_action(void *data)
+{
+	struct usb253x *hub = data;
+
+	regulator_disable(hub->vdd);
+}
+
+static int usb253x_probe(struct usb253x *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+	int err;
+
+	if (np) {
+		err = usb253x_get_ofdata(hub);
+		if (err) {
+			dev_err(dev, "failed to get ofdata: %d\n", err);
+			return err;
+		}
+	}
+
+	/*
+	 * usb253x 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 usb253x. 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 = usb253x_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,
+				       usb253x_regulator_disable_action, hub);
+	if (err)
+		return err;
+
+	err = usb253x_connect(hub);
+	if (err) {
+		dev_err(dev, "Failed to connect hub (%d)\n", err);
+		return err;
+	}
+
+	dev_info(dev, "Hub probed successfully\n");
+
+	return 0;
+}
+
+static int usb253x_i2c_gather_write(void *context,
+		const void *reg, size_t reg_len,
+		const void *val, size_t val_len)
+{
+	struct i2c_client *i2c = (struct i2c_client *)context;
+	size_t frame_len = val_len + 7;
+	char *buffer;
+	int err;
+
+	if (WARN_ON(reg_len != 2))
+		return -EINVAL;
+
+	buffer = kmalloc(frame_len, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	buffer[0] = 0x00;                 /* Memory address (high byte) */
+	buffer[1] = 0x00;                 /* Memory address (low byte) */
+	buffer[2] = val_len + 4;          /* Number of bytes to write to memory */
+	buffer[3] = 0x00;                 /* Register Write */
+	buffer[4] = val_len;              /* Write val_len data bytes */
+	buffer[5] = *((const char *)reg + 0);   /* Register address (high byte) */
+	buffer[6] = *((const char *)reg + 1);   /* Register address (low byte) */
+	memcpy(&buffer[7], val, val_len);
+
+	err = i2c_master_send(i2c, buffer, frame_len);
+	if (err != (int)frame_len) {
+		dev_err(&i2c->dev, "Writing to hub memory failed: %d\n", err);
+		goto error_kfree;
+	}
+
+	buffer[0] = 0x99;   /* Command (high byte) */
+	buffer[1] = 0x37;   /* Command (low byte) */
+	buffer[2] = 0x00;   /* Termination */
+	frame_len = 3;
+
+	err = i2c_master_send(i2c, buffer, frame_len);
+	if (err != (int)frame_len) {
+		dev_err(&i2c->dev, "Accessing configuration data failed: %d\n", err);
+		goto error_kfree;
+	}
+
+	err = 0;
+
+error_kfree:
+	kfree(buffer);
+	return err;
+}
+
+/* regmap puts address into data[0] and data[1]; value is in data[2] */
+static int usb253x_i2c_write(void *context, const void *data, size_t count)
+{
+	if (WARN_ON(count != 3))
+		return -EINVAL;
+
+	return usb253x_i2c_gather_write(context, data, 2, data + 2, 1);
+}
+
+static int usb253x_i2c_read(void *context, const void *reg_buf, size_t reg_size,
+		void *val_buf, size_t val_size)
+{
+	struct i2c_client *i2c = (struct i2c_client *)context;
+	char *tx, *rx;
+	int err;
+
+	if (WARN_ON(reg_size != 2)) {
+		err =  -EINVAL;
+		goto error_return;
+	}
+
+	tx = kmalloc(7, GFP_KERNEL);
+	if (!tx) {
+		err = -ENOMEM;
+		goto error_return;
+	}
+
+	rx = kmalloc(1 + val_size, GFP_KERNEL);
+	if (!rx) {
+		err = -ENOMEM;
+		goto error_free_tx;
+	}
+
+	tx[0] = 0x00;                 /* Memory address (high byte) */
+	tx[1] = 0x00;                 /* Memory address (low byte) */
+	tx[2] = 4;                    /* Number of bytes to write to memory */
+	tx[3] = 0x01;                 /* Register Read */
+	tx[4] = val_size;             /* Read val_size data bytes */
+	tx[5] = *((const char *)reg_buf + 0);   /* Register address (high byte) */
+	tx[6] = *((const char *)reg_buf + 1);   /* Register address (low byte) */
+
+	err = i2c_master_send(i2c, tx, 7);
+	if (err != 7) {
+		dev_err(&i2c->dev, "Writing to hub memory failed: %d\n", err);
+		goto error_free_rx;
+	}
+
+	tx[0] = 0x99;   /* Command (high byte) */
+	tx[1] = 0x37;   /* Command (low byte) */
+	tx[2] = 0x00;   /* Termination */
+
+	err = i2c_master_send(i2c, tx, 3);
+	if (err != 3) {
+		dev_err(&i2c->dev, "Accessing configuration data failed: %d\n", err);
+		goto error_free_rx;
+	}
+
+	/* Read from hub memory address 4 (according to application note) */
+	tx[0] = 0x00;    /* Memory address (high byte) */
+	tx[1] = 0x04;    /* Memory address (low byte) */
+
+	{
+		struct i2c_msg msgs[] = {
+			{
+				.addr = i2c->addr, .flags = 0,
+				.len  =         2, .buf = tx
+			},
+			{
+				.addr = i2c->addr, .flags = I2C_M_RD,
+				.len = 1 + val_size, .buf = rx
+			},
+		};
+
+		err = i2c_transfer(i2c->adapter, msgs, ARRAY_SIZE(msgs));
+		if (err < 0) {
+			dev_err(&i2c->dev, "Reading from hub memory failed: %d\n", err);
+			goto error_free_rx;
+		}
+	}
+
+	WARN_ON(rx[0] < val_size);
+
+	memcpy(val_buf, &rx[1], val_size);
+
+	err = 0;
+
+error_free_rx:
+	kfree(rx);
+error_free_tx:
+	kfree(tx);
+error_return:
+	return err;
+}
+
+/*
+ * The reason we need this custom regmap_bus instead of using regmap_init_i2c()
+ * is that accesses to the USB253x configuration memory have to be wrapped into
+ * a special protocol.
+ */
+static const struct regmap_bus usb253x_i2c_regmap = {
+	.write         = usb253x_i2c_write,
+	.gather_write  = usb253x_i2c_gather_write,
+	.read          = usb253x_i2c_read,
+	.max_raw_read  = 128,
+	.max_raw_write = 128,
+};
+
+/* Avoid reading bigger dead ranges inside hubs memory.  Same table for
+ * read and write.
+ */
+static const struct regmap_range usb253x_regmap_yes_ranges[] = {
+	{ .range_min = 0x0800, .range_max = 0x09ff },
+	{ .range_min = 0x3000, .range_max = 0x31ff },
+	{ .range_min = 0x3C00, .range_max = 0x3cff },
+	{ .range_min = 0x4100, .range_max = 0x42ff },
+	{ .range_min = 0x6000, .range_max = 0x72ff },
+};
+
+static const struct regmap_access_table usb253x_regmap_access_table = {
+	.yes_ranges   = usb253x_regmap_yes_ranges,
+	.n_yes_ranges = ARRAY_SIZE(usb253x_regmap_yes_ranges),
+};
+
+static int usb253x_i2c_probe(struct i2c_client *i2c,
+			      const struct i2c_device_id *id __always_unused)
+{
+	struct usb253x *hub;
+	struct regmap_config regmap_cfg = {
+		.name            = "usb253x_config_mem",
+		.reg_bits        = 16,
+		.val_bits        = 8,
+		.max_register    = 0x7fff,
+		.wr_table        = &usb253x_regmap_access_table,
+		.rd_table        = &usb253x_regmap_access_table,
+		.can_multi_write = 1,
+	};
+
+	hub = devm_kzalloc(&i2c->dev, sizeof(struct usb253x), GFP_KERNEL);
+	if (!hub)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, hub);
+	hub->dev = &i2c->dev;
+	hub->i2c = i2c;
+	hub->regmap = devm_regmap_init(&i2c->dev, &usb253x_i2c_regmap, i2c,
+			&regmap_cfg);
+	if (IS_ERR(hub->regmap))
+		return PTR_ERR(hub->regmap);
+
+	return usb253x_probe(hub);
+}
+
+static int usb253x_i2c_remove(struct i2c_client *i2c)
+{
+	struct usb253x *hub = i2c_get_clientdata(i2c);
+
+	usb253x_reset(hub, 0);
+
+	return 0;
+};
+
+static int __maybe_unused usb253x_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct usb253x *hub = i2c_get_clientdata(client);
+
+	return regulator_disable(hub->vdd);
+}
+
+static int __maybe_unused usb253x_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct usb253x *hub = i2c_get_clientdata(client);
+	int err;
+
+	err = regulator_enable(hub->vdd);
+	if (err)
+		return err;
+
+	return usb253x_connect(hub);
+}
+
+static SIMPLE_DEV_PM_OPS(usb253x_pm_ops, usb253x_suspend, usb253x_resume);
+
+static const struct i2c_device_id usb253x_id[] = {
+	{ "usb2532", 0 },
+	{ "usb2532i", 0 },
+	{ "usb2533", 0 },
+	{ "usb2533i", 0 },
+	{ "usb2534", 0 },
+	{ "usb2534i", 0 },
+	{ "usb3613", 0 },
+	{ "usb3613i", 0 },
+	{ "usb3813", 0 },
+	{ "usb3813i", 0 },
+	{ "usb4604", 0 },
+	{ "usb4604i", 0 },
+	{ "usb4624", 0 },
+	{ "usb4624i", 0 },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, usb253x_id);
+
+static struct i2c_driver usb253x_i2c_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.of_match_table = of_match_ptr(usb253x_of_match),
+		.pm = &usb253x_pm_ops,
+	},
+	.probe    = usb253x_i2c_probe,
+	.remove   = usb253x_i2c_remove,
+	.id_table = usb253x_id,
+};
+
+module_i2c_driver(usb253x_i2c_driver);
+
+MODULE_AUTHOR("Christian Eggers <ceggers@arri.de>");
+MODULE_DESCRIPTION("USB253x/xi USB 2.0 Hub Controller Driver");
+MODULE_LICENSE("GPL");
-- 
Christian Eggers
Embedded software developer

Arnold & Richter Cine Technik GmbH & Co. Betriebs KG
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRA 57918
Persoenlich haftender Gesellschafter: Arnold & Richter Cine Technik GmbH
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRB 54477
Geschaeftsfuehrer: Dr. Michael Neuhaeuser; Stephan Schenk; Walter Trauninger; Markus Zeiler


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v3 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support
  2020-07-27  8:33         ` [PATCH v3] Add two new configuration drivers for Microchip USB hubs Christian Eggers
  2020-07-27  8:33           ` [PATCH v3 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support Christian Eggers
  2020-07-27  8:33           ` [PATCH v3 2/4] usb: misc: Add USB253x/xi Hi-Speed Hub Controller Driver Christian Eggers
@ 2020-07-27  8:33           ` Christian Eggers
  2020-07-27  8:33           ` [PATCH v3 4/4] usb: misc: Add USB49xx/xi Hi-Speed Hub Controller Driver Christian Eggers
  2020-08-17  7:54           ` [PATCH v3] Add two new configuration drivers for Microchip USB hubs Richard Leitner
  4 siblings, 0 replies; 16+ messages in thread
From: Christian Eggers @ 2020-07-27  8:33 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Rob Hering, Rob Herring, Richard Leitner, devicetree, linux-usb,
	linux-kernel, ceggers

Add DT bindings for Microchip USB47xx/USB49xx driver.

Signed-off-by: Christian Eggers <ceggers@arri.de>
---
 .../devicetree/bindings/usb/usb49xx.yaml      | 238 ++++++++++++++++++
 1 file changed, 238 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/usb49xx.yaml

diff --git a/Documentation/devicetree/bindings/usb/usb49xx.yaml b/Documentation/devicetree/bindings/usb/usb49xx.yaml
new file mode 100644
index 000000000000..a4843f2cbefa
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/usb49xx.yaml
@@ -0,0 +1,238 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/usb49xx.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip USB47xx/USB49xx USB 2.0 Hi-Speed Hub Controller
+
+maintainers:
+  - Christian Eggers <ceggers@arri.de>
+
+description: |
+  http://ww1.microchip.com/downloads/en/Appnotes/AN2651-Configuration-of-Microchip-USB47xx-USB49xx-Application-Note-00002651B.pdf
+
+properties:
+  compatible:
+    enum:
+      - microchip,usb4712
+      - microchip,usb4712i
+      - microchip,usb4715
+      - microchip,usb4715i
+      - microchip,usb4912
+      - microchip,usb4912i
+      - microchip,usb4914
+      - microchip,usb4914i
+      - microchip,usb4916
+      - microchip,usb4916i
+      - microchip,usb4925
+      - microchip,usb4925i
+      - microchip,usb4927
+      - microchip,usb4927i
+
+  reg:
+    maxItems: 1
+    description:
+      I2C address on the selected bus (usually <0x2D>).
+
+  reset-gpios:
+    maxItems: 1
+    description:
+      Specify the gpio for hub reset.
+
+  vdd-supply:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Specify the regulator supplying vdd.
+
+  skip-config:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Skip Hub configuration, but only send the USB-Attach command.
+
+  vendor-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Vendor ID of the hub.
+
+  product-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Product ID of the hub.
+
+  device-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Device ID of the hub.
+
+  language-id:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maximum: 65535
+    description:
+      Set USB Language ID.
+
+  manufacturer:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Manufacturer string (max. 62 characters long).
+
+  product:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Set USB Product string (max. 62 characters long).
+
+  bus-powered:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects bus powered operation.
+
+  self-powered:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects self powered operation (default).
+
+  disable-hi-speed:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Disable USB Hi-Speed support.
+
+  multi-tt:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects multi-transaction-translator (default).
+
+  single-tt:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Selects single-transaction-translator.
+
+  disable-eop:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Disable End of Packet generation in full-speed mode.
+
+  ganged-sensing:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select ganged over-current sense type in self-powered mode.
+
+  individual-sensing:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select individual over-current sense type in self-powered mode (default).
+
+  ganged-port-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select ganged port power switching mode.
+
+  individual-port-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Select individual port power switching mode (default).
+
+  dynamic-power-switching:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Enable auto-switching from self- to bus-powered operation if the local
+      power source is removed or unavailable.
+
+  oc-delay-ns:
+    enum:
+      - 50
+      - 100
+      - 200
+      - 400
+    default: 200
+    description:
+      Delay time (in nanoseconds) for filtering the over-current sense inputs.
+      If an invalid value is given, the default is used instead.
+
+  compound-device:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Indicate the hub is part of a compound device.
+
+  port-mapping-mode:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Enable port mapping mode.
+
+  non-removable-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Should specify the ports which have a non-removable device connected.
+
+  sp-disabled-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Specifies the ports which will be self-power disabled.
+
+  bp-disabled-ports:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Specifies the ports which will be bus-power disabled.
+
+  power-on-time-ms:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    default: 100
+    minimum: 0
+    maximum: 510
+    description:
+      Specifies the time (in milliseconds) it takes from the time the host
+      initiates the power-on sequence to a port until the port has adequate
+      power.
+
+  ocs-min-width-ms:
+    default: 5
+    minimum: 0
+    maximum: 5
+    description:
+      Minimum OCS pulse width (in milliseconds) required to detect an OCS
+      event.
+
+  hub-controller-port:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Hub port where the internal hub controller shall be connected. Usually
+      <number of ports>+1.
+
+additionalProperties: false
+
+required:
+  - compatible
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    i2c0 {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      usb4916i@2d {
+        compatible = "microchip,usb4916i";
+        reg = <0x2d>;
+        pinctrl-names = "default";
+        pinctrl-0 = <&pinctrl_usb_hub>;
+        /* usb49xx.c already assumes low-active, don't negate twice */
+        reset-gpios = <&gpio3 19 GPIO_ACTIVE_HIGH>;
+        //skip-config;
+        //self-powered;         /* power on default */
+        //individual-sensing;   /* power on default */
+        //multi-tt;             /* power on default */
+        //disable-eop;          /* power on default */
+        //individual-port-switching;  /* power on default */
+        //oc-delay-ns = <200>;  /* power on default */
+        power-on-time-ms = <4>; /* T_ON,max = 4 ms for NCP380 */
+        ocs-min-width-ms = <0>; /* MIC2005 only outputs 2us FAULT pulses */
+        manufacturer = "Foo";
+        product = "Foo-Bar";
+        /* port 5 is connected to an internal SD-Card reader */
+        non-removable-ports = <5>;
+      };
+    };
+
+...
-- 
Christian Eggers
Embedded software developer

Arnold & Richter Cine Technik GmbH & Co. Betriebs KG
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRA 57918
Persoenlich haftender Gesellschafter: Arnold & Richter Cine Technik GmbH
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRB 54477
Geschaeftsfuehrer: Dr. Michael Neuhaeuser; Stephan Schenk; Walter Trauninger; Markus Zeiler


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v3 4/4] usb: misc: Add USB49xx/xi Hi-Speed Hub Controller Driver
  2020-07-27  8:33         ` [PATCH v3] Add two new configuration drivers for Microchip USB hubs Christian Eggers
                             ` (2 preceding siblings ...)
  2020-07-27  8:33           ` [PATCH v3 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support Christian Eggers
@ 2020-07-27  8:33           ` Christian Eggers
  2020-08-17  7:54           ` [PATCH v3] Add two new configuration drivers for Microchip USB hubs Richard Leitner
  4 siblings, 0 replies; 16+ messages in thread
From: Christian Eggers @ 2020-07-27  8:33 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Rob Hering, Rob Herring, Richard Leitner, devicetree, linux-usb,
	linux-kernel, ceggers

This patch adds a driver for configuration of the Microchip USB49xx/xi
USB 2.0 hub controller series with USB 2.0 upstream connectivity, SMBus
configuration interface and two to four USB 2.0 downstream ports.

Furthermore add myself as a maintainer for this driver.

The datasheet can be found at the manufacturers website, see [1]. All
device-tree exposed configuration features have been tested on a i.MX6
platform with a USB4916 hub.

[1] http://ww1.microchip.com/downloads/en/Appnotes/AN2651-Configuration-of-Microchip-USB47xx-USB49xx-Application-Note-00002651B.pdf

Signed-off-by: Christian Eggers <ceggers@arri.de>
---
 MAINTAINERS                |   7 +
 drivers/usb/misc/Kconfig   |  10 +
 drivers/usb/misc/Makefile  |   1 +
 drivers/usb/misc/usb49xx.c | 825 +++++++++++++++++++++++++++++++++++++
 4 files changed, 843 insertions(+)
 create mode 100644 drivers/usb/misc/usb49xx.c

diff --git a/MAINTAINERS b/MAINTAINERS
index fe1108ba6ce7..068d6e94122b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11363,6 +11363,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/usb/usb253x.yaml
 F:	drivers/usb/misc/usb253x.c
 
+MICROCHIP USB49XX DRIVER
+M:	Christian Eggers <ceggers@arri.de>
+L:	linux-usb@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/usb/usb49xx.yaml
+F:	drivers/usb/misc/usb49xx.c
+
 MICROCHIP USBA UDC DRIVER
 M:	Cristian Birsan <cristian.birsan@microchip.com>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 7a7b854e803a..14bea87c0f81 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -252,6 +252,16 @@ config USB_HUB_USB253X
 	  Configuration parameters may be set in devicetree or platform data.
 	  Say Y or M here if you need to configure such a device via I2C.
 
+config USB_HUB_USB49XX
+	tristate "USB47XX/USB49XX Hub Controller Configuration Driver"
+	depends on I2C
+	select REGMAP
+	help
+	  This option enables support for configuration via I2C bus of the
+	  Microchip USB47xx/49xx USB 2.0 Hub Controller series.
+	  Configuration parameters may be set in devicetree or platform data.
+	  Say Y or M here if you need to configure such a device via I2C.
+
 config USB_HSIC_USB3503
 	tristate "USB3503 HSIC to USB20 Driver"
 	depends on I2C
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 6fd8c53841e4..3832858aba1e 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_USB_SEVSEG)		+= usbsevseg.o
 obj-$(CONFIG_USB_YUREX)			+= yurex.o
 obj-$(CONFIG_USB_HUB_USB251XB)		+= usb251xb.o
 obj-$(CONFIG_USB_HUB_USB253X)		+= usb253x.o
+obj-$(CONFIG_USB_HUB_USB49XX)		+= usb49xx.o
 obj-$(CONFIG_USB_HSIC_USB3503)		+= usb3503.o
 obj-$(CONFIG_USB_HSIC_USB4604)		+= usb4604.o
 obj-$(CONFIG_USB_CHAOSKEY)		+= chaoskey.o
diff --git a/drivers/usb/misc/usb49xx.c b/drivers/usb/misc/usb49xx.c
new file mode 100644
index 000000000000..b713ef4557c2
--- /dev/null
+++ b/drivers/usb/misc/usb49xx.c
@@ -0,0 +1,825 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Microchip USB47xx and USB49xx USB 2.0 Hi-Speed Hub Controller
+ * Configuration via I2C.
+ *
+ * Copyright (c) 2020 ARRI Lighting
+ *
+ * This work is based on the USB251xb driver by Richard Leitner.
+ *
+ */
+
+#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/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+/* Internal Register Set Addresses acc. to DS00002651B */
+#define USB49XX_ADDR_VENDOR_ID                0xBF803000
+#define USB49XX_ADDR_PRODUCT_ID               0xBF803002
+#define USB49XX_ADDR_DEVICE_ID                0xBF803004
+#define USB49XX_ADDR_HUB_CFG                  0xBF803006
+#define USB49XX_HUB_CFG3_PRTMAP_EN            BIT(3)
+#define USB49XX_HUB_CFG3_STRING_EN            BIT(0)
+#define USB49XX_ADDR_NON_REMOVABLE_DEVICES    0xBF803009
+#define USB49XX_ADDR_PORT_DISABLE_SELF        0xBF80300A
+#define USB49XX_ADDR_PORT_DISABLE_BUS         0xBF80300B
+#define USB49XX_ADDR_POWER_ON_TIME            0xBF803010
+#define USB49XX_ADDR_OCS_MIN_WIDTH            0xBF8030EA
+#define USB49XX_ADDR_LANGUAGE_ID              0xBF803202
+#define USB49XX_STRING_BUFSIZE                62
+#define USB49XX_ADDR_MANUFACTURER_STRING_DESC 0xBFD23204
+#define USB49XX_ADDR_PRODUCT_STRING_DESC      0xBFD23244
+#define USB49XX_ADDR_MANUFACTURER_STRING_LEN  0xBFD2346A
+#define USB49XX_ADDR_PRODUCT_STRING_LEN       0xBFD23472
+#define USB49XX_ADDR_USB2_HUB_CTRL            0xBF803104
+#define USB49XX_USB2_HUB_CTRL_LPM_DISABLE     BIT(1)
+
+#define DRIVER_NAME "usb49xx"
+
+
+struct usb49xx {
+	struct device *dev;
+	struct i2c_client *i2c;
+	struct regulator *vdd;
+	struct regmap *regmap;
+	u8 skip_config;
+	struct gpio_desc *gpio_reset;
+};
+
+#ifdef CONFIG_GPIOLIB
+static int usb49xx_check_dev_children(struct device *dev, void *child)
+{
+	if (dev->type == &i2c_adapter_type) {
+		return device_for_each_child(dev, child,
+					     usb49xx_check_dev_children);
+	}
+
+	return (dev == child);
+}
+
+static int usb49xx_check_gpio_chip(struct usb49xx *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 = usb49xx_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 usb49xx_check_gpio_chip(struct usb49xx *hub)
+{
+	return 0;
+}
+#endif
+
+static void usb49xx_reset(struct usb49xx *hub, int state)
+{
+	if (state) {
+		/* During rising edge of reset, there must be no traffic on the i2c bus */
+		i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+		gpiod_set_value_cansleep(hub->gpio_reset, 1);
+		/* Wait for i2c logic to come up (hub seems to require ~10 ms)*/
+		msleep(20);
+		i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+	} else {
+		gpiod_set_value_cansleep(hub->gpio_reset, 0);
+		/* minimum reset pulse width is 5 us */
+		usleep_range(5, 10);
+	}
+}
+
+struct usb49xx_string_desc {
+	u8 length;   /* length of string in bytes + 2 */
+	u8 type;     /* always 0x03 */
+	wchar_t string[USB49XX_STRING_BUFSIZE/sizeof(wchar_t)];
+};
+
+static void usb49xx_get_ports_field(struct usb49xx *hub,
+				    const char *prop_name, u8 port_cnt,
+				    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 >= 1) && (port <= port_cnt))
+			*fld |= BIT(port);
+		else
+			dev_warn(dev, "port %u doesn't exist\n", port);
+	}
+}
+
+static int usb49xx_connect(struct usb49xx *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+	int err;
+	char attach_buffer[3];
+
+	usb49xx_reset(hub, 1);
+
+	if (hub->skip_config) {
+		dev_info(dev, "Skip hub configuration, only attach.\n");
+	} else {
+		u16 vendor_id, product_id, device_id, lang_id;
+		u8 hub_cfg[3];
+		u32 property_u32;
+		char const *manufacturer;
+		char const *product;
+		u8 non_rem_dev = 0;
+		u8 port_disable_sp = 0;
+		u8 port_disable_bp = 0;
+
+		if (!of_property_read_u16_array(np, "vendor-id", &vendor_id, 1)) {
+			char buffer[] = { vendor_id >> 8, vendor_id };
+
+			err = regmap_bulk_write(hub->regmap, USB49XX_ADDR_VENDOR_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting vendor id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "product-id", &product_id, 1)) {
+			char buffer[] = { product_id >> 8, product_id };
+
+			err = regmap_bulk_write(hub->regmap, USB49XX_ADDR_PRODUCT_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting product id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "device-id", &device_id, 1)) {
+			char buffer[] = { device_id >> 8, device_id };
+
+			err = regmap_bulk_write(hub->regmap, USB49XX_ADDR_DEVICE_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting device id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		err = regmap_bulk_read(hub->regmap, USB49XX_ADDR_HUB_CFG,
+				hub_cfg, ARRAY_SIZE(hub_cfg));
+		if (err) {
+			dev_err(dev, "Reading hub cfg failed: %d\n", err);
+			return err;
+		}
+
+		if (of_get_property(np, "self-powered", NULL)) {
+			hub_cfg[0] |= BIT(7);
+
+			/* Configure Over-Current sens when self-powered */
+			hub_cfg[0] &= ~BIT(2);
+			if (of_get_property(np, "ganged-sensing", NULL))
+				hub_cfg[0] &= ~BIT(1);
+			else if (of_get_property(np, "individual-sensing", NULL))
+				hub_cfg[0] |= BIT(1);
+		} else if (of_get_property(np, "bus-powered", NULL)) {
+			hub_cfg[0] &= ~BIT(7);
+
+			/* Disable Over-Current sense when bus-powered */
+			hub_cfg[0] |= BIT(2);
+		}
+
+		if (of_get_property(np, "disable-hi-speed", NULL))
+			hub_cfg[0] |= BIT(5);
+
+		if (of_get_property(np, "multi-tt", NULL))
+			hub_cfg[0] |= BIT(4);
+		else if (of_get_property(np, "single-tt", NULL))
+			hub_cfg[0] &= ~BIT(4);
+
+		if (of_get_property(np, "disable-eop", NULL))
+			hub_cfg[0] |= BIT(3);
+
+		if (of_get_property(np, "individual-port-switching", NULL))
+			hub_cfg[0] |= BIT(0);
+		else if (of_get_property(np, "ganged-port-switching", NULL))
+			hub_cfg[0] &= ~BIT(0);
+
+		if (of_get_property(np, "dynamic-power-switching", NULL))
+			hub_cfg[1] |= BIT(7);
+
+		if (!of_property_read_u32(np, "oc-delay-ns", &property_u32)) {
+			if (property_u32 == 50) {
+				/* 50 ns*/
+				hub_cfg[1] &= ~BIT(5);
+				hub_cfg[1] &= ~BIT(4);
+			} else if (property_u32 == 100) {
+				/* 100 ns */
+				hub_cfg[1] &= ~BIT(5);
+				hub_cfg[1] |= BIT(4);
+			} else if (property_u32 == 200) {
+				/* 400 ns */
+				hub_cfg[1] |= BIT(5);
+				hub_cfg[1] |= BIT(4);
+			} else {
+				/* 200 ns (DEFAULT) */
+				hub_cfg[1] |= BIT(5);
+				hub_cfg[1] &= ~BIT(4);
+			}
+		}
+
+		if (of_get_property(np, "compound-device", NULL))
+			hub_cfg[1] |= BIT(3);
+
+		if (of_get_property(np, "port-mapping-mode", NULL))
+			hub_cfg[2] |= USB49XX_HUB_CFG3_PRTMAP_EN;
+
+		/* port mapping requires that Link Power Management is disabled */
+		if (hub_cfg[2] & USB49XX_HUB_CFG3_PRTMAP_EN) {
+			unsigned int usb2_hub_ctrl;
+
+			/* Disable Link Power Management */
+			err = regmap_read(hub->regmap, USB49XX_ADDR_USB2_HUB_CTRL,
+					&usb2_hub_ctrl);
+			if (err) {
+				dev_err(dev, "Reading USB2 Hub Control failed: %d\n", err);
+				return err;
+			}
+
+			usb2_hub_ctrl |= USB49XX_USB2_HUB_CTRL_LPM_DISABLE;
+
+			err = regmap_write(hub->regmap, USB49XX_ADDR_USB2_HUB_CTRL,
+					usb2_hub_ctrl);
+			if (err) {
+				dev_err(dev, "Writing USB2 Hub Control failed: %d\n", err);
+				return err;
+			}
+		}
+
+		manufacturer = of_get_property(np, "manufacturer", NULL);
+		product = of_get_property(np, "product", NULL);
+
+		if (manufacturer || product)
+			hub_cfg[2] |= USB49XX_HUB_CFG3_STRING_EN;
+
+		err = regmap_bulk_write(hub->regmap, USB49XX_ADDR_HUB_CFG,
+				hub_cfg, ARRAY_SIZE(hub_cfg));
+		if (err) {
+			dev_err(dev, "Setting hub cfg failed: %d\n", err);
+			return err;
+		}
+
+		usb49xx_get_ports_field(hub, "non-removable-ports", 5, &non_rem_dev);
+		err = regmap_write(hub->regmap, USB49XX_ADDR_NON_REMOVABLE_DEVICES,
+				non_rem_dev);
+		if (err) {
+			dev_err(dev, "Setting non removable devices failed: %d\n", err);
+			return err;
+		}
+
+		usb49xx_get_ports_field(hub, "sp-disabled-ports", 7, &port_disable_sp);
+		err = regmap_write(hub->regmap, USB49XX_ADDR_PORT_DISABLE_SELF,
+				port_disable_sp);
+		if (err) {
+			dev_err(dev, "Setting port disable self powered failed: %d\n", err);
+			return err;
+		}
+
+		usb49xx_get_ports_field(hub, "bp-disabled-ports", 7, &port_disable_bp);
+		err = regmap_write(hub->regmap, USB49XX_ADDR_PORT_DISABLE_BUS,
+				port_disable_bp);
+		if (err) {
+			dev_err(dev, "Setting port disable powered powered failed: %d\n", err);
+			return err;
+		}
+
+		if (!of_property_read_u32(np, "power-on-time-ms", &property_u32)) {
+			u8 power_on_time = min_t(u8, property_u32 / 2, 255);
+
+			err = regmap_write(hub->regmap, USB49XX_ADDR_POWER_ON_TIME,
+					power_on_time);
+			if (err) {
+				dev_err(dev, "Setting power on time failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u32(np, "ocs-min-width-ms", &property_u32)) {
+			u8 ocs_min_width = min_t(u8, property_u32, 5);
+
+			err = regmap_write(hub->regmap, USB49XX_ADDR_OCS_MIN_WIDTH,
+					ocs_min_width);
+			if (err) {
+				dev_err(dev, "Setting ocs min width failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (!of_property_read_u16_array(np, "language-id", &lang_id, 1)) {
+			char buffer[] = { lang_id >> 8, lang_id };
+
+			err = regmap_bulk_write(hub->regmap, USB49XX_ADDR_LANGUAGE_ID,
+					buffer, ARRAY_SIZE(buffer));
+			if (err) {
+				dev_err(dev, "Setting language id failed: %d\n", err);
+				return err;
+			}
+		}
+
+		if (manufacturer) {
+			struct usb49xx_string_desc manufacturer_desc;
+
+			memset(&manufacturer_desc, 0, sizeof(manufacturer_desc));
+			manufacturer_desc.type = 0x3;
+			manufacturer_desc.length =
+					utf8s_to_utf16s(manufacturer, strlen(manufacturer),
+					UTF16_LITTLE_ENDIAN,
+					manufacturer_desc.string, USB49XX_STRING_BUFSIZE)
+					* sizeof(wchar_t) + 2;
+
+			err = regmap_bulk_write(hub->regmap,
+					USB49XX_ADDR_MANUFACTURER_STRING_DESC,
+					&manufacturer_desc, sizeof(manufacturer_desc));
+			if (err) {
+				dev_err(dev, "Setting manufacturer string descriptor failed: %d\n",
+						err);
+				return err;
+			}
+
+			err = regmap_write(hub->regmap, USB49XX_ADDR_MANUFACTURER_STRING_LEN,
+					manufacturer_desc.length);
+			if (err) {
+				dev_err(dev, "Setting manufacturer string length failed: %d\n",
+						err);
+				return err;
+			}
+		}
+
+		if (product) {
+			struct usb49xx_string_desc product_desc;
+
+			memset(&product_desc, 0, sizeof(product_desc));
+			product_desc.type = 0x3;
+			product_desc.length = utf8s_to_utf16s(product, strlen(product),
+					UTF16_LITTLE_ENDIAN,
+					product_desc.string, USB49XX_STRING_BUFSIZE)
+					* sizeof(wchar_t) + 2;
+
+			err = regmap_bulk_write(hub->regmap,
+					USB49XX_ADDR_PRODUCT_STRING_DESC,
+					&product_desc, sizeof(product_desc));
+			if (err) {
+				dev_err(dev, "Setting product string descriptor failed: %d\n", err);
+				return err;
+			}
+
+			err = regmap_write(hub->regmap, USB49XX_ADDR_PRODUCT_STRING_LEN,
+					product_desc.length);
+			if (err) {
+				dev_err(dev, "SSetting product string length failed: %d\n", err);
+				return err;
+			}
+		}
+	}
+
+#ifdef DEBUG
+	{
+		char *buffer = kzalloc(0x5000, GFP_KERNEL);
+
+		err = regmap_bulk_read(hub->regmap, 0xBF800000, buffer, 0x4);
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buffer, 0x4);
+		err = regmap_bulk_read(hub->regmap, 0xBF803000, buffer, 0x200);
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buffer, 0x200);
+		err = regmap_bulk_read(hub->regmap, 0xBFD23200, buffer, 0x200);
+		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buffer, 0x200);
+		kfree(buffer);
+	}
+#endif
+
+	/*
+	 *  attach
+	 */
+	/* n.b. Could also use 0xAA56 (attach with SMBus runtime access) */
+	attach_buffer[0] = 0xAA;   /* USB attach (high byte) */
+	attach_buffer[1] = 0x56;   /* USB attach (low byte) */
+	attach_buffer[2] = 0x00;   /* terminator */
+
+	err = i2c_master_send(hub->i2c, attach_buffer, 3);
+	if (err != 3) {
+		dev_err(dev, "attaching hub failed: %d\n", err);
+		return err;
+	}
+
+	dev_info(dev, "Hub configuration was successful.\n");
+	return 0;
+
+}
+
+#ifdef CONFIG_OF
+static int usb49xx_get_ofdata(struct usb49xx *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+
+	if (!np) {
+		dev_err(dev, "failed to get ofdata\n");
+		return -ENODEV;
+	}
+
+	if (of_get_property(np, "skip-config", NULL))
+		hub->skip_config = 1;
+	else
+		hub->skip_config = 0;
+
+	hub->gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(hub->gpio_reset))
+		return PTR_ERR(hub->gpio_reset);
+
+	return 0;
+}
+
+static const struct of_device_id usb49xx_of_match[] = {
+	{ .compatible = "microchip,usb4712",  },
+	{ .compatible = "microchip,usb4712i", },
+	{ .compatible = "microchip,usb4715",  },
+	{ .compatible = "microchip,usb4715i", },
+	{ .compatible = "microchip,usb4912",  },
+	{ .compatible = "microchip,usb4912i", },
+	{ .compatible = "microchip,usb4914",  },
+	{ .compatible = "microchip,usb4914i", },
+	{ .compatible = "microchip,usb4916",  },
+	{ .compatible = "microchip,usb4916i", },
+	{ .compatible = "microchip,usb4925",  },
+	{ .compatible = "microchip,usb4925i", },
+	{ .compatible = "microchip,usb4927",  },
+	{ .compatible = "microchip,usb4927i", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, usb49xx_of_match);
+#else /* CONFIG_OF */
+static int usb49xx_get_ofdata(struct usb49xx *hub)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+static void usb49xx_regulator_disable_action(void *data)
+{
+	struct usb49xx *hub = data;
+
+	regulator_disable(hub->vdd);
+}
+
+static int usb49xx_probe(struct usb49xx *hub)
+{
+	struct device *dev = hub->dev;
+	struct device_node *np = dev->of_node;
+	int err;
+
+	if (np) {
+		err = usb49xx_get_ofdata(hub);
+		if (err) {
+			dev_err(dev, "failed to get ofdata: %d\n", err);
+			return err;
+		}
+	}
+
+	/*
+	 * The usb49xx performs a check on the SMBus during the SMBUS_CHECK state.
+	 * In order to successful, SCL and SDA must be stable at this moment.
+	 * 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 = usb49xx_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,
+				       usb49xx_regulator_disable_action, hub);
+	if (err)
+		return err;
+
+	err = usb49xx_connect(hub);
+	if (err) {
+		dev_err(dev, "Failed to connect hub (%d)\n", err);
+		return err;
+	}
+
+	dev_info(dev, "Hub probed successfully\n");
+
+	return 0;
+}
+
+static int usb49xx_i2c_gather_write(void *context,
+		const void *reg, size_t reg_len,
+		const void *val, size_t val_len)
+{
+	struct i2c_client *i2c = (struct i2c_client *)context;
+	size_t frame_len = val_len + 9;
+	char *buffer;
+	int err;
+
+	if (WARN_ON(reg_len != 4))
+		return -EINVAL;
+
+	buffer = kmalloc(frame_len, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	buffer[0] = 0x00;                 /* Memory address (high byte) */
+	buffer[1] = 0x00;                 /* Memory address (low byte) */
+	buffer[2] = val_len + 6;          /* Number of bytes to write to memory */
+	buffer[3] = 0x00;                 /* Register Write */
+	buffer[4] = val_len;              /* Write val_len data bytes */
+	buffer[5] = *((const char *)reg + 0);   /* Register address (MSB) */
+	buffer[6] = *((const char *)reg + 1);   /* Register address */
+	buffer[7] = *((const char *)reg + 2);   /* Register address */
+	buffer[8] = *((const char *)reg + 3);   /* Register address (LSB) */
+	memcpy(&buffer[9], val, val_len);
+
+	err = i2c_master_send(i2c, buffer, frame_len);
+	if (err != (int)frame_len) {
+		dev_err(&i2c->dev, "Writing to hub memory failed: %d\n", err);
+		goto error_kfree;
+	}
+
+	buffer[0] = 0x99;   /* Command (high byte) */
+	buffer[1] = 0x37;   /* Command (low byte) */
+	buffer[2] = 0x00;   /* Termination */
+	frame_len = 3;
+
+	err = i2c_master_send(i2c, buffer, frame_len);
+	if (err != (int)frame_len) {
+		dev_err(&i2c->dev, "Accessing configuration data failed: %d\n", err);
+		goto error_kfree;
+	}
+
+	err = 0;
+
+error_kfree:
+	kfree(buffer);
+	return err;
+}
+
+/* regmap puts address into data[0] .. data[3]; value is in data[4] */
+static int usb49xx_i2c_write(void *context, const void *data, size_t count)
+{
+	if (WARN_ON(count != 5))
+		return -EINVAL;
+
+	return usb49xx_i2c_gather_write(context, data, 4, data + 4, 1);
+}
+
+static int usb49xx_i2c_read(void *context, const void *reg_buf, size_t reg_size,
+		void *val_buf, size_t val_size)
+{
+	struct i2c_client *i2c = (struct i2c_client *)context;
+	char *tx, *rx;
+	int err;
+
+	if (WARN_ON(reg_size != 4)) {
+		err =  -EINVAL;
+		goto error_return;
+	}
+
+	tx = kmalloc(9, GFP_KERNEL);
+	if (!tx) {
+		err = -ENOMEM;
+		goto error_return;
+	}
+
+	rx = kmalloc(1 + val_size, GFP_KERNEL);
+	if (!rx) {
+		err = -ENOMEM;
+		goto error_free_tx;
+	}
+
+	tx[0] = 0x00;                 /* Memory address (high byte) */
+	tx[1] = 0x00;                 /* Memory address (low byte) */
+	tx[2] = 6;                    /* Number of bytes to write to memory */
+	tx[3] = 0x01;                 /* Register Read */
+	tx[4] = val_size;             /* Read val_size data bytes */
+	tx[5] = *((const char *)reg_buf + 0);   /* Register address (MSB) */
+	tx[6] = *((const char *)reg_buf + 1);   /* Register address */
+	tx[7] = *((const char *)reg_buf + 2);   /* Register address */
+	tx[8] = *((const char *)reg_buf + 3);   /* Register address (LSB) */
+
+	err = i2c_master_send(i2c, tx, 9);
+	if (err != 9) {
+		dev_err(&i2c->dev, "Writing to hub memory failed: %d\n", err);
+		goto error_free_rx;
+	}
+
+	tx[0] = 0x99;   /* Command (high byte) */
+	tx[1] = 0x37;   /* Command (low byte) */
+	tx[2] = 0x00;   /* Termination */
+
+	err = i2c_master_send(i2c, tx, 3);
+	if (err != 3) {
+		dev_err(&i2c->dev, "Accessing configuration data failed: %d\n", err);
+		goto error_free_rx;
+	}
+
+	/* Read from hub memory address 6 (according to application note) */
+	tx[0] = 0x00;    /* Memory address (high byte) */
+	tx[1] = 0x06;    /* Memory address (low byte) */
+
+	{
+		struct i2c_msg msgs[] = {
+			{
+				.addr = i2c->addr, .flags = 0,
+				.len  =         2, .buf = tx
+			},
+			{
+				.addr = i2c->addr, .flags = I2C_M_RD,
+				.len = 1 + val_size, .buf = rx
+			},
+		};
+
+		err = i2c_transfer(i2c->adapter, msgs, ARRAY_SIZE(msgs));
+		if (err < 0) {
+			dev_err(&i2c->dev, "Reading from hub memory failed: %d\n", err);
+			goto error_free_rx;
+		}
+	}
+
+	WARN_ON(rx[0] < val_size);
+
+	memcpy(val_buf, &rx[1], val_size);
+
+	err = 0;
+
+error_free_rx:
+	kfree(rx);
+error_free_tx:
+	kfree(tx);
+error_return:
+	return err;
+}
+
+/*
+ * The reason we need this custom regmap_bus instead of using regmap_init_i2c()
+ * is that accesses to the USB49xx configuration memory have to be wrapped into
+ * a special protocol.
+ */
+static const struct regmap_bus usb49xx_i2c_regmap = {
+	.write         = usb49xx_i2c_write,
+	.gather_write  = usb49xx_i2c_gather_write,
+	.read          = usb49xx_i2c_read,
+	.max_raw_read  = 8,
+	.max_raw_write = 128,
+};
+
+/* Avoid reading bigger dead ranges inside hubs memory. Same table for
+ * read and write.
+ */
+static const struct regmap_range usb49xx_regmap_yes_ranges[] = {
+	{ .range_min = 0xBF800000, .range_max = 0xBF800003 },
+	{ .range_min = 0xBF800900, .range_max = 0xBF8009FF },
+	{ .range_min = 0xBF803000, .range_max = 0xBF803015 },
+	{ .range_min = 0xBF8030D0, .range_max = 0xBF803197 },
+	{ .range_min = 0xBF80398C, .range_max = 0xBF80398D },
+	{ .range_min = 0xBF803C00, .range_max = 0xBF803C3C },
+	{ .range_min = 0xBF8060CA, .range_max = 0xBF8060CC },
+	{ .range_min = 0xBF8064CA, .range_max = 0xBF8064CC },
+	{ .range_min = 0xBF8068CA, .range_max = 0xBF8068CC },
+	{ .range_min = 0xBF806CCA, .range_max = 0xBF806CCC },
+	{ .range_min = 0xBF8070CA, .range_max = 0xBF8070CC },
+	{ .range_min = 0xBF8074CA, .range_max = 0xBF8074CC },
+	{ .range_min = 0xBF8078CA, .range_max = 0xBF8078CC },
+
+	{ .range_min = 0xBFD22856, .range_max = 0xBFD22857 },
+	{ .range_min = 0xBFD23202, .range_max = 0xBFD23472 },
+};
+
+static const struct regmap_access_table usb49xx_regmap_access_table = {
+	.yes_ranges   = usb49xx_regmap_yes_ranges,
+	.n_yes_ranges = ARRAY_SIZE(usb49xx_regmap_yes_ranges),
+};
+
+static int usb49xx_i2c_probe(struct i2c_client *i2c,
+			      const struct i2c_device_id *id __always_unused)
+{
+	struct usb49xx *hub;
+	struct regmap_config regmap_cfg = {
+		.name            = "usb49xx_config_mem",
+		.reg_bits        = 32,
+		.val_bits        = 8,
+		.wr_table        = &usb49xx_regmap_access_table,
+		.rd_table        = &usb49xx_regmap_access_table,
+		.can_multi_write = 1,
+	};
+
+	hub = devm_kzalloc(&i2c->dev, sizeof(struct usb49xx), GFP_KERNEL);
+	if (!hub)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, hub);
+	hub->dev = &i2c->dev;
+	hub->i2c = i2c;
+	hub->regmap = devm_regmap_init(&i2c->dev, &usb49xx_i2c_regmap, i2c,
+			&regmap_cfg);
+	if (IS_ERR(hub->regmap))
+		return PTR_ERR(hub->regmap);
+
+	return usb49xx_probe(hub);
+}
+
+static int usb49xx_i2c_remove(struct i2c_client *i2c)
+{
+	struct usb49xx *hub = i2c_get_clientdata(i2c);
+
+	usb49xx_reset(hub, 0);
+
+	return 0;
+};
+
+static int __maybe_unused usb49xx_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct usb49xx *hub = i2c_get_clientdata(client);
+
+	return regulator_disable(hub->vdd);
+}
+
+static int __maybe_unused usb49xx_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct usb49xx *hub = i2c_get_clientdata(client);
+	int err;
+
+	err = regulator_enable(hub->vdd);
+	if (err)
+		return err;
+
+	return usb49xx_connect(hub);
+}
+
+static SIMPLE_DEV_PM_OPS(usb49xx_pm_ops, usb49xx_suspend, usb49xx_resume);
+
+static const struct i2c_device_id usb49xx_id[] = {
+	{ "usb4712", 0 },
+	{ "usb4712i", 0 },
+	{ "usb4715", 0 },
+	{ "usb4715i", 0 },
+	{ "usb4912", 0 },
+	{ "usb4912i", 0 },
+	{ "usb4914", 0 },
+	{ "usb4914i", 0 },
+	{ "usb4916", 0 },
+	{ "usb4916	i", 0 },
+	{ "usb4925", 0 },
+	{ "usb4925i", 0 },
+	{ "usb4927", 0 },
+	{ "usb4927i", 0 },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, usb49xx_id);
+
+static struct i2c_driver usb49xx_i2c_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.of_match_table = of_match_ptr(usb49xx_of_match),
+		.pm = &usb49xx_pm_ops,
+	},
+	.probe    = usb49xx_i2c_probe,
+	.remove   = usb49xx_i2c_remove,
+	.id_table = usb49xx_id,
+};
+
+module_i2c_driver(usb49xx_i2c_driver);
+
+MODULE_AUTHOR("Christian Eggers <ceggers@arri.de>");
+MODULE_DESCRIPTION("USB47xx/USB49xx USB 2.0 Hub Controller Driver");
+MODULE_LICENSE("GPL");
-- 
Christian Eggers
Embedded software developer

Arnold & Richter Cine Technik GmbH & Co. Betriebs KG
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRA 57918
Persoenlich haftender Gesellschafter: Arnold & Richter Cine Technik GmbH
Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRB 54477
Geschaeftsfuehrer: Dr. Michael Neuhaeuser; Stephan Schenk; Walter Trauninger; Markus Zeiler


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v3 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support
  2020-07-27  8:33           ` [PATCH v3 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support Christian Eggers
@ 2020-07-31 20:38             ` Rob Herring
  0 siblings, 0 replies; 16+ messages in thread
From: Rob Herring @ 2020-07-31 20:38 UTC (permalink / raw)
  To: Christian Eggers
  Cc: Greg Kroah-Hartman, Richard Leitner, devicetree, linux-usb, linux-kernel

On Mon, Jul 27, 2020 at 10:33:30AM +0200, Christian Eggers wrote:
> Add DT bindings for Microchip USB253x/USB3x13/USB46x4 driver.

Looks like a lot copied from usb251xb.txt. Many of these properties need 
to be defined in a common schema. That could be either common to USB 
devices, hubs, or Microchip hubs or some combination of those. 

> 
> Signed-off-by: Christian Eggers <ceggers@arri.de>
> ---
>  .../devicetree/bindings/usb/usb253x.yaml      | 234 ++++++++++++++++++
>  1 file changed, 234 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/usb/usb253x.yaml
> 
> diff --git a/Documentation/devicetree/bindings/usb/usb253x.yaml b/Documentation/devicetree/bindings/usb/usb253x.yaml
> new file mode 100644
> index 000000000000..88ea744147b6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/usb/usb253x.yaml
> @@ -0,0 +1,234 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/usb/usb253x.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Microchip USB253x/USB3x13/USB46x4 USB 2.0 Hi-Speed Hub Controller
> +
> +maintainers:
> +  - Christian Eggers <ceggers@arri.de>
> +
> +description: |
> +  http://ww1.microchip.com/downloads/en/AppNotes/00001801C.pdf
> +
> +properties:
> +  compatible:
> +    enum:
> +      - microchip,usb2532
> +      - microchip,usb2532i
> +      - microchip,usb2533
> +      - microchip,usb2533i
> +      - microchip,usb2534
> +      - microchip,usb2534i
> +      - microchip,usb3613
> +      - microchip,usb3613i
> +      - microchip,usb3813
> +      - microchip,usb3813i
> +      - microchip,usb4604
> +      - microchip,usb4604i
> +      - microchip,usb4624
> +      - microchip,usb4624i
> +
> +  reg:
> +    maxItems: 1
> +    description:
> +      I2C address on the selected bus (usually <0x2D>).
> +
> +  reset-gpios:
> +    maxItems: 1
> +    description:
> +      Specify the gpio for hub reset.
> +
> +  vdd-supply:
> +    $ref: /schemas/types.yaml#/definitions/phandle
> +    description:
> +      Specify the regulator supplying vdd.
> +
> +  skip-config:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Skip Hub configuration, but only send the USB-Attach command.
> +
> +  vendor-id:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maximum: 65535
> +    description:
> +      Set USB Vendor ID of the hub.
> +
> +  product-id:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maximum: 65535
> +    description:
> +      Set USB Product ID of the hub.
> +
> +  device-id:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maximum: 65535
> +    description:
> +      Set USB Device ID of the hub.
> +
> +  language-id:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maximum: 65535
> +    description:
> +      Set USB Language ID.
> +
> +  manufacturer:
> +    $ref: /schemas/types.yaml#/definitions/string
> +    description:
> +      Set USB Manufacturer string (max. a total of 93 characters for
> +      manufacturer, product and serial).
> +
> +  product:
> +    $ref: /schemas/types.yaml#/definitions/string
> +    description:
> +      Set USB Product string (max. a total of 93 characters for
> +      manufacturer, product and serial).
> +
> +  serial:
> +    $ref: /schemas/types.yaml#/definitions/string
> +    description:
> +      Set USB Serial string (max. a total of 93 characters for
> +      manufacturer, product and serial).
> +
> +  bus-powered:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Selects bus powered operation.
> +
> +  self-powered:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Selects self powered operation (default).
> +
> +  disable-hi-speed:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Disable USB Hi-Speed support.
> +
> +  multi-tt:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Selects multi-transaction-translator (default).
> +
> +  single-tt:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Selects single-transaction-translator.
> +
> +  disable-eop:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Disable End of Packet generation in full-speed mode.
> +
> +  ganged-sensing:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Select ganged over-current sense type in self-powered mode.
> +
> +  individual-sensing:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Select individual over-current sense type in self-powered mode (default).
> +
> +  ganged-port-switching:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Select ganged port power switching mode.
> +
> +  individual-port-switching:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Select individual port power switching mode (default).
> +
> +  dynamic-power-switching:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Enable auto-switching from self- to bus-powered operation if the local
> +      power source is removed or unavailable.
> +
> +  oc-delay-us:
> +    enum:
> +      - 100
> +      - 4000
> +      - 8000
> +      - 16000
> +    default: 8000
> +    description:
> +      Delay time (in microseconds) for filtering the over-current sense inputs.
> +      If an invalid value is given, the default is used instead.
> +
> +  compound-device:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Indicate the hub is part of a compound device.
> +
> +  port-mapping-mode:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Enable port mapping mode.
> +
> +  non-removable-ports:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description:
> +      Should specify the ports which have a non-removable device connected.
> +
> +  sp-disabled-ports:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description:
> +      Specifies the ports which will be self-power disabled.
> +
> +  bp-disabled-ports:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description:
> +      Specifies the ports which will be bus-power disabled.
> +
> +  power-on-time-ms:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    default: 100
> +    minimum: 0
> +    maximum: 510
> +    description:
> +      Specifies the time (in milliseconds) it takes from the time the host
> +      initiates the power-on sequence to a port until the port has adequate
> +      power.
> +
> +  hub-controller-port:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description:
> +      Hub port where the internal hub controller shall be connected. Usually
> +      <number of ports>+1.
> +
> +additionalProperties: false
> +
> +required:
> +  - compatible
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    i2c0 {
> +      #address-cells = <1>;
> +      #size-cells = <0>;
> +
> +      usb2534i@2d {
> +        compatible = "microchip,usb2534i";
> +        reg = <0x2d>;
> +        pinctrl-names = "default";
> +        pinctrl-0 = <&pinctrl_usb_hub>;
> +        /* usb253x.c already assumes low-active, don't negate twice */
> +        reset-gpios = <&gpio3 19 GPIO_ACTIVE_HIGH>;
> +        /*skip-config;*/
> +        /* T_ON,max = 4 ms for NCP380 */
> +        power-on-time-ms = <4>;
> +        manufacturer = "Foo";
> +        product = "Foo-Bar";
> +        /* port 2 is connected to an internal SD-Card reader */
> +        non-removable-ports = <2>;
> +        /* hub controller mapped to logical port 5 */
> +        hub-controller-port = <5>;
> +      };
> +    };
> +
> +...
> -- 
> Christian Eggers
> Embedded software developer
> 
> Arnold & Richter Cine Technik GmbH & Co. Betriebs KG
> Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRA 57918
> Persoenlich haftender Gesellschafter: Arnold & Richter Cine Technik GmbH
> Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRB 54477
> Geschaeftsfuehrer: Dr. Michael Neuhaeuser; Stephan Schenk; Walter Trauninger; Markus Zeiler
> 

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v3] Add two new configuration drivers for Microchip USB hubs
  2020-07-27  8:33         ` [PATCH v3] Add two new configuration drivers for Microchip USB hubs Christian Eggers
                             ` (3 preceding siblings ...)
  2020-07-27  8:33           ` [PATCH v3 4/4] usb: misc: Add USB49xx/xi Hi-Speed Hub Controller Driver Christian Eggers
@ 2020-08-17  7:54           ` Richard Leitner
  2020-08-24 10:31             ` Christian Eggers
  4 siblings, 1 reply; 16+ messages in thread
From: Richard Leitner @ 2020-08-17  7:54 UTC (permalink / raw)
  To: Christian Eggers
  Cc: Greg Kroah-Hartman, Rob Hering, Rob Herring, devicetree,
	linux-usb, linux-kernel

On Mon, Jul 27, 2020 at 10:33:29AM +0200, Christian Eggers wrote:
> On Sonday, greg k-h wrote:
> > Please resend the whole series, not just a single patch, as it makes it
> > very difficult to pick the "correct" patches to be applied...

Hi Christian,
sorry for the late reply. My MUA somehow didn't show me that series
earlier...

I haven't looked into the patches in detail, but at a first glance it
looks like a lot copy-n-paste.
Have you thought about merging the (after your series) 3 hub drivers
into one? Something like a "microchip i2c usb hub driver"?
Would that be feasible for your point of view?

regards;rl

> 
> Changes in v3:
>  - none (only resend the whole series)
> 
> Changes in v2:
>  - added property description for ocs-min-width-ms
>  - fixed property description for oc-delay-ns
> 

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v3] Add two new configuration drivers for Microchip USB hubs
  2020-08-17  7:54           ` [PATCH v3] Add two new configuration drivers for Microchip USB hubs Richard Leitner
@ 2020-08-24 10:31             ` Christian Eggers
  0 siblings, 0 replies; 16+ messages in thread
From: Christian Eggers @ 2020-08-24 10:31 UTC (permalink / raw)
  To: Richard Leitner
  Cc: Greg Kroah-Hartman, Rob Herring, Rob Herring, devicetree,
	linux-usb, linux-kernel

Hi Richard,

On Monday, 17 August 2020, 09:54:26 CEST, Richard Leitner wrote:
> Hi Christian,
> sorry for the late reply. My MUA somehow didn't show me that series
> earlier...
likewise... I was on holiday last week.

> I haven't looked into the patches in detail, but at a first glance it
> looks like a lot copy-n-paste.
> Have you thought about merging the (after your series) 3 hub drivers
> into one? Something like a "microchip i2c usb hub driver"?
> Would that be feasible for your point of view?
I'm not sure about the criteria for having separate drivers vs. a combined 
one.
As changing the driver usually requires testing with real hardware, keeping 
them separate may be easier. On the other hand, I already synced some changes 
from usb251xb into "my" drivers, so it is likely that such work will also have 
to done in future.

Rob Herring already suggested to create a common yaml document for the
device tree bindings. It would probably make sense to share the device tree 
code between our drivers. But I would like to keep the "hardware" side of the 
drivers independent, as there are subtle differences between the different hub 
series.

Compared to usb251x, the new drivers don't write the full configuration memory 
of the hub (the configuration memory space is much bigger than 8 bit and has 
many holes). They rely on the defaults after hardware reset on perform read-
modify-write for the properties set in the device tree.

If the usb251xb hubs could be programmed in a similar way, merging all drivers 
should be possible using a mic_usb_hub_ops struct with function pointers for 
applying the (common) device tree properties (e.g. set_product_id(), 
set_device_id(), ..., set_oc_delay(), set_manufacturer_string(), ...).

Best regards
Christian





^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2020-08-24 10:32 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-07-22 18:38 [PATCH] Add two new configuration drivers for Microchip USB hubs Christian Eggers
2020-07-22 18:38 ` [PATCH 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support Christian Eggers
2020-07-22 18:38 ` [PATCH 2/4] usb: misc: Add USB253x/xi Hi-Speed Hub Controller Driver Christian Eggers
2020-07-22 18:38 ` [PATCH 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support Christian Eggers
2020-07-23 15:35   ` Rob Herring
2020-07-23 19:29     ` [PATCH v2 " Christian Eggers
2020-07-26  8:41       ` Greg Kroah-Hartman
2020-07-27  8:33         ` [PATCH v3] Add two new configuration drivers for Microchip USB hubs Christian Eggers
2020-07-27  8:33           ` [PATCH v3 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support Christian Eggers
2020-07-31 20:38             ` Rob Herring
2020-07-27  8:33           ` [PATCH v3 2/4] usb: misc: Add USB253x/xi Hi-Speed Hub Controller Driver Christian Eggers
2020-07-27  8:33           ` [PATCH v3 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support Christian Eggers
2020-07-27  8:33           ` [PATCH v3 4/4] usb: misc: Add USB49xx/xi Hi-Speed Hub Controller Driver Christian Eggers
2020-08-17  7:54           ` [PATCH v3] Add two new configuration drivers for Microchip USB hubs Richard Leitner
2020-08-24 10:31             ` Christian Eggers
2020-07-22 18:38 ` [PATCH 4/4] usb: misc: Add USB49xx/xi Hi-Speed Hub Controller Driver Christian Eggers

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).