From patchwork Wed Jul 22 18:38:56 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Eggers X-Patchwork-Id: 11679149 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 50506913 for ; Wed, 22 Jul 2020 19:12:33 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3A1512065D for ; Wed, 22 Jul 2020 19:12:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726888AbgGVTMa (ORCPT ); Wed, 22 Jul 2020 15:12:30 -0400 Received: from mailout10.rmx.de ([94.199.88.75]:34384 "EHLO mailout10.rmx.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726322AbgGVTM3 (ORCPT ); Wed, 22 Jul 2020 15:12:29 -0400 X-Greylist: delayed 1944 seconds by postgrey-1.27 at vger.kernel.org; Wed, 22 Jul 2020 15:12:28 EDT Received: from kdin02.retarus.com (kdin02.dmz1.retloc [172.19.17.49]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mailout10.rmx.de (Postfix) with ESMTPS id 4BBkm14tvhz31LK; Wed, 22 Jul 2020 20:40:01 +0200 (CEST) Received: from mta.arri.de (unknown [217.111.95.66]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by kdin02.retarus.com (Postfix) with ESMTPS id 4BBklZ6bfCz2TSGr; Wed, 22 Jul 2020 20:39:38 +0200 (CEST) Received: from N95HX1G2.wgnetz.xx (192.168.54.68) by mta.arri.de (192.168.100.104) with Microsoft SMTP Server (TLS) id 14.3.408.0; Wed, 22 Jul 2020 20:39:38 +0200 From: Christian Eggers To: , CC: Greg Kroah-Hartman , Rob Herring , Richard Leitner , , Christian Eggers Subject: [PATCH 1/4] dt-bindings: usb: Add Microchip USB253x/USB3x13/USB46x4 support Date: Wed, 22 Jul 2020 20:38:56 +0200 Message-ID: <20200722183859.24470-2-ceggers@arri.de> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200722183859.24470-1-ceggers@arri.de> References: <20200722183859.24470-1-ceggers@arri.de> MIME-Version: 1.0 X-Originating-IP: [192.168.54.68] X-RMX-ID: 20200722-203938-4BBklZ6bfCz2TSGr-0@kdin02 X-RMX-SOURCE: 217.111.95.66 Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org Add DT bindings for Microchip USB253x/USB3x13/USB46x4 driver. Signed-off-by: Christian Eggers --- .../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 + +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 + +1. + +additionalProperties: false + +required: + - compatible + +examples: + - | + #include + 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>; + }; + }; + +... From patchwork Wed Jul 22 18:38:57 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Eggers X-Patchwork-Id: 11679155 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 5D918913 for ; Wed, 22 Jul 2020 19:13:42 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 336F620781 for ; Wed, 22 Jul 2020 19:13:42 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1732531AbgGVTNf (ORCPT ); Wed, 22 Jul 2020 15:13:35 -0400 Received: from mailout05.rmx.de ([94.199.90.90]:36392 "EHLO mailout05.rmx.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726462AbgGVTNe (ORCPT ); Wed, 22 Jul 2020 15:13:34 -0400 Received: from kdin01.retarus.com (kdin01.dmz1.retloc [172.19.17.48]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mailout05.rmx.de (Postfix) with ESMTPS id 4BBkmW1fdYz9xKk; Wed, 22 Jul 2020 20:40:27 +0200 (CEST) Received: from mta.arri.de (unknown [217.111.95.66]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by kdin01.retarus.com (Postfix) with ESMTPS id 4BBkm22tY4z2xFN; Wed, 22 Jul 2020 20:40:02 +0200 (CEST) Received: from N95HX1G2.wgnetz.xx (192.168.54.68) by mta.arri.de (192.168.100.104) with Microsoft SMTP Server (TLS) id 14.3.408.0; Wed, 22 Jul 2020 20:40:02 +0200 From: Christian Eggers To: , CC: Greg Kroah-Hartman , Rob Herring , Richard Leitner , , Christian Eggers Subject: [PATCH 2/4] usb: misc: Add USB253x/xi Hi-Speed Hub Controller Driver Date: Wed, 22 Jul 2020 20:38:57 +0200 Message-ID: <20200722183859.24470-3-ceggers@arri.de> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200722183859.24470-1-ceggers@arri.de> References: <20200722183859.24470-1-ceggers@arri.de> MIME-Version: 1.0 X-Originating-IP: [192.168.54.68] X-RMX-ID: 20200722-204002-4BBkm22tY4z2xFN-0@kdin01 X-RMX-SOURCE: 217.111.95.66 Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org 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 --- 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 +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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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, + ®map_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 "); +MODULE_DESCRIPTION("USB253x/xi USB 2.0 Hub Controller Driver"); +MODULE_LICENSE("GPL"); From patchwork Wed Jul 22 18:38:58 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Eggers X-Patchwork-Id: 11679151 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 0F61D13B1 for ; Wed, 22 Jul 2020 19:13:10 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id ED30B20781 for ; Wed, 22 Jul 2020 19:13:09 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1732321AbgGVTNG (ORCPT ); Wed, 22 Jul 2020 15:13:06 -0400 Received: from mailout12.rmx.de ([94.199.88.78]:36241 "EHLO mailout12.rmx.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728821AbgGVTNG (ORCPT ); Wed, 22 Jul 2020 15:13:06 -0400 Received: from kdin02.retarus.com (kdin02.dmz1.retloc [172.19.17.49]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mailout12.rmx.de (Postfix) with ESMTPS id 4BBkn24scMzRq9f; Wed, 22 Jul 2020 20:40:54 +0200 (CEST) Received: from mta.arri.de (unknown [217.111.95.66]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by kdin02.retarus.com (Postfix) with ESMTPS id 4BBkmW6hFtz2TRlg; Wed, 22 Jul 2020 20:40:27 +0200 (CEST) Received: from N95HX1G2.wgnetz.xx (192.168.54.68) by mta.arri.de (192.168.100.104) with Microsoft SMTP Server (TLS) id 14.3.408.0; Wed, 22 Jul 2020 20:40:27 +0200 From: Christian Eggers To: , CC: Greg Kroah-Hartman , Rob Herring , Richard Leitner , , Christian Eggers Subject: [PATCH 3/4] dt-bindings: usb: Add Microchip USB47xx/USB49xx support Date: Wed, 22 Jul 2020 20:38:58 +0200 Message-ID: <20200722183859.24470-4-ceggers@arri.de> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200722183859.24470-1-ceggers@arri.de> References: <20200722183859.24470-1-ceggers@arri.de> MIME-Version: 1.0 X-Originating-IP: [192.168.54.68] X-RMX-ID: 20200722-204027-4BBkmW6hFtz2TRlg-0@kdin02 X-RMX-SOURCE: 217.111.95.66 Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org Add DT bindings for Microchip USB47xx/USB49xx driver. Signed-off-by: Christian Eggers --- .../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 + +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 + +1. + +additionalProperties: false + +required: + - compatible + +examples: + - | + #include + 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>; + }; + }; + +... From patchwork Wed Jul 22 18:38:59 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Eggers X-Patchwork-Id: 11679153 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 84623913 for ; Wed, 22 Jul 2020 19:13:41 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5241F20781 for ; Wed, 22 Jul 2020 19:13:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1732635AbgGVTNg (ORCPT ); Wed, 22 Jul 2020 15:13:36 -0400 Received: from mailout08.rmx.de ([94.199.90.85]:57504 "EHLO mailout08.rmx.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728821AbgGVTNg (ORCPT ); Wed, 22 Jul 2020 15:13:36 -0400 Received: from kdin01.retarus.com (kdin01.dmz1.retloc [172.19.17.48]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mailout08.rmx.de (Postfix) with ESMTPS id 4BBknc153rzMnT5; Wed, 22 Jul 2020 20:41:24 +0200 (CEST) Received: from mta.arri.de (unknown [217.111.95.66]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by kdin01.retarus.com (Postfix) with ESMTPS id 4BBkn32Wkqz2xCK; Wed, 22 Jul 2020 20:40:55 +0200 (CEST) Received: from N95HX1G2.wgnetz.xx (192.168.54.68) by mta.arri.de (192.168.100.104) with Microsoft SMTP Server (TLS) id 14.3.408.0; Wed, 22 Jul 2020 20:40:55 +0200 From: Christian Eggers To: , CC: Greg Kroah-Hartman , Rob Herring , Richard Leitner , , Christian Eggers Subject: [PATCH 4/4] usb: misc: Add USB49xx/xi Hi-Speed Hub Controller Driver Date: Wed, 22 Jul 2020 20:38:59 +0200 Message-ID: <20200722183859.24470-5-ceggers@arri.de> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200722183859.24470-1-ceggers@arri.de> References: <20200722183859.24470-1-ceggers@arri.de> MIME-Version: 1.0 X-Originating-IP: [192.168.54.68] X-RMX-ID: 20200722-204055-4BBkn32Wkqz2xCK-0@kdin01 X-RMX-SOURCE: 217.111.95.66 Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org 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 --- 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 +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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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, + ®map_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 "); +MODULE_DESCRIPTION("USB47xx/USB49xx USB 2.0 Hub Controller Driver"); +MODULE_LICENSE("GPL");