From 1657eaf7691b7133bda04be202ef6d775ddc1f59 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 4 Mar 2016 17:19:31 +0100 Subject: [PATCH 01/19] dt-bindings: phy: Add NVIDIA Tegra XUSB pad controller binding The NVIDIA Tegra XUSB pad controller provides a set of pads, each with a set of lanes that are used for PCIe, SATA and USB. Signed-off-by: Thierry Reding Acked-by: Rob Herring Acked-by: Stephen Warren --- .../phy/nvidia,tegra124-xusb-padctl.txt | 376 ++++++++++++++++++ .../pinctrl/nvidia,tegra124-xusb-padctl.txt | 5 + 2 files changed, 381 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt diff --git a/Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt b/Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt new file mode 100644 index 00000000000000..8b642d9e343357 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt @@ -0,0 +1,376 @@ +Device tree binding for NVIDIA Tegra XUSB pad controller +======================================================== + +The Tegra XUSB pad controller manages a set of I/O lanes (with differential +signals) which connect directly to pins/pads on the SoC package. Each lane +is controlled by a HW block referred to as a "pad" in the Tegra hardware +documentation. Each such "pad" may control either one or multiple lanes, +and thus contains any logic common to all its lanes. Each lane can be +separately configured and powered up. + +Some of the lanes are high-speed lanes, which can be used for PCIe, SATA or +super-speed USB. Other lanes are for various types of low-speed, full-speed +or high-speed USB (such as UTMI, ULPI and HSIC). The XUSB pad controller +contains a software-configurable mux that sits between the I/O controller +ports (e.g. PCIe) and the lanes. + +In addition to per-lane configuration, USB 3.0 ports may require additional +settings on a per-board basis. + +Pads will be represented as children of the top-level XUSB pad controller +device tree node. Each lane exposed by the pad will be represented by its +own subnode and can be referenced by users of the lane using the standard +PHY bindings, as described by the phy-bindings.txt file in this directory. + +The Tegra hardware documentation refers to the connection between the XUSB +pad controller and the XUSB controller as "ports". This is confusing since +"port" is typically used to denote the physical USB receptacle. The device +tree binding in this document uses the term "port" to refer to the logical +abstraction of the signals that are routed to a USB receptacle (i.e. a PHY +for the USB signal, the VBUS power supply, the USB 2.0 companion port for +USB 3.0 receptacles, ...). + +Required properties: +-------------------- +- compatible: Must be: + - Tegra124: "nvidia,tegra124-xusb-padctl" + - Tegra132: "nvidia,tegra132-xusb-padctl", "nvidia,tegra124-xusb-padctl" +- reg: Physical base address and length of the controller's registers. +- resets: Must contain an entry for each entry in reset-names. +- reset-names: Must include the following entries: + - "padctl" + + +Pad nodes: +========== + +A required child node named "pads" contains a list of subnodes, one for each +of the pads exposed by the XUSB pad controller. Each pad may need additional +resources that can be referenced in its pad node. + +The "status" property is used to enable or disable the use of a pad. If set +to "disabled", the pad will not be used on the given board. In order to use +the pad and any of its lanes, this property must be set to "okay". + +For Tegra124 and Tegra132, the following pads exist: usb2, ulpi, hsic, pcie +and sata. No extra resources are required for operation of these pads. + + +PHY nodes: +========== + +Each pad node has one or more children, each representing one of the lanes +controlled by the pad. + +Required properties: +-------------------- +- status: Defines the operation status of the PHY. Valid values are: + - "disabled": the PHY is disabled + - "okay": the PHY is enabled +- #phy-cells: Should be 0. Since each lane represents a single PHY, there is + no need for an additional specifier. +- nvidia,function: The output function of the PHY. See below for a list of + valid functions per SoC generation. + +For Tegra124 and Tegra132, the list of valid PHY nodes is given below: +- usb2: usb2-0, usb2-1, usb2-2 + - functions: "snps", "xusb", "uart" +- ulpi: ulpi-0 + - functions: "snps", "xusb" +- hsic: hsic-0, hsic-1 + - functions: "snps", "xusb" +- pcie: pcie-0, pcie-1, pcie-2, pcie-3, pcie-4 + - functions: "pcie", "usb3-ss" +- sata: sata-0 + - functions: "usb3-ss", "sata" + + +Port nodes: +=========== + +A required child node named "ports" contains a list of all the ports exposed +by the XUSB pad controller. Per-port configuration is only required for USB. + +USB2 ports: +----------- + +Required properties: +- status: Defines the operation status of the port. Valid values are: + - "disabled": the port is disabled + - "okay": the port is enabled +- mode: A string that determines the mode in which to run the port. Valid + values are: + - "host": for USB host mode + - "device": for USB device mode + - "otg": for USB OTG mode + +Optional properties: +- nvidia,internal: A boolean property whose presence determines that a port + is internal. In the absence of this property the port is considered to be + external. +- vbus-supply: phandle to a regulator supplying the VBUS voltage. + +ULPI ports: +----------- + +Optional properties: +- status: Defines the operation status of the port. Valid values are: + - "disabled": the port is disabled + - "okay": the port is enabled +- nvidia,internal: A boolean property whose presence determines that a port + is internal. In the absence of this property the port is considered to be + external. +- vbus-supply: phandle to a regulator supplying the VBUS voltage. + +HSIC ports: +----------- + +Required properties: +- status: Defines the operation status of the port. Valid values are: + - "disabled": the port is disabled + - "okay": the port is enabled + +Optional properties: +- vbus-supply: phandle to a regulator supplying the VBUS voltage. + +Super-speed USB ports: +---------------------- + +Required properties: +- status: Defines the operation status of the port. Valid values are: + - "disabled": the port is disabled + - "okay": the port is enabled +- nvidia,usb2-companion: A single cell that specifies the physical port number + to map this super-speed USB port to. The range of valid port numbers varies + with the SoC generation: + - 0-2: for Tegra124 and Tegra132 + +Optional properties: +- nvidia,internal: A boolean property whose presence determines that a port + is internal. In the absence of this property the port is considered to be + external. + +For Tegra124 and Tegra132, the XUSB pad controller exposes the following +ports: +- 3x USB2: usb2-0, usb2-1, usb2-2 +- 1x ULPI: ulpi-0 +- 2x HSIC: hsic-0, hsic-1 +- 2x super-speed USB: usb3-0, usb3-1 + + +Examples: +========= + +Tegra124 and Tegra132: +---------------------- + +SoC include: + + padctl@0,7009f000 { + /* for Tegra124 */ + compatible = "nvidia,tegra124-xusb-padctl"; + /* for Tegra132 */ + compatible = "nvidia,tegra132-xusb-padctl", + "nvidia,tegra124-xusb-padctl"; + reg = <0x0 0x7009f000 0x0 0x1000>; + resets = <&tegra_car 142>; + reset-names = "padctl"; + + pads { + usb2 { + status = "disabled"; + + usb2-0 { + status = "disabled"; + #phy-cells = <0>; + }; + + usb2-1 { + status = "disabled"; + #phy-cells = <0>; + }; + + usb2-2 { + status = "disabled"; + #phy-cells = <0>; + }; + }; + + ulpi { + status = "disabled"; + + ulpi-0 { + status = "disabled"; + #phy-cells = <0>; + }; + }; + + hsic { + status = "disabled"; + + hsic-0 { + status = "disabled"; + #phy-cells = <0>; + }; + + hsic-1 { + status = "disabled"; + #phy-cells = <0>; + }; + }; + + pcie { + status = "disabled"; + + pcie-0 { + status = "disabled"; + #phy-cells = <0>; + }; + + pcie-1 { + status = "disabled"; + #phy-cells = <0>; + }; + + pcie-2 { + status = "disabled"; + #phy-cells = <0>; + }; + + pcie-3 { + status = "disabled"; + #phy-cells = <0>; + }; + + pcie-4 { + status = "disabled"; + #phy-cells = <0>; + }; + }; + + sata { + status = "disabled"; + + sata-0 { + status = "disabled"; + #phy-cells = <0>; + }; + }; + }; + + ports { + usb2-0 { + status = "disabled"; + }; + + usb2-1 { + status = "disabled"; + }; + + usb2-2 { + status = "disabled"; + }; + + ulpi-0 { + status = "disabled"; + }; + + hsic-0 { + status = "disabled"; + }; + + hsic-1 { + status = "disabled"; + }; + + usb3-0 { + status = "disabled"; + }; + + usb3-1 { + status = "disabled"; + }; + }; + }; + +Board file: + + padctl@0,7009f000 { + status = "okay"; + + pads { + usb2 { + status = "okay"; + + usb2-0 { + nvidia,function = "xusb"; + status = "okay"; + }; + + usb2-1 { + nvidia,function = "xusb"; + status = "okay"; + }; + + usb2-2 { + nvidia,function = "xusb"; + status = "okay"; + }; + }; + + pcie { + status = "okay"; + + pcie-0 { + nvidia,function = "usb3-ss"; + status = "okay"; + }; + + pcie-2 { + nvidia,function = "pcie"; + status = "okay"; + }; + + pcie-4 { + nvidia,function = "pcie"; + status = "okay"; + }; + }; + + sata { + status = "okay"; + + sata-0 { + nvidia,function = "sata"; + status = "okay"; + }; + }; + }; + + ports { + /* Micro A/B */ + usb2-0 { + status = "okay"; + mode = "otg"; + }; + + /* Mini PCIe */ + usb2-1 { + status = "okay"; + mode = "host"; + }; + + /* USB3 */ + usb2-2 { + status = "okay"; + mode = "host"; + + vbus-supply = <&vdd_usb3_vbus>; + }; + + usb3-0 { + nvidia,port = <2>; + status = "okay"; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt index 30676ded85bb3a..77dfba05ccfd07 100644 --- a/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt +++ b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt @@ -1,6 +1,11 @@ Device tree binding for NVIDIA Tegra XUSB pad controller ======================================================== +NOTE: It turns out that this binding isn't an accurate description of the XUSB +pad controller. While the description is good enough for the functional subset +required for PCIe and SATA, it lacks the flexibility to represent the features +needed for USB. For the new binding, see ../phy/nvidia,tegra-xusb-padctl.txt. + The Tegra XUSB pad controller manages a set of lanes, each of which can be assigned to one out of a set of different pads. Some of these pads have an associated PHY that must be powered up before the pad can be used. From 81f84d1d96c75db574add2704fb58efe811b02d6 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 4 Mar 2016 17:19:32 +0100 Subject: [PATCH 02/19] dt-bindings: pinctrl: Deprecate Tegra XUSB pad controller binding This is an old version of the binding that isn't flexible enough to describe all aspects of the XUSB pad controller. Specifically with the addition of XUSB support (for SuperSpeed USB) the existing binding is no longer suitable. Signed-off-by: Thierry Reding Acked-by: Rob Herring Acked-by: Stephen Warren --- .../devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt index 77dfba05ccfd07..8a6223dbc143e2 100644 --- a/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt +++ b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt @@ -5,6 +5,7 @@ NOTE: It turns out that this binding isn't an accurate description of the XUSB pad controller. While the description is good enough for the functional subset required for PCIe and SATA, it lacks the flexibility to represent the features needed for USB. For the new binding, see ../phy/nvidia,tegra-xusb-padctl.txt. +The binding described in this file is deprecated and should not be used. The Tegra XUSB pad controller manages a set of lanes, each of which can be assigned to one out of a set of different pads. Some of these pads have an From 67bd3a630a6835fc4b358aa7024f035dda5a5a8d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 4 Mar 2016 17:19:33 +0100 Subject: [PATCH 03/19] dt-bindings: phy: tegra-xusb-padctl: Add Tegra210 support Extend the binding to cover the set of feature found in Tegra210. Signed-off-by: Thierry Reding Acked-by: Rob Herring Acked-by: Stephen Warren --- .../phy/nvidia,tegra124-xusb-padctl.txt | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt b/Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt index 8b642d9e343357..8cbfeb60f864fd 100644 --- a/Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt +++ b/Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt @@ -35,6 +35,7 @@ Required properties: - compatible: Must be: - Tegra124: "nvidia,tegra124-xusb-padctl" - Tegra132: "nvidia,tegra132-xusb-padctl", "nvidia,tegra124-xusb-padctl" + - Tegra210: "nvidia,tegra210-xusb-padctl" - reg: Physical base address and length of the controller's registers. - resets: Must contain an entry for each entry in reset-names. - reset-names: Must include the following entries: @@ -55,6 +56,44 @@ the pad and any of its lanes, this property must be set to "okay". For Tegra124 and Tegra132, the following pads exist: usb2, ulpi, hsic, pcie and sata. No extra resources are required for operation of these pads. +For Tegra210, the following pads exist: usb2, hsic, pcie and sata. Below is +a description of the properties of each pad. + +UTMI pad: +--------- + +Required properties: +- clocks: Must contain an entry for each entry in clock-names. +- clock-names: Must contain the following entries: + - "trk": phandle and specifier referring to the USB2 tracking clock + +HSIC pad: +--------- + +Required properties: +- clocks: Must contain an entry for each entry in clock-names. +- clock-names: Must contain the following entries: + - "trk": phandle and specifier referring to the HSIC tracking clock + +PCIe pad: +--------- + +Required properties: +- clocks: Must contain an entry for each entry in clock-names. +- clock-names: Must contain the following entries: + - "pll": phandle and specifier referring to the PLLE +- resets: Must contain an entry for each entry in reset-names. +- reset-names: Must contain the following entries: + - "phy": reset for the PCIe UPHY block + +SATA pad: +--------- + +Required properties: +- resets: Must contain an entry for each entry in reset-names. +- reset-names: Must contain the following entries: + - "phy": reset for the SATA UPHY block + PHY nodes: ========== @@ -84,6 +123,16 @@ For Tegra124 and Tegra132, the list of valid PHY nodes is given below: - sata: sata-0 - functions: "usb3-ss", "sata" +For Tegra210, the list of valid PHY nodes is given below: +- utmi: utmi-0, utmi-1, utmi-2, utmi-3 + - functions: "snps", "xusb", "uart" +- hsic: hsic-0, hsic-1 + - functions: "snps", "xusb" +- pcie: pcie-0, pcie-1, pcie-2, pcie-3, pcie-4, pcie-5, pcie-6 + - functions: "pcie-x1", "usb3-ss", "pcie-x4" +- sata: sata-0 + - functions: "usb3-ss", "sata" + Port nodes: =========== @@ -144,6 +193,7 @@ Required properties: to map this super-speed USB port to. The range of valid port numbers varies with the SoC generation: - 0-2: for Tegra124 and Tegra132 + - 0-3: for Tegra210 Optional properties: - nvidia,internal: A boolean property whose presence determines that a port @@ -157,6 +207,11 @@ ports: - 2x HSIC: hsic-0, hsic-1 - 2x super-speed USB: usb3-0, usb3-1 +For Tegra210, the XUSB pad controller exposes the following ports: +- 4x USB2: usb2-0, usb2-1, usb2-2, usb2-3 +- 2x HSIC: hsic-0, hsic-1 +- 4x super-speed USB: usb3-0, usb3-1, usb3-2, usb3-3 + Examples: ========= @@ -374,3 +429,275 @@ Board file: }; }; }; + +Tegra210: +--------- + +SoC include: + + padctl@0,7009f000 { + compatible = "nvidia,tegra210-xusb-padctl"; + reg = <0x0 0x7009f000 0x0 0x1000>; + resets = <&tegra_car 142>; + reset-names = "padctl"; + + status = "disabled"; + + pads { + usb2 { + clocks = <&tegra_car TEGRA210_CLK_USB2_TRK>; + clock-names = "trk"; + status = "disabled"; + + usb2-0 { + status = "disabled"; + #phy-cells = <0>; + }; + + usb2-1 { + status = "disabled"; + #phy-cells = <0>; + }; + + usb2-2 { + status = "disabled"; + #phy-cells = <0>; + }; + + usb2-3 { + status = "disabled"; + #phy-cells = <0>; + }; + }; + + hsic { + clocks = <&tegra_car TEGRA210_CLK_HSIC_TRK>; + clock-names = "trk"; + status = "disabled"; + + hsic-0 { + status = "disabled"; + #phy-cells = <0>; + }; + + hsic-1 { + status = "disabled"; + #phy-cells = <0>; + }; + }; + + pcie { + clocks = <&tegra_car TEGRA210_CLK_PLL_E>; + clock-names = "pll"; + resets = <&tegra_car 205>; + reset-names = "phy"; + status = "disabled"; + + pcie-0 { + status = "disabled"; + #phy-cells = <0>; + }; + + pcie-1 { + status = "disabled"; + #phy-cells = <0>; + }; + + pcie-2 { + status = "disabled"; + #phy-cells = <0>; + }; + + pcie-3 { + status = "disabled"; + #phy-cells = <0>; + }; + + pcie-4 { + status = "disabled"; + #phy-cells = <0>; + }; + + pcie-5 { + status = "disabled"; + #phy-cells = <0>; + }; + + pcie-6 { + status = "disabled"; + #phy-cells = <0>; + }; + }; + + sata { + clocks = <&tegra_car TEGRA210_CLK_PLL_E>; + clock-names = "pll"; + resets = <&tegra_car 204>; + reset-names = "phy"; + status = "disabled"; + + sata-0 { + status = "disabled"; + #phy-cells = <0>; + }; + }; + }; + + ports { + usb2-0 { + status = "disabled"; + }; + + usb2-1 { + status = "disabled"; + }; + + usb2-2 { + status = "disabled"; + }; + + usb2-3 { + status = "disabled"; + }; + + hsic-0 { + status = "disabled"; + }; + + hsic-1 { + status = "disabled"; + }; + + usb3-0 { + status = "disabled"; + }; + + usb3-1 { + status = "disabled"; + }; + + usb3-2 { + status = "disabled"; + }; + + usb3-3 { + status = "disabled"; + }; + }; + }; + +Board file: + + padctl@0,7009f000 { + status = "okay"; + + pads { + usb2 { + status = "okay"; + + usb2-0 { + nvidia,function = "xusb"; + status = "okay"; + }; + + usb2-1 { + nvidia,function = "xusb"; + status = "okay"; + }; + + usb2-2 { + nvidia,function = "xusb"; + status = "okay"; + }; + + usb2-3 { + nvidia,function = "xusb"; + status = "okay"; + }; + }; + + pcie { + status = "okay"; + + pcie-0 { + nvidia,function = "pcie-x1"; + status = "okay"; + }; + + pcie-1 { + nvidia,function = "pcie-x4"; + status = "okay"; + }; + + pcie-2 { + nvidia,function = "pcie-x4"; + status = "okay"; + }; + + pcie-3 { + nvidia,function = "pcie-x4"; + status = "okay"; + }; + + pcie-4 { + nvidia,function = "pcie-x4"; + status = "okay"; + }; + + pcie-5 { + nvidia,function = "usb3-ss"; + status = "okay"; + }; + + pcie-6 { + nvidia,function = "usb3-ss"; + status = "okay"; + }; + }; + + sata { + status = "okay"; + + sata-0 { + nvidia,function = "sata"; + status = "okay"; + }; + }; + }; + + ports { + usb2-0 { + status = "okay"; + mode = "otg"; + }; + + usb2-1 { + status = "okay"; + vbus-supply = <&vdd_5v0_rtl>; + mode = "host"; + }; + + usb2-2 { + status = "okay"; + vbus-supply = <&vdd_usb_vbus>; + mode = "host"; + }; + + usb2-3 { + status = "okay"; + mode = "host"; + }; + + usb3-0 { + status = "okay"; + nvidia,lanes = "pcie-6"; + nvidia,port = <1>; + }; + + usb3-1 { + status = "okay"; + nvidia,lanes = "pcie-5"; + nvidia,port = <2>; + }; + }; + }; From 8cde7c618fd36250c8513cb958c5ade5278fa2f3 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 4 Mar 2016 17:19:34 +0100 Subject: [PATCH 04/19] phy: Add Tegra XUSB pad controller support Add a new driver for the XUSB pad controller found on NVIDIA Tegra SoCs. This hardware block used to be exposed as a pin controller, but it turns out that this isn't a good fit. The new driver and DT binding much more accurately describe the hardware and are more flexible in supporting new SoC generations. Signed-off-by: Thierry Reding --- drivers/phy/Kconfig | 2 + drivers/phy/Makefile | 2 + drivers/phy/tegra/Kconfig | 8 + drivers/phy/tegra/Makefile | 5 + drivers/phy/tegra/xusb-tegra124.c | 1747 ++++++++++++++++++++ drivers/phy/tegra/xusb.c | 1017 ++++++++++++ drivers/phy/tegra/xusb.h | 421 +++++ drivers/pinctrl/tegra/pinctrl-tegra-xusb.c | 20 +- include/linux/phy/tegra/xusb.h | 30 + 9 files changed, 3236 insertions(+), 16 deletions(-) create mode 100644 drivers/phy/tegra/Kconfig create mode 100644 drivers/phy/tegra/Makefile create mode 100644 drivers/phy/tegra/xusb-tegra124.c create mode 100644 drivers/phy/tegra/xusb.c create mode 100644 drivers/phy/tegra/xusb.h create mode 100644 include/linux/phy/tegra/xusb.h diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 26566db09de096..27e5f6ee9a2ae3 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -421,4 +421,6 @@ config PHY_CYGNUS_PCIE Enable this to support the Broadcom Cygnus PCIe PHY. If unsure, say N. +source "drivers/phy/tegra/Kconfig" + endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 24596a96a8876f..d4f06e69fd9ab9 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -52,3 +52,5 @@ obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o obj-$(CONFIG_PHY_BRCMSTB_SATA) += phy-brcmstb-sata.o obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o + +obj-$(CONFIG_ARCH_TEGRA) += tegra/ diff --git a/drivers/phy/tegra/Kconfig b/drivers/phy/tegra/Kconfig new file mode 100644 index 00000000000000..a3b1de953fb70b --- /dev/null +++ b/drivers/phy/tegra/Kconfig @@ -0,0 +1,8 @@ +config PHY_TEGRA_XUSB + tristate "NVIDIA Tegra XUSB pad controller driver" + depends on ARCH_TEGRA + help + Choose this option if you have an NVIDIA Tegra SoC. + + To compile this driver as a module, choose M here: the module will + be called phy-tegra-xusb. diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile new file mode 100644 index 00000000000000..31150b4337cd3c --- /dev/null +++ b/drivers/phy/tegra/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_PHY_TEGRA_XUSB) += phy-tegra-xusb.o + +phy-tegra-xusb-y += xusb.o +phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_124_SOC) += xusb-tegra124.o +phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_SOC) += xusb-tegra124.o diff --git a/drivers/phy/tegra/xusb-tegra124.c b/drivers/phy/tegra/xusb-tegra124.c new file mode 100644 index 00000000000000..6340d43688d3bb --- /dev/null +++ b/drivers/phy/tegra/xusb-tegra124.c @@ -0,0 +1,1747 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "xusb.h" + +#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(x) ((x) ? 15 : 0) +#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK 0x3f +#define FUSE_SKU_CALIB_HS_IREF_CAP_SHIFT 13 +#define FUSE_SKU_CALIB_HS_IREF_CAP_MASK 0x3 +#define FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_SHIFT 11 +#define FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_MASK 0x3 +#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT 7 +#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK 0xf + +#define XUSB_PADCTL_USB2_PORT_CAP 0x008 +#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(x) ((x) * 4) +#define XUSB_PADCTL_USB2_PORT_CAP_PORT_CAP_MASK 0x3 +#define XUSB_PADCTL_USB2_PORT_CAP_DISABLED 0x0 +#define XUSB_PADCTL_USB2_PORT_CAP_HOST 0x1 +#define XUSB_PADCTL_USB2_PORT_CAP_DEVICE 0x2 +#define XUSB_PADCTL_USB2_PORT_CAP_OTG 0x3 + +#define XUSB_PADCTL_SS_PORT_MAP 0x014 +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(x) (1 << (((x) * 4) + 3)) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_SHIFT(x) ((x) * 4) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(x) (0x7 << ((x) * 4)) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(x, v) (((v) & 0x7) << ((x) * 4)) +#define XUSB_PADCTL_SS_PORT_MAP_PORT_MAP_MASK 0x7 + +#define XUSB_PADCTL_ELPG_PROGRAM 0x01c +#define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN (1 << 26) +#define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 25) +#define XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN (1 << 24) +#define XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(x) (1 << (18 + (x) * 4)) +#define XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(x) \ + (1 << (17 + (x) * 4)) +#define XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN(x) (1 << (16 + (x) * 4)) + +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1 0x040 +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL0_LOCKDET (1 << 19) +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK (0xf << 12) +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST (1 << 1) + +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2 0x044 +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_REFCLKBUF_EN (1 << 6) +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_EN (1 << 5) +#define XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_SEL (1 << 4) + +#define XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(x) (0x058 + (x) * 4) +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT 24 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_MASK 0xff +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_VAL 0x24 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT 16 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK 0x3f +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT 8 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK 0x3f +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT 8 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_MASK 0xffff +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_VAL 0xf070 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT 4 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_MASK 0xf +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_VAL 0xf + +#define XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(x) (0x068 + (x) * 4) +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT 24 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK 0x1f +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT 16 +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK 0x7f +#define XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_VAL 0x002008ee + +#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL2(x) ((x) < 2 ? 0x078 + (x) * 4 : \ + 0x0f8 + (x) * 4) +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT 28 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_MASK 0x3 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_VAL 0x1 + +#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL5(x) ((x) < 2 ? 0x090 + (x) * 4 : \ + 0x11c + (x) * 4) +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL5_RX_QEYE_EN (1 << 8) + +#define XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL6(x) ((x) < 2 ? 0x098 + (x) * 4 : \ + 0x128 + (x) * 4) +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT 24 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK 0x3f +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_TAP_MASK 0x1f +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_AMP_MASK 0x7f +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT 16 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK 0xff +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_G_Z 0x21 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_TAP 0x32 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_AMP 0x33 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_CTLE_Z 0x48 +#define XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_LATCH_G_Z 0xa1 + +#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x0a0 + (x) * 4) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI (1 << 21) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 (1 << 20) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD (1 << 19) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT 14 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_MASK 0x3 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_VAL(x) ((x) ? 0x0 : 0x3) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT 6 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_MASK 0x3f +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_VAL 0x0e +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT 0 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK 0x3f + +#define XUSB_PADCTL_USB2_OTG_PADX_CTL1(x) (0x0ac + (x) * 4) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT 9 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_MASK 0x3 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT 3 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK 0x7 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR (1 << 2) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_FORCE_POWERUP (1 << 1) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_FORCE_POWERUP (1 << 0) + +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x0b8 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD (1 << 12) +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT 2 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK 0x7 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL 0x5 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT 0 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK 0x3 + +#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x0c0 + (x) * 4) +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT 12 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_MASK 0x7 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT 8 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_MASK 0x7 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT 4 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_MASK 0x7 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT 0 +#define XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_MASK 0x7 + +#define XUSB_PADCTL_HSIC_PADX_CTL1(x) (0x0c8 + (x) * 4) +#define XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE (1 << 10) +#define XUSB_PADCTL_HSIC_PAD_CTL1_RPU_DATA (1 << 9) +#define XUSB_PADCTL_HSIC_PAD_CTL1_RPD_STROBE (1 << 8) +#define XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA (1 << 7) +#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI (1 << 5) +#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX (1 << 4) +#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX (1 << 3) +#define XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX (1 << 2) +#define XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN (1 << 0) + +#define XUSB_PADCTL_HSIC_PADX_CTL2(x) (0x0d0 + (x) * 4) +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT 4 +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK 0x7 +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT 0 +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK 0x7 + +#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL 0x0e0 +#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL_STRB_TRIM_MASK 0x1f + +#define XUSB_PADCTL_USB3_PAD_MUX 0x134 +#define XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(x) (1 << (1 + (x))) +#define XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(x) (1 << (6 + (x))) + +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1 0x138 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_LOCKDET (1 << 27) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE (1 << 24) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_SHIFT 20 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_MASK 0x3 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD (1 << 3) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST (1 << 1) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ (1 << 0) + +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2 0x13c +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT 20 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_MASK 0xf +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT 16 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_MASK 0xf +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TCLKOUT_EN (1 << 12) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TXCLKREF_SEL (1 << 4) +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT 0 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_MASK 0x7 + +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL3 0x140 +#define XUSB_PADCTL_IOPHY_PLL_S0_CTL3_RCAL_BYPASS (1 << 7) + +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1 0x148 +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD (1 << 1) +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ (1 << 0) + +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2 0x14c + +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5 0x158 + +#define XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6 0x15c + +struct tegra124_xusb_fuse_calibration { + u32 hs_curr_level[3]; + u32 hs_iref_cap; + u32 hs_term_range_adj; + u32 hs_squelch_level; +}; + +struct tegra124_xusb_padctl { + struct tegra_xusb_padctl base; + + struct tegra124_xusb_fuse_calibration fuse; +}; + +static inline struct tegra124_xusb_padctl * +to_tegra124_xusb_padctl(struct tegra_xusb_padctl *padctl) +{ + return container_of(padctl, struct tegra124_xusb_padctl, base); +} + +static int tegra124_xusb_padctl_enable(struct tegra_xusb_padctl *padctl) +{ + u32 value; + + mutex_lock(&padctl->lock); + + if (padctl->enable++ > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + +out: + mutex_unlock(&padctl->lock); + return 0; +} + +static int tegra124_xusb_padctl_disable(struct tegra_xusb_padctl *padctl) +{ + u32 value; + + mutex_lock(&padctl->lock); + + if (WARN_ON(padctl->enable == 0)) + goto out; + + if (--padctl->enable > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_VCORE_DOWN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN_EARLY; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_AUX_MUX_LP0_CLAMP_EN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + +out: + mutex_unlock(&padctl->lock); + return 0; +} + +static int tegra124_usb3_save_context(struct tegra_xusb_padctl *padctl, + unsigned int index) +{ + struct tegra_xusb_usb3_port *port; + struct tegra_xusb_lane *lane; + u32 value, offset; + + port = tegra_xusb_find_usb3_port(padctl, index); + if (!port) + return -ENODEV; + + port->context_saved = true; + lane = port->base.lane; + + if (lane->pad == padctl->pcie) + offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL6(lane->index); + else + offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL6; + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_TAP << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT; + padctl_writel(padctl, value, offset); + + value = padctl_readl(padctl, offset) >> + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT; + port->tap1 = value & XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_TAP_MASK; + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_AMP << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT; + padctl_writel(padctl, value, offset); + + value = padctl_readl(padctl, offset) >> + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT; + port->amp = value & XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_AMP_MASK; + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index)); + value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT)); + value |= (port->tap1 << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) | + (port->amp << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index)); + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_LATCH_G_Z << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT; + padctl_writel(padctl, value, offset); + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_G_Z << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT; + padctl_writel(padctl, value, offset); + + value = padctl_readl(padctl, offset) >> + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT; + port->ctle_g = value & + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK; + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_CTLE_Z << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SEL_SHIFT; + padctl_writel(padctl, value, offset); + + value = padctl_readl(padctl, offset) >> + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_SHIFT; + port->ctle_z = value & + XUSB_PADCTL_IOPHY_MISC_PAD_CTL6_MISC_OUT_G_Z_MASK; + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index)); + value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT)); + value |= (port->ctle_g << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) | + (port->ctle_z << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index)); + + return 0; +} + +static int tegra124_hsic_set_idle(struct tegra_xusb_padctl *padctl, + unsigned int index, bool idle) +{ + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + if (idle) + value |= XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA | + XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE; + else + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA | + XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE); + + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + return 0; +} + +#define TEGRA124_LANE(_name, _offset, _shift, _mask, _type) \ + { \ + .name = _name, \ + .offset = _offset, \ + .shift = _shift, \ + .mask = _mask, \ + .num_funcs = ARRAY_SIZE(tegra124_##_type##_functions), \ + .funcs = tegra124_##_type##_functions, \ + } + +static const char * const tegra124_usb2_functions[] = { + "snps", + "xusb", + "uart", +}; + +static const struct tegra_xusb_lane_soc tegra124_usb2_lanes[] = { + TEGRA124_LANE("usb2-0", 0x004, 0, 0x3, usb2), + TEGRA124_LANE("usb2-1", 0x004, 2, 0x3, usb2), + TEGRA124_LANE("usb2-2", 0x004, 4, 0x3, usb2), +}; + +static struct tegra_xusb_lane * +tegra124_usb2_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_usb2_lane *usb2; + int err; + + usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&usb2->base.list); + usb2->base.soc = &pad->soc->lanes[index]; + usb2->base.index = index; + usb2->base.pad = pad; + usb2->base.np = np; + + err = tegra_xusb_lane_parse_dt(&usb2->base, np); + if (err < 0) { + kfree(usb2); + return ERR_PTR(err); + } + + return &usb2->base; +} + +static void tegra124_usb2_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane); + + kfree(usb2); +} + +static const struct tegra_xusb_lane_ops tegra124_usb2_lane_ops = { + .probe = tegra124_usb2_lane_probe, + .remove = tegra124_usb2_lane_remove, +}; + +static int tegra124_usb2_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra124_usb2_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra124_usb2_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane); + struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra124_xusb_padctl *priv; + struct tegra_xusb_usb2_port *port; + unsigned int index = lane->index; + u32 value; + int err; + + port = tegra_xusb_find_usb2_port(padctl, index); + if (!port) { + dev_err(&phy->dev, "no port found for USB2 lane %u\n", index); + return -ENODEV; + } + + priv = to_tegra124_xusb_padctl(padctl); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) | + (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT)); + value |= (priv->fuse.hs_squelch_level << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) | + (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP); + value &= ~(XUSB_PADCTL_USB2_PORT_CAP_PORT_CAP_MASK << + XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(index)); + value |= XUSB_PADCTL_USB2_PORT_CAP_HOST << + XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_SHIFT(index); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT) | + (XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT) | + (XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT) | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI); + value |= (priv->fuse.hs_curr_level[index] + + usb2->hs_curr_level_offset) << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT; + value |= XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_VAL << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_SLEW_SHIFT; + value |= XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_VAL(index) << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_LS_RSLEW_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) | + (XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT) | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_FORCE_POWERUP | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_FORCE_POWERUP); + value |= (priv->fuse.hs_term_range_adj << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) | + (priv->fuse.hs_iref_cap << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_HS_IREF_CAP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + + err = regulator_enable(port->supply); + if (err) + return err; + + mutex_lock(&pad->lock); + + if (pad->enable++ > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value &= ~XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + +out: + mutex_unlock(&pad->lock); + return 0; +} + +static int tegra124_usb2_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb2_port *port; + u32 value; + + port = tegra_xusb_find_usb2_port(padctl, lane->index); + if (!port) { + dev_err(&phy->dev, "no port found for USB2 lane %u\n", + lane->index); + return -ENODEV; + } + + mutex_lock(&pad->lock); + + if (WARN_ON(pad->enable == 0)) + goto out; + + if (--pad->enable > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value |= XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + +out: + regulator_disable(port->supply); + mutex_unlock(&pad->lock); + return 0; +} + +static const struct phy_ops tegra124_usb2_phy_ops = { + .init = tegra124_usb2_phy_init, + .exit = tegra124_usb2_phy_exit, + .power_on = tegra124_usb2_phy_power_on, + .power_off = tegra124_usb2_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra124_usb2_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_usb2_pad *usb2; + struct tegra_xusb_pad *pad; + int err; + + usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return ERR_PTR(-ENOMEM); + + mutex_init(&usb2->lock); + + pad = &usb2->base; + pad->ops = &tegra124_usb2_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) + goto free; + + err = tegra_xusb_pad_register(pad, &tegra124_usb2_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +free: + kfree(usb2); + return ERR_PTR(err); +} + +static void tegra124_usb2_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad); + + kfree(usb2); +} + +static const struct tegra_xusb_pad_ops tegra124_usb2_ops = { + .probe = tegra124_usb2_pad_probe, + .remove = tegra124_usb2_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra124_usb2_pad = { + .name = "usb2", + .num_lanes = ARRAY_SIZE(tegra124_usb2_lanes), + .lanes = tegra124_usb2_lanes, + .ops = &tegra124_usb2_ops, +}; + +static const char * const tegra124_ulpi_functions[] = { + "snps", + "xusb", +}; + +static const struct tegra_xusb_lane_soc tegra124_ulpi_lanes[] = { + TEGRA124_LANE("ulpi-0", 0x004, 12, 0x1, ulpi), +}; + +static struct tegra_xusb_lane * +tegra124_ulpi_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_ulpi_lane *ulpi; + int err; + + ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL); + if (!ulpi) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&ulpi->base.list); + ulpi->base.soc = &pad->soc->lanes[index]; + ulpi->base.index = index; + ulpi->base.pad = pad; + ulpi->base.np = np; + + err = tegra_xusb_lane_parse_dt(&ulpi->base, np); + if (err < 0) { + kfree(ulpi); + return ERR_PTR(err); + } + + return &ulpi->base; +} + +static void tegra124_ulpi_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_ulpi_lane *ulpi = to_ulpi_lane(lane); + + kfree(ulpi); +} + +static const struct tegra_xusb_lane_ops tegra124_ulpi_lane_ops = { + .probe = tegra124_ulpi_lane_probe, + .remove = tegra124_ulpi_lane_remove, +}; + +static int tegra124_ulpi_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra124_ulpi_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra124_ulpi_phy_power_on(struct phy *phy) +{ + return 0; +} + +static int tegra124_ulpi_phy_power_off(struct phy *phy) +{ + return 0; +} + +static const struct phy_ops tegra124_ulpi_phy_ops = { + .init = tegra124_ulpi_phy_init, + .exit = tegra124_ulpi_phy_exit, + .power_on = tegra124_ulpi_phy_power_on, + .power_off = tegra124_ulpi_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra124_ulpi_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_ulpi_pad *ulpi; + struct tegra_xusb_pad *pad; + int err; + + ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL); + if (!ulpi) + return ERR_PTR(-ENOMEM); + + pad = &ulpi->base; + pad->ops = &tegra124_ulpi_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) + goto free; + + err = tegra_xusb_pad_register(pad, &tegra124_ulpi_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +free: + kfree(ulpi); + return ERR_PTR(err); +} + +static void tegra124_ulpi_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_ulpi_pad *ulpi = to_ulpi_pad(pad); + + kfree(ulpi); +} + +static const struct tegra_xusb_pad_ops tegra124_ulpi_ops = { + .probe = tegra124_ulpi_pad_probe, + .remove = tegra124_ulpi_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra124_ulpi_pad = { + .name = "ulpi", + .num_lanes = ARRAY_SIZE(tegra124_ulpi_lanes), + .lanes = tegra124_ulpi_lanes, + .ops = &tegra124_ulpi_ops, +}; + +static const char * const tegra124_hsic_functions[] = { + "snps", + "xusb", +}; + +static const struct tegra_xusb_lane_soc tegra124_hsic_lanes[] = { + TEGRA124_LANE("hsic-0", 0x004, 14, 0x1, hsic), + TEGRA124_LANE("hsic-1", 0x004, 15, 0x1, hsic), +}; + +static struct tegra_xusb_lane * +tegra124_hsic_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_hsic_lane *hsic; + int err; + + hsic = kzalloc(sizeof(*hsic), GFP_KERNEL); + if (!hsic) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&hsic->base.list); + hsic->base.soc = &pad->soc->lanes[index]; + hsic->base.index = index; + hsic->base.pad = pad; + hsic->base.np = np; + + err = tegra_xusb_lane_parse_dt(&hsic->base, np); + if (err < 0) { + kfree(hsic); + return ERR_PTR(err); + } + + return &hsic->base; +} + +static void tegra124_hsic_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane); + + kfree(hsic); +} + +static const struct tegra_xusb_lane_ops tegra124_hsic_lane_ops = { + .probe = tegra124_hsic_lane_probe, + .remove = tegra124_hsic_lane_remove, +}; + +static int tegra124_hsic_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra124_hsic_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra124_hsic_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane); + struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + u32 value; + int err; + + err = regulator_enable(pad->supply); + if (err) + return err; + + padctl_writel(padctl, hsic->strobe_trim, + XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + if (hsic->auto_term) + value |= XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN; + else + value &= ~XUSB_PADCTL_HSIC_PAD_CTL1_AUTO_TERM_EN; + + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + value &= ~((XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_MASK << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_MASK << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_MASK << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_MASK << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT)); + value |= (hsic->tx_rtune_n << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEN_SHIFT) | + (hsic->tx_rtune_p << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RTUNEP_SHIFT) | + (hsic->tx_rslew_n << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWN_SHIFT) | + (hsic->tx_rslew_p << + XUSB_PADCTL_HSIC_PAD_CTL0_TX_RSLEWP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL2(index)); + value &= ~((XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT)); + value |= (hsic->rx_strobe_trim << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) | + (hsic->rx_data_trim << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL2(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_RPD_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL1_RPU_DATA | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX); + value |= XUSB_PADCTL_HSIC_PAD_CTL1_RPD_DATA | + XUSB_PADCTL_HSIC_PAD_CTL1_RPU_STROBE; + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + return 0; +} + +static int tegra124_hsic_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + value |= XUSB_PADCTL_HSIC_PAD_CTL1_PD_RX | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_ZI | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_TRX | + XUSB_PADCTL_HSIC_PAD_CTL1_PD_TX; + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + regulator_disable(pad->supply); + + return 0; +} + +static const struct phy_ops tegra124_hsic_phy_ops = { + .init = tegra124_hsic_phy_init, + .exit = tegra124_hsic_phy_exit, + .power_on = tegra124_hsic_phy_power_on, + .power_off = tegra124_hsic_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra124_hsic_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_hsic_pad *hsic; + struct tegra_xusb_pad *pad; + int err; + + hsic = kzalloc(sizeof(*hsic), GFP_KERNEL); + if (!hsic) + return ERR_PTR(-ENOMEM); + + pad = &hsic->base; + pad->ops = &tegra124_hsic_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) + goto free; + + err = tegra_xusb_pad_register(pad, &tegra124_hsic_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +free: + kfree(hsic); + return ERR_PTR(err); +} + +static void tegra124_hsic_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_hsic_pad *hsic = to_hsic_pad(pad); + + kfree(hsic); +} + +static const struct tegra_xusb_pad_ops tegra124_hsic_ops = { + .probe = tegra124_hsic_pad_probe, + .remove = tegra124_hsic_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra124_hsic_pad = { + .name = "hsic", + .num_lanes = ARRAY_SIZE(tegra124_hsic_lanes), + .lanes = tegra124_hsic_lanes, + .ops = &tegra124_hsic_ops, +}; + +static const char * const tegra124_pcie_functions[] = { + "pcie", + "usb3-ss", + "sata", +}; + +static const struct tegra_xusb_lane_soc tegra124_pcie_lanes[] = { + TEGRA124_LANE("pcie-0", 0x134, 16, 0x3, pcie), + TEGRA124_LANE("pcie-1", 0x134, 18, 0x3, pcie), + TEGRA124_LANE("pcie-2", 0x134, 20, 0x3, pcie), + TEGRA124_LANE("pcie-3", 0x134, 22, 0x3, pcie), + TEGRA124_LANE("pcie-4", 0x134, 24, 0x3, pcie), +}; + +static struct tegra_xusb_lane * +tegra124_pcie_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_pcie_lane *pcie; + int err; + + pcie = kzalloc(sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&pcie->base.list); + pcie->base.soc = &pad->soc->lanes[index]; + pcie->base.index = index; + pcie->base.pad = pad; + pcie->base.np = np; + + err = tegra_xusb_lane_parse_dt(&pcie->base, np); + if (err < 0) { + kfree(pcie); + return ERR_PTR(err); + } + + return &pcie->base; +} + +static void tegra124_pcie_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_pcie_lane *pcie = to_pcie_lane(lane); + + kfree(pcie); +} + +static const struct tegra_xusb_lane_ops tegra124_pcie_lane_ops = { + .probe = tegra124_pcie_lane_probe, + .remove = tegra124_pcie_lane_remove, +}; + +static int tegra124_pcie_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra124_pcie_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra124_pcie_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned long timeout; + int err = -ETIMEDOUT; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_PLL_P0_CTL1_REFCLK_SEL_MASK; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL2); + value |= XUSB_PADCTL_IOPHY_PLL_P0_CTL2_REFCLKBUF_EN | + XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_EN | + XUSB_PADCTL_IOPHY_PLL_P0_CTL2_TXCLKREF_SEL; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + value |= XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + + timeout = jiffies + msecs_to_jiffies(50); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + if (value & XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL0_LOCKDET) { + err = 0; + break; + } + + usleep_range(100, 200); + } + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value |= XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + return err; +} + +static int tegra124_pcie_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value &= ~XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_PLL_P0_CTL1_PLL_RST; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_P0_CTL1); + + return 0; +} + +static const struct phy_ops tegra124_pcie_phy_ops = { + .init = tegra124_pcie_phy_init, + .exit = tegra124_pcie_phy_exit, + .power_on = tegra124_pcie_phy_power_on, + .power_off = tegra124_pcie_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra124_pcie_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_pcie_pad *pcie; + struct tegra_xusb_pad *pad; + int err; + + pcie = kzalloc(sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return ERR_PTR(-ENOMEM); + + pad = &pcie->base; + pad->ops = &tegra124_pcie_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) + goto free; + + err = tegra_xusb_pad_register(pad, &tegra124_pcie_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +free: + kfree(pcie); + return ERR_PTR(err); +} + +static void tegra124_pcie_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(pad); + + kfree(pcie); +} + +static const struct tegra_xusb_pad_ops tegra124_pcie_ops = { + .probe = tegra124_pcie_pad_probe, + .remove = tegra124_pcie_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra124_pcie_pad = { + .name = "pcie", + .num_lanes = ARRAY_SIZE(tegra124_pcie_lanes), + .lanes = tegra124_pcie_lanes, + .ops = &tegra124_pcie_ops, +}; + +static const struct tegra_xusb_lane_soc tegra124_sata_lanes[] = { + TEGRA124_LANE("sata-0", 0x134, 26, 0x3, pcie), +}; + +static struct tegra_xusb_lane * +tegra124_sata_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_sata_lane *sata; + int err; + + sata = kzalloc(sizeof(*sata), GFP_KERNEL); + if (!sata) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&sata->base.list); + sata->base.soc = &pad->soc->lanes[index]; + sata->base.index = index; + sata->base.pad = pad; + sata->base.np = np; + + err = tegra_xusb_lane_parse_dt(&sata->base, np); + if (err < 0) { + kfree(sata); + return ERR_PTR(err); + } + + return &sata->base; +} + +static void tegra124_sata_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_sata_lane *sata = to_sata_lane(lane); + + kfree(sata); +} + +static const struct tegra_xusb_lane_ops tegra124_sata_lane_ops = { + .probe = tegra124_sata_lane_probe, + .remove = tegra124_sata_lane_remove, +}; + +static int tegra124_sata_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra124_sata_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra124_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra124_sata_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned long timeout; + int err = -ETIMEDOUT; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD; + value &= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD; + value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + timeout = jiffies + msecs_to_jiffies(50); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + if (value & XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_LOCKDET) { + err = 0; + break; + } + + usleep_range(100, 200); + } + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value |= XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + return err; +} + +static int tegra124_sata_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value &= ~XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_RST; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL1_MODE; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_PWR_OVRD; + value |= XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); + value |= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ_OVRD; + value |= ~XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL1); + + return 0; +} + +static const struct phy_ops tegra124_sata_phy_ops = { + .init = tegra124_sata_phy_init, + .exit = tegra124_sata_phy_exit, + .power_on = tegra124_sata_phy_power_on, + .power_off = tegra124_sata_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra124_sata_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_sata_pad *sata; + struct tegra_xusb_pad *pad; + int err; + + sata = kzalloc(sizeof(*sata), GFP_KERNEL); + if (!sata) + return ERR_PTR(-ENOMEM); + + pad = &sata->base; + pad->ops = &tegra124_sata_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) + goto free; + + err = tegra_xusb_pad_register(pad, &tegra124_sata_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +free: + kfree(sata); + return ERR_PTR(err); +} + +static void tegra124_sata_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_sata_pad *sata = to_sata_pad(pad); + + kfree(sata); +} + +static const struct tegra_xusb_pad_ops tegra124_sata_ops = { + .probe = tegra124_sata_pad_probe, + .remove = tegra124_sata_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra124_sata_pad = { + .name = "sata", + .num_lanes = ARRAY_SIZE(tegra124_sata_lanes), + .lanes = tegra124_sata_lanes, + .ops = &tegra124_sata_ops, +}; + +static const struct tegra_xusb_pad_soc *tegra124_pads[] = { + &tegra124_usb2_pad, + &tegra124_ulpi_pad, + &tegra124_hsic_pad, + &tegra124_pcie_pad, + &tegra124_sata_pad, +}; + +static int tegra124_usb2_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra124_usb2_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra124_usb2_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "usb2", port->index); +} + +static const struct tegra_xusb_port_ops tegra124_usb2_port_ops = { + .enable = tegra124_usb2_port_enable, + .disable = tegra124_usb2_port_disable, + .map = tegra124_usb2_port_map, +}; + +static int tegra124_ulpi_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra124_ulpi_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra124_ulpi_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "ulpi", port->index); +} + +static const struct tegra_xusb_port_ops tegra124_ulpi_port_ops = { + .enable = tegra124_ulpi_port_enable, + .disable = tegra124_ulpi_port_disable, + .map = tegra124_ulpi_port_map, +}; + +static int tegra124_hsic_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra124_hsic_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra124_hsic_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "hsic", port->index); +} + +static const struct tegra_xusb_port_ops tegra124_hsic_port_ops = { + .enable = tegra124_hsic_port_enable, + .disable = tegra124_hsic_port_disable, + .map = tegra124_hsic_port_map, +}; + +static int tegra124_usb3_port_enable(struct tegra_xusb_port *port) +{ + struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port); + struct tegra_xusb_padctl *padctl = port->padctl; + struct tegra_xusb_lane *lane = usb3->base.lane; + unsigned int index = port->index, offset; + int ret = 0; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); + + if (!usb3->internal) + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); + else + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); + + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index); + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, usb3->port); + padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP); + + /* + * TODO: move this code into the PCIe/SATA PHY ->power_on() callbacks + * and conditionalize based on mux function? This seems to work, but + * might not be the exact proper sequence. + */ + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index)); + value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT)); + value |= (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_VAL << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_WANDER_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_VAL << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_CDR_CNTL_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_VAL << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_SHIFT); + + if (usb3->context_saved) { + value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT)); + value |= (usb3->ctle_g << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_G_SHIFT) | + (usb3->ctle_z << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL2_RX_EQ_Z_SHIFT); + } + + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL2(index)); + + value = XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_VAL; + + if (usb3->context_saved) { + value &= ~((XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) | + (XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_MASK << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT)); + value |= (usb3->tap1 << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_TAP_SHIFT) | + (usb3->amp << + XUSB_PADCTL_IOPHY_USB3_PAD_CTL4_DFE_CNTL_AMP_SHIFT); + } + + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_USB3_PADX_CTL4(index)); + + if (lane->pad == padctl->pcie) + offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL2(lane->index); + else + offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL2; + + value = padctl_readl(padctl, offset); + value &= ~(XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_MASK << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_VAL << + XUSB_PADCTL_IOPHY_MISC_PAD_CTL2_SPARE_IN_SHIFT; + padctl_writel(padctl, value, offset); + + if (lane->pad == padctl->pcie) + offset = XUSB_PADCTL_IOPHY_MISC_PAD_PX_CTL5(lane->index); + else + offset = XUSB_PADCTL_IOPHY_MISC_PAD_S0_CTL5; + + value = padctl_readl(padctl, offset); + value |= XUSB_PADCTL_IOPHY_MISC_PAD_CTL5_RX_QEYE_EN; + padctl_writel(padctl, value, offset); + + /* Enable SATA PHY when SATA lane is used */ + if (lane->pad == padctl->sata) { + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + value &= ~(XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_MASK << + XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_SHIFT); + value |= 0x2 << + XUSB_PADCTL_IOPHY_PLL_S0_CTL1_PLL0_REFCLK_NDIV_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL2); + value &= ~((XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_MASK << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT) | + (XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_MASK << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT) | + (XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_MASK << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT) | + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TCLKOUT_EN); + value |= (0x7 << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_XDIGCLK_SEL_SHIFT) | + (0x8 << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL1_CP_CNTL_SHIFT) | + (0x8 << + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_PLL0_CP_CNTL_SHIFT) | + XUSB_PADCTL_IOPHY_PLL_S0_CTL2_TXCLKREF_SEL; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_IOPHY_PLL_S0_CTL3); + value &= ~XUSB_PADCTL_IOPHY_PLL_S0_CTL3_RCAL_BYPASS; + padctl_writel(padctl, value, XUSB_PADCTL_IOPHY_PLL_S0_CTL3); + } + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value &= ~XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + return ret; +} + +static void tegra124_usb3_port_disable(struct tegra_xusb_port *port) +{ + struct tegra_xusb_padctl *padctl = port->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN_EARLY(port->index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_CLAMP_EN(port->index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + usleep_range(250, 350); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM); + value |= XUSB_PADCTL_ELPG_PROGRAM_SSPX_ELPG_VCORE_DOWN(port->index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM); + + value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(port->index); + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(port->index, 0x7); + padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP); +} + +static const struct tegra_xusb_lane_map tegra124_usb3_map[] = { + { 0, "pcie", 0 }, + { 1, "pcie", 1 }, + { 1, "sata", 0 }, + { 0, NULL, 0 }, +}; + +static struct tegra_xusb_lane * +tegra124_usb3_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_port_find_lane(port, tegra124_usb3_map, "usb3-ss"); +} + +static const struct tegra_xusb_port_ops tegra124_usb3_port_ops = { + .enable = tegra124_usb3_port_enable, + .disable = tegra124_usb3_port_disable, + .map = tegra124_usb3_port_map, +}; + +static int +tegra124_xusb_read_fuse_calibration(struct tegra124_xusb_fuse_calibration *fuse) +{ + unsigned int i; + int err; + u32 value; + + err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(fuse->hs_curr_level); i++) { + fuse->hs_curr_level[i] = + (value >> FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(i)) & + FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK; + } + fuse->hs_iref_cap = + (value >> FUSE_SKU_CALIB_HS_IREF_CAP_SHIFT) & + FUSE_SKU_CALIB_HS_IREF_CAP_MASK; + fuse->hs_term_range_adj = + (value >> FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT) & + FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK; + fuse->hs_squelch_level = + (value >> FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_SHIFT) & + FUSE_SKU_CALIB_HS_SQUELCH_LEVEL_MASK; + + return 0; +} + +static struct tegra_xusb_padctl * +tegra124_xusb_padctl_probe(struct device *dev, + const struct tegra_xusb_padctl_soc *soc) +{ + struct tegra124_xusb_padctl *padctl; + int err; + + padctl = devm_kzalloc(dev, sizeof(*padctl), GFP_KERNEL); + if (!padctl) + return ERR_PTR(-ENOMEM); + + padctl->base.dev = dev; + padctl->base.soc = soc; + + err = tegra124_xusb_read_fuse_calibration(&padctl->fuse); + if (err < 0) + return ERR_PTR(err); + + return &padctl->base; +} + +static void tegra124_xusb_padctl_remove(struct tegra_xusb_padctl *padctl) +{ +} + +static const struct tegra_xusb_padctl_ops tegra124_xusb_padctl_ops = { + .probe = tegra124_xusb_padctl_probe, + .remove = tegra124_xusb_padctl_remove, + .usb3_save_context = tegra124_usb3_save_context, + .hsic_set_idle = tegra124_hsic_set_idle, +}; + +const struct tegra_xusb_padctl_soc tegra124_xusb_padctl_soc = { + .num_pads = ARRAY_SIZE(tegra124_pads), + .pads = tegra124_pads, + .ports = { + .usb2 = { + .ops = &tegra124_usb2_port_ops, + .count = 3, + }, + .ulpi = { + .ops = &tegra124_ulpi_port_ops, + .count = 1, + }, + .hsic = { + .ops = &tegra124_hsic_port_ops, + .count = 2, + }, + .usb3 = { + .ops = &tegra124_usb3_port_ops, + .count = 2, + }, + }, + .ops = &tegra124_xusb_padctl_ops, +}; +EXPORT_SYMBOL_GPL(tegra124_xusb_padctl_soc); + +MODULE_AUTHOR("Thierry Reding "); +MODULE_DESCRIPTION("NVIDIA Tegra 124 XUSB Pad Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c new file mode 100644 index 00000000000000..809998f6ce85ea --- /dev/null +++ b/drivers/phy/tegra/xusb.c @@ -0,0 +1,1017 @@ +/* + * Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "xusb.h" + +static struct phy *tegra_xusb_pad_of_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct tegra_xusb_pad *pad = dev_get_drvdata(dev); + struct phy *phy = NULL; + unsigned int i; + + if (args->args_count != 0) + return ERR_PTR(-EINVAL); + + for (i = 0; i < pad->soc->num_lanes; i++) { + if (!pad->lanes[i]) + continue; + + if (pad->lanes[i]->dev.of_node == args->np) { + phy = pad->lanes[i]; + break; + } + } + + if (phy == NULL) + phy = ERR_PTR(-ENODEV); + + return phy; +} + +static const struct of_device_id tegra_xusb_padctl_of_match[] = { +#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC) + { + .compatible = "nvidia,tegra124-xusb-padctl", + .data = &tegra124_xusb_padctl_soc, + }, +#endif +#if defined(CONFIG_ARCH_TEGRA_210_SOC) + { + .compatible = "nvidia,tegra210-xusb-padctl", + .data = &tegra210_xusb_padctl_soc, + }, +#endif + { } +}; +MODULE_DEVICE_TABLE(of, tegra_xusb_padctl_of_match); + +static struct device_node * +tegra_xusb_find_pad_node(struct tegra_xusb_padctl *padctl, const char *name) +{ + /* + * of_find_node_by_name() drops a reference, so make sure to grab one. + */ + struct device_node *np = of_node_get(padctl->dev->of_node); + + np = of_find_node_by_name(np, "pads"); + if (np) + np = of_find_node_by_name(np, name); + + return np; +} + +static struct device_node * +tegra_xusb_pad_find_phy_node(struct tegra_xusb_pad *pad, unsigned int index) +{ + /* + * of_find_node_by_name() drops a reference, so make sure to grab one. + */ + struct device_node *np = of_node_get(pad->dev.of_node); + char *name; + + name = kasprintf(GFP_KERNEL, "%s-%u", np->name, index); + if (!name) { + of_node_put(np); + return NULL; + } + + np = of_find_node_by_name(np, name); + + kfree(name); + + return np; +} + +int tegra_xusb_lane_lookup_function(struct tegra_xusb_lane *lane, + const char *function) +{ + unsigned int i; + + for (i = 0; i < lane->soc->num_funcs; i++) + if (strcmp(function, lane->soc->funcs[i]) == 0) + return i; + + return -EINVAL; +} + +int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane, + struct device_node *np) +{ + struct device *dev = &lane->pad->dev; + const char *function; + int err; + + err = of_property_read_string(np, "nvidia,function", &function); + if (err < 0) + return err; + + err = tegra_xusb_lane_lookup_function(lane, function); + if (err < 0) { + dev_err(dev, "invalid function \"%s\" for lane \"%s\"\n", + function, np->name); + return err; + } + + lane->function = err; + + return 0; +} + +static void tegra_xusb_lane_destroy(struct phy *phy) +{ + if (phy) { + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + lane->pad->ops->remove(lane); + phy_destroy(phy); + } +} + +static void tegra_xusb_pad_release(struct device *dev) +{ + struct tegra_xusb_pad *pad = to_tegra_xusb_pad(dev); + + pad->soc->ops->remove(pad); +} + +static struct device_type tegra_xusb_pad_type = { + .release = tegra_xusb_pad_release, +}; + +int tegra_xusb_pad_init(struct tegra_xusb_pad *pad, + struct tegra_xusb_padctl *padctl, + struct device_node *np) +{ + int err; + + device_initialize(&pad->dev); + INIT_LIST_HEAD(&pad->list); + pad->dev.parent = padctl->dev; + pad->dev.type = &tegra_xusb_pad_type; + pad->dev.of_node = np; + pad->padctl = padctl; + + err = dev_set_name(&pad->dev, "%s", pad->soc->name); + if (err < 0) + goto unregister; + + err = device_add(&pad->dev); + if (err < 0) + goto unregister; + + return 0; + +unregister: + device_unregister(&pad->dev); + return err; +} + +int tegra_xusb_pad_register(struct tegra_xusb_pad *pad, + const struct phy_ops *ops) +{ + struct phy *lane; + unsigned int i; + int err; + + pad->lanes = devm_kcalloc(&pad->dev, pad->soc->num_lanes, sizeof(lane), + GFP_KERNEL); + if (!pad->lanes) + return -ENOMEM; + + for (i = 0; i < pad->soc->num_lanes; i++) { + struct device_node *np = tegra_xusb_pad_find_phy_node(pad, i); + struct tegra_xusb_lane *lane; + + /* skip disabled lanes */ + if (!np || !of_device_is_available(np)) + continue; + + pad->lanes[i] = phy_create(&pad->dev, np, ops); + if (IS_ERR(pad->lanes[i])) { + err = PTR_ERR(pad->lanes[i]); + goto remove; + } + + lane = pad->ops->probe(pad, np, i); + if (IS_ERR(lane)) { + phy_destroy(pad->lanes[i]); + err = PTR_ERR(lane); + goto remove; + } + + list_add_tail(&lane->list, &pad->padctl->lanes); + phy_set_drvdata(pad->lanes[i], lane); + } + + pad->provider = of_phy_provider_register(&pad->dev, + tegra_xusb_pad_of_xlate); + if (IS_ERR(pad->provider)) { + err = PTR_ERR(pad->provider); + goto remove; + } + + return 0; + +remove: + while (i--) + tegra_xusb_lane_destroy(pad->lanes[i]); + + return err; +} + +void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad) +{ + unsigned int i = pad->soc->num_lanes; + + of_phy_provider_unregister(pad->provider); + + while (i--) + tegra_xusb_lane_destroy(pad->lanes[i]); + + device_unregister(&pad->dev); +} + +static struct tegra_xusb_pad * +tegra_xusb_pad_create(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc) +{ + struct tegra_xusb_pad *pad; + struct device_node *np; + int err; + + np = tegra_xusb_find_pad_node(padctl, soc->name); + if (!np || !of_device_is_available(np)) + return NULL; + + pad = soc->ops->probe(padctl, soc, np); + if (IS_ERR(pad)) { + err = PTR_ERR(pad); + dev_err(padctl->dev, "failed to create pad %s: %d\n", + soc->name, err); + return ERR_PTR(err); + } + + /* XXX move this into ->probe() to avoid string comparison */ + if (strcmp(soc->name, "pcie") == 0) + padctl->pcie = pad; + + if (strcmp(soc->name, "sata") == 0) + padctl->sata = pad; + + if (strcmp(soc->name, "usb2") == 0) + padctl->usb2 = pad; + + if (strcmp(soc->name, "ulpi") == 0) + padctl->ulpi = pad; + + if (strcmp(soc->name, "hsic") == 0) + padctl->hsic = pad; + + return pad; +} + +static void __tegra_xusb_remove_pads(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_pad *pad, *tmp; + + list_for_each_entry_safe_reverse(pad, tmp, &padctl->pads, list) { + list_del(&pad->list); + tegra_xusb_pad_unregister(pad); + } +} + +static void tegra_xusb_remove_pads(struct tegra_xusb_padctl *padctl) +{ + mutex_lock(&padctl->lock); + __tegra_xusb_remove_pads(padctl); + mutex_unlock(&padctl->lock); +} + +static void tegra_xusb_lane_program(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + const struct tegra_xusb_lane_soc *soc = lane->soc; + u32 value; + + /* choose function */ + value = padctl_readl(padctl, soc->offset); + value &= ~(soc->mask << soc->shift); + value |= lane->function << soc->shift; + padctl_writel(padctl, value, soc->offset); +} + +static void tegra_xusb_pad_program(struct tegra_xusb_pad *pad) +{ + unsigned int i; + + for (i = 0; i < pad->soc->num_lanes; i++) { + struct tegra_xusb_lane *lane; + + if (pad->lanes[i]) { + lane = phy_get_drvdata(pad->lanes[i]); + tegra_xusb_lane_program(lane); + } + } +} + +static int tegra_xusb_setup_pads(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_pad *pad; + unsigned int i; + + mutex_lock(&padctl->lock); + + for (i = 0; i < padctl->soc->num_pads; i++) { + const struct tegra_xusb_pad_soc *soc = padctl->soc->pads[i]; + int err; + + pad = tegra_xusb_pad_create(padctl, soc); + if (IS_ERR(pad)) { + err = PTR_ERR(pad); + dev_err(padctl->dev, "failed to create pad %s: %d\n", + soc->name, err); + __tegra_xusb_remove_pads(padctl); + mutex_unlock(&padctl->lock); + return err; + } + + if (!pad) + continue; + + list_add_tail(&pad->list, &padctl->pads); + } + + list_for_each_entry(pad, &padctl->pads, list) + tegra_xusb_pad_program(pad); + + mutex_unlock(&padctl->lock); + return 0; +} + +static bool tegra_xusb_lane_check(struct tegra_xusb_lane *lane, + const char *function) +{ + if (lane) { + const char *func = lane->soc->funcs[lane->function]; + + return strcmp(function, func) == 0; + } + + return false; +} + +struct tegra_xusb_lane *tegra_xusb_find_lane(struct tegra_xusb_padctl *padctl, + const char *type, + unsigned int index) +{ + struct tegra_xusb_lane *lane, *hit = NULL; + char *name; + + name = kasprintf(GFP_KERNEL, "%s-%u", type, index); + if (!name) + return NULL; + + list_for_each_entry(lane, &padctl->lanes, list) { + if (strcmp(lane->soc->name, name) == 0) { + hit = lane; + break; + } + } + + kfree(name); + return hit; +} + +struct tegra_xusb_lane * +tegra_xusb_port_find_lane(struct tegra_xusb_port *port, + const struct tegra_xusb_lane_map *map, + const char *function) +{ + struct tegra_xusb_lane *lane, *match = NULL; + + for (map = map; map->type; map++) { + if (port->index != map->port) + continue; + + lane = tegra_xusb_find_lane(port->padctl, map->type, + map->index); + if (!tegra_xusb_lane_check(lane, function)) + continue; + + if (match) + dev_err(&port->dev, "conflicting match: %s-%u / %s\n", + map->type, map->index, match->soc->name); + else + match = lane; + } + + return match; +} + +static struct device_node * +tegra_xusb_find_port_node(struct tegra_xusb_padctl *padctl, const char *type, + unsigned int index) +{ + /* + * of_find_node_by_name() drops a reference, so make sure to grab one. + */ + struct device_node *np = of_node_get(padctl->dev->of_node); + + np = of_find_node_by_name(np, "ports"); + if (np) { + char *name; + + name = kasprintf(GFP_KERNEL, "%s-%u", type, index); + np = of_find_node_by_name(np, name); + kfree(name); + } + + return np; +} + +struct tegra_xusb_port * +tegra_xusb_find_port(struct tegra_xusb_padctl *padctl, const char *type, + unsigned int index) +{ + struct tegra_xusb_port *port; + struct device_node *np; + + np = tegra_xusb_find_port_node(padctl, type, index); + if (!np) + return NULL; + + list_for_each_entry(port, &padctl->ports, list) { + if (np == port->dev.of_node) { + of_node_put(np); + return port; + } + } + + of_node_put(np); + + return NULL; +} + +struct tegra_xusb_usb2_port * +tegra_xusb_find_usb2_port(struct tegra_xusb_padctl *padctl, unsigned int index) +{ + struct tegra_xusb_port *port; + + port = tegra_xusb_find_port(padctl, "usb2", index); + if (port) + return to_usb2_port(port); + + return NULL; +} + +struct tegra_xusb_usb3_port * +tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl, unsigned int index) +{ + struct tegra_xusb_port *port; + + port = tegra_xusb_find_port(padctl, "usb3", index); + if (port) + return to_usb3_port(port); + + return NULL; +} + +static void tegra_xusb_port_release(struct device *dev) +{ +} + +static struct device_type tegra_xusb_port_type = { + .release = tegra_xusb_port_release, +}; + +static int tegra_xusb_port_init(struct tegra_xusb_port *port, + struct tegra_xusb_padctl *padctl, + struct device_node *np, + const char *name, + unsigned int index) +{ + int err; + + INIT_LIST_HEAD(&port->list); + port->padctl = padctl; + port->index = index; + + device_initialize(&port->dev); + port->dev.type = &tegra_xusb_port_type; + port->dev.of_node = of_node_get(np); + port->dev.parent = padctl->dev; + + err = dev_set_name(&port->dev, "%s-%u", name, index); + if (err < 0) + goto unregister; + + err = device_add(&port->dev); + if (err < 0) + goto unregister; + + return 0; + +unregister: + device_unregister(&port->dev); + return err; +} + +static void tegra_xusb_port_unregister(struct tegra_xusb_port *port) +{ + device_unregister(&port->dev); +} + +static int tegra_xusb_usb2_port_parse_dt(struct tegra_xusb_usb2_port *usb2) +{ + struct tegra_xusb_port *port = &usb2->base; + struct device_node *np = port->dev.of_node; + + usb2->internal = of_property_read_bool(np, "nvidia,internal"); + + usb2->supply = devm_regulator_get(&port->dev, "vbus"); + if (IS_ERR(usb2->supply)) + return PTR_ERR(usb2->supply); + + return 0; +} + +static int tegra_xusb_add_usb2_port(struct tegra_xusb_padctl *padctl, + unsigned int index) +{ + struct tegra_xusb_usb2_port *usb2; + struct device_node *np; + int err = 0; + + /* + * USB2 ports don't require additional properties, but if the port is + * marked as disabled there is no reason to register it. + */ + np = tegra_xusb_find_port_node(padctl, "usb2", index); + if (!np || !of_device_is_available(np)) + goto out; + + usb2 = devm_kzalloc(padctl->dev, sizeof(*usb2), GFP_KERNEL); + if (!usb2) { + err = -ENOMEM; + goto out; + } + + err = tegra_xusb_port_init(&usb2->base, padctl, np, "usb2", index); + if (err < 0) + goto out; + + usb2->base.ops = padctl->soc->ports.usb2.ops; + + usb2->base.lane = usb2->base.ops->map(&usb2->base); + if (IS_ERR(usb2->base.lane)) { + err = PTR_ERR(usb2->base.lane); + goto out; + } + + err = tegra_xusb_usb2_port_parse_dt(usb2); + if (err < 0) { + tegra_xusb_port_unregister(&usb2->base); + goto out; + } + + list_add_tail(&usb2->base.list, &padctl->ports); + +out: + of_node_put(np); + return err; +} + +static int tegra_xusb_ulpi_port_parse_dt(struct tegra_xusb_ulpi_port *ulpi) +{ + struct tegra_xusb_port *port = &ulpi->base; + struct device_node *np = port->dev.of_node; + + ulpi->internal = of_property_read_bool(np, "nvidia,internal"); + + return 0; +} + +static int tegra_xusb_add_ulpi_port(struct tegra_xusb_padctl *padctl, + unsigned int index) +{ + struct tegra_xusb_ulpi_port *ulpi; + struct device_node *np; + int err = 0; + + np = tegra_xusb_find_port_node(padctl, "ulpi", index); + if (!np || !of_device_is_available(np)) + goto out; + + ulpi = devm_kzalloc(padctl->dev, sizeof(*ulpi), GFP_KERNEL); + if (!ulpi) { + err = -ENOMEM; + goto out; + } + + err = tegra_xusb_port_init(&ulpi->base, padctl, np, "ulpi", index); + if (err < 0) + goto out; + + ulpi->base.ops = padctl->soc->ports.ulpi.ops; + + ulpi->base.lane = ulpi->base.ops->map(&ulpi->base); + if (IS_ERR(ulpi->base.lane)) { + err = PTR_ERR(ulpi->base.lane); + goto out; + } + + err = tegra_xusb_ulpi_port_parse_dt(ulpi); + if (err < 0) { + tegra_xusb_port_unregister(&ulpi->base); + goto out; + } + + list_add_tail(&ulpi->base.list, &padctl->ports); + +out: + of_node_put(np); + return err; +} + +static int tegra_xusb_hsic_port_parse_dt(struct tegra_xusb_hsic_port *hsic) +{ + /* XXX */ + return 0; +} + +static int tegra_xusb_add_hsic_port(struct tegra_xusb_padctl *padctl, + unsigned int index) +{ + struct tegra_xusb_hsic_port *hsic; + struct device_node *np; + int err = 0; + + np = tegra_xusb_find_port_node(padctl, "hsic", index); + if (!np || !of_device_is_available(np)) + goto out; + + hsic = devm_kzalloc(padctl->dev, sizeof(*hsic), GFP_KERNEL); + if (!hsic) { + err = -ENOMEM; + goto out; + } + + err = tegra_xusb_port_init(&hsic->base, padctl, np, "hsic", index); + if (err < 0) + goto out; + + hsic->base.ops = padctl->soc->ports.hsic.ops; + + hsic->base.lane = hsic->base.ops->map(&hsic->base); + if (IS_ERR(hsic->base.lane)) { + err = PTR_ERR(hsic->base.lane); + goto out; + } + + err = tegra_xusb_hsic_port_parse_dt(hsic); + if (err < 0) { + tegra_xusb_port_unregister(&hsic->base); + goto out; + } + + list_add_tail(&hsic->base.list, &padctl->ports); + +out: + of_node_put(np); + return err; +} + +static int tegra_xusb_usb3_port_parse_dt(struct tegra_xusb_usb3_port *usb3) +{ + struct tegra_xusb_port *port = &usb3->base; + struct device_node *np = port->dev.of_node; + u32 value; + int err; + + err = of_property_read_u32(np, "nvidia,usb2-companion", &value); + if (err < 0) { + dev_err(&port->dev, "failed to read port: %d\n", err); + return err; + } + + usb3->port = value; + + usb3->internal = of_property_read_bool(np, "nvidia,internal"); + + usb3->supply = devm_regulator_get(&port->dev, "vbus"); + if (IS_ERR(usb3->supply)) + return PTR_ERR(usb3->supply); + + return 0; +} + +static int tegra_xusb_add_usb3_port(struct tegra_xusb_padctl *padctl, + unsigned int index) +{ + struct tegra_xusb_usb3_port *usb3; + struct device_node *np; + int err = 0; + + /* + * If there is no supplemental configuration in the device tree the + * port is unusable. But it is valid to configure only a single port, + * hence return 0 instead of an error to allow ports to be optional. + */ + np = tegra_xusb_find_port_node(padctl, "usb3", index); + if (!np || !of_device_is_available(np)) + goto out; + + usb3 = devm_kzalloc(padctl->dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) { + err = -ENOMEM; + goto out; + } + + err = tegra_xusb_port_init(&usb3->base, padctl, np, "usb3", index); + if (err < 0) + goto out; + + usb3->base.ops = padctl->soc->ports.usb3.ops; + + usb3->base.lane = usb3->base.ops->map(&usb3->base); + if (IS_ERR(usb3->base.lane)) { + err = PTR_ERR(usb3->base.lane); + goto out; + } + + err = tegra_xusb_usb3_port_parse_dt(usb3); + if (err < 0) { + tegra_xusb_port_unregister(&usb3->base); + goto out; + } + + list_add_tail(&usb3->base.list, &padctl->ports); + +out: + of_node_put(np); + return err; +} + +static void __tegra_xusb_remove_ports(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_port *port, *tmp; + + list_for_each_entry_safe_reverse(port, tmp, &padctl->ports, list) { + list_del(&port->list); + tegra_xusb_port_unregister(port); + } +} + +static int tegra_xusb_setup_ports(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_port *port; + unsigned int i; + int err = 0; + + mutex_lock(&padctl->lock); + + for (i = 0; i < padctl->soc->ports.usb2.count; i++) { + err = tegra_xusb_add_usb2_port(padctl, i); + if (err < 0) + goto remove_ports; + } + + for (i = 0; i < padctl->soc->ports.ulpi.count; i++) { + err = tegra_xusb_add_ulpi_port(padctl, i); + if (err < 0) + goto remove_ports; + } + + for (i = 0; i < padctl->soc->ports.hsic.count; i++) { + err = tegra_xusb_add_hsic_port(padctl, i); + if (err < 0) + goto remove_ports; + } + + for (i = 0; i < padctl->soc->ports.usb3.count; i++) { + err = tegra_xusb_add_usb3_port(padctl, i); + if (err < 0) + goto remove_ports; + } + + list_for_each_entry(port, &padctl->ports, list) { + err = port->ops->enable(port); + if (err < 0) + dev_err(padctl->dev, "failed to enable port %s: %d\n", + dev_name(&port->dev), err); + } + + goto unlock; + +remove_ports: + __tegra_xusb_remove_ports(padctl); +unlock: + mutex_unlock(&padctl->lock); + return err; +} + +static void tegra_xusb_remove_ports(struct tegra_xusb_padctl *padctl) +{ + mutex_lock(&padctl->lock); + __tegra_xusb_remove_ports(padctl); + mutex_unlock(&padctl->lock); +} + +static int tegra_xusb_padctl_probe(struct platform_device *pdev) +{ + struct device_node *np = of_node_get(pdev->dev.of_node); + const struct tegra_xusb_padctl_soc *soc; + struct tegra_xusb_padctl *padctl; + const struct of_device_id *match; + struct resource *res; + int err; + + /* for backwards compatibility with old device trees */ + np = of_find_node_by_name(np, "pads"); + if (!np) { + dev_warn(&pdev->dev, "deprecated DT, using legacy driver\n"); + return tegra_xusb_padctl_legacy_probe(pdev); + } + + of_node_put(np); + + match = of_match_node(tegra_xusb_padctl_of_match, pdev->dev.of_node); + soc = match->data; + + padctl = soc->ops->probe(&pdev->dev, soc); + if (IS_ERR(padctl)) + return PTR_ERR(padctl); + + platform_set_drvdata(pdev, padctl); + INIT_LIST_HEAD(&padctl->ports); + INIT_LIST_HEAD(&padctl->lanes); + INIT_LIST_HEAD(&padctl->pads); + mutex_init(&padctl->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + padctl->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(padctl->regs)) { + err = PTR_ERR(padctl->regs); + goto remove; + } + + padctl->rst = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(padctl->rst)) { + err = PTR_ERR(padctl->rst); + goto remove; + } + + err = reset_control_deassert(padctl->rst); + if (err < 0) + goto remove; + + err = tegra_xusb_setup_pads(padctl); + if (err < 0) { + dev_err(&pdev->dev, "failed to setup pads: %d\n", err); + goto reset; + } + + err = tegra_xusb_setup_ports(padctl); + if (err) { + dev_err(&pdev->dev, "failed to setup XUSB ports: %d\n", err); + goto remove_pads; + } + + return 0; + +remove_pads: + tegra_xusb_remove_pads(padctl); +reset: + reset_control_assert(padctl->rst); +remove: + soc->ops->remove(padctl); + return err; +} + +static int tegra_xusb_padctl_remove(struct platform_device *pdev) +{ + struct tegra_xusb_padctl *padctl = platform_get_drvdata(pdev); + int err; + + tegra_xusb_remove_ports(padctl); + tegra_xusb_remove_pads(padctl); + + err = reset_control_assert(padctl->rst); + if (err < 0) + dev_err(&pdev->dev, "failed to assert reset: %d\n", err); + + padctl->soc->ops->remove(padctl); + + return err; +} + +static struct platform_driver tegra_xusb_padctl_driver = { + .driver = { + .name = "tegra-xusb-padctl", + .of_match_table = tegra_xusb_padctl_of_match, + }, + .probe = tegra_xusb_padctl_probe, + .remove = tegra_xusb_padctl_remove, +}; +module_platform_driver(tegra_xusb_padctl_driver); + +struct tegra_xusb_padctl *tegra_xusb_padctl_get(struct device *dev) +{ + struct tegra_xusb_padctl *padctl; + struct platform_device *pdev; + struct device_node *np; + + np = of_parse_phandle(dev->of_node, "nvidia,xusb-padctl", 0); + if (!np) + return ERR_PTR(-EINVAL); + + /* + * This is slightly ugly. A better implementation would be to keep a + * registry of pad controllers, but since there will almost certainly + * only ever be one per SoC that would be a little overkill. + */ + pdev = of_find_device_by_node(np); + if (!pdev) { + of_node_put(np); + return ERR_PTR(-ENODEV); + } + + of_node_put(np); + + padctl = platform_get_drvdata(pdev); + if (!padctl) { + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + return padctl; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_get); + +void tegra_xusb_padctl_put(struct tegra_xusb_padctl *padctl) +{ + if (padctl) + put_device(padctl->dev); +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_put); + +int tegra_xusb_padctl_usb3_save_context(struct tegra_xusb_padctl *padctl, + unsigned int port) +{ + if (padctl->soc->ops->usb3_save_context) + return padctl->soc->ops->usb3_save_context(padctl, port); + + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_usb3_save_context); + +int tegra_xusb_padctl_hsic_set_idle(struct tegra_xusb_padctl *padctl, + unsigned int port, bool idle) +{ + if (padctl->soc->ops->hsic_set_idle) + return padctl->soc->ops->hsic_set_idle(padctl, port, idle); + + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_hsic_set_idle); + +int tegra_xusb_padctl_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl, + unsigned int port, bool enable) +{ + if (padctl->soc->ops->usb3_set_lfps_detect) + return padctl->soc->ops->usb3_set_lfps_detect(padctl, port, + enable); + + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_usb3_set_lfps_detect); + +MODULE_AUTHOR("Thierry Reding "); +MODULE_DESCRIPTION("Tegra XUSB Pad Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h new file mode 100644 index 00000000000000..b49dbc36efa32e --- /dev/null +++ b/drivers/phy/tegra/xusb.h @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2015, Google Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef __PHY_TEGRA_XUSB_H +#define __PHY_TEGRA_XUSB_H + +#include +#include +#include + +/* legacy entry points for backwards-compatibility */ +int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev); +int tegra_xusb_padctl_legacy_remove(struct platform_device *pdev); + +struct phy; +struct phy_provider; +struct platform_device; +struct regulator; + +/* + * lanes + */ +struct tegra_xusb_lane_soc { + const char *name; + + unsigned int offset; + unsigned int shift; + unsigned int mask; + + const char * const *funcs; + unsigned int num_funcs; +}; + +struct tegra_xusb_lane { + const struct tegra_xusb_lane_soc *soc; + struct tegra_xusb_pad *pad; + struct device_node *np; + struct list_head list; + unsigned int function; + unsigned int index; +}; + +int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane, + struct device_node *np); + +struct tegra_xusb_usb2_lane { + struct tegra_xusb_lane base; + + u32 hs_curr_level_offset; +}; + +static inline struct tegra_xusb_usb2_lane * +to_usb2_lane(struct tegra_xusb_lane *lane) +{ + return container_of(lane, struct tegra_xusb_usb2_lane, base); +} + +struct tegra_xusb_ulpi_lane { + struct tegra_xusb_lane base; +}; + +static inline struct tegra_xusb_ulpi_lane * +to_ulpi_lane(struct tegra_xusb_lane *lane) +{ + return container_of(lane, struct tegra_xusb_ulpi_lane, base); +} + +struct tegra_xusb_hsic_lane { + struct tegra_xusb_lane base; + + u32 strobe_trim; + u32 rx_strobe_trim; + u32 rx_data_trim; + u32 tx_rtune_n; + u32 tx_rtune_p; + u32 tx_rslew_n; + u32 tx_rslew_p; + bool auto_term; +}; + +static inline struct tegra_xusb_hsic_lane * +to_hsic_lane(struct tegra_xusb_lane *lane) +{ + return container_of(lane, struct tegra_xusb_hsic_lane, base); +} + +struct tegra_xusb_pcie_lane { + struct tegra_xusb_lane base; +}; + +static inline struct tegra_xusb_pcie_lane * +to_pcie_lane(struct tegra_xusb_lane *lane) +{ + return container_of(lane, struct tegra_xusb_pcie_lane, base); +} + +struct tegra_xusb_sata_lane { + struct tegra_xusb_lane base; +}; + +static inline struct tegra_xusb_sata_lane * +to_sata_lane(struct tegra_xusb_lane *lane) +{ + return container_of(lane, struct tegra_xusb_sata_lane, base); +} + +struct tegra_xusb_lane_ops { + struct tegra_xusb_lane *(*probe)(struct tegra_xusb_pad *pad, + struct device_node *np, + unsigned int index); + void (*remove)(struct tegra_xusb_lane *lane); +}; + +/* + * pads + */ +struct tegra_xusb_pad_soc; +struct tegra_xusb_padctl; + +struct tegra_xusb_pad_ops { + struct tegra_xusb_pad *(*probe)(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np); + void (*remove)(struct tegra_xusb_pad *pad); +}; + +struct tegra_xusb_pad_soc { + const char *name; + + const struct tegra_xusb_lane_soc *lanes; + unsigned int num_lanes; + + const struct tegra_xusb_pad_ops *ops; +}; + +struct tegra_xusb_pad { + const struct tegra_xusb_pad_soc *soc; + struct tegra_xusb_padctl *padctl; + struct phy_provider *provider; + struct phy **lanes; + struct device dev; + + const struct tegra_xusb_lane_ops *ops; + + struct list_head list; +}; + +static inline struct tegra_xusb_pad *to_tegra_xusb_pad(struct device *dev) +{ + return container_of(dev, struct tegra_xusb_pad, dev); +} + +int tegra_xusb_pad_init(struct tegra_xusb_pad *pad, + struct tegra_xusb_padctl *padctl, + struct device_node *np); +int tegra_xusb_pad_register(struct tegra_xusb_pad *pad, + const struct phy_ops *ops); +void tegra_xusb_pad_unregister(struct tegra_xusb_pad *pad); + +struct tegra_xusb_usb2_pad { + struct tegra_xusb_pad base; + + struct clk *clk; + unsigned int enable; + struct mutex lock; +}; + +static inline struct tegra_xusb_usb2_pad * +to_usb2_pad(struct tegra_xusb_pad *pad) +{ + return container_of(pad, struct tegra_xusb_usb2_pad, base); +} + +struct tegra_xusb_ulpi_pad { + struct tegra_xusb_pad base; +}; + +static inline struct tegra_xusb_ulpi_pad * +to_ulpi_pad(struct tegra_xusb_pad *pad) +{ + return container_of(pad, struct tegra_xusb_ulpi_pad, base); +} + +struct tegra_xusb_hsic_pad { + struct tegra_xusb_pad base; + + struct regulator *supply; + struct clk *clk; +}; + +static inline struct tegra_xusb_hsic_pad * +to_hsic_pad(struct tegra_xusb_pad *pad) +{ + return container_of(pad, struct tegra_xusb_hsic_pad, base); +} + +struct tegra_xusb_pcie_pad { + struct tegra_xusb_pad base; + + struct reset_control *rst; + struct clk *pll; + + unsigned int enable; +}; + +static inline struct tegra_xusb_pcie_pad * +to_pcie_pad(struct tegra_xusb_pad *pad) +{ + return container_of(pad, struct tegra_xusb_pcie_pad, base); +} + +struct tegra_xusb_sata_pad { + struct tegra_xusb_pad base; + + struct reset_control *rst; + struct clk *pll; + + unsigned int enable; +}; + +static inline struct tegra_xusb_sata_pad * +to_sata_pad(struct tegra_xusb_pad *pad) +{ + return container_of(pad, struct tegra_xusb_sata_pad, base); +} + +/* + * ports + */ +struct tegra_xusb_port_ops; + +struct tegra_xusb_port { + struct tegra_xusb_padctl *padctl; + struct tegra_xusb_lane *lane; + unsigned int index; + + struct list_head list; + struct device dev; + + const struct tegra_xusb_port_ops *ops; +}; + +struct tegra_xusb_lane_map { + unsigned int port; + const char *type; + unsigned int index; + const char *func; +}; + +struct tegra_xusb_lane * +tegra_xusb_port_find_lane(struct tegra_xusb_port *port, + const struct tegra_xusb_lane_map *map, + const char *function); + +struct tegra_xusb_port * +tegra_xusb_find_port(struct tegra_xusb_padctl *padctl, const char *type, + unsigned int index); + +struct tegra_xusb_usb2_port { + struct tegra_xusb_port base; + + struct regulator *supply; + bool internal; +}; + +static inline struct tegra_xusb_usb2_port * +to_usb2_port(struct tegra_xusb_port *port) +{ + return container_of(port, struct tegra_xusb_usb2_port, base); +} + +struct tegra_xusb_usb2_port * +tegra_xusb_find_usb2_port(struct tegra_xusb_padctl *padctl, + unsigned int index); + +struct tegra_xusb_ulpi_port { + struct tegra_xusb_port base; + + struct regulator *supply; + bool internal; +}; + +static inline struct tegra_xusb_ulpi_port * +to_ulpi_port(struct tegra_xusb_port *port) +{ + return container_of(port, struct tegra_xusb_ulpi_port, base); +} + +struct tegra_xusb_hsic_port { + struct tegra_xusb_port base; +}; + +static inline struct tegra_xusb_hsic_port * +to_hsic_port(struct tegra_xusb_port *port) +{ + return container_of(port, struct tegra_xusb_hsic_port, base); +} + +struct tegra_xusb_usb3_port { + struct tegra_xusb_port base; + struct regulator *supply; + bool context_saved; + unsigned int port; + bool internal; + + u32 tap1; + u32 amp; + u32 ctle_z; + u32 ctle_g; +}; + +static inline struct tegra_xusb_usb3_port * +to_usb3_port(struct tegra_xusb_port *port) +{ + return container_of(port, struct tegra_xusb_usb3_port, base); +} + +struct tegra_xusb_usb3_port * +tegra_xusb_find_usb3_port(struct tegra_xusb_padctl *padctl, + unsigned int index); + +struct tegra_xusb_port_ops { + int (*enable)(struct tegra_xusb_port *port); + void (*disable)(struct tegra_xusb_port *port); + struct tegra_xusb_lane *(*map)(struct tegra_xusb_port *port); +}; + +/* + * pad controller + */ +struct tegra_xusb_padctl_soc; + +struct tegra_xusb_padctl_ops { + struct tegra_xusb_padctl * + (*probe)(struct device *dev, + const struct tegra_xusb_padctl_soc *soc); + void (*remove)(struct tegra_xusb_padctl *padctl); + + int (*usb3_save_context)(struct tegra_xusb_padctl *padctl, + unsigned int index); + int (*hsic_set_idle)(struct tegra_xusb_padctl *padctl, + unsigned int index, bool idle); + int (*usb3_set_lfps_detect)(struct tegra_xusb_padctl *padctl, + unsigned int index, bool enable); +}; + +struct tegra_xusb_padctl_soc { + const struct tegra_xusb_pad_soc * const *pads; + unsigned int num_pads; + + struct { + struct { + const struct tegra_xusb_port_ops *ops; + unsigned int count; + } usb2, ulpi, hsic, usb3; + } ports; + + const struct tegra_xusb_padctl_ops *ops; +}; + +struct tegra_xusb_padctl { + struct device *dev; + void __iomem *regs; + struct mutex lock; + struct reset_control *rst; + + const struct tegra_xusb_padctl_soc *soc; + + struct tegra_xusb_pad *pcie; + struct tegra_xusb_pad *sata; + struct tegra_xusb_pad *ulpi; + struct tegra_xusb_pad *usb2; + struct tegra_xusb_pad *hsic; + + struct list_head ports; + struct list_head lanes; + struct list_head pads; + + unsigned int enable; + + struct clk *clk; +}; + +static inline void padctl_writel(struct tegra_xusb_padctl *padctl, u32 value, + unsigned long offset) +{ + dev_dbg(padctl->dev, "%08lx < %08x\n", offset, value); + writel(value, padctl->regs + offset); +} + +static inline u32 padctl_readl(struct tegra_xusb_padctl *padctl, + unsigned long offset) +{ + u32 value = readl(padctl->regs + offset); + dev_dbg(padctl->dev, "%08lx > %08x\n", offset, value); + return value; +} + +struct tegra_xusb_lane *tegra_xusb_find_lane(struct tegra_xusb_padctl *padctl, + const char *name, + unsigned int index); + +#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC) +extern const struct tegra_xusb_padctl_soc tegra124_xusb_padctl_soc; +#endif +#if defined(CONFIG_ARCH_TEGRA_210_SOC) +extern const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc; +#endif + +#endif /* __PHY_TEGRA_XUSB_H */ diff --git a/drivers/pinctrl/tegra/pinctrl-tegra-xusb.c b/drivers/pinctrl/tegra/pinctrl-tegra-xusb.c index 2f06029c940543..946cda3fee3535 100644 --- a/drivers/pinctrl/tegra/pinctrl-tegra-xusb.c +++ b/drivers/pinctrl/tegra/pinctrl-tegra-xusb.c @@ -873,7 +873,7 @@ static const struct of_device_id tegra_xusb_padctl_of_match[] = { }; MODULE_DEVICE_TABLE(of, tegra_xusb_padctl_of_match); -static int tegra_xusb_padctl_probe(struct platform_device *pdev) +int tegra_xusb_padctl_legacy_probe(struct platform_device *pdev) { struct tegra_xusb_padctl *padctl; const struct of_device_id *match; @@ -955,8 +955,9 @@ static int tegra_xusb_padctl_probe(struct platform_device *pdev) reset_control_assert(padctl->rst); return err; } +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_legacy_probe); -static int tegra_xusb_padctl_remove(struct platform_device *pdev) +int tegra_xusb_padctl_legacy_remove(struct platform_device *pdev) { struct tegra_xusb_padctl *padctl = platform_get_drvdata(pdev); int err; @@ -969,17 +970,4 @@ static int tegra_xusb_padctl_remove(struct platform_device *pdev) return err; } - -static struct platform_driver tegra_xusb_padctl_driver = { - .driver = { - .name = "tegra-xusb-padctl", - .of_match_table = tegra_xusb_padctl_of_match, - }, - .probe = tegra_xusb_padctl_probe, - .remove = tegra_xusb_padctl_remove, -}; -module_platform_driver(tegra_xusb_padctl_driver); - -MODULE_AUTHOR("Thierry Reding "); -MODULE_DESCRIPTION("Tegra 124 XUSB Pad Control driver"); -MODULE_LICENSE("GPL v2"); +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_legacy_remove); diff --git a/include/linux/phy/tegra/xusb.h b/include/linux/phy/tegra/xusb.h new file mode 100644 index 00000000000000..8e1a57a78d9fa7 --- /dev/null +++ b/include/linux/phy/tegra/xusb.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef PHY_TEGRA_XUSB_H +#define PHY_TEGRA_XUSB_H + +struct tegra_xusb_padctl; +struct device; + +struct tegra_xusb_padctl *tegra_xusb_padctl_get(struct device *dev); +void tegra_xusb_padctl_put(struct tegra_xusb_padctl *padctl); + +int tegra_xusb_padctl_usb3_save_context(struct tegra_xusb_padctl *padctl, + unsigned int port); +int tegra_xusb_padctl_hsic_set_idle(struct tegra_xusb_padctl *padctl, + unsigned int port, bool idle); +int tegra_xusb_padctl_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl, + unsigned int port, bool enable); + +#endif /* PHY_TEGRA_XUSB_H */ From e22cbfea851785c1ff62812241c4f7be08d7532c Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 4 Mar 2016 17:19:35 +0100 Subject: [PATCH 05/19] phy: tegra: Add Tegra210 support Add support for the XUSB pad controller found on Tegra210 SoCs. The hardware is roughly the same, but some of the registers have been moved around and the number and type of supported pads has changed. Signed-off-by: Thierry Reding --- drivers/phy/tegra/Makefile | 1 + drivers/phy/tegra/xusb-tegra210.c | 2041 +++++++++++++++++++++++++++++ include/soc/tegra/fuse.h | 1 + 3 files changed, 2043 insertions(+) create mode 100644 drivers/phy/tegra/xusb-tegra210.c diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile index 31150b4337cd3c..898589238fd94c 100644 --- a/drivers/phy/tegra/Makefile +++ b/drivers/phy/tegra/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_PHY_TEGRA_XUSB) += phy-tegra-xusb.o phy-tegra-xusb-y += xusb.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_124_SOC) += xusb-tegra124.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_SOC) += xusb-tegra124.o +phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c new file mode 100644 index 00000000000000..194ed98a5577d4 --- /dev/null +++ b/drivers/phy/tegra/xusb-tegra210.c @@ -0,0 +1,2041 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + * Copyright (C) 2015 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "xusb.h" + +#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(x) \ + ((x) ? (11 + ((x) - 1) * 6) : 0) +#define FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK 0x3f +#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT 7 +#define FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK 0xf + +#define FUSE_USB_CALIB_EXT_RPD_CTRL_SHIFT 0 +#define FUSE_USB_CALIB_EXT_RPD_CTRL_MASK 0x1f + +#define XUSB_PADCTL_USB2_PAD_MUX 0x004 +#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT 16 +#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_MASK 0x3 +#define XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_XUSB 0x1 +#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT 18 +#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK 0x3 +#define XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB 0x1 + +#define XUSB_PADCTL_USB2_PORT_CAP 0x008 +#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_HOST(x) (0x1 << ((x) * 4)) +#define XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_MASK(x) (0x3 << ((x) * 4)) + +#define XUSB_PADCTL_SS_PORT_MAP 0x014 +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(x) (1 << (((x) * 5) + 4)) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_SHIFT(x) ((x) * 5) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(x) (0x7 << ((x) * 5)) +#define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(x, v) (((v) & 0x7) << ((x) * 5)) + +#define XUSB_PADCTL_ELPG_PROGRAM1 0x024 +#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN (1 << 31) +#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 30) +#define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN (1 << 29) +#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(x) (1 << (2 + (x) * 3)) +#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(x) \ + (1 << (1 + (x) * 3)) +#define XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(x) (1 << ((x) * 3)) + +#define XUSB_PADCTL_USB3_PAD_MUX 0x028 +#define XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(x) (1 << (1 + (x))) +#define XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(x) (1 << (8 + (x))) + +#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(x) (0x084 + (x) * 0x40) +#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_SHIFT 7 +#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_MASK 0x3 +#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_FIX18 (1 << 6) + +#define XUSB_PADCTL_USB2_OTG_PADX_CTL0(x) (0x088 + (x) * 0x40) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI (1 << 29) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 (1 << 27) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD (1 << 26) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT 0 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK 0x3f + +#define XUSB_PADCTL_USB2_OTG_PADX_CTL1(x) (0x08c + (x) * 0x40) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT 26 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_MASK 0x1f +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT 3 +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK 0xf +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR (1 << 2) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_OVRD (1 << 1) +#define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_OVRD (1 << 0) + +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x284 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD (1 << 11) +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT 3 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK 0x7 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL 0x7 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT 0 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK 0x7 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_VAL 0x2 + +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1 0x288 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_PD_TRK (1 << 26) +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT 19 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_MASK 0x7f +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_VAL 0x0a +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT 12 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK 0x7f +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_VAL 0x1e + +#define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x300 + (x) * 0x20) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE (1 << 18) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 (1 << 17) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 (1 << 16) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE (1 << 15) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 (1 << 14) +#define XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 (1 << 13) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE (1 << 9) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 (1 << 8) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 (1 << 7) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE (1 << 6) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 (1 << 5) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 (1 << 4) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE (1 << 3) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 (1 << 2) +#define XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 (1 << 1) + +#define XUSB_PADCTL_HSIC_PADX_CTL1(x) (0x304 + (x) * 0x20) +#define XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT 0 +#define XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_MASK 0xf + +#define XUSB_PADCTL_HSIC_PADX_CTL2(x) (0x308 + (x) * 0x20) +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT 8 +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK 0xf +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT 0 +#define XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK 0xff + +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL 0x340 +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_PD_TRK (1 << 19) +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT 12 +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_MASK 0x7f +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_VAL 0x0a +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT 5 +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_MASK 0x7f +#define XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_VAL 0x1e + +#define XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL 0x344 + +#define XUSB_PADCTL_UPHY_PLL_P0_CTL1 0x360 +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT 20 +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK 0xff +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL 0x19 +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SATA_VAL 0x1e +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT 16 +#define XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK 0x3 +#define XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS (1 << 15) +#define XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD (1 << 4) +#define XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE (1 << 3) +#define XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT 1 +#define XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK 0x3 +#define XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ (1 << 0) + +#define XUSB_PADCTL_UPHY_PLL_P0_CTL2 0x364 +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT 4 +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK 0xffffff +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL 0x136 +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD (1 << 2) +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE (1 << 1) +#define XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN (1 << 0) + +#define XUSB_PADCTL_UPHY_PLL_P0_CTL4 0x36c +#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN (1 << 15) +#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT 12 +#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK 0x3 +#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL 0x2 +#define XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SATA_VAL 0x0 +#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN (1 << 8) +#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT 4 +#define XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK 0xf + +#define XUSB_PADCTL_UPHY_PLL_P0_CTL5 0x370 +#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT 16 +#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK 0xff +#define XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL 0x2a + +#define XUSB_PADCTL_UPHY_PLL_P0_CTL8 0x37c +#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE (1 << 31) +#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD (1 << 15) +#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN (1 << 13) +#define XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN (1 << 12) + +#define XUSB_PADCTL_UPHY_MISC_PAD_PX_CTL1(x) (0x460 + (x) * 0x40) +#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT 20 +#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_MASK 0x3 +#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_VAL 0x1 +#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN BIT(18) +#define XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD BIT(13) + +#define XUSB_PADCTL_UPHY_PLL_S0_CTL1 0x860 + +#define XUSB_PADCTL_UPHY_PLL_S0_CTL2 0x864 + +#define XUSB_PADCTL_UPHY_PLL_S0_CTL4 0x86c + +#define XUSB_PADCTL_UPHY_PLL_S0_CTL5 0x870 + +#define XUSB_PADCTL_UPHY_PLL_S0_CTL8 0x87c + +#define XUSB_PADCTL_UPHY_MISC_PAD_S0_CTL1 0x960 + +#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(x) (0xa60 + (x) * 0x40) +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT 16 +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_MASK 0x3 +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_VAL 0x2 + +#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(x) (0xa64 + (x) * 0x40) +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT 0 +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_MASK 0xffff +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_VAL 0x00fc + +#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL3(x) (0xa68 + (x) * 0x40) +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL3_RX_DFE_VAL 0xc0077f1f + +#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(x) (0xa6c + (x) * 0x40) +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT 16 +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_MASK 0xffff +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_VAL 0x01c7 + +#define XUSB_PADCTL_UPHY_USB3_PADX_ECTL6(x) (0xa74 + (x) * 0x40) +#define XUSB_PADCTL_UPHY_USB3_PAD_ECTL6_RX_EQ_CTRL_H_VAL 0xfcf01368 + +struct tegra210_xusb_fuse_calibration { + u32 hs_curr_level[4]; + u32 hs_term_range_adj; + u32 rpd_ctrl; +}; + +struct tegra210_xusb_padctl { + struct tegra_xusb_padctl base; + + struct tegra210_xusb_fuse_calibration fuse; +}; + +static inline struct tegra210_xusb_padctl * +to_tegra210_xusb_padctl(struct tegra_xusb_padctl *padctl) +{ + return container_of(padctl, struct tegra210_xusb_padctl, base); +} + +/* must be called under padctl->lock */ +static int tegra210_pex_uphy_enable(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(padctl->pcie); + unsigned long timeout; + u32 value; + int err; + + if (pcie->enable > 0) { + pcie->enable++; + return 0; + } + + err = clk_prepare_enable(pcie->pll); + if (err < 0) + return err; + + err = reset_control_deassert(pcie->rst); + if (err < 0) + goto disable; + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL << + XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL5); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL << + XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL5); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value |= XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL4); + value &= ~((XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) | + (XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT)); + value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL << + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) | + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL4); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value &= ~((XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT) | + (XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT)); + value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + usleep_range(10, 20); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL4); + value |= XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL4); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + if (value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + if (!(value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE)) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value |= XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + if (value & XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN | + XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + if (value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + if (!(value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE)) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + + tegra210_xusb_pll_hw_control_enable(); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_P0_CTL8); + + usleep_range(10, 20); + + tegra210_xusb_pll_hw_sequence_start(); + + pcie->enable++; + + return 0; + +reset: + reset_control_assert(pcie->rst); +disable: + clk_disable_unprepare(pcie->pll); + return err; +} + +static void tegra210_pex_uphy_disable(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(padctl->pcie); + + mutex_lock(&padctl->lock); + + if (WARN_ON(pcie->enable == 0)) + goto unlock; + + if (--pcie->enable > 0) + goto unlock; + + reset_control_assert(pcie->rst); + clk_disable_unprepare(pcie->pll); + +unlock: + mutex_unlock(&padctl->lock); +} + +/* must be called under padctl->lock */ +static int tegra210_sata_uphy_enable(struct tegra_xusb_padctl *padctl, bool usb) +{ + struct tegra_xusb_sata_pad *sata = to_sata_pad(padctl->sata); + unsigned long timeout; + u32 value; + int err; + + if (sata->enable > 0) { + sata->enable++; + return 0; + } + + err = clk_prepare_enable(sata->pll); + if (err < 0) + return err; + + err = reset_control_deassert(sata->rst); + if (err < 0) + goto disable; + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_VAL << + XUSB_PADCTL_UPHY_PLL_CTL2_CAL_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL5); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_VAL << + XUSB_PADCTL_UPHY_PLL_CTL5_DCO_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL5); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value |= XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL4); + value &= ~((XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT) | + (XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_MASK << + XUSB_PADCTL_UPHY_PLL_CTL4_REFCLK_SEL_SHIFT)); + value |= XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_EN; + + if (usb) + value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_USB_VAL << + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT); + else + value |= (XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SATA_VAL << + XUSB_PADCTL_UPHY_PLL_CTL4_TXCLKREF_SEL_SHIFT); + + /* XXX PLL0_XDIGCLK_EN */ + /* + value &= ~(1 << 19); + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL4); + */ + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value &= ~((XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_MDIV_SHIFT) | + (XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT)); + + if (usb) + value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_USB_VAL << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT; + else + value |= XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SATA_VAL << + XUSB_PADCTL_UPHY_PLL_CTL1_FREQ_NDIV_SHIFT; + + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_IDDQ; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value &= ~(XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_MASK << + XUSB_PADCTL_UPHY_PLL_CTL1_SLEEP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + usleep_range(10, 20); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL4); + value |= XUSB_PADCTL_UPHY_PLL_CTL4_REFCLKBUF_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL4); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + value |= XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + if (value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + if (!(value & XUSB_PADCTL_UPHY_PLL_CTL2_CAL_DONE)) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value |= XUSB_PADCTL_UPHY_PLL_CTL1_ENABLE; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + if (value & XUSB_PADCTL_UPHY_PLL_CTL1_LOCKDET_STATUS) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + value |= XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN | + XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + if (value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + + timeout = jiffies + msecs_to_jiffies(100); + + while (time_before(jiffies, timeout)) { + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + if (!(value & XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_DONE)) + break; + + usleep_range(10, 20); + } + + if (time_after_eq(jiffies, timeout)) { + err = -ETIMEDOUT; + goto reset; + } + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_CLK_EN; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + + tegra210_sata_pll_hw_control_enable(); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL1_PWR_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL2_CAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL2); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + value &= ~XUSB_PADCTL_UPHY_PLL_CTL8_RCAL_OVRD; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_PLL_S0_CTL8); + + usleep_range(10, 20); + + tegra210_sata_pll_hw_sequence_start(); + + sata->enable++; + + return 0; + +reset: + reset_control_assert(sata->rst); +disable: + clk_disable_unprepare(sata->pll); + return err; +} + +static void tegra210_sata_uphy_disable(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_sata_pad *sata = to_sata_pad(padctl->sata); + + mutex_lock(&padctl->lock); + + if (WARN_ON(sata->enable == 0)) + goto unlock; + + if (--sata->enable > 0) + goto unlock; + + reset_control_assert(sata->rst); + clk_disable_unprepare(sata->pll); + +unlock: + mutex_unlock(&padctl->lock); +} + +static int tegra210_xusb_padctl_enable(struct tegra_xusb_padctl *padctl) +{ + u32 value; + + mutex_lock(&padctl->lock); + + if (padctl->enable++ > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + +out: + mutex_unlock(&padctl->lock); + return 0; +} + +static int tegra210_xusb_padctl_disable(struct tegra_xusb_padctl *padctl) +{ + u32 value; + + mutex_lock(&padctl->lock); + + if (WARN_ON(padctl->enable == 0)) + goto out; + + if (--padctl->enable > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN; + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + +out: + mutex_unlock(&padctl->lock); + return 0; +} + +static int tegra210_hsic_set_idle(struct tegra_xusb_padctl *padctl, + unsigned int index, bool idle) +{ + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE); + + if (idle) + value |= XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE; + else + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE); + + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + + return 0; +} + +static int tegra210_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl, + unsigned int index, bool enable) +{ + struct tegra_xusb_port *port; + struct tegra_xusb_lane *lane; + u32 value, offset; + + port = tegra_xusb_find_port(padctl, "usb3", index); + if (!port) + return -ENODEV; + + lane = port->lane; + + if (lane->pad == padctl->pcie) + offset = XUSB_PADCTL_UPHY_MISC_PAD_PX_CTL1(lane->index); + else + offset = XUSB_PADCTL_UPHY_MISC_PAD_S0_CTL1; + + value = padctl_readl(padctl, offset); + + value &= ~((XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_MASK << + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT) | + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN | + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD); + + if (!enable) { + value |= (XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_VAL << + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_IDLE_MODE_SHIFT) | + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_TERM_EN | + XUSB_PADCTL_UPHY_MISC_PAD_CTL1_AUX_RX_MODE_OVRD; + } + + padctl_writel(padctl, value, offset); + + return 0; +} + +#define TEGRA210_LANE(_name, _offset, _shift, _mask, _type) \ + { \ + .name = _name, \ + .offset = _offset, \ + .shift = _shift, \ + .mask = _mask, \ + .num_funcs = ARRAY_SIZE(tegra210_##_type##_functions), \ + .funcs = tegra210_##_type##_functions, \ + } + +static const char *tegra210_usb2_functions[] = { + "snps", + "xusb", + "uart" +}; + +static const struct tegra_xusb_lane_soc tegra210_usb2_lanes[] = { + TEGRA210_LANE("usb2-0", 0x004, 0, 0x3, usb2), + TEGRA210_LANE("usb2-1", 0x004, 2, 0x3, usb2), + TEGRA210_LANE("usb2-2", 0x004, 4, 0x3, usb2), + TEGRA210_LANE("usb2-3", 0x004, 6, 0x3, usb2), +}; + +static struct tegra_xusb_lane * +tegra210_usb2_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_usb2_lane *usb2; + int err; + + usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&usb2->base.list); + usb2->base.soc = &pad->soc->lanes[index]; + usb2->base.index = index; + usb2->base.pad = pad; + usb2->base.np = np; + + err = tegra_xusb_lane_parse_dt(&usb2->base, np); + if (err < 0) { + kfree(usb2); + return ERR_PTR(err); + } + + return &usb2->base; +} + +static void tegra210_usb2_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane); + + kfree(usb2); +} + +static const struct tegra_xusb_lane_ops tegra210_usb2_lane_ops = { + .probe = tegra210_usb2_lane_probe, + .remove = tegra210_usb2_lane_remove, +}; + +static int tegra210_usb2_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX); + value &= ~(XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK << + XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT); + value |= XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB << + XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX); + + return tegra210_xusb_padctl_enable(padctl); +} + +static int tegra210_usb2_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra210_usb2_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_usb2_lane *usb2 = to_usb2_lane(lane); + struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra210_xusb_padctl *priv; + struct tegra_xusb_usb2_port *port; + unsigned int index = lane->index; + u32 value; + int err; + + port = tegra_xusb_find_usb2_port(padctl, index); + if (!port) { + dev_err(&phy->dev, "no port found for USB2 lane %u\n", index); + return -ENODEV; + } + + priv = to_tegra210_xusb_padctl(padctl); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) | + (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT)); + value |= (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_VAL << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_DISCON_LEVEL_SHIFT); + + if (tegra_sku_info.revision < TEGRA_REVISION_A02) + value |= + (XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_VAL << + XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT); + + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP); + value &= ~XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_MASK(index); + value |= XUSB_PADCTL_USB2_PORT_CAP_PORTX_CAP_HOST(index); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_PORT_CAP); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT) | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD2 | + XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD_ZI); + value |= (priv->fuse.hs_curr_level[index] + + usb2->hs_curr_level_offset) << + XUSB_PADCTL_USB2_OTG_PAD_CTL0_HS_CURR_LEVEL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + value &= ~((XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) | + (XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_MASK << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT) | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_OVRD | + XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_OVRD); + value |= (priv->fuse.hs_term_range_adj << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_TERM_RANGE_ADJ_SHIFT) | + (priv->fuse.rpd_ctrl << + XUSB_PADCTL_USB2_OTG_PAD_CTL1_RPD_CTRL_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + + value = padctl_readl(padctl, + XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index)); + value &= ~(XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_MASK << + XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_LEV_SHIFT); + value |= XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD_CTL1_VREG_FIX18; + padctl_writel(padctl, value, + XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index)); + + err = regulator_enable(port->supply); + if (err) + return err; + + mutex_lock(&padctl->lock); + + if (pad->enable > 0) { + pad->enable++; + mutex_unlock(&padctl->lock); + return 0; + } + + err = clk_prepare_enable(pad->clk); + if (err) + goto disable_regulator; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT) | + (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_MASK << + XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT)); + value |= (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_VAL << + XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT) | + (XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_VAL << + XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_DONE_RESET_TIMER_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value &= ~XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + + udelay(1); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + value &= ~XUSB_PADCTL_USB2_BIAS_PAD_CTL1_PD_TRK; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + + udelay(50); + + clk_disable_unprepare(pad->clk); + + pad->enable++; + mutex_unlock(&padctl->lock); + + return 0; + +disable_regulator: + regulator_disable(port->supply); + mutex_unlock(&padctl->lock); + return err; +} + +static int tegra210_usb2_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb2_port *port; + u32 value; + + port = tegra_xusb_find_usb2_port(padctl, lane->index); + if (!port) { + dev_err(&phy->dev, "no port found for USB2 lane %u\n", + lane->index); + return -ENODEV; + } + + mutex_lock(&padctl->lock); + + if (WARN_ON(pad->enable == 0)) + goto out; + + if (--pad->enable > 0) + goto out; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + value |= XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); + +out: + regulator_disable(port->supply); + mutex_unlock(&padctl->lock); + return 0; +} + +static const struct phy_ops tegra210_usb2_phy_ops = { + .init = tegra210_usb2_phy_init, + .exit = tegra210_usb2_phy_exit, + .power_on = tegra210_usb2_phy_power_on, + .power_off = tegra210_usb2_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra210_usb2_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_usb2_pad *usb2; + struct tegra_xusb_pad *pad; + int err; + + usb2 = kzalloc(sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return ERR_PTR(-ENOMEM); + + pad = &usb2->base; + pad->ops = &tegra210_usb2_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) + goto free; + + usb2->clk = devm_clk_get(&pad->dev, "trk"); + if (IS_ERR(usb2->clk)) { + err = PTR_ERR(usb2->clk); + dev_err(&pad->dev, "failed to get trk clock: %d\n", err); + goto unregister; + } + + err = tegra_xusb_pad_register(pad, &tegra210_usb2_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +free: + kfree(usb2); + return ERR_PTR(err); +} + +static void tegra210_usb2_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_usb2_pad *usb2 = to_usb2_pad(pad); + + kfree(usb2); +} + +static const struct tegra_xusb_pad_ops tegra210_usb2_ops = { + .probe = tegra210_usb2_pad_probe, + .remove = tegra210_usb2_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra210_usb2_pad = { + .name = "usb2", + .num_lanes = ARRAY_SIZE(tegra210_usb2_lanes), + .lanes = tegra210_usb2_lanes, + .ops = &tegra210_usb2_ops, +}; + +static const char *tegra210_hsic_functions[] = { + "snps", + "xusb", +}; + +static const struct tegra_xusb_lane_soc tegra210_hsic_lanes[] = { + TEGRA210_LANE("hsic-0", 0x004, 14, 0x1, hsic), +}; + +static struct tegra_xusb_lane * +tegra210_hsic_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_hsic_lane *hsic; + int err; + + hsic = kzalloc(sizeof(*hsic), GFP_KERNEL); + if (!hsic) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&hsic->base.list); + hsic->base.soc = &pad->soc->lanes[index]; + hsic->base.index = index; + hsic->base.pad = pad; + hsic->base.np = np; + + err = tegra_xusb_lane_parse_dt(&hsic->base, np); + if (err < 0) { + kfree(hsic); + return ERR_PTR(err); + } + + return &hsic->base; +} + +static void tegra210_hsic_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane); + + kfree(hsic); +} + +static const struct tegra_xusb_lane_ops tegra210_hsic_lane_ops = { + .probe = tegra210_hsic_lane_probe, + .remove = tegra210_hsic_lane_remove, +}; + +static int tegra210_hsic_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX); + value &= ~(XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_MASK << + XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT); + value |= XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_XUSB << + XUSB_PADCTL_USB2_PAD_MUX_HSIC_PAD_TRK_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX); + + return tegra210_xusb_padctl_enable(padctl); +} + +static int tegra210_hsic_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra210_hsic_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_hsic_lane *hsic = to_hsic_lane(lane); + struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra210_xusb_padctl *priv; + unsigned int index = lane->index; + u32 value; + int err; + + priv = to_tegra210_xusb_padctl(padctl); + + err = regulator_enable(pad->supply); + if (err) + return err; + + padctl_writel(padctl, hsic->strobe_trim, + XUSB_PADCTL_HSIC_STRB_TRIM_CONTROL); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_MASK << + XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT); + value |= (hsic->tx_rtune_p << + XUSB_PADCTL_HSIC_PAD_CTL1_TX_RTUNEP_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL2(index)); + value &= ~((XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_MASK << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_MASK << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT)); + value |= (hsic->rx_strobe_trim << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_STROBE_TRIM_SHIFT) | + (hsic->rx_data_trim << + XUSB_PADCTL_HSIC_PAD_CTL2_RX_DATA_TRIM_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL2(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + value &= ~(XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPU_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE); + value |= XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPD_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_RPD_STROBE; + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + + err = clk_prepare_enable(pad->clk); + if (err) + goto disable; + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PAD_TRK_CTL); + value &= ~((XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_MASK << + XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_MASK << + XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT)); + value |= (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_VAL << + XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_START_TIMER_SHIFT) | + (XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_VAL << + XUSB_PADCTL_HSIC_PAD_TRK_CTL_TRK_DONE_RESET_TIMER_SHIFT); + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PAD_TRK_CTL); + + udelay(1); + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PAD_TRK_CTL); + value &= ~XUSB_PADCTL_HSIC_PAD_TRK_CTL_PD_TRK; + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PAD_TRK_CTL); + + udelay(50); + + clk_disable_unprepare(pad->clk); + + return 0; + +disable: + regulator_disable(pad->supply); + return err; +} + +static int tegra210_hsic_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_hsic_pad *pad = to_hsic_pad(lane->pad); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_HSIC_PADX_CTL0(index)); + value |= XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_RX_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_ZI_STROBE | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA0 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_DATA1 | + XUSB_PADCTL_HSIC_PAD_CTL0_PD_TX_STROBE; + padctl_writel(padctl, value, XUSB_PADCTL_HSIC_PADX_CTL1(index)); + + regulator_disable(pad->supply); + + return 0; +} + +static const struct phy_ops tegra210_hsic_phy_ops = { + .init = tegra210_hsic_phy_init, + .exit = tegra210_hsic_phy_exit, + .power_on = tegra210_hsic_phy_power_on, + .power_off = tegra210_hsic_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra210_hsic_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_hsic_pad *hsic; + struct tegra_xusb_pad *pad; + int err; + + hsic = kzalloc(sizeof(*hsic), GFP_KERNEL); + if (!hsic) + return ERR_PTR(-ENOMEM); + + pad = &hsic->base; + pad->ops = &tegra210_hsic_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) + goto free; + + hsic->clk = devm_clk_get(&pad->dev, "trk"); + if (IS_ERR(hsic->clk)) { + err = PTR_ERR(hsic->clk); + dev_err(&pad->dev, "failed to get trk clock: %d\n", err); + goto unregister; + } + + err = tegra_xusb_pad_register(pad, &tegra210_hsic_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +free: + kfree(hsic); + return ERR_PTR(err); +} + +static void tegra210_hsic_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_hsic_pad *hsic = to_hsic_pad(pad); + + kfree(hsic); +} + +static const struct tegra_xusb_pad_ops tegra210_hsic_ops = { + .probe = tegra210_hsic_pad_probe, + .remove = tegra210_hsic_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra210_hsic_pad = { + .name = "hsic", + .num_lanes = ARRAY_SIZE(tegra210_hsic_lanes), + .lanes = tegra210_hsic_lanes, + .ops = &tegra210_hsic_ops, +}; + +static const char *tegra210_pcie_functions[] = { + "pcie-x1", + "usb3-ss", + "sata", + "pcie-x4", +}; + +static const struct tegra_xusb_lane_soc tegra210_pcie_lanes[] = { + TEGRA210_LANE("pcie-0", 0x028, 12, 0x3, pcie), + TEGRA210_LANE("pcie-1", 0x028, 14, 0x3, pcie), + TEGRA210_LANE("pcie-2", 0x028, 16, 0x3, pcie), + TEGRA210_LANE("pcie-3", 0x028, 18, 0x3, pcie), + TEGRA210_LANE("pcie-4", 0x028, 20, 0x3, pcie), + TEGRA210_LANE("pcie-5", 0x028, 22, 0x3, pcie), + TEGRA210_LANE("pcie-6", 0x028, 24, 0x3, pcie), +}; + +static struct tegra_xusb_lane * +tegra210_pcie_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_pcie_lane *pcie; + int err; + + pcie = kzalloc(sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&pcie->base.list); + pcie->base.soc = &pad->soc->lanes[index]; + pcie->base.index = index; + pcie->base.pad = pad; + pcie->base.np = np; + + err = tegra_xusb_lane_parse_dt(&pcie->base, np); + if (err < 0) { + kfree(pcie); + return ERR_PTR(err); + } + + return &pcie->base; +} + +static void tegra210_pcie_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_pcie_lane *pcie = to_pcie_lane(lane); + + kfree(pcie); +} + +static const struct tegra_xusb_lane_ops tegra210_pcie_lane_ops = { + .probe = tegra210_pcie_lane_probe, + .remove = tegra210_pcie_lane_remove, +}; + +static int tegra210_pcie_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra210_pcie_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra210_pcie_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + int err; + + mutex_lock(&padctl->lock); + + err = tegra210_pex_uphy_enable(padctl); + if (err < 0) + goto unlock; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value |= XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + +unlock: + mutex_unlock(&padctl->lock); + return err; +} + +static int tegra210_pcie_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value &= ~XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + tegra210_pex_uphy_disable(padctl); + + return 0; +} + +static const struct phy_ops tegra210_pcie_phy_ops = { + .init = tegra210_pcie_phy_init, + .exit = tegra210_pcie_phy_exit, + .power_on = tegra210_pcie_phy_power_on, + .power_off = tegra210_pcie_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra210_pcie_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_pcie_pad *pcie; + struct tegra_xusb_pad *pad; + int err; + + pcie = kzalloc(sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return ERR_PTR(-ENOMEM); + + pad = &pcie->base; + pad->ops = &tegra210_pcie_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) + goto free; + + pcie->pll = devm_clk_get(&pad->dev, "pll"); + if (IS_ERR(pcie->pll)) { + err = PTR_ERR(pcie->pll); + dev_err(&pad->dev, "failed to get PLL: %d\n", err); + goto unregister; + } + + pcie->rst = devm_reset_control_get(&pad->dev, "phy"); + if (IS_ERR(pcie->rst)) { + err = PTR_ERR(pcie->rst); + dev_err(&pad->dev, "failed to get PCIe pad reset: %d\n", err); + goto unregister; + } + + err = tegra_xusb_pad_register(pad, &tegra210_pcie_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +free: + kfree(pcie); + return ERR_PTR(err); +} + +static void tegra210_pcie_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(pad); + + kfree(pcie); +} + +static const struct tegra_xusb_pad_ops tegra210_pcie_ops = { + .probe = tegra210_pcie_pad_probe, + .remove = tegra210_pcie_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra210_pcie_pad = { + .name = "pcie", + .num_lanes = ARRAY_SIZE(tegra210_pcie_lanes), + .lanes = tegra210_pcie_lanes, + .ops = &tegra210_pcie_ops, +}; + +static const struct tegra_xusb_lane_soc tegra210_sata_lanes[] = { + TEGRA210_LANE("sata-0", 0x028, 30, 0x3, pcie), +}; + +static struct tegra_xusb_lane * +tegra210_sata_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, + unsigned int index) +{ + struct tegra_xusb_sata_lane *sata; + int err; + + sata = kzalloc(sizeof(*sata), GFP_KERNEL); + if (!sata) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&sata->base.list); + sata->base.soc = &pad->soc->lanes[index]; + sata->base.index = index; + sata->base.pad = pad; + sata->base.np = np; + + err = tegra_xusb_lane_parse_dt(&sata->base, np); + if (err < 0) { + kfree(sata); + return ERR_PTR(err); + } + + return &sata->base; +} + +static void tegra210_sata_lane_remove(struct tegra_xusb_lane *lane) +{ + struct tegra_xusb_sata_lane *sata = to_sata_lane(lane); + + kfree(sata); +} + +static const struct tegra_xusb_lane_ops tegra210_sata_lane_ops = { + .probe = tegra210_sata_lane_probe, + .remove = tegra210_sata_lane_remove, +}; + +static int tegra210_sata_phy_init(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_enable(lane->pad->padctl); +} + +static int tegra210_sata_phy_exit(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + + return tegra210_xusb_padctl_disable(lane->pad->padctl); +} + +static int tegra210_sata_phy_power_on(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + int err; + + mutex_lock(&padctl->lock); + + err = tegra210_sata_uphy_enable(padctl, false); + if (err < 0) + goto unlock; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value |= XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + +unlock: + mutex_unlock(&padctl->lock); + return err; +} + +static int tegra210_sata_phy_power_off(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value &= ~XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + + tegra210_sata_uphy_disable(lane->pad->padctl); + + return 0; +} + +static const struct phy_ops tegra210_sata_phy_ops = { + .init = tegra210_sata_phy_init, + .exit = tegra210_sata_phy_exit, + .power_on = tegra210_sata_phy_power_on, + .power_off = tegra210_sata_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct tegra_xusb_pad * +tegra210_sata_pad_probe(struct tegra_xusb_padctl *padctl, + const struct tegra_xusb_pad_soc *soc, + struct device_node *np) +{ + struct tegra_xusb_sata_pad *sata; + struct tegra_xusb_pad *pad; + int err; + + sata = kzalloc(sizeof(*sata), GFP_KERNEL); + if (!sata) + return ERR_PTR(-ENOMEM); + + pad = &sata->base; + pad->ops = &tegra210_sata_lane_ops; + pad->soc = soc; + + err = tegra_xusb_pad_init(pad, padctl, np); + if (err < 0) + goto free; + + sata->rst = devm_reset_control_get(&pad->dev, "phy"); + if (IS_ERR(sata->rst)) { + err = PTR_ERR(sata->rst); + dev_err(&pad->dev, "failed to get SATA pad reset: %d\n", err); + goto unregister; + } + + err = tegra_xusb_pad_register(pad, &tegra210_sata_phy_ops); + if (err < 0) + goto unregister; + + dev_set_drvdata(&pad->dev, pad); + + return pad; + +unregister: + device_unregister(&pad->dev); +free: + kfree(sata); + return ERR_PTR(err); +} + +static void tegra210_sata_pad_remove(struct tegra_xusb_pad *pad) +{ + struct tegra_xusb_sata_pad *sata = to_sata_pad(pad); + + kfree(sata); +} + +static const struct tegra_xusb_pad_ops tegra210_sata_ops = { + .probe = tegra210_sata_pad_probe, + .remove = tegra210_sata_pad_remove, +}; + +static const struct tegra_xusb_pad_soc tegra210_sata_pad = { + .name = "sata", + .num_lanes = ARRAY_SIZE(tegra210_sata_lanes), + .lanes = tegra210_sata_lanes, + .ops = &tegra210_sata_ops, +}; + +static const struct tegra_xusb_pad_soc * const tegra210_pads[] = { + &tegra210_usb2_pad, + &tegra210_hsic_pad, + &tegra210_pcie_pad, + &tegra210_sata_pad, +}; + +static int tegra210_usb2_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra210_usb2_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra210_usb2_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "usb2", port->index); +} + +static const struct tegra_xusb_port_ops tegra210_usb2_port_ops = { + .enable = tegra210_usb2_port_enable, + .disable = tegra210_usb2_port_disable, + .map = tegra210_usb2_port_map, +}; + +static int tegra210_hsic_port_enable(struct tegra_xusb_port *port) +{ + return 0; +} + +static void tegra210_hsic_port_disable(struct tegra_xusb_port *port) +{ +} + +static struct tegra_xusb_lane * +tegra210_hsic_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_find_lane(port->padctl, "hsic", port->index); +} + +static const struct tegra_xusb_port_ops tegra210_hsic_port_ops = { + .enable = tegra210_hsic_port_enable, + .disable = tegra210_hsic_port_disable, + .map = tegra210_hsic_port_map, +}; + +static int tegra210_usb3_port_enable(struct tegra_xusb_port *port) +{ + struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port); + struct tegra_xusb_padctl *padctl = port->padctl; + struct tegra_xusb_lane *lane = usb3->base.lane; + unsigned int index = port->index; + u32 value; + int err; + + value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); + + if (!usb3->internal) + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); + else + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); + + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index); + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, usb3->port); + padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP); + + /* + * TODO: move this code into the PCIe/SATA PHY ->power_on() callbacks + * and conditionalize based on mux function? This seems to work, but + * might not be the exact proper sequence. + */ + err = regulator_enable(usb3->supply); + if (err < 0) + return err; + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index)); + value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_MASK << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_VAL << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index)); + value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_MASK << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT); + value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_VAL << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index)); + + padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL3_RX_DFE_VAL, + XUSB_PADCTL_UPHY_USB3_PADX_ECTL3(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index)); + value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_MASK << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_VAL << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index)); + + padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL6_RX_EQ_CTRL_H_VAL, + XUSB_PADCTL_UPHY_USB3_PADX_ECTL6(index)); + + if (lane->pad == padctl->sata) + err = tegra210_sata_uphy_enable(padctl, true); + else + err = tegra210_pex_uphy_enable(padctl); + + if (err) { + dev_err(&port->dev, "%s: failed to enable UPHY: %d\n", + __func__, err); + return err; + } + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + return 0; +} + +static void tegra210_usb3_port_disable(struct tegra_xusb_port *port) +{ + struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port); + struct tegra_xusb_padctl *padctl = port->padctl; + struct tegra_xusb_lane *lane = port->lane; + unsigned int index = port->index; + u32 value; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(250, 350); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + if (lane->pad == padctl->sata) + tegra210_sata_uphy_disable(padctl); + else + tegra210_pex_uphy_disable(padctl); + + regulator_disable(usb3->supply); + + value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index); + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, 0x7); + padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP); +} + +static const struct tegra_xusb_lane_map tegra210_usb3_map[] = { + { 0, "pcie", 6 }, + { 1, "pcie", 5 }, + { 2, "pcie", 0 }, + { 2, "pcie", 3 }, + { 3, "pcie", 4 }, + { 3, "pcie", 4 }, + { 0, NULL, 0 } +}; + +static struct tegra_xusb_lane * +tegra210_usb3_port_map(struct tegra_xusb_port *port) +{ + return tegra_xusb_port_find_lane(port, tegra210_usb3_map, "usb3-ss"); +} + +static const struct tegra_xusb_port_ops tegra210_usb3_port_ops = { + .enable = tegra210_usb3_port_enable, + .disable = tegra210_usb3_port_disable, + .map = tegra210_usb3_port_map, +}; + +static int +tegra210_xusb_read_fuse_calibration(struct tegra210_xusb_fuse_calibration *fuse) +{ + unsigned int i; + u32 value; + int err; + + err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(fuse->hs_curr_level); i++) { + fuse->hs_curr_level[i] = + (value >> FUSE_SKU_CALIB_HS_CURR_LEVEL_PADX_SHIFT(i)) & + FUSE_SKU_CALIB_HS_CURR_LEVEL_PAD_MASK; + } + + fuse->hs_term_range_adj = + (value >> FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_SHIFT) & + FUSE_SKU_CALIB_HS_TERM_RANGE_ADJ_MASK; + + err = tegra_fuse_readl(TEGRA_FUSE_USB_CALIB_EXT_0, &value); + if (err < 0) + return err; + + fuse->rpd_ctrl = + (value >> FUSE_USB_CALIB_EXT_RPD_CTRL_SHIFT) & + FUSE_USB_CALIB_EXT_RPD_CTRL_MASK; + + return 0; +} + +static struct tegra_xusb_padctl * +tegra210_xusb_padctl_probe(struct device *dev, + const struct tegra_xusb_padctl_soc *soc) +{ + struct tegra210_xusb_padctl *padctl; + int err; + + padctl = devm_kzalloc(dev, sizeof(*padctl), GFP_KERNEL); + if (!padctl) + return ERR_PTR(-ENOMEM); + + padctl->base.dev = dev; + padctl->base.soc = soc; + + err = tegra210_xusb_read_fuse_calibration(&padctl->fuse); + if (err < 0) + return ERR_PTR(err); + + return &padctl->base; +} + +static void tegra210_xusb_padctl_remove(struct tegra_xusb_padctl *padctl) +{ +} + +static const struct tegra_xusb_padctl_ops tegra210_xusb_padctl_ops = { + .probe = tegra210_xusb_padctl_probe, + .remove = tegra210_xusb_padctl_remove, + .usb3_set_lfps_detect = tegra210_usb3_set_lfps_detect, + .hsic_set_idle = tegra210_hsic_set_idle, +}; + +const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc = { + .num_pads = ARRAY_SIZE(tegra210_pads), + .pads = tegra210_pads, + .ports = { + .usb2 = { + .ops = &tegra210_usb2_port_ops, + .count = 4, + }, + .hsic = { + .ops = &tegra210_hsic_port_ops, + .count = 1, + }, + .usb3 = { + .ops = &tegra210_usb3_port_ops, + .count = 4, + }, + }, + .ops = &tegra210_xusb_padctl_ops, +}; +EXPORT_SYMBOL_GPL(tegra210_xusb_padctl_soc); + +MODULE_AUTHOR("Andrew Bresticker "); +MODULE_DESCRIPTION("NVIDIA Tegra 210 XUSB Pad Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/soc/tegra/fuse.h b/include/soc/tegra/fuse.h index 961b821b6a46d0..b4c9219e7f9565 100644 --- a/include/soc/tegra/fuse.h +++ b/include/soc/tegra/fuse.h @@ -26,6 +26,7 @@ #define TEGRA_FUSE_SKU_CALIB_0 0xf0 #define TEGRA30_FUSE_SATA_CALIB 0x124 +#define TEGRA_FUSE_USB_CALIB_EXT_0 0x250 #ifndef __ASSEMBLY__ From 967d34bbff6d9200a209c19e491e97e7eb23909d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 4 Mar 2016 17:19:36 +0100 Subject: [PATCH 06/19] dt-bindings: usb: Add NVIDIA Tegra XUSB controller binding Add device-tree binding documentation for the XUSB controller present on Tegra124 and later SoCs. This controller supports USB 3.0 via an xHCI compliant interface. Based on work by Andrew Bresticker . Cc: Rob Herring Cc: Pawel Moll Cc: Mark Rutland Cc: Ian Campbell Cc: Kumar Gala Cc: Mathias Nyman Cc: Greg Kroah-Hartman Signed-off-by: Thierry Reding Acked-by: Stephen Warren --- .../bindings/usb/nvidia,tegra124-xusb.txt | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt b/Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt new file mode 100644 index 00000000000000..79616f9268d853 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt @@ -0,0 +1,108 @@ +NVIDIA Tegra xHCI controller +============================ + +The Tegra xHCI controller supports both USB2 and USB3 interfaces exposed by +the Tegra XUSB pad controller. + +Required properties: +-------------------- +- compatible: Must be: + - Tegra124: "nvidia,tegra124-xusb" + - Tegra132: "nvidia,tegra132-xusb", "nvidia,tegra124-xusb" +- reg: Must contain the base and length of the xHCI host registers, XUSB FPCI + registers and XUSB IPFS registers. +- reg-names: Must contain the following entries: + - "hcd" + - "fpci" + - "ipfs" +- interrupts: Must contain the xHCI host interrupt and the mailbox interrupt. +- clocks: Must contain an entry for each entry in clock-names. + See ../clock/clock-bindings.txt for details. +- clock-names: Must include the following entries: + - xusb_host + - xusb_host_src + - xusb_falcon_src + - xusb_ss + - xusb_ss_src + - xusb_ss_div2 + - xusb_hs_src + - xusb_fs_src + - pll_u_480m + - clk_m + - pll_e +- resets: Must contain an entry for each entry in reset-names. + See ../reset/reset.txt for details. +- reset-names: Must include the following entries: + - xusb_host + - xusb_ss + - xusb_src + Note that xusb_src is the shared reset for xusb_{ss,hs,fs,falcon,host}_src. +- nvidia,xusb-padctl: phandle to the XUSB pad controller that is used to + configure the USB pads used by the XHCI controller + +For Tegra124 and Tegra132: +- avddio-pex-supply: PCIe/USB3 analog logic power supply. Must supply 1.05 V. +- dvddio-pex-supply: PCIe/USB3 digital logic power supply. Must supply 1.05 V. +- avdd-usb-supply: USB controller power supply. Must supply 3.3 V. +- avdd-pll-utmip-supply: UTMI PLL power supply. Must supply 1.8 V. +- avdd-pll-erefe-supply: PLLE reference PLL power supply. Must supply 1.05 V. +- avdd-usb-ss-pll-supply: PCIe/USB3 PLL power supply. Must supply 1.05 V. +- hvdd-usb-ss-supply: High-voltage PCIe/USB3 power supply. Must supply 3.3 V. +- hvdd-usb-ss-pll-e-supply: High-voltage PLLE power supply. Must supply 3.3 V. + +Optional properties: +-------------------- +- phys: Must contain an entry for each entry in phy-names. + See ../phy/phy-bindings.txt for details. +- phy-names: Should include an entry for each PHY used by the controller. The + following PHYs are available: + - Tegra124: usb2-0, usb2-1, usb2-2, hsic-0, hsic-1, usb3-0, usb3-1 + - Tegra132: usb2-0, usb2-1, usb2-2, hsic-0, hsic-1, usb3-0, usb3-1 + +Example: +-------- + + usb@0,70090000 { + compatible = "nvidia,tegra124-xusb"; + reg = <0x0 0x70090000 0x0 0x8000>, + <0x0 0x70098000 0x0 0x1000>, + <0x0 0x70099000 0x0 0x1000>; + reg-names = "hcd", "fpci", "ipfs"; + + interrupts = , + ; + + clocks = <&tegra_car TEGRA124_CLK_XUSB_HOST>, + <&tegra_car TEGRA124_CLK_XUSB_HOST_SRC>, + <&tegra_car TEGRA124_CLK_XUSB_FALCON_SRC>, + <&tegra_car TEGRA124_CLK_XUSB_SS>, + <&tegra_car TEGRA124_CLK_XUSB_SS_DIV2>, + <&tegra_car TEGRA124_CLK_XUSB_SS_SRC>, + <&tegra_car TEGRA124_CLK_XUSB_HS_SRC>, + <&tegra_car TEGRA124_CLK_XUSB_FS_SRC>, + <&tegra_car TEGRA124_CLK_PLL_U_480M>, + <&tegra_car TEGRA124_CLK_CLK_M>, + <&tegra_car TEGRA124_CLK_PLL_E>; + clock-names = "xusb_host", "xusb_host_src", "xusb_falcon_src", + "xusb_ss", "xusb_ss_div2", "xusb_ss_src", + "xusb_hs_src", "xusb_fs_src", "pll_u_480m", + "clk_m", "pll_e"; + resets = <&tegra_car 89>, <&tegra_car 156>, <&tegra_car 143>; + reset-names = "xusb_host", "xusb_ss", "xusb_src"; + + nvidia,xusb-padctl = <&padctl>; + + phys = <&{/padctl@0,7009f000/pads/usb2/usb2-1}>, /* mini-PCIe USB */ + <&{/padctl@0,7009f000/pads/usb2/usb2-2}>, /* USB A */ + <&{/padctl@0,7009f000/pads/pcie/pcie-0}>; /* USB A */ + phy-names = "utmi-1", "utmi-2", "usb3-0"; + + avddio-pex-supply = <&vdd_1v05_run>; + dvddio-pex-supply = <&vdd_1v05_run>; + avdd-usb-supply = <&vdd_3v3_lp0>; + avdd-pll-utmip-supply = <&vddio_1v8>; + avdd-pll-erefe-supply = <&avdd_1v05_run>; + avdd-usb-ss-pll-supply = <&vdd_1v05_run>; + hvdd-usb-ss-supply = <&vdd_3v3_lp0>; + hvdd-usb-ss-pll-e-supply = <&vdd_3v3_lp0>; + }; From 28c9b04fd22b8380140f0aa8741a008d63211a1e Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 4 Mar 2016 17:19:37 +0100 Subject: [PATCH 07/19] dt-bindings: usb: xhci-tegra: Add Tegra210 XUSB controller support Extend the Tegra XUSB controller device tree binding with Tegra210 support. Signed-off-by: Thierry Reding Acked-by: Rob Herring Acked-by: Stephen Warren --- .../devicetree/bindings/usb/nvidia,tegra124-xusb.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt b/Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt index 79616f9268d853..d28295a3e55f14 100644 --- a/Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt @@ -9,6 +9,7 @@ Required properties: - compatible: Must be: - Tegra124: "nvidia,tegra124-xusb" - Tegra132: "nvidia,tegra132-xusb", "nvidia,tegra124-xusb" + - Tegra210: "nvidia,tegra210-xusb" - reg: Must contain the base and length of the xHCI host registers, XUSB FPCI registers and XUSB IPFS registers. - reg-names: Must contain the following entries: @@ -50,6 +51,15 @@ For Tegra124 and Tegra132: - hvdd-usb-ss-supply: High-voltage PCIe/USB3 power supply. Must supply 3.3 V. - hvdd-usb-ss-pll-e-supply: High-voltage PLLE power supply. Must supply 3.3 V. +For Tegra210: +- dvddio-pex-supply: PCIe/USB3 analog logic power supply. Must supply 1.05 V. +- hvddio-pex-supply: High-voltage PCIe/USB3 power supply. Must supply 1.8 V. +- avdd-usb-supply: USB controller power supply. Must supply 3.3 V. +- avdd-pll-utmip-supply: UTMI PLL power supply. Must supply 1.8 V. +- avdd-pll-uerefe-supply: PLLE reference PLL power supply. Must supply 1.05 V. +- dvdd-pex-pll-supply: PCIe/USB3 PLL power supply. Must supply 1.05 V. +- hvdd-pex-pll-e-supply: High-voltage PLLE power supply. Must supply 1.8 V. + Optional properties: -------------------- - phys: Must contain an entry for each entry in phy-names. @@ -58,6 +68,8 @@ Optional properties: following PHYs are available: - Tegra124: usb2-0, usb2-1, usb2-2, hsic-0, hsic-1, usb3-0, usb3-1 - Tegra132: usb2-0, usb2-1, usb2-2, hsic-0, hsic-1, usb3-0, usb3-1 + - Tegra210: usb2-0, usb2-1, usb2-2, usb2-3, hsic-0, usb3-0, usb3-1, usb3-2, + usb3-3 Example: -------- From 04cef5700ea112bdd70cbe59575f0c39264f8839 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 4 Mar 2016 17:19:38 +0100 Subject: [PATCH 08/19] usb: xhci: Add NVIDIA Tegra XUSB controller driver Add support for the on-chip XUSB controller present on Tegra SoCs. This controller, when loaded with external firmware, exposes an interface compliant with xHCI. This driver loads the firmware, starts the controller, and is able to service host-specific messages sent by the controller's firmware. The controller also supports USB device mode as well as powergating of the SuperSpeed and host-controller logic when not in use, but support for these is not yet implemented. Based on work by: Ajay Gupta Bharath Yadav Andrew Bresticker Cc: Greg Kroah-Hartman Cc: Mathias Nyman Signed-off-by: Thierry Reding --- drivers/usb/host/Kconfig | 9 + drivers/usb/host/Makefile | 1 + drivers/usb/host/xhci-tegra.c | 1332 +++++++++++++++++++++++++++++++++ 3 files changed, 1342 insertions(+) create mode 100644 drivers/usb/host/xhci-tegra.c diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 3050b18b24477c..6acac6201af0b1 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -69,6 +69,15 @@ config USB_XHCI_RCAR Say 'Y' to enable the support for the xHCI host controller found in Renesas R-Car ARM SoCs. +config USB_XHCI_TEGRA + tristate "xHCI support for NVIDIA Tegra SoCs" + depends on PHY_TEGRA_XUSB + depends on RESET_CONTROLLER + select FW_LOADER + ---help--- + Say 'Y' to enable the support for the xHCI host controller + found in NVIDIA Tegra124 and later SoCs. + endif # USB_XHCI_HCD config USB_EHCI_HCD diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index a9ddd3c9ec9499..6ef785b0ea8ff0 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o obj-$(CONFIG_USB_XHCI_MTK) += xhci-mtk.o +obj-$(CONFIG_USB_XHCI_TEGRA) += xhci-tegra.o obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c new file mode 100644 index 00000000000000..ab6f9856c5c4a6 --- /dev/null +++ b/drivers/usb/host/xhci-tegra.c @@ -0,0 +1,1332 @@ +/* + * NVIDIA Tegra xHCI host controller driver + * + * Copyright (C) 2014 NVIDIA Corporation + * Copyright (C) 2014 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" + +#define TEGRA_XHCI_SS_HIGH_SPEED 120000000 +#define TEGRA_XHCI_SS_LOW_SPEED 12000000 + +/* FPCI CFG registers */ +#define XUSB_CFG_1 0x004 +#define XUSB_IO_SPACE_EN BIT(0) +#define XUSB_MEM_SPACE_EN BIT(1) +#define XUSB_BUS_MASTER_EN BIT(2) +#define XUSB_CFG_4 0x010 +#define XUSB_BASE_ADDR_SHIFT 15 +#define XUSB_BASE_ADDR_MASK 0x1ffff +#define XUSB_CFG_ARU_C11_CSBRANGE 0x41c +#define XUSB_CFG_CSB_BASE_ADDR 0x800 + +/* FPCI mailbox registers */ +#define XUSB_CFG_ARU_MBOX_CMD 0x0e4 +#define MBOX_DEST_FALC BIT(27) +#define MBOX_DEST_PME BIT(28) +#define MBOX_DEST_SMI BIT(29) +#define MBOX_DEST_XHCI BIT(30) +#define MBOX_INT_EN BIT(31) +#define XUSB_CFG_ARU_MBOX_DATA_IN 0x0e8 +#define CMD_DATA_SHIFT 0 +#define CMD_DATA_MASK 0xffffff +#define CMD_TYPE_SHIFT 24 +#define CMD_TYPE_MASK 0xff +#define XUSB_CFG_ARU_MBOX_DATA_OUT 0x0ec +#define XUSB_CFG_ARU_MBOX_OWNER 0x0f0 +#define MBOX_OWNER_NONE 0 +#define MBOX_OWNER_FW 1 +#define MBOX_OWNER_SW 2 +#define XUSB_CFG_ARU_SMI_INTR 0x428 +#define MBOX_SMI_INTR_FW_HANG BIT(1) +#define MBOX_SMI_INTR_EN BIT(3) + +/* IPFS registers */ +#define IPFS_XUSB_HOST_CONFIGURATION_0 0x180 +#define IPFS_EN_FPCI BIT(0) +#define IPFS_XUSB_HOST_INTR_MASK_0 0x188 +#define IPFS_IP_INT_MASK BIT(16) +#define IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0 0x1bc + +#define CSB_PAGE_SELECT_MASK 0x7fffff +#define CSB_PAGE_SELECT_SHIFT 9 +#define CSB_PAGE_OFFSET_MASK 0x1ff +#define CSB_PAGE_SELECT(addr) ((addr) >> (CSB_PAGE_SELECT_SHIFT) & \ + CSB_PAGE_SELECT_MASK) +#define CSB_PAGE_OFFSET(addr) ((addr) & CSB_PAGE_OFFSET_MASK) + +/* Falcon CSB registers */ +#define XUSB_FALC_CPUCTL 0x100 +#define CPUCTL_STARTCPU BIT(1) +#define CPUCTL_STATE_HALTED BIT(4) +#define CPUCTL_STATE_STOPPED BIT(5) +#define XUSB_FALC_BOOTVEC 0x104 +#define XUSB_FALC_DMACTL 0x10c +#define XUSB_FALC_IMFILLRNG1 0x154 +#define IMFILLRNG1_TAG_MASK 0xffff +#define IMFILLRNG1_TAG_LO_SHIFT 0 +#define IMFILLRNG1_TAG_HI_SHIFT 16 +#define XUSB_FALC_IMFILLCTL 0x158 + +/* MP CSB registers */ +#define XUSB_CSB_MP_ILOAD_ATTR 0x101a00 +#define XUSB_CSB_MP_ILOAD_BASE_LO 0x101a04 +#define XUSB_CSB_MP_ILOAD_BASE_HI 0x101a08 +#define XUSB_CSB_MP_L2IMEMOP_SIZE 0x101a10 +#define L2IMEMOP_SIZE_SRC_OFFSET_SHIFT 8 +#define L2IMEMOP_SIZE_SRC_OFFSET_MASK 0x3ff +#define L2IMEMOP_SIZE_SRC_COUNT_SHIFT 24 +#define L2IMEMOP_SIZE_SRC_COUNT_MASK 0xff +#define XUSB_CSB_MP_L2IMEMOP_TRIG 0x101a14 +#define L2IMEMOP_ACTION_SHIFT 24 +#define L2IMEMOP_INVALIDATE_ALL (0x40 << L2IMEMOP_ACTION_SHIFT) +#define L2IMEMOP_LOAD_LOCKED_RESULT (0x11 << L2IMEMOP_ACTION_SHIFT) +#define XUSB_CSB_MP_APMAP 0x10181c +#define APMAP_BOOTPATH BIT(31) + +#define IMEM_BLOCK_SIZE 256 + +struct tegra_xusb_fw_header { + u32 boot_loadaddr_in_imem; + u32 boot_codedfi_offset; + u32 boot_codetag; + u32 boot_codesize; + u32 phys_memaddr; + u16 reqphys_memsize; + u16 alloc_phys_memsize; + u32 rodata_img_offset; + u32 rodata_section_start; + u32 rodata_section_end; + u32 main_fnaddr; + u32 fwimg_cksum; + u32 fwimg_created_time; + u32 imem_resident_start; + u32 imem_resident_end; + u32 idirect_start; + u32 idirect_end; + u32 l2_imem_start; + u32 l2_imem_end; + u32 version_id; + u8 init_ddirect; + u8 reserved[3]; + u32 phys_addr_log_buffer; + u32 total_log_entries; + u32 dequeue_ptr; + u32 dummy_var[2]; + u32 fwimg_len; + u8 magic[8]; + u32 ss_low_power_entry_timeout; + u8 num_hsic_port; + u8 padding[139]; /* Pad to 256 bytes */ +}; + +struct tegra_xusb_phy_type { + const char *name; + unsigned int num; +}; + +struct tegra_xusb_soc { + const char *firmware_file; + const char * const *supply_names; + unsigned int num_supplies; + const struct tegra_xusb_phy_type *phy_types; + unsigned int num_types; + + struct { + struct { + unsigned int offset; + unsigned int count; + } usb2, ulpi, hsic, usb3; + } ports; +}; + +struct tegra_xusb { + struct device *dev; + struct usb_hcd *hcd; + + struct mutex lock; + + int xhci_irq; + int mbox_irq; + + void __iomem *ipfs_base; + void __iomem *fpci_base; + + const struct tegra_xusb_soc *soc; + + struct regulator_bulk_data *supplies; + + struct tegra_xusb_padctl *padctl; + + struct clk *host_clk; + struct clk *falcon_clk; + struct clk *ss_clk; + struct clk *ss_src_clk; + struct clk *hs_src_clk; + struct clk *fs_src_clk; + struct clk *pll_u_480m; + struct clk *clk_m; + struct clk *pll_e; + + struct reset_control *host_rst; + struct reset_control *ss_rst; + + struct phy **phys; + unsigned int num_phys; + + /* Firmware loading related */ + void *fw_data; + size_t fw_size; + dma_addr_t fw_dma_addr; + bool fw_loaded; +}; + +static struct hc_driver __read_mostly tegra_xhci_hc_driver; + +static inline u32 fpci_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + return readl(tegra->fpci_base + offset); +} + +static inline void fpci_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + writel(value, tegra->fpci_base + offset); +} + +static inline u32 ipfs_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + return readl(tegra->ipfs_base + offset); +} + +static inline void ipfs_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + writel(value, tegra->ipfs_base + offset); +} + +static u32 csb_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + u32 page = CSB_PAGE_SELECT(offset); + u32 ofs = CSB_PAGE_OFFSET(offset); + + fpci_writel(tegra, page, XUSB_CFG_ARU_C11_CSBRANGE); + + return fpci_readl(tegra, XUSB_CFG_CSB_BASE_ADDR + ofs); +} + +static void csb_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + u32 page = CSB_PAGE_SELECT(offset); + u32 ofs = CSB_PAGE_OFFSET(offset); + + fpci_writel(tegra, page, XUSB_CFG_ARU_C11_CSBRANGE); + fpci_writel(tegra, value, XUSB_CFG_CSB_BASE_ADDR + ofs); +} + +static int tegra_xusb_set_ss_clk(struct tegra_xusb *tegra, + unsigned long rate) +{ + unsigned long new_parent_rate, old_parent_rate; + struct clk *clk = tegra->ss_src_clk; + unsigned int div; + int err; + + if (clk_get_rate(clk) == rate) + return 0; + + switch (rate) { + case TEGRA_XHCI_SS_HIGH_SPEED: + /* + * Reparent to PLLU_480M. Set divider first to avoid + * overclocking. + */ + old_parent_rate = clk_get_rate(clk_get_parent(clk)); + new_parent_rate = clk_get_rate(tegra->pll_u_480m); + div = new_parent_rate / rate; + + err = clk_set_rate(clk, old_parent_rate / div); + if (err) + return err; + + err = clk_set_parent(clk, tegra->pll_u_480m); + if (err) + return err; + + /* + * The rate should already be correct, but set it again just + * to be sure. + */ + err = clk_set_rate(clk, rate); + if (err) + return err; + + break; + + case TEGRA_XHCI_SS_LOW_SPEED: + /* Reparent to CLK_M */ + err = clk_set_parent(clk, tegra->clk_m); + if (err) + return err; + + err = clk_set_rate(clk, rate); + if (err) + return err; + + break; + + default: + dev_err(tegra->dev, "Invalid SS rate: %lu Hz\n", rate); + return -EINVAL; + } + + if (clk_get_rate(clk) != rate) { + dev_err(tegra->dev, "SS clock doesn't match requested rate\n"); + return -EINVAL; + } + + return 0; +} + +static unsigned long extract_field(u32 value, unsigned int start, + unsigned int count) +{ + return (value >> start) & ((1 << count) - 1); +} + +/* Command requests from the firmware */ +enum tegra_xusb_mbox_cmd { + MBOX_CMD_MSG_ENABLED = 1, + MBOX_CMD_INC_FALC_CLOCK, + MBOX_CMD_DEC_FALC_CLOCK, + MBOX_CMD_INC_SSPI_CLOCK, + MBOX_CMD_DEC_SSPI_CLOCK, + MBOX_CMD_SET_BW, /* no ACK/NAK required */ + MBOX_CMD_SET_SS_PWR_GATING, + MBOX_CMD_SET_SS_PWR_UNGATING, + MBOX_CMD_SAVE_DFE_CTLE_CTX, + MBOX_CMD_AIRPLANE_MODE_ENABLED, /* unused */ + MBOX_CMD_AIRPLANE_MODE_DISABLED, /* unused */ + MBOX_CMD_START_HSIC_IDLE, + MBOX_CMD_STOP_HSIC_IDLE, + MBOX_CMD_DBC_WAKE_STACK, /* unused */ + MBOX_CMD_HSIC_PRETEND_CONNECT, + MBOX_CMD_RESET_SSPI, + MBOX_CMD_DISABLE_SS_LFPS_DETECTION, + MBOX_CMD_ENABLE_SS_LFPS_DETECTION, + + MBOX_CMD_MAX, + + /* Response message to above commands */ + MBOX_CMD_ACK = 128, + MBOX_CMD_NAK +}; + +static const char * const mbox_cmd_name[] = { + [ 1] = "MSG_ENABLE", + [ 2] = "INC_FALCON_CLOCK", + [ 3] = "DEC_FALCON_CLOCK", + [ 4] = "INC_SSPI_CLOCK", + [ 5] = "DEC_SSPI_CLOCK", + [ 6] = "SET_BW", + [ 7] = "SET_SS_PWR_GATING", + [ 8] = "SET_SS_PWR_UNGATING", + [ 9] = "SAVE_DFE_CTLE_CTX", + [ 10] = "AIRPLANE_MODE_ENABLED", + [ 11] = "AIRPLANE_MODE_DISABLED", + [ 12] = "START_HSIC_IDLE", + [ 13] = "STOP_HSIC_IDLE", + [ 14] = "DBC_WAKE_STACK", + [ 15] = "HSIC_PRETEND_CONNECT", + [ 16] = "RESET_SSPI", + [ 17] = "DISABLE_SS_LFPS_DETECTION", + [ 18] = "ENABLE_SS_LFPS_DETECTION", + [128] = "ACK", + [129] = "NAK", +}; + +struct tegra_xusb_mbox_msg { + u32 cmd; + u32 data; +}; + +static inline u32 tegra_xusb_mbox_pack(const struct tegra_xusb_mbox_msg *msg) +{ + return (msg->cmd & CMD_TYPE_MASK) << CMD_TYPE_SHIFT | + (msg->data & CMD_DATA_MASK) << CMD_DATA_SHIFT; +} +static inline void tegra_xusb_mbox_unpack(struct tegra_xusb_mbox_msg *msg, + u32 value) +{ + msg->cmd = (value >> CMD_TYPE_SHIFT) & CMD_TYPE_MASK; + msg->data = (value >> CMD_DATA_SHIFT) & CMD_DATA_MASK; +} + +static bool tegra_xusb_mbox_cmd_requires_ack(enum tegra_xusb_mbox_cmd cmd) +{ + switch (cmd) { + case MBOX_CMD_SET_BW: + case MBOX_CMD_ACK: + case MBOX_CMD_NAK: + return false; + + default: + return true; + } +} + +static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, + const struct tegra_xusb_mbox_msg *msg) +{ + bool wait_for_idle = false; + u32 value; + + /* + * Acquire the mailbox. The firmware still owns the mailbox for + * ACK/NAK messages. + */ + if (!(msg->cmd == MBOX_CMD_ACK || msg->cmd == MBOX_CMD_NAK)) { + value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER); + if (value != MBOX_OWNER_NONE) { + dev_err(tegra->dev, "mailbox is busy\n"); + return -EBUSY; + } + + fpci_writel(tegra, MBOX_OWNER_SW, XUSB_CFG_ARU_MBOX_OWNER); + + value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER); + if (value != MBOX_OWNER_SW) { + dev_err(tegra->dev, "failed to acquire mailbox\n"); + return -EBUSY; + } + + wait_for_idle = true; + } + + value = tegra_xusb_mbox_pack(msg); + fpci_writel(tegra, value, XUSB_CFG_ARU_MBOX_DATA_IN); + + value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_CMD); + value |= MBOX_INT_EN | MBOX_DEST_FALC; + fpci_writel(tegra, value, XUSB_CFG_ARU_MBOX_CMD); + + if (wait_for_idle) { + unsigned long timeout = jiffies + msecs_to_jiffies(250); + + while (time_before(jiffies, timeout)) { + value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER); + if (value == MBOX_OWNER_NONE) + break; + + usleep_range(10, 20); + } + + if (time_after(jiffies, timeout)) + value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_OWNER); + + if (value != MBOX_OWNER_NONE) + return -ETIMEDOUT; + } + + return 0; +} + +static irqreturn_t tegra_xusb_mbox_irq(int irq, void *data) +{ + struct tegra_xusb *tegra = data; + u32 value; + + /* clear mailbox interrupts */ + value = fpci_readl(tegra, XUSB_CFG_ARU_SMI_INTR); + fpci_writel(tegra, value, XUSB_CFG_ARU_SMI_INTR); + + if (value & MBOX_SMI_INTR_FW_HANG) + dev_err(tegra->dev, "controller firmware hang\n"); + + return IRQ_WAKE_THREAD; +} + +static void tegra_xusb_mbox_handle(struct tegra_xusb *tegra, + const struct tegra_xusb_mbox_msg *msg) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + const struct tegra_xusb_soc *soc = tegra->soc; + struct device *dev = tegra->dev; + struct tegra_xusb_mbox_msg rsp; + unsigned long mask; + unsigned int port; + bool idle, enable; + int err; + + memset(&rsp, 0, sizeof(rsp)); + + switch (msg->cmd) { + case MBOX_CMD_INC_FALC_CLOCK: + case MBOX_CMD_DEC_FALC_CLOCK: + rsp.data = clk_get_rate(tegra->falcon_clk) / 1000; + if (rsp.data != msg->data) + rsp.cmd = MBOX_CMD_NAK; + else + rsp.cmd = MBOX_CMD_ACK; + + break; + + case MBOX_CMD_INC_SSPI_CLOCK: + case MBOX_CMD_DEC_SSPI_CLOCK: + err = tegra_xusb_set_ss_clk(tegra, msg->data * 1000); + if (err < 0) + rsp.cmd = MBOX_CMD_NAK; + else + rsp.cmd = MBOX_CMD_ACK; + + rsp.data = clk_get_rate(tegra->ss_src_clk) / 1000; + break; + + case MBOX_CMD_SET_BW: + /* + * TODO: Request bandwidth once EMC scaling is supported. + * Ignore for now since ACK/NAK is not required for SET_BW + * messages. + */ + break; + + case MBOX_CMD_SAVE_DFE_CTLE_CTX: + err = tegra_xusb_padctl_usb3_save_context(padctl, msg->data); + if (err < 0) { + dev_err(dev, "failed to save context for USB3#%u: %d\n", + msg->data, err); + rsp.cmd = MBOX_CMD_NAK; + } else { + rsp.cmd = MBOX_CMD_ACK; + } + + rsp.data = msg->data; + break; + + case MBOX_CMD_START_HSIC_IDLE: + case MBOX_CMD_STOP_HSIC_IDLE: + if (msg->cmd == MBOX_CMD_STOP_HSIC_IDLE) + idle = false; + else + idle = true; + + mask = extract_field(msg->data, 1 + soc->ports.hsic.offset, + soc->ports.hsic.count); + + for_each_set_bit(port, &mask, 32) { + err = tegra_xusb_padctl_hsic_set_idle(padctl, port, + idle); + if (err < 0) + break; + } + + if (err < 0) { + dev_err(dev, "failed to set HSIC#%u %s: %d\n", port, + idle ? "idle" : "busy", err); + rsp.cmd = MBOX_CMD_NAK; + } else { + rsp.cmd = MBOX_CMD_ACK; + } + + rsp.data = msg->data; + break; + + case MBOX_CMD_DISABLE_SS_LFPS_DETECTION: + case MBOX_CMD_ENABLE_SS_LFPS_DETECTION: + if (msg->cmd == MBOX_CMD_DISABLE_SS_LFPS_DETECTION) + enable = false; + else + enable = true; + + mask = extract_field(msg->data, 1 + soc->ports.usb3.offset, + soc->ports.usb3.count); + + for_each_set_bit(port, &mask, soc->ports.usb3.count) { + err = tegra_xusb_padctl_usb3_set_lfps_detect(padctl, + port, + enable); + if (err < 0) + break; + } + + if (err < 0) { + dev_err(dev, + "failed to %s LFPS detection on USB3#%u: %d\n", + enable ? "enable" : "disable", port, err); + rsp.cmd = MBOX_CMD_NAK; + } else { + rsp.cmd = MBOX_CMD_ACK; + } + + rsp.data = msg->data; + break; + + default: + dev_warn(dev, "unknown message: %#x\n", msg->cmd); + break; + } + + if (rsp.cmd) { + const char *cmd = (rsp.cmd == MBOX_CMD_ACK) ? "ACK" : "NAK"; + + err = tegra_xusb_mbox_send(tegra, &rsp); + if (err < 0) + dev_err(dev, "failed to send %s: %d\n", cmd, err); + } +} + +static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) +{ + struct tegra_xusb *tegra = data; + struct tegra_xusb_mbox_msg msg; + u32 value; + + mutex_lock(&tegra->lock); + + value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_DATA_OUT); + tegra_xusb_mbox_unpack(&msg, value); + + value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_CMD); + value &= ~MBOX_DEST_SMI; + fpci_writel(tegra, value, XUSB_CFG_ARU_MBOX_CMD); + + /* clear mailbox owner if no ACK/NAK is required */ + if (!tegra_xusb_mbox_cmd_requires_ack(msg.cmd)) + fpci_writel(tegra, MBOX_OWNER_NONE, XUSB_CFG_ARU_MBOX_OWNER); + + tegra_xusb_mbox_handle(tegra, &msg); + + mutex_unlock(&tegra->lock); + return IRQ_HANDLED; +} + +static void tegra_xusb_ipfs_config(struct tegra_xusb *tegra) +{ + u32 value; + + value = ipfs_readl(tegra, IPFS_XUSB_HOST_CONFIGURATION_0); + value |= IPFS_EN_FPCI; + ipfs_writel(tegra, value, IPFS_XUSB_HOST_CONFIGURATION_0); + + usleep_range(10, 20); + + /* Program BAR0 space */ + value = fpci_readl(tegra, XUSB_CFG_4); + value &= ~(XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT); + value |= tegra->hcd->rsrc_start & (XUSB_BASE_ADDR_MASK << + XUSB_BASE_ADDR_SHIFT); + fpci_writel(tegra, value, XUSB_CFG_4); + + usleep_range(100, 200); + + /* Enable bus master */ + value = fpci_readl(tegra, XUSB_CFG_1); + value |= XUSB_IO_SPACE_EN | XUSB_MEM_SPACE_EN | XUSB_BUS_MASTER_EN; + fpci_writel(tegra, value, XUSB_CFG_1); + + /* Enable interrupt assertion */ + value = ipfs_readl(tegra, IPFS_XUSB_HOST_INTR_MASK_0); + value |= IPFS_IP_INT_MASK; + ipfs_writel(tegra, value, IPFS_XUSB_HOST_INTR_MASK_0); + + /* Set hysteresis */ + ipfs_writel(tegra, 0x80, IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0); +} + +static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) +{ + unsigned int code_tag_blocks, code_size_blocks, code_blocks; + struct tegra_xusb_fw_header *header; + struct device *dev = tegra->dev; + unsigned long timeout; + struct tm fw_tm; + time_t fw_time; + u64 fw_base; + u32 value; + + if (csb_readl(tegra, XUSB_CSB_MP_ILOAD_BASE_LO) != 0) { + dev_info(dev, "Firmware already loaded, Falcon state %#x\n", + csb_readl(tegra, XUSB_FALC_CPUCTL)); + return 0; + } + + header = (struct tegra_xusb_fw_header *)tegra->fw_data; + + /* Program the size of DFI into ILOAD_ATTR. */ + csb_writel(tegra, tegra->fw_size, XUSB_CSB_MP_ILOAD_ATTR); + + /* + * Boot code of the firmware reads the ILOAD_BASE registers + * to get to the start of the DFI in system memory. + */ + fw_base = tegra->fw_dma_addr + sizeof(*header); + csb_writel(tegra, fw_base, XUSB_CSB_MP_ILOAD_BASE_LO); + csb_writel(tegra, fw_base >> 32, XUSB_CSB_MP_ILOAD_BASE_HI); + + /* Set BOOTPATH to 1 in APMAP. */ + csb_writel(tegra, APMAP_BOOTPATH, XUSB_CSB_MP_APMAP); + + /* Invalidate L2IMEM. */ + csb_writel(tegra, L2IMEMOP_INVALIDATE_ALL, XUSB_CSB_MP_L2IMEMOP_TRIG); + + /* + * Initiate fetch of bootcode from system memory into L2IMEM. + * Program bootcode location and size in system memory. + */ + code_tag_blocks = DIV_ROUND_UP(le32_to_cpu(header->boot_codetag), + IMEM_BLOCK_SIZE); + code_size_blocks = DIV_ROUND_UP(le32_to_cpu(header->boot_codesize), + IMEM_BLOCK_SIZE); + code_blocks = code_tag_blocks + code_size_blocks; + + value = ((code_tag_blocks & L2IMEMOP_SIZE_SRC_OFFSET_MASK) << + L2IMEMOP_SIZE_SRC_OFFSET_SHIFT) | + ((code_size_blocks & L2IMEMOP_SIZE_SRC_COUNT_MASK) << + L2IMEMOP_SIZE_SRC_COUNT_SHIFT); + csb_writel(tegra, value, XUSB_CSB_MP_L2IMEMOP_SIZE); + + /* Trigger L2IMEM load operation. */ + csb_writel(tegra, L2IMEMOP_LOAD_LOCKED_RESULT, + XUSB_CSB_MP_L2IMEMOP_TRIG); + + /* Setup Falcon auto-fill. */ + csb_writel(tegra, code_size_blocks, XUSB_FALC_IMFILLCTL); + + value = ((code_tag_blocks & IMFILLRNG1_TAG_MASK) << + IMFILLRNG1_TAG_LO_SHIFT) | + ((code_blocks & IMFILLRNG1_TAG_MASK) << + IMFILLRNG1_TAG_HI_SHIFT); + csb_writel(tegra, value, XUSB_FALC_IMFILLRNG1); + + csb_writel(tegra, 0, XUSB_FALC_DMACTL); + + msleep(50); + + csb_writel(tegra, le32_to_cpu(header->boot_codetag), + XUSB_FALC_BOOTVEC); + + /* Boot Falcon CPU and wait for it to enter the STOPPED (idle) state. */ + timeout = jiffies + msecs_to_jiffies(5); + + csb_writel(tegra, CPUCTL_STARTCPU, XUSB_FALC_CPUCTL); + + while (time_before(jiffies, timeout)) { + if (csb_readl(tegra, XUSB_FALC_CPUCTL) == CPUCTL_STATE_STOPPED) + break; + + usleep_range(100, 200); + } + + if (csb_readl(tegra, XUSB_FALC_CPUCTL) != CPUCTL_STATE_STOPPED) { + dev_err(dev, "Falcon failed to start, state: %#x\n", + csb_readl(tegra, XUSB_FALC_CPUCTL)); + return -EIO; + } + + fw_time = le32_to_cpu(header->fwimg_created_time); + time_to_tm(fw_time, 0, &fw_tm); + + dev_info(dev, "Firmware timestamp: %ld-%02d-%02d %02d:%02d:%02d UTC\n", + fw_tm.tm_year + 1900, fw_tm.tm_mon + 1, fw_tm.tm_mday, + fw_tm.tm_hour, fw_tm.tm_min, fw_tm.tm_sec); + + return 0; +} + +static int tegra_xusb_clk_enable(struct tegra_xusb *tegra) +{ + int err; + + err = clk_prepare_enable(tegra->pll_e); + if (err < 0) + return err; + + err = clk_prepare_enable(tegra->host_clk); + if (err < 0) + goto disable_plle; + + err = clk_prepare_enable(tegra->ss_clk); + if (err < 0) + goto disable_host; + + err = clk_prepare_enable(tegra->falcon_clk); + if (err < 0) + goto disable_ss; + + err = clk_prepare_enable(tegra->fs_src_clk); + if (err < 0) + goto disable_falc; + + err = clk_prepare_enable(tegra->hs_src_clk); + if (err < 0) + goto disable_fs_src; + + err = tegra_xusb_set_ss_clk(tegra, TEGRA_XHCI_SS_HIGH_SPEED); + if (err < 0) + goto disable_hs_src; + + return 0; + +disable_hs_src: + clk_disable_unprepare(tegra->hs_src_clk); +disable_fs_src: + clk_disable_unprepare(tegra->fs_src_clk); +disable_falc: + clk_disable_unprepare(tegra->falcon_clk); +disable_ss: + clk_disable_unprepare(tegra->ss_clk); +disable_host: + clk_disable_unprepare(tegra->host_clk); +disable_plle: + clk_disable_unprepare(tegra->pll_e); + return err; +} + +static void tegra_xusb_clk_disable(struct tegra_xusb *tegra) +{ + clk_disable_unprepare(tegra->pll_e); + clk_disable_unprepare(tegra->host_clk); + clk_disable_unprepare(tegra->ss_clk); + clk_disable_unprepare(tegra->falcon_clk); + clk_disable_unprepare(tegra->fs_src_clk); + clk_disable_unprepare(tegra->hs_src_clk); +} + +static int tegra_xusb_phy_enable(struct tegra_xusb *tegra) +{ + unsigned int i; + int err; + + for (i = 0; i < tegra->num_phys; i++) { + err = phy_init(tegra->phys[i]); + if (err) + goto disable_phy; + + err = phy_power_on(tegra->phys[i]); + if (err) { + phy_exit(tegra->phys[i]); + goto disable_phy; + } + } + + return 0; + +disable_phy: + while (i--) { + phy_power_off(tegra->phys[i]); + phy_exit(tegra->phys[i]); + } + + return err; +} + +static void tegra_xusb_phy_disable(struct tegra_xusb *tegra) +{ + unsigned int i; + + for (i = 0; i < tegra->num_phys; i++) { + phy_power_off(tegra->phys[i]); + phy_exit(tegra->phys[i]); + } +} + +static void tegra_xhci_quirks(struct device *dev, struct xhci_hcd *xhci) +{ + xhci->quirks |= XHCI_PLAT; +} + +static int tegra_xhci_setup(struct usb_hcd *hcd) +{ + return xhci_gen_setup(hcd, tegra_xhci_quirks); +} + +static const char * const tegra124_supply_names[] = { + "avddio-pex", + "dvddio-pex", + "avdd-usb", + "avdd-pll-utmip", + "avdd-pll-erefe", + "avdd-usb-ss-pll", + "hvdd-usb-ss", + "hvdd-usb-ss-pll-e", +}; + +static const struct tegra_xusb_phy_type tegra124_phy_types[] = { + { .name = "usb3", .num = 2, }, + { .name = "usb2", .num = 3, }, + { .name = "hsic", .num = 2, }, +}; + +static const struct tegra_xusb_soc tegra124_soc = { + .firmware_file = "nvidia/tegra124/xusb.bin", + .supply_names = tegra124_supply_names, + .num_supplies = ARRAY_SIZE(tegra124_supply_names), + .phy_types = tegra124_phy_types, + .num_types = ARRAY_SIZE(tegra124_phy_types), + .ports = { + .usb2 = { .offset = 4, .count = 4, }, + .hsic = { .offset = 6, .count = 2, }, + .usb3 = { .offset = 0, .count = 2, }, + }, +}; +MODULE_FIRMWARE("nvidia/tegra124/xusb.bin"); + +static const struct of_device_id tegra_xusb_of_match[] = { + { .compatible = "nvidia,tegra124-xusb", .data = &tegra124_soc }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_xusb_of_match); + +static void tegra_xusb_probe_finish(const struct firmware *fw, void *context) +{ + struct tegra_xusb_fw_header *header; + struct tegra_xusb *tegra = context; + struct device *dev = tegra->dev; + struct tegra_xusb_mbox_msg msg; + struct xhci_hcd *xhci = NULL; + int err; + + if (!fw) { + dev_err(tegra->dev, "no firmware loaded\n"); + goto put_usb2_hcd; + } + + /* Load Falcon controller with its firmware. */ + header = (struct tegra_xusb_fw_header *)fw->data; + tegra->fw_size = le32_to_cpu(header->fwimg_len); + + tegra->fw_data = dma_alloc_coherent(dev, tegra->fw_size, + &tegra->fw_dma_addr, + GFP_KERNEL); + if (!tegra->fw_data) { + dev_err(tegra->dev, "failed to allocate memory for firmware\n"); + goto put_usb2_hcd; + } + + memcpy(tegra->fw_data, fw->data, tegra->fw_size); + + err = tegra_xusb_load_firmware(tegra); + if (err < 0) { + dev_err(tegra->dev, "failed to parse firmware: %d\n", err); + goto put_usb2_hcd; + } + + err = usb_add_hcd(tegra->hcd, tegra->xhci_irq, IRQF_SHARED); + if (err < 0) { + dev_err(tegra->dev, "failed to add USB HCD: %d\n", err); + goto put_usb2_hcd; + } + + device_wakeup_enable(tegra->hcd->self.controller); + + /* + * USB 2.0 roothub is stored in drvdata now. Swap it with the Tegra HCD. + */ + tegra->hcd = dev_get_drvdata(dev); + dev_set_drvdata(dev, tegra); + xhci = hcd_to_xhci(tegra->hcd); + + xhci->shared_hcd = usb_create_shared_hcd(&tegra_xhci_hc_driver, + dev, dev_name(dev), + tegra->hcd); + if (!xhci->shared_hcd) { + dev_err(tegra->dev, "failed to create shared HCD\n"); + goto dealloc_usb2_hcd; + } + + err = usb_add_hcd(xhci->shared_hcd, tegra->xhci_irq, IRQF_SHARED); + if (err < 0) { + dev_err(tegra->dev, "failed to add shared HCD: %d\n", err); + goto put_usb3_hcd; + } + + mutex_lock(&tegra->lock); + + /* Enable firmware messages from controller. */ + msg.cmd = MBOX_CMD_MSG_ENABLED; + msg.data = 0; + + err = tegra_xusb_mbox_send(tegra, &msg); + if (err < 0) { + dev_err(tegra->dev, "failed to enable messages: %d\n", err); + mutex_unlock(&tegra->lock); + goto dealloc_usb3_hcd; + } + + mutex_unlock(&tegra->lock); + + err = devm_request_threaded_irq(dev, tegra->mbox_irq, + tegra_xusb_mbox_irq, + tegra_xusb_mbox_thread, 0, + dev_name(dev), tegra); + if (err < 0) { + dev_err(tegra->dev, "failed to request IRQ: %d\n", err); + goto dealloc_usb3_hcd; + } + + tegra->fw_loaded = true; + release_firmware(fw); + + return; + + /* Free up as much as we can and wait to be unbound. */ +dealloc_usb3_hcd: + usb_remove_hcd(xhci->shared_hcd); +put_usb3_hcd: + usb_put_hcd(xhci->shared_hcd); +dealloc_usb2_hcd: + usb_remove_hcd(tegra->hcd); +put_usb2_hcd: + usb_put_hcd(tegra->hcd); + tegra->hcd = NULL; + release_firmware(fw); +} + +static int tegra_xusb_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct tegra_xusb *tegra; + unsigned int i, j, k; + struct resource *res; + struct usb_hcd *hcd; + struct phy *phy; + int err; + + BUILD_BUG_ON(sizeof(struct tegra_xusb_fw_header) != 256); + + tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); + if (!tegra) + return -ENOMEM; + + platform_set_drvdata(pdev, tegra); + mutex_init(&tegra->lock); + tegra->dev = &pdev->dev; + + match = of_match_device(tegra_xusb_of_match, &pdev->dev); + tegra->soc = match->data; + + hcd = usb_create_hcd(&tegra_xhci_hc_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!hcd) + return -ENOMEM; + + tegra->hcd = hcd; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hcd->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(hcd->regs)) { + err = PTR_ERR(hcd->regs); + goto put_hcd; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + tegra->fpci_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tegra->fpci_base)) { + err = PTR_ERR(tegra->fpci_base); + goto put_hcd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + tegra->ipfs_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tegra->ipfs_base)) { + err = PTR_ERR(tegra->ipfs_base); + goto put_hcd; + } + + tegra->xhci_irq = platform_get_irq(pdev, 0); + if (tegra->xhci_irq < 0) { + err = tegra->xhci_irq; + goto put_hcd; + } + + tegra->mbox_irq = platform_get_irq(pdev, 1); + if (tegra->mbox_irq < 0) { + err = tegra->mbox_irq; + goto put_hcd; + } + + tegra->padctl = tegra_xusb_padctl_get(&pdev->dev); + if (IS_ERR(tegra->padctl)) { + err = PTR_ERR(tegra->padctl); + goto put_hcd; + } + + tegra->host_rst = devm_reset_control_get(&pdev->dev, "xusb_host"); + if (IS_ERR(tegra->host_rst)) { + err = PTR_ERR(tegra->host_rst); + dev_err(&pdev->dev, "failed to get xusb_host reset: %d\n", err); + goto put_padctl; + } + + tegra->ss_rst = devm_reset_control_get(&pdev->dev, "xusb_ss"); + if (IS_ERR(tegra->ss_rst)) { + err = PTR_ERR(tegra->ss_rst); + dev_err(&pdev->dev, "failed to get xusb_ss reset: %d\n", err); + goto put_padctl; + } + + tegra->host_clk = devm_clk_get(&pdev->dev, "xusb_host"); + if (IS_ERR(tegra->host_clk)) { + err = PTR_ERR(tegra->host_clk); + dev_err(&pdev->dev, "failed to get xusb_host: %d\n", err); + goto put_padctl; + } + + tegra->falcon_clk = devm_clk_get(&pdev->dev, "xusb_falcon_src"); + if (IS_ERR(tegra->falcon_clk)) { + err = PTR_ERR(tegra->falcon_clk); + dev_err(&pdev->dev, "failed to get xusb_falcon_src: %d\n", err); + goto put_padctl; + } + + tegra->ss_clk = devm_clk_get(&pdev->dev, "xusb_ss"); + if (IS_ERR(tegra->ss_clk)) { + err = PTR_ERR(tegra->ss_clk); + dev_err(&pdev->dev, "failed to get xusb_ss: %d\n", err); + goto put_padctl; + } + + tegra->ss_src_clk = devm_clk_get(&pdev->dev, "xusb_ss_src"); + if (IS_ERR(tegra->ss_src_clk)) { + err = PTR_ERR(tegra->ss_src_clk); + dev_err(&pdev->dev, "failed to get xusb_ss_src: %d\n", err); + goto put_padctl; + } + + tegra->hs_src_clk = devm_clk_get(&pdev->dev, "xusb_hs_src"); + if (IS_ERR(tegra->hs_src_clk)) { + err = PTR_ERR(tegra->hs_src_clk); + dev_err(&pdev->dev, "failed to get xusb_hs_src: %d\n", err); + goto put_padctl; + } + + tegra->fs_src_clk = devm_clk_get(&pdev->dev, "xusb_fs_src"); + if (IS_ERR(tegra->fs_src_clk)) { + err = PTR_ERR(tegra->fs_src_clk); + dev_err(&pdev->dev, "failed to get xusb_fs_src: %d\n", err); + goto put_padctl; + } + + tegra->pll_u_480m = devm_clk_get(&pdev->dev, "pll_u_480m"); + if (IS_ERR(tegra->pll_u_480m)) { + err = PTR_ERR(tegra->pll_u_480m); + dev_err(&pdev->dev, "failed to get pll_u_480m: %d\n", err); + goto put_padctl; + } + + tegra->clk_m = devm_clk_get(&pdev->dev, "clk_m"); + if (IS_ERR(tegra->clk_m)) { + err = PTR_ERR(tegra->clk_m); + dev_err(&pdev->dev, "failed to get clk_m: %d\n", err); + goto put_padctl; + } + + tegra->pll_e = devm_clk_get(&pdev->dev, "pll_e"); + if (IS_ERR(tegra->pll_e)) { + err = PTR_ERR(tegra->pll_e); + dev_err(&pdev->dev, "failed to get pll_e: %d\n", err); + goto put_padctl; + } + + err = tegra_xusb_clk_enable(tegra); + if (err) { + dev_err(&pdev->dev, "failed to enable clocks: %d\n", err); + goto put_padctl; + } + + tegra->supplies = devm_kcalloc(&pdev->dev, tegra->soc->num_supplies, + sizeof(*tegra->supplies), GFP_KERNEL); + if (!tegra->supplies) { + err = -ENOMEM; + goto disable_clk; + } + + for (i = 0; i < tegra->soc->num_supplies; i++) + tegra->supplies[i].supply = tegra->soc->supply_names[i]; + + err = devm_regulator_bulk_get(&pdev->dev, tegra->soc->num_supplies, + tegra->supplies); + if (err) { + dev_err(&pdev->dev, "failed to get regulators: %d\n", err); + goto disable_clk; + } + + err = regulator_bulk_enable(tegra->soc->num_supplies, tegra->supplies); + if (err) { + dev_err(&pdev->dev, "failed to enable regulators: %d\n", err); + goto disable_clk; + } + + for (i = 0; i < tegra->soc->num_types; i++) + tegra->num_phys += tegra->soc->phy_types[i].num; + + tegra->phys = devm_kcalloc(&pdev->dev, tegra->num_phys, + sizeof(*tegra->phys), GFP_KERNEL); + if (!tegra->phys) { + dev_err(&pdev->dev, "failed to allocate PHY array\n"); + err = -ENOMEM; + goto disable_regulator; + } + + for (i = 0, k = 0; i < tegra->soc->num_types; i++) { + char prop[8]; + + for (j = 0; j < tegra->soc->phy_types[i].num; j++) { + snprintf(prop, sizeof(prop), "%s-%d", + tegra->soc->phy_types[i].name, j); + + phy = devm_phy_optional_get(&pdev->dev, prop); + if (IS_ERR(phy)) { + dev_err(&pdev->dev, "failed to get PHY %s: %ld\n", prop, PTR_ERR(phy)); + err = PTR_ERR(phy); + goto disable_regulator; + } + + tegra->phys[k++] = phy; + } + } + + tegra_xusb_ipfs_config(tegra); + + err = tegra_xusb_phy_enable(tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to enable PHYs: %d\n", err); + goto disable_regulator; + } + + err = request_firmware_nowait(THIS_MODULE, true, + tegra->soc->firmware_file, + tegra->dev, GFP_KERNEL, tegra, + tegra_xusb_probe_finish); + if (err < 0) { + dev_err(&pdev->dev, "failed to request firmware: %d\n", err); + goto disable_phy; + } + + return 0; + +disable_phy: + tegra_xusb_phy_disable(tegra); +disable_regulator: + regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); +disable_clk: + tegra_xusb_clk_disable(tegra); +put_padctl: + tegra_xusb_padctl_put(tegra->padctl); +put_hcd: + usb_put_hcd(hcd); + return err; +} + +static int tegra_xusb_remove(struct platform_device *pdev) +{ + struct tegra_xusb *tegra = platform_get_drvdata(pdev); + struct usb_hcd *hcd = tegra->hcd; + struct xhci_hcd *xhci; + + if (tegra->fw_loaded) { + xhci = hcd_to_xhci(hcd); + usb_remove_hcd(xhci->shared_hcd); + usb_put_hcd(xhci->shared_hcd); + usb_remove_hcd(hcd); + tegra_xusb_padctl_put(tegra->padctl); + usb_put_hcd(hcd); + kfree(xhci); + } else if (hcd) { + /* Unbound after probe(), but before firmware loading. */ + tegra_xusb_padctl_put(tegra->padctl); + usb_put_hcd(hcd); + } + + if (tegra->fw_data) + dma_free_coherent(tegra->dev, tegra->fw_size, tegra->fw_data, + tegra->fw_dma_addr); + + tegra_xusb_phy_disable(tegra); + regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); + tegra_xusb_clk_disable(tegra); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra_xusb_suspend(struct device *dev) +{ + struct tegra_xusb *tegra = dev_get_drvdata(dev); + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + bool wakeup = device_may_wakeup(dev); + + /* TODO: Powergate controller across suspend/resume. */ + return xhci_suspend(xhci, wakeup); +} + +static int tegra_xusb_resume(struct device *dev) +{ + struct tegra_xusb *tegra = dev_get_drvdata(dev); + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + + return xhci_resume(xhci, 0); +} +#endif + +static const struct dev_pm_ops tegra_xusb_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tegra_xusb_suspend, tegra_xusb_resume) +}; + +static struct platform_driver tegra_xusb_driver = { + .probe = tegra_xusb_probe, + .remove = tegra_xusb_remove, + .driver = { + .name = "tegra-xusb", + .pm = &tegra_xusb_pm_ops, + .of_match_table = tegra_xusb_of_match, + }, +}; + +static const struct xhci_driver_overrides tegra_xhci_overrides __initconst = { + .extra_priv_size = sizeof(struct xhci_hcd), + .reset = tegra_xhci_setup, +}; + +static int __init tegra_xusb_init(void) +{ + xhci_init_driver(&tegra_xhci_hc_driver, &tegra_xhci_overrides); + + return platform_driver_register(&tegra_xusb_driver); +} +module_init(tegra_xusb_init); + +static void __exit tegra_xusb_exit(void) +{ + platform_driver_unregister(&tegra_xusb_driver); +} +module_exit(tegra_xusb_exit); + +MODULE_AUTHOR("Andrew Bresticker "); +MODULE_DESCRIPTION("NVIDIA Tegra XUSB xHCI host-controller driver"); +MODULE_LICENSE("GPL v2"); From 6bf2ce439f925ac48795edbd5d9e50b6468a3441 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 4 Mar 2016 17:19:39 +0100 Subject: [PATCH 09/19] usb: xhci: tegra: Add Tegra210 support Parameterize more parts of the driver and add support for Tegra210. Cc: Greg Kroah-Hartman Cc: Mathias Nyman Signed-off-by: Thierry Reding --- drivers/usb/host/xhci-tegra.c | 59 ++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index ab6f9856c5c4a6..426e8d922cf465 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -159,6 +159,8 @@ struct tegra_xusb_soc { unsigned int count; } usb2, ulpi, hsic, usb3; } ports; + + bool scale_ss_clock; }; struct tegra_xusb { @@ -495,13 +497,19 @@ static void tegra_xusb_mbox_handle(struct tegra_xusb *tegra, case MBOX_CMD_INC_SSPI_CLOCK: case MBOX_CMD_DEC_SSPI_CLOCK: - err = tegra_xusb_set_ss_clk(tegra, msg->data * 1000); - if (err < 0) - rsp.cmd = MBOX_CMD_NAK; - else + if (tegra->soc->scale_ss_clock) { + err = tegra_xusb_set_ss_clk(tegra, msg->data * 1000); + if (err < 0) + rsp.cmd = MBOX_CMD_NAK; + else + rsp.cmd = MBOX_CMD_ACK; + + rsp.data = clk_get_rate(tegra->ss_src_clk) / 1000; + } else { rsp.cmd = MBOX_CMD_ACK; + rsp.data = msg->data; + } - rsp.data = clk_get_rate(tegra->ss_src_clk) / 1000; break; case MBOX_CMD_SET_BW: @@ -783,9 +791,11 @@ static int tegra_xusb_clk_enable(struct tegra_xusb *tegra) if (err < 0) goto disable_fs_src; - err = tegra_xusb_set_ss_clk(tegra, TEGRA_XHCI_SS_HIGH_SPEED); - if (err < 0) - goto disable_hs_src; + if (tegra->soc->scale_ss_clock) { + err = tegra_xusb_set_ss_clk(tegra, TEGRA_XHCI_SS_HIGH_SPEED); + if (err < 0) + goto disable_hs_src; + } return 0; @@ -890,11 +900,44 @@ static const struct tegra_xusb_soc tegra124_soc = { .hsic = { .offset = 6, .count = 2, }, .usb3 = { .offset = 0, .count = 2, }, }, + .scale_ss_clock = true, }; MODULE_FIRMWARE("nvidia/tegra124/xusb.bin"); +static const char * const tegra210_supply_names[] = { + "dvddio-pex", + "hvddio-pex", + "avdd-usb", + "avdd-pll-utmip", + "avdd-pll-uerefe", + "dvdd-pex-pll", + "hvdd-pex-pll-e", +}; + +static const struct tegra_xusb_phy_type tegra210_phy_types[] = { + { .name = "usb3", .num = 4, }, + { .name = "usb2", .num = 4, }, + { .name = "hsic", .num = 1, }, +}; + +static const struct tegra_xusb_soc tegra210_soc = { + .firmware_file = "nvidia/tegra210/xusb.bin", + .supply_names = tegra210_supply_names, + .num_supplies = ARRAY_SIZE(tegra210_supply_names), + .phy_types = tegra210_phy_types, + .num_types = ARRAY_SIZE(tegra210_phy_types), + .ports = { + .usb2 = { .offset = 4, .count = 4, }, + .hsic = { .offset = 8, .count = 1, }, + .usb3 = { .offset = 0, .count = 4, }, + }, + .scale_ss_clock = false, +}; +MODULE_FIRMWARE("nvidia/tegra210/xusb.bin"); + static const struct of_device_id tegra_xusb_of_match[] = { { .compatible = "nvidia,tegra124-xusb", .data = &tegra124_soc }, + { .compatible = "nvidia,tegra210-xusb", .data = &tegra210_soc }, { }, }; MODULE_DEVICE_TABLE(of, tegra_xusb_of_match); From 8f02e4f786997681e654e80801c3277b92d12215 Mon Sep 17 00:00:00 2001 From: Andrew Bresticker Date: Thu, 18 Jun 2015 17:28:40 -0400 Subject: [PATCH 10/19] clk: tegra: Add interface to enable hardware control of SATA/XUSB PLLs On Tegra210, hardware control of the SATA and XUSB pad PLLs must be done during the UPHY enable sequence rather than the PLLE enable sequence. Export functions to do this so that hardware control can be enabled from the XUSB padctl driver. Signed-off-by: Andrew Bresticker Signed-off-by: Rhyland Klein --- drivers/clk/tegra/clk-tegra210.c | 58 ++++++++++++++++++++++++++++++++ include/linux/clk/tegra.h | 5 +++ 2 files changed, 63 insertions(+) diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c index 637041fd53ad11..3d0edee1f9fe49 100644 --- a/drivers/clk/tegra/clk-tegra210.c +++ b/drivers/clk/tegra/clk-tegra210.c @@ -175,6 +175,19 @@ #define UTMIP_PLL_CFG1_FORCE_PLL_ENABLE_POWERDOWN BIT(14) #define UTMIP_PLL_CFG1_FORCE_PLL_ACTIVE_POWERDOWN BIT(12) +#define SATA_PLL_CFG0 0x490 +#define SATA_PLL_CFG0_PADPLL_RESET_SWCTL BIT(0) +#define SATA_PLL_CFG0_PADPLL_USE_LOCKDET BIT(2) +#define SATA_PLL_CFG0_PADPLL_SLEEP_IDDQ BIT(13) +#define SATA_PLL_CFG0_SEQ_ENABLE BIT(24) + +#define XUSBIO_PLL_CFG0 0x51c +#define XUSBIO_PLL_CFG0_PADPLL_RESET_SWCTL BIT(0) +#define XUSBIO_PLL_CFG0_CLK_ENABLE_SWCTL BIT(2) +#define XUSBIO_PLL_CFG0_PADPLL_USE_LOCKDET BIT(6) +#define XUSBIO_PLL_CFG0_PADPLL_SLEEP_IDDQ BIT(13) +#define XUSBIO_PLL_CFG0_SEQ_ENABLE BIT(24) + #define UTMIPLL_HW_PWRDN_CFG0 0x52c #define UTMIPLL_HW_PWRDN_CFG0_UTMIPLL_LOCK BIT(31) #define UTMIPLL_HW_PWRDN_CFG0_SEQ_START_STATE BIT(25) @@ -416,6 +429,51 @@ static const char *mux_pllmcp_clkm[] = { #define PLLU_MISC0_WRITE_MASK 0xbfffffff #define PLLU_MISC1_WRITE_MASK 0x00000007 +void tegra210_xusb_pll_hw_control_enable(void) +{ + u32 val; + + val = readl_relaxed(clk_base + XUSBIO_PLL_CFG0); + val &= ~(XUSBIO_PLL_CFG0_CLK_ENABLE_SWCTL | + XUSBIO_PLL_CFG0_PADPLL_RESET_SWCTL); + val |= XUSBIO_PLL_CFG0_PADPLL_USE_LOCKDET | + XUSBIO_PLL_CFG0_PADPLL_SLEEP_IDDQ; + writel_relaxed(val, clk_base + XUSBIO_PLL_CFG0); +} +EXPORT_SYMBOL_GPL(tegra210_xusb_pll_hw_control_enable); + +void tegra210_xusb_pll_hw_sequence_start(void) +{ + u32 val; + + val = readl_relaxed(clk_base + XUSBIO_PLL_CFG0); + val |= XUSBIO_PLL_CFG0_SEQ_ENABLE; + writel_relaxed(val, clk_base + XUSBIO_PLL_CFG0); +} +EXPORT_SYMBOL_GPL(tegra210_xusb_pll_hw_sequence_start); + +void tegra210_sata_pll_hw_control_enable(void) +{ + u32 val; + + val = readl_relaxed(clk_base + SATA_PLL_CFG0); + val &= ~SATA_PLL_CFG0_PADPLL_RESET_SWCTL; + val |= SATA_PLL_CFG0_PADPLL_USE_LOCKDET | + SATA_PLL_CFG0_PADPLL_SLEEP_IDDQ; + writel_relaxed(val, clk_base + SATA_PLL_CFG0); +} +EXPORT_SYMBOL_GPL(tegra210_sata_pll_hw_control_enable); + +void tegra210_sata_pll_hw_sequence_start(void) +{ + u32 val; + + val = readl_relaxed(clk_base + SATA_PLL_CFG0); + val |= SATA_PLL_CFG0_SEQ_ENABLE; + writel_relaxed(val, clk_base + SATA_PLL_CFG0); +} +EXPORT_SYMBOL_GPL(tegra210_sata_pll_hw_sequence_start); + static inline void _pll_misc_chk_default(void __iomem *base, struct tegra_clk_pll_params *params, u8 misc_num, u32 default_val, u32 mask) diff --git a/include/linux/clk/tegra.h b/include/linux/clk/tegra.h index 57bf7aab4516ea..7007a5f480802e 100644 --- a/include/linux/clk/tegra.h +++ b/include/linux/clk/tegra.h @@ -121,4 +121,9 @@ static inline void tegra_cpu_clock_resume(void) } #endif +extern void tegra210_xusb_pll_hw_control_enable(void); +extern void tegra210_xusb_pll_hw_sequence_start(void); +extern void tegra210_sata_pll_hw_control_enable(void); +extern void tegra210_sata_pll_hw_sequence_start(void); + #endif /* __LINUX_CLK_TEGRA_H_ */ From f71eb0c7096662a8e2b5d5a2114e06251b846447 Mon Sep 17 00:00:00 2001 From: Andrew Bresticker Date: Mon, 24 Nov 2014 16:17:18 -0800 Subject: [PATCH 11/19] of: Update Tegra XUSB pad controller binding for USB Add new bindings used for USB support by the Tegra XUSB pad controller. This includes additional PHY types, USB-specific pinconfig properties, etc. Signed-off-by: Andrew Bresticker Acked-by: Linus Walleij Reviewed-by: Stephen Warren --- .../pinctrl/nvidia,tegra124-xusb-padctl.txt | 63 +++++++++++++++++-- .../dt-bindings/pinctrl/pinctrl-tegra-xusb.h | 7 +++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt index 8a6223dbc143e2..793c3549c37717 100644 --- a/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt +++ b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt @@ -29,6 +29,15 @@ Required properties: - padctl - #phy-cells: Should be 1. The specifier is the index of the PHY to reference. See for the list of valid values. +- mboxes: Must contain an entry for the XUSB mailbox channel. + See ../mailbox/mailbox.txt for details. +- mbox-names: Must include the following entries: + - xusb + +Optional properties: +------------------- +- vbus-{0,1,2}-supply: VBUS regulator for the corresponding UTMI pad. +- vddio-hsic-supply: VDDIO regulator for the HSIC pads. Lane muxing: ------------ @@ -58,26 +67,46 @@ Optional properties: pin or group should be assigned to. Valid values for function names are listed below. - nvidia,iddq: Enables IDDQ mode of the lane. (0: no, 1: yes) +- nvidia,usb2-port: USB2 port (0, 1, or 2) to which the lane is mapped. +- nvidia,usb3-port: USB3 port (0 or 1) to which the lane is mapped. +- nvidia,hsic-strobe-trim: HSIC strobe trimmer value. +- nvidia,hsic-rx-strobe-trim: HSIC RX strobe trimmer value. (0 - 7) +- nvidia,hsic-rx-data-trim: HSIC RX data trimmer value. (0 - 7) +- nvidia,hsic-tx-rtune-n: HSIC TX RTUNEN value. (0 - 7) +- nvidia,hsic-tx-rtune-p: HSIC TX RTUNEP value. (0 - 7) +- nvidia,hsic-tx-slew-n: HSIC TX SLEWN value. (0 - 7) +- nvidia,hsic-tx-slew-p: HSIC TX SLEWP value. (0 - 7) +- nvidia,hsic-auto-term: Enables HSIC AUTO_TERM. (0: no, 1: yes) +- nvidia,otg-hs-curr-level-offset: Offset to be applied to the pad's fused + HS_CURR_LEVEL value. (0 - 63) Note that not all of these properties are valid for all lanes. Lanes can be -divided into three groups: +divided into four groups: - otg-0, otg-1, otg-2: Valid functions for this group are: "snps", "xusb", "uart", "rsvd". - The nvidia,iddq property does not apply to this group. + Only the nvidia,otg-hs-curr-level-offset property applies. + + - ulpi-0: - - ulpi-0, hsic-0, hsic-1: + Valid functions for this group are: "snps", "xusb". + + - hsic-0, hsic-1: Valid functions for this group are: "snps", "xusb". - The nvidia,iddq property does not apply to this group. + Only the nvidia,hsic-* properties apply, and only when the function is + xusb. - pcie-0, pcie-1, pcie-2, pcie-3, pcie-4, sata-0: Valid functions for this group are: "pcie", "usb3", "sata", "rsvd". + Only the nvidia,iddq, nvidia,usb2-port, and nvidia,usb3-port properties + apply. The nvidia,usb2-port and nvidia,usb3-port properties are required + when the function is usb3. Example: ======== @@ -90,6 +119,8 @@ SoC file extract: reg = <0x0 0x7009f000 0x0 0x1000>; resets = <&tegra_car 142>; reset-names = "padctl"; + mboxes = <&xusb_mbox>; + mbox-names = "xusb"; #phy-cells = <1>; }; @@ -108,15 +139,35 @@ Board file extract: ... + usb@0,70090000 { + ... + + phys = <&padctl 5>, <&padctl 6>, <&padctl 7>; + phy-names = "utmi-1", "utmi-2", "usb3-0"; + + ... + } + + ... + padctl: padctl@0,7009f000 { pinctrl-0 = <&padctl_default>; pinctrl-names = "default"; + vbus-2-supply = <&vdd_usb3_vbus>; + padctl_default: pinmux { - usb3 { - nvidia,lanes = "pcie-0", "pcie-1"; + otg { + nvidia,lanes = "otg-1", "otg-2"; + nvidia,function = "xusb"; + }; + + usb3p0 { + nvidia,lanes = "pcie-0"; nvidia,function = "usb3"; nvidia,iddq = <0>; + nvidia,usb2-port = <2>; + nvidia,usb3-port = <0>; }; pcie { diff --git a/include/dt-bindings/pinctrl/pinctrl-tegra-xusb.h b/include/dt-bindings/pinctrl/pinctrl-tegra-xusb.h index 914d56da932421..c83a4d44edde39 100644 --- a/include/dt-bindings/pinctrl/pinctrl-tegra-xusb.h +++ b/include/dt-bindings/pinctrl/pinctrl-tegra-xusb.h @@ -3,5 +3,12 @@ #define TEGRA_XUSB_PADCTL_PCIE 0 #define TEGRA_XUSB_PADCTL_SATA 1 +#define TEGRA_XUSB_PADCTL_USB3_P0 2 +#define TEGRA_XUSB_PADCTL_USB3_P1 3 +#define TEGRA_XUSB_PADCTL_UTMI_P0 4 +#define TEGRA_XUSB_PADCTL_UTMI_P1 5 +#define TEGRA_XUSB_PADCTL_UTMI_P2 6 +#define TEGRA_XUSB_PADCTL_HSIC_P0 7 +#define TEGRA_XUSB_PADCTL_HSIC_P1 8 #endif /* _DT_BINDINGS_PINCTRL_TEGRA_XUSB_H */ From 5d26d1d2505e622a4e170e87a7097192e7bbd6f8 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Wed, 30 Mar 2016 19:59:48 +0530 Subject: [PATCH 12/19] gpio: max77620: add gpio driver for MAX77620/MAX20024 MAXIM Semiconductor's PMIC, MAX77620/MAX20024 has 8 GPIO pins. It also supports interrupts from these pins. Add GPIO driver for these pins to control via GPIO APIs. Signed-off-by: Laxman Dewangan Reviewed-by: Linus Walleij --- drivers/gpio/Kconfig | 9 ++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-max77620.c | 238 +++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 drivers/gpio/gpio-max77620.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 5f3429f0bf4697..e8328d52c52cf2 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -851,6 +851,15 @@ config GPIO_LP3943 LP3943 can be used as a GPIO expander which provides up to 16 GPIOs. Open drain outputs are required for this usage. +config GPIO_MAX77620 + bool "GPIO support for PMIC MAX77620 and MAX20024" + depends on MFD_MAX77620 + help + GPIO driver for MAX77620 and MAX20024 PMIC from Maxim Semiconductor. + MAX77620 PMIC has 8 pins that can be configured as GPIOs. The + driver also provides interrupt support for each of the gpios. + Say yes here to enable the max77620 to be used as gpio controller. + config GPIO_MSIC bool "Intel MSIC mixed signal gpio support" depends on MFD_INTEL_MSIC diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 1e0b74f3b1ed98..3ff36039190b91 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_GPIO_MAX730X) += gpio-max730x.o obj-$(CONFIG_GPIO_MAX7300) += gpio-max7300.o obj-$(CONFIG_GPIO_MAX7301) += gpio-max7301.o obj-$(CONFIG_GPIO_MAX732X) += gpio-max732x.o +obj-$(CONFIG_GPIO_MAX77620) += gpio-max77620.o obj-$(CONFIG_GPIO_MB86S7X) += gpio-mb86s7x.o obj-$(CONFIG_GPIO_MENZ127) += gpio-menz127.o obj-$(CONFIG_GPIO_MC33880) += gpio-mc33880.o diff --git a/drivers/gpio/gpio-max77620.c b/drivers/gpio/gpio-max77620.c new file mode 100644 index 00000000000000..d9275623577c77 --- /dev/null +++ b/drivers/gpio/gpio-max77620.c @@ -0,0 +1,238 @@ +/* + * MAXIM MAX77620 GPIO driver + * + * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#define GPIO_REG_ADDR(offset) (MAX77620_REG_GPIO0 + offset) + +struct max77620_gpio { + struct gpio_chip gpio_chip; + struct regmap *rmap; + struct device *dev; + int gpio_irq; + int irq_base; + int gpio_base; +}; + +static const struct regmap_irq max77620_gpio_irqs[] = { + REGMAP_IRQ_REG(0, 0, MAX77620_IRQ_LVL2_GPIO_EDGE0), + REGMAP_IRQ_REG(1, 0, MAX77620_IRQ_LVL2_GPIO_EDGE1), + REGMAP_IRQ_REG(2, 0, MAX77620_IRQ_LVL2_GPIO_EDGE2), + REGMAP_IRQ_REG(3, 0, MAX77620_IRQ_LVL2_GPIO_EDGE3), + REGMAP_IRQ_REG(4, 0, MAX77620_IRQ_LVL2_GPIO_EDGE4), + REGMAP_IRQ_REG(5, 0, MAX77620_IRQ_LVL2_GPIO_EDGE5), + REGMAP_IRQ_REG(6, 0, MAX77620_IRQ_LVL2_GPIO_EDGE6), + REGMAP_IRQ_REG(7, 0, MAX77620_IRQ_LVL2_GPIO_EDGE7), +}; + +static struct regmap_irq_chip max77620_gpio_irq_chip = { + .name = "max77620-gpio", + .irqs = max77620_gpio_irqs, + .num_irqs = ARRAY_SIZE(max77620_gpio_irqs), + .num_regs = 1, + .irq_reg_stride = 1, + .status_base = MAX77620_REG_IRQ_LVL2_GPIO, +}; + +static int max77620_gpio_dir_input(struct gpio_chip *gc, unsigned int offset) +{ + struct max77620_gpio *mgpio = gpiochip_get_data(gc); + int ret; + + ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset), + MAX77620_CNFG_GPIO_DIR_MASK, + MAX77620_CNFG_GPIO_DIR_INPUT); + if (ret < 0) + dev_err(mgpio->dev, "CNFG_GPIOx dir update failed: %d\n", ret); + + return ret; +} + +static int max77620_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct max77620_gpio *mgpio = gpiochip_get_data(gc); + unsigned int val; + int ret; + + ret = regmap_read(mgpio->rmap, GPIO_REG_ADDR(offset), &val); + if (ret < 0) { + dev_err(mgpio->dev, "CNFG_GPIOx read failed: %d\n", ret); + return ret; + } + + return !!(val & MAX77620_CNFG_GPIO_INPUT_VAL_MASK); +} + +static int max77620_gpio_dir_output(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct max77620_gpio *mgpio = gpiochip_get_data(gc); + u8 val; + int ret; + + val = (value) ? MAX77620_CNFG_GPIO_OUTPUT_VAL_HIGH : + MAX77620_CNFG_GPIO_OUTPUT_VAL_LOW; + + ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset), + MAX77620_CNFG_GPIO_OUTPUT_VAL_MASK, val); + if (ret < 0) { + dev_err(mgpio->dev, "CNFG_GPIOx val update failed: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset), + MAX77620_CNFG_GPIO_DIR_MASK, + MAX77620_CNFG_GPIO_DIR_OUTPUT); + if (ret < 0) + dev_err(mgpio->dev, "CNFG_GPIOx dir update failed: %d\n", ret); + + return ret; +} + +static int max77620_gpio_set_debounce(struct gpio_chip *gc, + unsigned int offset, + unsigned int debounce) +{ + struct max77620_gpio *mgpio = gpiochip_get_data(gc); + u8 val; + int ret; + + switch (debounce) { + case 0: + val = MAX77620_CNFG_GPIO_DBNC_None; + break; + case 1 ... 8: + val = MAX77620_CNFG_GPIO_DBNC_8ms; + break; + case 9 ... 16: + val = MAX77620_CNFG_GPIO_DBNC_16ms; + break; + case 17 ... 32: + val = MAX77620_CNFG_GPIO_DBNC_32ms; + break; + default: + dev_err(mgpio->dev, "Illegal value %u\n", debounce); + return -EINVAL; + } + + ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset), + MAX77620_CNFG_GPIO_DBNC_MASK, val); + if (ret < 0) + dev_err(mgpio->dev, "CNFG_GPIOx_DBNC update failed: %d\n", ret); + + return ret; +} + +static void max77620_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct max77620_gpio *mgpio = gpiochip_get_data(gc); + u8 val; + int ret; + + val = (value) ? MAX77620_CNFG_GPIO_OUTPUT_VAL_HIGH : + MAX77620_CNFG_GPIO_OUTPUT_VAL_LOW; + + ret = regmap_update_bits(mgpio->rmap, GPIO_REG_ADDR(offset), + MAX77620_CNFG_GPIO_OUTPUT_VAL_MASK, val); + if (ret < 0) + dev_err(mgpio->dev, "CNFG_GPIO_OUT update failed: %d\n", ret); +} + +static int max77620_gpio_to_irq(struct gpio_chip *gc, unsigned int offset) +{ + struct max77620_gpio *mgpio = gpiochip_get_data(gc); + struct max77620_chip *chip = dev_get_drvdata(mgpio->dev->parent); + + return regmap_irq_get_virq(chip->gpio_irq_data, offset); +} + +static int max77620_gpio_probe(struct platform_device *pdev) +{ + struct max77620_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct max77620_gpio *mgpio; + int gpio_irq; + int ret; + + gpio_irq = platform_get_irq(pdev, 0); + if (gpio_irq <= 0) { + dev_err(&pdev->dev, "GPIO irq not available %d\n", gpio_irq); + return -ENODEV; + } + + mgpio = devm_kzalloc(&pdev->dev, sizeof(*mgpio), GFP_KERNEL); + if (!mgpio) + return -ENOMEM; + + mgpio->rmap = chip->rmap; + mgpio->dev = &pdev->dev; + mgpio->gpio_irq = gpio_irq; + + mgpio->gpio_chip.label = pdev->name; + mgpio->gpio_chip.parent = &pdev->dev; + mgpio->gpio_chip.direction_input = max77620_gpio_dir_input; + mgpio->gpio_chip.get = max77620_gpio_get; + mgpio->gpio_chip.direction_output = max77620_gpio_dir_output; + mgpio->gpio_chip.set_debounce = max77620_gpio_set_debounce; + mgpio->gpio_chip.set = max77620_gpio_set; + mgpio->gpio_chip.to_irq = max77620_gpio_to_irq; + mgpio->gpio_chip.ngpio = MAX77620_GPIO_NR; + mgpio->gpio_chip.can_sleep = 1; + mgpio->gpio_chip.base = -1; + mgpio->irq_base = -1; +#ifdef CONFIG_OF_GPIO + mgpio->gpio_chip.of_node = pdev->dev.parent->of_node; +#endif + + platform_set_drvdata(pdev, mgpio); + + ret = devm_gpiochip_add_data(&pdev->dev, &mgpio->gpio_chip, mgpio); + if (ret < 0) { + dev_err(&pdev->dev, "gpio_init: Failed to add max77620_gpio\n"); + return ret; + } + + mgpio->gpio_base = mgpio->gpio_chip.base; + ret = devm_regmap_add_irq_chip(&pdev->dev, chip->rmap, mgpio->gpio_irq, + IRQF_ONESHOT, mgpio->irq_base, + &max77620_gpio_irq_chip, + &chip->gpio_irq_data); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to add gpio irq_chip %d\n", ret); + return ret; + } + + return 0; +} + +static const struct platform_device_id max77620_gpio_devtype[] = { + { .name = "max77620-gpio", }, + {}, +}; +MODULE_DEVICE_TABLE(platform, max77620_gpio_devtype); + +static struct platform_driver max77620_gpio_driver = { + .driver.name = "max77620-gpio", + .probe = max77620_gpio_probe, + .id_table = max77620_gpio_devtype, +}; + +module_platform_driver(max77620_gpio_driver); + +MODULE_DESCRIPTION("GPIO interface for MAX77620 and MAX20024 PMIC"); +MODULE_AUTHOR("Laxman Dewangan "); +MODULE_AUTHOR("Chaitanya Bandi "); +MODULE_ALIAS("platform:max77620-gpio"); +MODULE_LICENSE("GPL v2"); From 373ceebb4e18ff7d74747c00ed675ab6ed6dc6bc Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Wed, 30 Mar 2016 19:59:47 +0530 Subject: [PATCH 13/19] gpio: add DT binding doc for gpio of PMIC max77620/max20024 Maxim Semiconductor's PMIC MAX77620/MAX20024 has 8 GPIO pins which act as GPIO as well as special function mode. Add DT binding document to support these pins in GPIO mode via GPIO framework. Signed-off-by: Laxman Dewangan Acked-by: Rob Herring Acked-by: Linus Walleij --- .../bindings/gpio/gpio-max77620.txt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/gpio-max77620.txt diff --git a/Documentation/devicetree/bindings/gpio/gpio-max77620.txt b/Documentation/devicetree/bindings/gpio/gpio-max77620.txt new file mode 100644 index 00000000000000..410e716fd3d22c --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/gpio-max77620.txt @@ -0,0 +1,25 @@ +GPIO driver for MAX77620 Power management IC from Maxim Semiconductor. + +Device has 8 GPIO pins which can be configured as GPIO as well as the +special IO functions. + +Required properties: +------------------- +- gpio-controller : Marks the device node as a gpio controller. +- #gpio-cells : Should be two. The first cell is the pin number and + the second cell is used to specify the gpio polarity: + 0 = active high + 1 = active low +For more details, please refer generic GPIO DT binding document +. + +Example: +-------- +#include +... +max77620@3c { + compatible = "maxim,max77620"; + + gpio-controller; + #gpio-cells = <2>; +}; From df545b9e6ff448c36ac46fa816fcdf740075c10b Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Wed, 30 Mar 2016 19:59:46 +0530 Subject: [PATCH 14/19] pinctrl: max77620: add pincontrol driver for MAX77620/MAX20024 MAXIM Semiconductor's PMIC, MAX77620/MAX20024 has 8 GPIO pins which also act as the special function in alternate mode. Also there is configuration like push-pull, open drain, FPS timing etc for these pins. Add pin control driver to configure these parameters through pin control APIs. Signed-off-by: Laxman Dewangan Reviewed-by: Linus Walleij --- drivers/pinctrl/Kconfig | 10 + drivers/pinctrl/Makefile | 1 + drivers/pinctrl/pinctrl-max77620.c | 688 +++++++++++++++++++++++++++++ 3 files changed, 699 insertions(+) create mode 100644 drivers/pinctrl/pinctrl-max77620.c diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index fb8200b8e8ec60..15214e6cde8e39 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -196,6 +196,16 @@ config PINCTRL_COH901 COH 901 335 and COH 901 571/3. They contain 3, 5 or 7 ports of 8 GPIO pins each. +config PINCTRL_MAX77620 + bool "MAX77620/MAX20024 Pincontrol support" + depends on MFD_MAX77620 + select GENERIC_PINCONF + help + Say Yes here to enable Pin control support for Maxim PMIC MAX77620. + This PMIC has 8 GPIO pins that work as GPIO as well as special + function in alternate mode. This driver also configure push-pull, + open drain, FPS slots etc. + config PINCTRL_PALMAS bool "Pinctrl driver for the PALMAS Series MFD devices" depends on OF && MFD_PALMAS diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index e4bc1151e04f3c..d6f349c0af4bfa 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_PINCTRL_AMD) += pinctrl-amd.o obj-$(CONFIG_PINCTRL_DIGICOLOR) += pinctrl-digicolor.o obj-$(CONFIG_PINCTRL_FALCON) += pinctrl-falcon.o obj-$(CONFIG_PINCTRL_MESON) += meson/ +obj-$(CONFIG_PINCTRL_MAX77620) += pinctrl-max77620.o obj-$(CONFIG_PINCTRL_PALMAS) += pinctrl-palmas.o obj-$(CONFIG_PINCTRL_PIC32) += pinctrl-pic32.o obj-$(CONFIG_PINCTRL_PISTACHIO) += pinctrl-pistachio.o diff --git a/drivers/pinctrl/pinctrl-max77620.c b/drivers/pinctrl/pinctrl-max77620.c new file mode 100644 index 00000000000000..9d76f0eaa2da72 --- /dev/null +++ b/drivers/pinctrl/pinctrl-max77620.c @@ -0,0 +1,688 @@ +/* + * MAX77620 pin control driver. + * + * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. + * + * Author: + * Chaitanya Bandi + * Laxman Dewangan + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "pinconf.h" +#include "pinctrl-utils.h" + +#define MAX77620_PIN_NUM 8 + +enum max77620_pin_ppdrv { + MAX77620_PIN_UNCONFIG_DRV, + MAX77620_PIN_OD_DRV, + MAX77620_PIN_PP_DRV, +}; + +enum max77620_pinconf_param { + MAX77620_ACTIVE_FPS_SOURCE = PIN_CONFIG_END + 1, + MAX77620_ACTIVE_FPS_POWER_ON_SLOTS, + MAX77620_ACTIVE_FPS_POWER_DOWN_SLOTS, + MAX77620_SUSPEND_FPS_SOURCE, + MAX77620_SUSPEND_FPS_POWER_ON_SLOTS, + MAX77620_SUSPEND_FPS_POWER_DOWN_SLOTS, +}; + +struct max77620_pin_function { + const char *name; + const char * const *groups; + unsigned int ngroups; + int mux_option; +}; + +struct max77620_cfg_param { + const char *property; + enum max77620_pinconf_param param; +}; + +static const struct pinconf_generic_params max77620_cfg_params[] = { + { + .property = "maxim,active-fps-source", + .param = MAX77620_ACTIVE_FPS_SOURCE, + }, { + .property = "maxim,active-fps-power-up-slot", + .param = MAX77620_ACTIVE_FPS_POWER_ON_SLOTS, + }, { + .property = "maxim,active-fps-power-down-slot", + .param = MAX77620_ACTIVE_FPS_POWER_DOWN_SLOTS, + }, { + .property = "maxim,suspend-fps-source", + .param = MAX77620_SUSPEND_FPS_SOURCE, + }, { + .property = "maxim,suspend-fps-power-up-slot", + .param = MAX77620_SUSPEND_FPS_POWER_ON_SLOTS, + }, { + .property = "maxim,suspend-fps-power-down-slot", + .param = MAX77620_SUSPEND_FPS_POWER_DOWN_SLOTS, + }, +}; + +enum max77620_alternate_pinmux_option { + MAX77620_PINMUX_GPIO = 0, + MAX77620_PINMUX_LOW_POWER_MODE_CONTROL_IN = 1, + MAX77620_PINMUX_FLEXIBLE_POWER_SEQUENCER_OUT = 2, + MAX77620_PINMUX_32K_OUT1 = 3, + MAX77620_PINMUX_SD0_DYNAMIC_VOLTAGE_SCALING_IN = 4, + MAX77620_PINMUX_SD1_DYNAMIC_VOLTAGE_SCALING_IN = 5, + MAX77620_PINMUX_REFERENCE_OUT = 6, +}; + +struct max77620_pingroup { + const char *name; + const unsigned int pins[1]; + unsigned int npins; + enum max77620_alternate_pinmux_option alt_option; +}; + +struct max77620_pin_info { + enum max77620_pin_ppdrv drv_type; + int pull_config; +}; + +struct max77620_fps_config { + int active_fps_src; + int active_power_up_slots; + int active_power_down_slots; + int suspend_fps_src; + int suspend_power_up_slots; + int suspend_power_down_slots; +}; + +struct max77620_pctrl_info { + struct device *dev; + struct pinctrl_dev *pctl; + struct regmap *rmap; + int pins_current_opt[MAX77620_GPIO_NR]; + const struct max77620_pin_function *functions; + unsigned int num_functions; + const struct max77620_pingroup *pin_groups; + int num_pin_groups; + const struct pinctrl_pin_desc *pins; + unsigned int num_pins; + struct max77620_pin_info pin_info[MAX77620_PIN_NUM]; + struct max77620_fps_config fps_config[MAX77620_PIN_NUM]; +}; + +static const struct pinctrl_pin_desc max77620_pins_desc[] = { + PINCTRL_PIN(MAX77620_GPIO0, "gpio0"), + PINCTRL_PIN(MAX77620_GPIO1, "gpio1"), + PINCTRL_PIN(MAX77620_GPIO2, "gpio2"), + PINCTRL_PIN(MAX77620_GPIO3, "gpio3"), + PINCTRL_PIN(MAX77620_GPIO4, "gpio4"), + PINCTRL_PIN(MAX77620_GPIO5, "gpio5"), + PINCTRL_PIN(MAX77620_GPIO6, "gpio6"), + PINCTRL_PIN(MAX77620_GPIO7, "gpio7"), +}; + +static const char * const gpio_groups[] = { + "gpio0", + "gpio1", + "gpio2", + "gpio3", + "gpio4", + "gpio5", + "gpio6", + "gpio7", +}; + +#define FUNCTION_GROUP(fname, mux) \ + { \ + .name = fname, \ + .groups = gpio_groups, \ + .ngroups = ARRAY_SIZE(gpio_groups), \ + .mux_option = MAX77620_PINMUX_##mux, \ + } + +static const struct max77620_pin_function max77620_pin_function[] = { + FUNCTION_GROUP("gpio", GPIO), + FUNCTION_GROUP("lpm-control-in", LOW_POWER_MODE_CONTROL_IN), + FUNCTION_GROUP("fps-out", FLEXIBLE_POWER_SEQUENCER_OUT), + FUNCTION_GROUP("32k-out1", 32K_OUT1), + FUNCTION_GROUP("sd0-dvs-in", SD0_DYNAMIC_VOLTAGE_SCALING_IN), + FUNCTION_GROUP("sd1-dvs-in", SD1_DYNAMIC_VOLTAGE_SCALING_IN), + FUNCTION_GROUP("reference-out", REFERENCE_OUT), +}; + +#define MAX77620_PINGROUP(pg_name, pin_id, option) \ + { \ + .name = #pg_name, \ + .pins = {MAX77620_##pin_id}, \ + .npins = 1, \ + .alt_option = MAX77620_PINMUX_##option, \ + } + +static const struct max77620_pingroup max77620_pingroups[] = { + MAX77620_PINGROUP(gpio0, GPIO0, LOW_POWER_MODE_CONTROL_IN), + MAX77620_PINGROUP(gpio1, GPIO1, FLEXIBLE_POWER_SEQUENCER_OUT), + MAX77620_PINGROUP(gpio2, GPIO2, FLEXIBLE_POWER_SEQUENCER_OUT), + MAX77620_PINGROUP(gpio3, GPIO3, FLEXIBLE_POWER_SEQUENCER_OUT), + MAX77620_PINGROUP(gpio4, GPIO4, 32K_OUT1), + MAX77620_PINGROUP(gpio5, GPIO5, SD0_DYNAMIC_VOLTAGE_SCALING_IN), + MAX77620_PINGROUP(gpio6, GPIO6, SD1_DYNAMIC_VOLTAGE_SCALING_IN), + MAX77620_PINGROUP(gpio7, GPIO7, REFERENCE_OUT), +}; + +static int max77620_pinctrl_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct max77620_pctrl_info *mpci = pinctrl_dev_get_drvdata(pctldev); + + return mpci->num_pin_groups; +} + +static const char *max77620_pinctrl_get_group_name( + struct pinctrl_dev *pctldev, unsigned int group) +{ + struct max77620_pctrl_info *mpci = pinctrl_dev_get_drvdata(pctldev); + + return mpci->pin_groups[group].name; +} + +static int max77620_pinctrl_get_group_pins( + struct pinctrl_dev *pctldev, unsigned int group, + const unsigned int **pins, unsigned int *num_pins) +{ + struct max77620_pctrl_info *mpci = pinctrl_dev_get_drvdata(pctldev); + + *pins = mpci->pin_groups[group].pins; + *num_pins = mpci->pin_groups[group].npins; + + return 0; +} + +static const struct pinctrl_ops max77620_pinctrl_ops = { + .get_groups_count = max77620_pinctrl_get_groups_count, + .get_group_name = max77620_pinctrl_get_group_name, + .get_group_pins = max77620_pinctrl_get_group_pins, + .dt_node_to_map = pinconf_generic_dt_node_to_map_pin, + .dt_free_map = pinctrl_utils_dt_free_map, +}; + +static int max77620_pinctrl_get_funcs_count(struct pinctrl_dev *pctldev) +{ + struct max77620_pctrl_info *mpci = pinctrl_dev_get_drvdata(pctldev); + + return mpci->num_functions; +} + +static const char *max77620_pinctrl_get_func_name(struct pinctrl_dev *pctldev, + unsigned int function) +{ + struct max77620_pctrl_info *mpci = pinctrl_dev_get_drvdata(pctldev); + + return mpci->functions[function].name; +} + +static int max77620_pinctrl_get_func_groups(struct pinctrl_dev *pctldev, + unsigned int function, + const char * const **groups, + unsigned int * const num_groups) +{ + struct max77620_pctrl_info *mpci = pinctrl_dev_get_drvdata(pctldev); + + *groups = mpci->functions[function].groups; + *num_groups = mpci->functions[function].ngroups; + + return 0; +} + +static int max77620_pinctrl_enable(struct pinctrl_dev *pctldev, + unsigned int function, unsigned int group) +{ + struct max77620_pctrl_info *mpci = pinctrl_dev_get_drvdata(pctldev); + u8 val; + int ret; + + if (function == MAX77620_PINMUX_GPIO) { + val = 0; + } else if (function == mpci->pin_groups[group].alt_option) { + val = 1 << group; + } else { + dev_err(mpci->dev, "GPIO %u doesn't have function %u\n", + group, function); + return -EINVAL; + } + ret = regmap_update_bits(mpci->rmap, MAX77620_REG_AME_GPIO, + BIT(group), val); + if (ret < 0) + dev_err(mpci->dev, "REG AME GPIO update failed: %d\n", ret); + + return ret; +} + +static const struct pinmux_ops max77620_pinmux_ops = { + .get_functions_count = max77620_pinctrl_get_funcs_count, + .get_function_name = max77620_pinctrl_get_func_name, + .get_function_groups = max77620_pinctrl_get_func_groups, + .set_mux = max77620_pinctrl_enable, +}; + +static int max77620_pinconf_get(struct pinctrl_dev *pctldev, + unsigned int pin, unsigned long *config) +{ + struct max77620_pctrl_info *mpci = pinctrl_dev_get_drvdata(pctldev); + struct device *dev = mpci->dev; + enum pin_config_param param = pinconf_to_config_param(*config); + unsigned int val; + int arg = 0; + int ret; + + switch (param) { + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + if (mpci->pin_info[pin].drv_type == MAX77620_PIN_OD_DRV) + arg = 1; + break; + + case PIN_CONFIG_DRIVE_PUSH_PULL: + if (mpci->pin_info[pin].drv_type == MAX77620_PIN_PP_DRV) + arg = 1; + break; + + case PIN_CONFIG_BIAS_PULL_UP: + ret = regmap_read(mpci->rmap, MAX77620_REG_PUE_GPIO, &val); + if (ret < 0) { + dev_err(dev, "Reg PUE_GPIO read failed: %d\n", ret); + return ret; + } + if (val & BIT(pin)) + arg = 1; + break; + + case PIN_CONFIG_BIAS_PULL_DOWN: + ret = regmap_read(mpci->rmap, MAX77620_REG_PDE_GPIO, &val); + if (ret < 0) { + dev_err(dev, "Reg PDE_GPIO read failed: %d\n", ret); + return ret; + } + if (val & BIT(pin)) + arg = 1; + break; + + default: + dev_err(dev, "Properties not supported\n"); + return -ENOTSUPP; + } + + *config = pinconf_to_config_packed(param, (u16)arg); + + return 0; +} + +static int max77620_get_default_fps(struct max77620_pctrl_info *mpci, + int addr, int *fps) +{ + unsigned int val; + int ret; + + ret = regmap_read(mpci->rmap, addr, &val); + if (ret < 0) { + dev_err(mpci->dev, "Reg PUE_GPIO read failed: %d\n", ret); + return ret; + } + *fps = (val & MAX77620_FPS_SRC_MASK) >> MAX77620_FPS_SRC_SHIFT; + + return 0; +} + +static int max77620_set_fps_param(struct max77620_pctrl_info *mpci, + int pin, int param) +{ + struct max77620_fps_config *fps_config = &mpci->fps_config[pin]; + int addr, ret; + int param_val; + int mask, shift; + + if ((pin < MAX77620_GPIO1) || (pin > MAX77620_GPIO3)) + return 0; + + addr = MAX77620_REG_FPS_GPIO1 + pin - 1; + switch (param) { + case MAX77620_ACTIVE_FPS_SOURCE: + case MAX77620_SUSPEND_FPS_SOURCE: + mask = MAX77620_FPS_SRC_MASK; + shift = MAX77620_FPS_SRC_SHIFT; + param_val = fps_config->active_fps_src; + if (param == MAX77620_SUSPEND_FPS_SOURCE) + param_val = fps_config->suspend_fps_src; + break; + + case MAX77620_ACTIVE_FPS_POWER_ON_SLOTS: + case MAX77620_SUSPEND_FPS_POWER_ON_SLOTS: + mask = MAX77620_FPS_PU_PERIOD_MASK; + shift = MAX77620_FPS_PU_PERIOD_SHIFT; + param_val = fps_config->active_power_up_slots; + if (param == MAX77620_SUSPEND_FPS_POWER_ON_SLOTS) + param_val = fps_config->suspend_power_up_slots; + break; + + case MAX77620_ACTIVE_FPS_POWER_DOWN_SLOTS: + case MAX77620_SUSPEND_FPS_POWER_DOWN_SLOTS: + mask = MAX77620_FPS_PD_PERIOD_MASK; + shift = MAX77620_FPS_PD_PERIOD_SHIFT; + param_val = fps_config->active_power_down_slots; + if (param == MAX77620_SUSPEND_FPS_POWER_DOWN_SLOTS) + param_val = fps_config->suspend_power_down_slots; + break; + + default: + dev_err(mpci->dev, "Invalid parameter %d for pin %d\n", + param, pin); + return -EINVAL; + } + + if (param_val < 0) + return 0; + + ret = regmap_update_bits(mpci->rmap, addr, mask, param_val << shift); + if (ret < 0) + dev_err(mpci->dev, "Reg 0x%02x update failed %d\n", addr, ret); + + return ret; +} + +static int max77620_pinconf_set(struct pinctrl_dev *pctldev, + unsigned int pin, unsigned long *configs, + unsigned int num_configs) +{ + struct max77620_pctrl_info *mpci = pinctrl_dev_get_drvdata(pctldev); + struct device *dev = mpci->dev; + struct max77620_fps_config *fps_config; + int param; + u16 param_val; + unsigned int val; + unsigned int pu_val; + unsigned int pd_val; + int addr, ret; + int i; + + for (i = 0; i < num_configs; i++) { + param = pinconf_to_config_param(configs[i]); + param_val = pinconf_to_config_argument(configs[i]); + + switch (param) { + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + val = param_val ? 0 : 1; + ret = regmap_update_bits(mpci->rmap, + MAX77620_REG_GPIO0 + pin, + MAX77620_CNFG_GPIO_DRV_MASK, + val); + if (ret < 0) { + dev_err(dev, "Reg 0x%02x update failed %d\n", + MAX77620_REG_GPIO0 + pin, ret); + return ret; + } + mpci->pin_info[pin].drv_type = val ? + MAX77620_PIN_PP_DRV : MAX77620_PIN_OD_DRV; + break; + + case PIN_CONFIG_DRIVE_PUSH_PULL: + val = param_val ? 1 : 0; + ret = regmap_update_bits(mpci->rmap, + MAX77620_REG_GPIO0 + pin, + MAX77620_CNFG_GPIO_DRV_MASK, + val); + if (ret < 0) { + dev_err(dev, "Reg 0x%02x update failed %d\n", + MAX77620_REG_GPIO0 + pin, ret); + return ret; + } + mpci->pin_info[pin].drv_type = val ? + MAX77620_PIN_PP_DRV : MAX77620_PIN_OD_DRV; + break; + + case MAX77620_ACTIVE_FPS_SOURCE: + case MAX77620_ACTIVE_FPS_POWER_ON_SLOTS: + case MAX77620_ACTIVE_FPS_POWER_DOWN_SLOTS: + if ((pin < MAX77620_GPIO1) || (pin > MAX77620_GPIO3)) + return -EINVAL; + + fps_config = &mpci->fps_config[pin]; + + if ((param == MAX77620_ACTIVE_FPS_SOURCE) && + (param_val == MAX77620_FPS_SRC_DEF)) { + addr = MAX77620_REG_FPS_GPIO1 + pin - 1; + ret = max77620_get_default_fps( + mpci, addr, + &fps_config->active_fps_src); + if (ret < 0) + return ret; + break; + } + + if (param == MAX77620_ACTIVE_FPS_SOURCE) + fps_config->active_fps_src = param_val; + else if (param == MAX77620_ACTIVE_FPS_POWER_ON_SLOTS) + fps_config->active_power_up_slots = param_val; + else + fps_config->active_power_down_slots = param_val; + + ret = max77620_set_fps_param(mpci, pin, param); + if (ret < 0) + return ret; + break; + + case MAX77620_SUSPEND_FPS_SOURCE: + case MAX77620_SUSPEND_FPS_POWER_ON_SLOTS: + case MAX77620_SUSPEND_FPS_POWER_DOWN_SLOTS: + if ((pin < MAX77620_GPIO1) || (pin > MAX77620_GPIO3)) + return -EINVAL; + + fps_config = &mpci->fps_config[pin]; + + if ((param == MAX77620_SUSPEND_FPS_SOURCE) && + (param_val == MAX77620_FPS_SRC_DEF)) { + addr = MAX77620_REG_FPS_GPIO1 + pin - 1; + ret = max77620_get_default_fps( + mpci, addr, + &fps_config->suspend_fps_src); + if (ret < 0) + return ret; + break; + } + + if (param == MAX77620_SUSPEND_FPS_SOURCE) + fps_config->suspend_fps_src = param_val; + else if (param == MAX77620_SUSPEND_FPS_POWER_ON_SLOTS) + fps_config->suspend_power_up_slots = param_val; + else + fps_config->suspend_power_down_slots = + param_val; + break; + + case PIN_CONFIG_BIAS_PULL_UP: + case PIN_CONFIG_BIAS_PULL_DOWN: + pu_val = (param == PIN_CONFIG_BIAS_PULL_UP) ? + BIT(pin) : 0; + pd_val = (param == PIN_CONFIG_BIAS_PULL_DOWN) ? + BIT(pin) : 0; + + ret = regmap_update_bits(mpci->rmap, + MAX77620_REG_PUE_GPIO, + BIT(pin), pu_val); + if (ret < 0) { + dev_err(dev, "PUE_GPIO update failed: %d\n", + ret); + return ret; + } + + ret = regmap_update_bits(mpci->rmap, + MAX77620_REG_PDE_GPIO, + BIT(pin), pd_val); + if (ret < 0) { + dev_err(dev, "PDE_GPIO update failed: %d\n", + ret); + return ret; + } + break; + + default: + dev_err(dev, "Properties not supported\n"); + return -ENOTSUPP; + } + } + + return 0; +} + +static const struct pinconf_ops max77620_pinconf_ops = { + .pin_config_get = max77620_pinconf_get, + .pin_config_set = max77620_pinconf_set, +}; + +static struct pinctrl_desc max77620_pinctrl_desc = { + .pctlops = &max77620_pinctrl_ops, + .pmxops = &max77620_pinmux_ops, + .confops = &max77620_pinconf_ops, +}; + +static int max77620_pinctrl_probe(struct platform_device *pdev) +{ + struct max77620_chip *max77620 = dev_get_drvdata(pdev->dev.parent); + struct max77620_pctrl_info *mpci; + int i; + + mpci = devm_kzalloc(&pdev->dev, sizeof(*mpci), GFP_KERNEL); + if (!mpci) + return -ENOMEM; + + mpci->dev = &pdev->dev; + mpci->dev->of_node = pdev->dev.parent->of_node; + mpci->rmap = max77620->rmap; + + mpci->pins = max77620_pins_desc; + mpci->num_pins = ARRAY_SIZE(max77620_pins_desc); + mpci->functions = max77620_pin_function; + mpci->num_functions = ARRAY_SIZE(max77620_pin_function); + mpci->pin_groups = max77620_pingroups; + mpci->num_pin_groups = ARRAY_SIZE(max77620_pingroups); + platform_set_drvdata(pdev, mpci); + + max77620_pinctrl_desc.name = dev_name(&pdev->dev); + max77620_pinctrl_desc.pins = max77620_pins_desc; + max77620_pinctrl_desc.npins = ARRAY_SIZE(max77620_pins_desc); + max77620_pinctrl_desc.num_custom_params = + ARRAY_SIZE(max77620_cfg_params); + max77620_pinctrl_desc.custom_params = max77620_cfg_params; + + for (i = 0; i < MAX77620_PIN_NUM; ++i) { + mpci->fps_config[i].active_fps_src = -1; + mpci->fps_config[i].active_power_up_slots = -1; + mpci->fps_config[i].active_power_down_slots = -1; + mpci->fps_config[i].suspend_fps_src = -1; + mpci->fps_config[i].suspend_power_up_slots = -1; + mpci->fps_config[i].suspend_power_down_slots = -1; + } + + mpci->pctl = pinctrl_register(&max77620_pinctrl_desc, + &pdev->dev, mpci); + if (IS_ERR(mpci->pctl)) { + dev_err(&pdev->dev, "Couldn't register pinctrl driver\n"); + return PTR_ERR(mpci->pctl); + } + + return 0; +} + +static int max77620_pinctrl_remove(struct platform_device *pdev) +{ + struct max77620_pctrl_info *mpci = platform_get_drvdata(pdev); + + pinctrl_unregister(mpci->pctl); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max77620_suspend_fps_param[] = { + MAX77620_SUSPEND_FPS_SOURCE, + MAX77620_SUSPEND_FPS_POWER_ON_SLOTS, + MAX77620_SUSPEND_FPS_POWER_DOWN_SLOTS, +}; + +static int max77620_active_fps_param[] = { + MAX77620_ACTIVE_FPS_SOURCE, + MAX77620_ACTIVE_FPS_POWER_ON_SLOTS, + MAX77620_ACTIVE_FPS_POWER_DOWN_SLOTS, +}; + +static int max77620_pinctrl_suspend(struct device *dev) +{ + struct max77620_pctrl_info *mpci = dev_get_drvdata(dev); + int pin, p; + + for (pin = 0; pin < MAX77620_PIN_NUM; ++pin) { + if ((pin < MAX77620_GPIO1) || (pin > MAX77620_GPIO3)) + continue; + for (p = 0; p < 3; ++p) + max77620_set_fps_param( + mpci, pin, max77620_suspend_fps_param[p]); + } + + return 0; +}; + +static int max77620_pinctrl_resume(struct device *dev) +{ + struct max77620_pctrl_info *mpci = dev_get_drvdata(dev); + int pin, p; + + for (pin = 0; pin < MAX77620_PIN_NUM; ++pin) { + if ((pin < MAX77620_GPIO1) || (pin > MAX77620_GPIO3)) + continue; + for (p = 0; p < 3; ++p) + max77620_set_fps_param( + mpci, pin, max77620_active_fps_param[p]); + } + + return 0; +} +#endif + +static const struct dev_pm_ops max77620_pinctrl_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS( + max77620_pinctrl_suspend, max77620_pinctrl_resume) +}; + +static const struct platform_device_id max77620_pinctrl_devtype[] = { + { .name = "max77620-pinctrl", }, + { .name = "max20024-pinctrl", }, + {}, +}; +MODULE_DEVICE_TABLE(platform, max77620_pinctrl_devtype); + +static struct platform_driver max77620_pinctrl_driver = { + .driver = { + .name = "max77620-pinctrl", + .pm = &max77620_pinctrl_pm_ops, + }, + .probe = max77620_pinctrl_probe, + .remove = max77620_pinctrl_remove, + .id_table = max77620_pinctrl_devtype, +}; + +module_platform_driver(max77620_pinctrl_driver); + +MODULE_DESCRIPTION("MAX77620/MAX20024 pin control driver"); +MODULE_AUTHOR("Chaitanya Bandi"); +MODULE_AUTHOR("Laxman Dewangan"); +MODULE_ALIAS("platform:max77620-pinctrl"); +MODULE_LICENSE("GPL v2"); From 953c5d8b9ea21a9964971f090f836f389c9f0863 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Wed, 30 Mar 2016 19:59:45 +0530 Subject: [PATCH 15/19] pinctrl: add DT binding doc for pincontrol of PMIC max77620/max20024 Maxim Semiconductor's PMIC MAX77620/MAX20024 has 8 GPIO pins which act as GPIO as well as special function mode. Add DT binding document to configure pins in function mode as well as pin configuration parameters. Signed-off-by: Laxman Dewangan Acked-by: Rob Herring Acked-by: Linus Walleij --- .../bindings/pinctrl/pinctrl-max77620.txt | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 Documentation/devicetree/bindings/pinctrl/pinctrl-max77620.txt diff --git a/Documentation/devicetree/bindings/pinctrl/pinctrl-max77620.txt b/Documentation/devicetree/bindings/pinctrl/pinctrl-max77620.txt new file mode 100644 index 00000000000000..ad4fce3552bbec --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/pinctrl-max77620.txt @@ -0,0 +1,127 @@ +Pincontrol driver for MAX77620 Power management IC from Maxim Semiconductor. + +Device has 8 GPIO pins which can be configured as GPIO as well as the +special IO functions. + +Please refer file +for details of the common pinctrl bindings used by client devices, +including the meaning of the phrase "pin configuration node". + +Optional Pinmux properties: +-------------------------- +Following properties are required if default setting of pins are required +at boot. +- pinctrl-names: A pinctrl state named per . +- pinctrl[0...n]: Properties to contain the phandle for pinctrl states per + . + +The pin configurations are defined as child of the pinctrl states node. Each +sub-node have following properties: + +Required properties: +------------------ +- pins: List of pins. Valid values of pins properties are: + gpio0, gpio1, gpio2, gpio3, gpio4, gpio5, gpio6, gpio7. + +Optional properties: +------------------- +Following are optional properties defined as pinmux DT binding document +. Absence of properties will leave the configuration +on default. + function, + drive-push-pull, + drive-open-drain, + bias-pull-up, + bias-pull-down. + +Valid values for function properties are: + gpio, lpm-control-in, fps-out, 32k-out, sd0-dvs-in, sd1-dvs-in, + reference-out + +Theres is also customised properties for the GPIO1, GPIO2 and GPIO3. These +customised properties are required to configure FPS configuration parameters +of these GPIOs. Please refer for more +detail of Flexible Power Sequence (FPS). + +- maxim,active-fps-source: FPS source for the GPIOs to get + enabled/disabled when system is in + active state. Valid values are: + - MAX77620_FPS_SRC_0, + FPS source is FPS0. + - MAX77620_FPS_SRC_1, + FPS source is FPS1 + - MAX77620_FPS_SRC_2 and + FPS source is FPS2 + - MAX77620_FPS_SRC_NONE. + GPIO is not controlled + by FPS events and it gets + enabled/disabled by register + access. + Absence of this property will leave + the FPS configuration register for that + GPIO to default configuration. + +- maxim,active-fps-power-up-slot: Sequencing event slot number on which + the GPIO get enabled when + master FPS input event set to HIGH. + Valid values are 0 to 7. + This is applicable if FPS source is + selected as FPS0, FPS1 or FPS2. + +- maxim,active-fps-power-down-slot: Sequencing event slot number on which + the GPIO get disabled when master + FPS input event set to LOW. + Valid values are 0 to 7. + This is applicable if FPS source is + selected as FPS0, FPS1 or FPS2. + +- maxim,suspend-fps-source: This is same as property + "maxim,active-fps-source" but value + get configured when system enters in + to suspend state. + +- maxim,suspend-fps-power-up-slot: This is same as property + "maxim,active-fps-power-up-slot" but + this value get configured into FPS + configuration register when system + enters into suspend. + This is applicable if suspend state + FPS source is selected as FPS0, FPS1 or + +- maxim,suspend-fps-power-down-slot: This is same as property + "maxim,active-fps-power-down-slot" but + this value get configured into FPS + configuration register when system + enters into suspend. + This is applicable if suspend state + FPS source is selected as FPS0, FPS1 or + FPS2. + +Example: +-------- +#include +... +max77620@3c { + + pinctrl-names = "default"; + pinctrl-0 = <&spmic_default>; + + spmic_default: pinmux@0 { + pin_gpio0 { + pins = "gpio0"; + function = "gpio"; + }; + + pin_gpio1 { + pins = "gpio1"; + function = "fps-out"; + maxim,active-fps-source = ; + }; + + pin_gpio2 { + pins = "gpio2"; + function = "fps-out"; + maxim,active-fps-source = ; + }; + }; +}; From b1cba39ab8ec860fd494bb327595df8e74362f04 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Wed, 30 Mar 2016 19:59:44 +0530 Subject: [PATCH 16/19] mfd: max77620: add core driver for MAX77620/MAX20024 MAX77620/MAX20024 are Power Management IC from the MAXIM. It supports RTC, multiple GPIOs, multiple DCDC and LDOs, watchdog, clock etc. Add MFD drier to provides common support for accessing the device; additional drivers is developed on respected subsystem in order to use the functionality of the device. Signed-off-by: Laxman Dewangan Signed-off-by: Mallikarjun Kasoju Reviewed-by: Krzysztof Kozlowski --- drivers/mfd/Kconfig | 15 + drivers/mfd/Makefile | 1 + drivers/mfd/max77620.c | 544 +++++++++++++++++++++++++++++++++++ include/linux/mfd/max77620.h | 337 ++++++++++++++++++++++ 4 files changed, 897 insertions(+) create mode 100644 drivers/mfd/max77620.c create mode 100644 include/linux/mfd/max77620.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index eea61e349e26af..585d6e3d98c566 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -527,6 +527,21 @@ config MFD_MAX14577 additional drivers must be enabled in order to use the functionality of the device. +config MFD_MAX77620 + bool "Maxim Semiconductor MAX77620 and MAX20024 PMIC Support" + depends on I2C=y + depends on OF + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + select IRQ_DOMAIN + help + Say yes here to add support for Maxim Semiconductor MAX77620 and + MAX20024 which are Power Management IC with General purpose pins, + RTC, regulators, clock generator, watchdog etc. This driver + provides common support for accessing the device; additional drivers + must be enabled in order to use the functionality of the device. + config MFD_MAX77686 tristate "Maxim Semiconductor MAX77686/802 PMIC Support" depends on I2C diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 5eaa6465d0a6e2..921a08dad9b374 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -128,6 +128,7 @@ obj-$(CONFIG_MFD_DA9063) += da9063.o obj-$(CONFIG_MFD_DA9150) += da9150-core.o obj-$(CONFIG_MFD_MAX14577) += max14577.o +obj-$(CONFIG_MFD_MAX77620) += max77620.o obj-$(CONFIG_MFD_MAX77686) += max77686.o obj-$(CONFIG_MFD_MAX77693) += max77693.o obj-$(CONFIG_MFD_MAX77843) += max77843.o diff --git a/drivers/mfd/max77620.c b/drivers/mfd/max77620.c new file mode 100644 index 00000000000000..41daaf7336cbe1 --- /dev/null +++ b/drivers/mfd/max77620.c @@ -0,0 +1,544 @@ +/* + * Maxim MAX77620 MFD Driver + * + * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved. + * + * Author: + * Laxman Dewangan + * Chaitanya Bandi + * Mallikarjun Kasoju + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct resource gpio_resources[] = { + DEFINE_RES_IRQ(MAX77620_IRQ_TOP_GPIO), +}; + +static struct resource power_resources[] = { + DEFINE_RES_IRQ(MAX77620_IRQ_LBT_MBATLOW), +}; + +static struct resource rtc_resources[] = { + DEFINE_RES_IRQ(MAX77620_IRQ_TOP_RTC), +}; + +static struct resource thermal_resources[] = { + DEFINE_RES_IRQ(MAX77620_IRQ_LBT_TJALRM1), + DEFINE_RES_IRQ(MAX77620_IRQ_LBT_TJALRM2), +}; + +static const struct regmap_irq max77620_top_irqs[] = { + REGMAP_IRQ_REG(MAX77620_IRQ_TOP_GLBL, 0, MAX77620_IRQ_TOP_GLBL_MASK), + REGMAP_IRQ_REG(MAX77620_IRQ_TOP_SD, 0, MAX77620_IRQ_TOP_SD_MASK), + REGMAP_IRQ_REG(MAX77620_IRQ_TOP_LDO, 0, MAX77620_IRQ_TOP_LDO_MASK), + REGMAP_IRQ_REG(MAX77620_IRQ_TOP_GPIO, 0, MAX77620_IRQ_TOP_GPIO_MASK), + REGMAP_IRQ_REG(MAX77620_IRQ_TOP_RTC, 0, MAX77620_IRQ_TOP_RTC_MASK), + REGMAP_IRQ_REG(MAX77620_IRQ_TOP_32K, 0, MAX77620_IRQ_TOP_32K_MASK), + REGMAP_IRQ_REG(MAX77620_IRQ_TOP_ONOFF, 0, MAX77620_IRQ_TOP_ONOFF_MASK), + REGMAP_IRQ_REG(MAX77620_IRQ_LBT_MBATLOW, 1, MAX77620_IRQ_LBM_MASK), + REGMAP_IRQ_REG(MAX77620_IRQ_LBT_TJALRM1, 1, MAX77620_IRQ_TJALRM1_MASK), + REGMAP_IRQ_REG(MAX77620_IRQ_LBT_TJALRM2, 1, MAX77620_IRQ_TJALRM2_MASK), +}; + +#define MAX77620_MFD_CELL_NAME(_name) \ + { \ + .name = (_name), \ + } + +#define MAX77620_MFD_CELL_RES(_name, _res) \ + { \ + .name = (_name), \ + .resources = (_res), \ + .num_resources = ARRAY_SIZE((_res)), \ + } + +static struct mfd_cell max77620_children[] = { + MAX77620_MFD_CELL_NAME("max77620-pinctrl"), + MAX77620_MFD_CELL_RES("max77620-gpio", gpio_resources), + MAX77620_MFD_CELL_NAME("max77620-pmic"), + MAX77620_MFD_CELL_RES("max77620-rtc", rtc_resources), + MAX77620_MFD_CELL_RES("max77620-power", power_resources), + MAX77620_MFD_CELL_NAME("max77620-watchdog"), + MAX77620_MFD_CELL_NAME("max77620-clock"), + MAX77620_MFD_CELL_RES("max77620-thermal", thermal_resources), +}; + +static struct mfd_cell max20024_children[] = { + MAX77620_MFD_CELL_NAME("max20024-pinctrl"), + MAX77620_MFD_CELL_RES("max20024-gpio", gpio_resources), + MAX77620_MFD_CELL_NAME("max20024-pmic"), + MAX77620_MFD_CELL_RES("max77620-rtc", rtc_resources), + MAX77620_MFD_CELL_RES("max20024-power", power_resources), + MAX77620_MFD_CELL_NAME("max20024-watchdog"), + MAX77620_MFD_CELL_NAME("max20024-clock"), +}; + +static struct regmap_irq_chip max77620_top_irq_chip = { + .name = "max77620-top", + .irqs = max77620_top_irqs, + .num_irqs = ARRAY_SIZE(max77620_top_irqs), + .num_regs = 2, + .status_base = MAX77620_REG_IRQTOP, + .mask_base = MAX77620_REG_IRQTOPM, +}; + +static const struct regmap_range max77620_readable_ranges[] = { + regmap_reg_range(MAX77620_REG_CNFGGLBL1, MAX77620_REG_DVSSD4), +}; + +static const struct regmap_access_table max77620_readable_table = { + .yes_ranges = max77620_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(max77620_readable_ranges), +}; + +static const struct regmap_range max20024_readable_ranges[] = { + regmap_reg_range(MAX77620_REG_CNFGGLBL1, MAX77620_REG_DVSSD4), + regmap_reg_range(MAX20024_REG_MAX_ADD, MAX20024_REG_MAX_ADD), +}; + +static const struct regmap_access_table max20024_readable_table = { + .yes_ranges = max20024_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(max20024_readable_ranges), +}; + +static const struct regmap_range max77620_writable_ranges[] = { + regmap_reg_range(MAX77620_REG_CNFGGLBL1, MAX77620_REG_DVSSD4), +}; + +static const struct regmap_access_table max77620_writable_table = { + .yes_ranges = max77620_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(max77620_writable_ranges), +}; + +static const struct regmap_range max77620_cacheable_ranges[] = { + regmap_reg_range(MAX77620_REG_SD0_CFG, MAX77620_REG_LDO_CFG3), + regmap_reg_range(MAX77620_REG_FPS_CFG0, MAX77620_REG_FPS_SD3), +}; + +static const struct regmap_access_table max77620_volatile_table = { + .no_ranges = max77620_cacheable_ranges, + .n_no_ranges = ARRAY_SIZE(max77620_cacheable_ranges), +}; + +static const struct regmap_config max77620_regmap_config = { + .name = "power-slave", + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77620_REG_DVSSD4 + 1, + .cache_type = REGCACHE_RBTREE, + .rd_table = &max77620_readable_table, + .wr_table = &max77620_writable_table, + .volatile_table = &max77620_volatile_table, +}; + +static const struct regmap_config max20024_regmap_config = { + .name = "power-slave", + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX20024_REG_MAX_ADD + 1, + .cache_type = REGCACHE_RBTREE, + .rd_table = &max20024_readable_table, + .wr_table = &max77620_writable_table, + .volatile_table = &max77620_volatile_table, +}; + +static int max77620_get_fps_period_reg_value(struct max77620_chip *chip, + int tperiod) +{ + int base_fps_time = (chip->chip_id == MAX20024) ? 20 : 40; + int x, i; + + for (i = 0; i < 0x7; i++) { + x = base_fps_time * BIT(i); + if (x >= tperiod) + return i; + } + + return i; +} + +static int max77620_config_fps(struct max77620_chip *chip, + struct device_node *fps_np) +{ + struct device *dev = chip->dev; + unsigned int mask = 0, config = 0; + u32 param_val; + int tperiod, fps_id; + int ret; + char fps_name[10]; + + for (fps_id = 0; fps_id < MAX77620_FPS_COUNT; fps_id++) { + sprintf(fps_name, "fps%d", fps_id); + if (!strcmp(fps_np->name, fps_name)) + break; + } + + if (fps_id == MAX77620_FPS_COUNT) { + dev_err(dev, "FPS node name %s is not valid\n", fps_np->name); + return -EINVAL; + } + + ret = of_property_read_u32(fps_np, "maxim,shutdown-fps-time-period-us", + ¶m_val); + if (!ret) { + mask |= MAX77620_FPS_TIME_PERIOD_MASK; + chip->shutdown_fps_period[fps_id] = min(param_val, 5120U); + tperiod = max77620_get_fps_period_reg_value(chip, + chip->shutdown_fps_period[fps_id]); + config |= tperiod << MAX77620_FPS_TIME_PERIOD_SHIFT; + } + + ret = of_property_read_u32(fps_np, "maxim,suspend-fps-time-period-us", + ¶m_val); + if (!ret) + chip->suspend_fps_period[fps_id] = min(param_val, 5120U); + + ret = of_property_read_u32(fps_np, "maxim,fps-event-source", + ¶m_val); + if (!ret) { + if (param_val > 2) { + dev_err(dev, "FPS%d event-source invalid\n", fps_id); + return -EINVAL; + } + mask |= MAX77620_FPS_EN_SRC_MASK; + config |= param_val << MAX77620_FPS_EN_SRC_SHIFT; + if (param_val == 2) { + mask |= MAX77620_FPS_ENFPS_SW_MASK; + config |= MAX77620_FPS_ENFPS_SW; + } + } + + if (!chip->sleep_enable && !chip->enable_global_lpm) { + ret = of_property_read_u32(fps_np, + "maxim,device-state-on-disabled-event", + ¶m_val); + if (!ret) { + if (param_val == 0) + chip->sleep_enable = true; + else if (param_val == 1) + chip->enable_global_lpm = true; + } + } + + ret = regmap_update_bits(chip->rmap, MAX77620_REG_FPS_CFG0 + fps_id, + mask, config); + if (ret < 0) { + dev_err(dev, "Failed to Reg 0x%02x update: %d\n", + MAX77620_REG_FPS_CFG0 + fps_id, ret); + return ret; + } + + return 0; +} + +static int max77620_initialise_fps(struct max77620_chip *chip) +{ + struct device *dev = chip->dev; + struct device_node *fps_np, *fps_child; + u8 config; + int fps_id; + int ret; + + for (fps_id = 0; fps_id < MAX77620_FPS_COUNT; fps_id++) { + chip->shutdown_fps_period[fps_id] = -1; + chip->suspend_fps_period[fps_id] = -1; + } + + fps_np = of_get_child_by_name(dev->of_node, "fps"); + if (!fps_np) + goto skip_fps; + + for_each_child_of_node(fps_np, fps_child) { + ret = max77620_config_fps(chip, fps_child); + if (ret < 0) + return ret; + } + + config = chip->enable_global_lpm ? MAX77620_ONOFFCNFG2_SLP_LPM_MSK : 0; + ret = regmap_update_bits(chip->rmap, MAX77620_REG_ONOFFCNFG2, + MAX77620_ONOFFCNFG2_SLP_LPM_MSK, config); + if (ret < 0) { + dev_err(dev, "Failed to reg ONOFFCNFG2 update: %d\n", ret); + return ret; + } + +skip_fps: + /* Enable wake on EN0 pin */ + ret = regmap_update_bits(chip->rmap, MAX77620_REG_ONOFFCNFG2, + MAX77620_ONOFFCNFG2_WK_EN0, + MAX77620_ONOFFCNFG2_WK_EN0); + if (ret < 0) { + dev_err(dev, "Failed to reg ONOFFCNFG2 update: %d\n", ret); + return ret; + } + + /* For MAX20024, SLPEN will be POR reset if CLRSE is b11 */ + if ((chip->chip_id == MAX20024) && chip->sleep_enable) { + config = MAX77620_ONOFFCNFG1_SLPEN | MAX20024_ONOFFCNFG1_CLRSE; + ret = regmap_update_bits(chip->rmap, MAX77620_REG_ONOFFCNFG1, + config, config); + if (ret < 0) { + dev_err(dev, "Failed to reg ONOFFCNFG1 update: %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int max77620_read_es_version(struct max77620_chip *chip) +{ + unsigned int val; + u8 cid_val[6]; + int i; + int ret; + + for (i = MAX77620_REG_CID0; i <= MAX77620_REG_CID5; i++) { + ret = regmap_read(chip->rmap, i, &val); + if (ret < 0) { + dev_err(chip->dev, "Failed to reg CID%d read: %d\n", + i - MAX77620_REG_CID0, ret); + return ret; + } + dev_dbg(chip->dev, "CID%d: 0x%02x\n", + i - MAX77620_REG_CID0, val); + cid_val[i - MAX77620_REG_CID0] = val; + } + + /* CID4 is OTP Version and CID5 is ES version */ + dev_info(chip->dev, "PMIC Version OTP:0x%02X and ES:0x%X\n", + cid_val[4], MAX77620_CID5_DIDM(cid_val[5])); + + return ret; +} + +static int max77620_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct regmap_config *rmap_config; + struct max77620_chip *chip; + struct mfd_cell *mfd_cells; + int n_mfd_cells; + int ret; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + chip->dev = &client->dev; + chip->irq_base = -1; + chip->chip_irq = client->irq; + chip->chip_id = (enum max77620_chip_id)id->driver_data; + + switch (chip->chip_id) { + case MAX77620: + mfd_cells = max77620_children; + n_mfd_cells = ARRAY_SIZE(max77620_children); + rmap_config = &max77620_regmap_config; + break; + case MAX20024: + mfd_cells = max20024_children; + n_mfd_cells = ARRAY_SIZE(max20024_children); + rmap_config = &max20024_regmap_config; + break; + default: + dev_err(chip->dev, "ChipID is invalid %d\n", chip->chip_id); + return -EINVAL; + } + + chip->rmap = devm_regmap_init_i2c(client, rmap_config); + if (IS_ERR(chip->rmap)) { + ret = PTR_ERR(chip->rmap); + dev_err(chip->dev, "Failed to regmap init: %d\n", ret); + return ret; + } + + ret = max77620_read_es_version(chip); + if (ret < 0) + return ret; + + ret = devm_regmap_add_irq_chip(chip->dev, chip->rmap, client->irq, + IRQF_ONESHOT | IRQF_SHARED, + chip->irq_base, &max77620_top_irq_chip, + &chip->top_irq_data); + if (ret < 0) { + dev_err(chip->dev, "Failed to add regmap irq: %d\n", ret); + return ret; + } + + ret = max77620_initialise_fps(chip); + if (ret < 0) + return ret; + + ret = mfd_add_devices(chip->dev, PLATFORM_DEVID_NONE, + mfd_cells, n_mfd_cells, NULL, 0, + regmap_irq_get_domain(chip->top_irq_data)); + if (ret < 0) { + dev_err(chip->dev, "Failed to add sub devices: %d\n", ret); + return ret; + } + + return 0; +} + +static int max77620_remove(struct i2c_client *client) +{ + struct max77620_chip *chip = i2c_get_clientdata(client); + + mfd_remove_devices(chip->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max77620_set_fps_period(struct max77620_chip *chip, + int fps_id, int time_period) +{ + int period = max77620_get_fps_period_reg_value(chip, time_period); + int ret; + + ret = regmap_update_bits(chip->rmap, MAX77620_REG_FPS_CFG0 + fps_id, + MAX77620_FPS_TIME_PERIOD_MASK, + period << MAX77620_FPS_TIME_PERIOD_SHIFT); + if (ret < 0) { + dev_err(chip->dev, "Failed to reg 0x%02x write: %d\n", + MAX77620_REG_FPS_CFG0 + fps_id, ret); + return ret; + } + + return 0; +} + +static int max77620_i2c_suspend(struct device *dev) +{ + struct max77620_chip *chip = dev_get_drvdata(dev); + struct i2c_client *client = to_i2c_client(dev); + unsigned int config; + int fps; + int ret; + + for (fps = 0; fps < MAX77620_FPS_COUNT; fps++) { + if (chip->suspend_fps_period[fps] < 0) + continue; + + ret = max77620_set_fps_period(chip, fps, + chip->suspend_fps_period[fps]); + if (ret < 0) + dev_err(dev, "Failed to FPS%d config: %d\n", fps, ret); + } + + /* + * For MAX20024: No need to configure SLPEN on suspend as + * it will be configured on Init. + */ + if (chip->chip_id == MAX20024) + goto out; + + config = (chip->sleep_enable) ? MAX77620_ONOFFCNFG1_SLPEN : 0; + ret = regmap_update_bits(chip->rmap, MAX77620_REG_ONOFFCNFG1, + MAX77620_ONOFFCNFG1_SLPEN, + config); + if (ret < 0) { + dev_err(dev, "Failed to reg ONOFFCNFG1 update: %d\n", ret); + return ret; + } + + /* Disable WK_EN0 */ + ret = regmap_update_bits(chip->rmap, MAX77620_REG_ONOFFCNFG2, + MAX77620_ONOFFCNFG2_WK_EN0, 0); + if (ret < 0) { + dev_err(dev, "Failed to reg ONOFFCNFG2 update: %d\n", ret); + return ret; + } + +out: + disable_irq(client->irq); + + return 0; +} + +static int max77620_i2c_resume(struct device *dev) +{ + struct max77620_chip *chip = dev_get_drvdata(dev); + struct i2c_client *client = to_i2c_client(dev); + int ret; + int fps; + + for (fps = 0; fps < MAX77620_FPS_COUNT; fps++) { + if (chip->shutdown_fps_period[fps] < 0) + continue; + + ret = max77620_set_fps_period(chip, fps, + chip->shutdown_fps_period[fps]); + if (ret < 0) + dev_err(dev, "Failed to FPS%d config: %d\n", fps, ret); + } + + /* + * For MAX20024: No need to configure WKEN0 on resume as + * it is configured on Init. + */ + if (chip->chip_id == MAX20024) + goto out; + + /* Enable WK_EN0 */ + ret = regmap_update_bits(chip->rmap, MAX77620_REG_ONOFFCNFG2, + MAX77620_ONOFFCNFG2_WK_EN0, + MAX77620_ONOFFCNFG2_WK_EN0); + if (ret < 0) { + dev_err(dev, "Failed to reg ONOFFCNFG2 WK_EN0 update: %d\n", + ret); + return ret; + } + +out: + enable_irq(client->irq); + + return 0; +} +#endif + +static const struct i2c_device_id max77620_id[] = { + {"max77620", MAX77620}, + {"max20024", MAX20024}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, max77620_id); + +static const struct dev_pm_ops max77620_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(max77620_i2c_suspend, max77620_i2c_resume) +}; + +static struct i2c_driver max77620_driver = { + .driver = { + .name = "max77620", + .pm = &max77620_pm_ops, + }, + .probe = max77620_probe, + .remove = max77620_remove, + .id_table = max77620_id, +}; + +module_i2c_driver(max77620_driver); + +MODULE_DESCRIPTION("MAX77620/MAX20024 Multi Function Device Core Driver"); +MODULE_AUTHOR("Laxman Dewangan "); +MODULE_AUTHOR("Chaitanya Bandi "); +MODULE_AUTHOR("Mallikarjun Kasoju "); +MODULE_ALIAS("i2c:max77620"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/max77620.h b/include/linux/mfd/max77620.h new file mode 100644 index 00000000000000..db3410745030cb --- /dev/null +++ b/include/linux/mfd/max77620.h @@ -0,0 +1,337 @@ +/* + * Defining registers address and its bit definitions of MAX77620 and MAX20024 + * + * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#ifndef _LINUX_MFD_MAX77620_H_ +#define _LINUX_MFD_MAX77620_H_ + +#include + +/* GLOBAL, PMIC, GPIO, FPS, ONOFFC, CID Registers */ +#define MAX77620_REG_CNFGGLBL1 0x00 +#define MAX77620_REG_CNFGGLBL2 0x01 +#define MAX77620_REG_CNFGGLBL3 0x02 +#define MAX77620_REG_CNFG1_32K 0x03 +#define MAX77620_REG_CNFGBBC 0x04 +#define MAX77620_REG_IRQTOP 0x05 +#define MAX77620_REG_INTLBT 0x06 +#define MAX77620_REG_IRQSD 0x07 +#define MAX77620_REG_IRQ_LVL2_L0_7 0x08 +#define MAX77620_REG_IRQ_LVL2_L8 0x09 +#define MAX77620_REG_IRQ_LVL2_GPIO 0x0A +#define MAX77620_REG_ONOFFIRQ 0x0B +#define MAX77620_REG_NVERC 0x0C +#define MAX77620_REG_IRQTOPM 0x0D +#define MAX77620_REG_INTENLBT 0x0E +#define MAX77620_REG_IRQMASKSD 0x0F +#define MAX77620_REG_IRQ_MSK_L0_7 0x10 +#define MAX77620_REG_IRQ_MSK_L8 0x11 +#define MAX77620_REG_ONOFFIRQM 0x12 +#define MAX77620_REG_STATLBT 0x13 +#define MAX77620_REG_STATSD 0x14 +#define MAX77620_REG_ONOFFSTAT 0x15 + +/* SD and LDO Registers */ +#define MAX77620_REG_SD0 0x16 +#define MAX77620_REG_SD1 0x17 +#define MAX77620_REG_SD2 0x18 +#define MAX77620_REG_SD3 0x19 +#define MAX77620_REG_SD4 0x1A +#define MAX77620_REG_DVSSD0 0x1B +#define MAX77620_REG_DVSSD1 0x1C +#define MAX77620_REG_SD0_CFG 0x1D +#define MAX77620_REG_SD1_CFG 0x1E +#define MAX77620_REG_SD2_CFG 0x1F +#define MAX77620_REG_SD3_CFG 0x20 +#define MAX77620_REG_SD4_CFG 0x21 +#define MAX77620_REG_SD_CFG2 0x22 +#define MAX77620_REG_LDO0_CFG 0x23 +#define MAX77620_REG_LDO0_CFG2 0x24 +#define MAX77620_REG_LDO1_CFG 0x25 +#define MAX77620_REG_LDO1_CFG2 0x26 +#define MAX77620_REG_LDO2_CFG 0x27 +#define MAX77620_REG_LDO2_CFG2 0x28 +#define MAX77620_REG_LDO3_CFG 0x29 +#define MAX77620_REG_LDO3_CFG2 0x2A +#define MAX77620_REG_LDO4_CFG 0x2B +#define MAX77620_REG_LDO4_CFG2 0x2C +#define MAX77620_REG_LDO5_CFG 0x2D +#define MAX77620_REG_LDO5_CFG2 0x2E +#define MAX77620_REG_LDO6_CFG 0x2F +#define MAX77620_REG_LDO6_CFG2 0x30 +#define MAX77620_REG_LDO7_CFG 0x31 +#define MAX77620_REG_LDO7_CFG2 0x32 +#define MAX77620_REG_LDO8_CFG 0x33 +#define MAX77620_REG_LDO8_CFG2 0x34 +#define MAX77620_REG_LDO_CFG3 0x35 + +#define MAX77620_LDO_SLEW_RATE_MASK 0x1 + +/* LDO Configuration 3 */ +#define MAX77620_TRACK4_MASK BIT(5) +#define MAX77620_TRACK4_SHIFT 5 + +/* Voltage */ +#define MAX77620_SDX_VOLT_MASK 0xFF +#define MAX77620_SD0_VOLT_MASK 0x3F +#define MAX77620_SD1_VOLT_MASK 0x7F +#define MAX77620_LDO_VOLT_MASK 0x3F + +#define MAX77620_REG_GPIO0 0x36 +#define MAX77620_REG_GPIO1 0x37 +#define MAX77620_REG_GPIO2 0x38 +#define MAX77620_REG_GPIO3 0x39 +#define MAX77620_REG_GPIO4 0x3A +#define MAX77620_REG_GPIO5 0x3B +#define MAX77620_REG_GPIO6 0x3C +#define MAX77620_REG_GPIO7 0x3D +#define MAX77620_REG_PUE_GPIO 0x3E +#define MAX77620_REG_PDE_GPIO 0x3F +#define MAX77620_REG_AME_GPIO 0x40 +#define MAX77620_REG_ONOFFCNFG1 0x41 +#define MAX77620_REG_ONOFFCNFG2 0x42 + +/* FPS Registers */ +#define MAX77620_REG_FPS_CFG0 0x43 +#define MAX77620_REG_FPS_CFG1 0x44 +#define MAX77620_REG_FPS_CFG2 0x45 +#define MAX77620_REG_FPS_LDO0 0x46 +#define MAX77620_REG_FPS_LDO1 0x47 +#define MAX77620_REG_FPS_LDO2 0x48 +#define MAX77620_REG_FPS_LDO3 0x49 +#define MAX77620_REG_FPS_LDO4 0x4A +#define MAX77620_REG_FPS_LDO5 0x4B +#define MAX77620_REG_FPS_LDO6 0x4C +#define MAX77620_REG_FPS_LDO7 0x4D +#define MAX77620_REG_FPS_LDO8 0x4E +#define MAX77620_REG_FPS_SD0 0x4F +#define MAX77620_REG_FPS_SD1 0x50 +#define MAX77620_REG_FPS_SD2 0x51 +#define MAX77620_REG_FPS_SD3 0x52 +#define MAX77620_REG_FPS_SD4 0x53 +#define MAX77620_REG_FPS_NONE 0 + +#define MAX77620_FPS_SRC_MASK 0xC0 +#define MAX77620_FPS_SRC_SHIFT 6 +#define MAX77620_FPS_PU_PERIOD_MASK 0x38 +#define MAX77620_FPS_PU_PERIOD_SHIFT 3 +#define MAX77620_FPS_PD_PERIOD_MASK 0x07 +#define MAX77620_FPS_PD_PERIOD_SHIFT 0 +#define MAX77620_FPS_TIME_PERIOD_MASK 0x38 +#define MAX77620_FPS_TIME_PERIOD_SHIFT 3 +#define MAX77620_FPS_EN_SRC_MASK 0x06 +#define MAX77620_FPS_EN_SRC_SHIFT 1 +#define MAX77620_FPS_ENFPS_SW_MASK 0x01 +#define MAX77620_FPS_ENFPS_SW 0x01 + +#define MAX77620_REG_FPS_GPIO1 0x54 +#define MAX77620_REG_FPS_GPIO2 0x55 +#define MAX77620_REG_FPS_GPIO3 0x56 +#define MAX77620_REG_FPS_RSO 0x57 +#define MAX77620_REG_CID0 0x58 +#define MAX77620_REG_CID1 0x59 +#define MAX77620_REG_CID2 0x5A +#define MAX77620_REG_CID3 0x5B +#define MAX77620_REG_CID4 0x5C +#define MAX77620_REG_CID5 0x5D + +#define MAX77620_REG_DVSSD4 0x5E +#define MAX20024_REG_MAX_ADD 0x70 + +#define MAX77620_CID_DIDM_MASK 0xF0 +#define MAX77620_CID_DIDM_SHIFT 4 + +/* CNCG2SD */ +#define MAX77620_SD_CNF2_ROVS_EN_SD1 BIT(1) +#define MAX77620_SD_CNF2_ROVS_EN_SD0 BIT(2) + +/* Device Identification Metal */ +#define MAX77620_CID5_DIDM(n) (((n) >> 4) & 0xF) +/* Device Indentification OTP */ +#define MAX77620_CID5_DIDO(n) ((n) & 0xF) + +/* SD CNFG1 */ +#define MAX77620_SD_SR_MASK 0xC0 +#define MAX77620_SD_SR_SHIFT 6 +#define MAX77620_SD_POWER_MODE_MASK 0x30 +#define MAX77620_SD_POWER_MODE_SHIFT 4 +#define MAX77620_SD_CFG1_ADE_MASK BIT(3) +#define MAX77620_SD_CFG1_ADE_DISABLE 0 +#define MAX77620_SD_CFG1_ADE_ENABLE BIT(3) +#define MAX77620_SD_FPWM_MASK 0x04 +#define MAX77620_SD_FPWM_SHIFT 2 +#define MAX77620_SD_FSRADE_MASK 0x01 +#define MAX77620_SD_FSRADE_SHIFT 0 +#define MAX77620_SD_CFG1_FPWM_SD_MASK BIT(2) +#define MAX77620_SD_CFG1_FPWM_SD_SKIP 0 +#define MAX77620_SD_CFG1_FPWM_SD_FPWM BIT(2) +#define MAX77620_SD_CFG1_FSRADE_SD_MASK BIT(0) +#define MAX77620_SD_CFG1_FSRADE_SD_DISABLE 0 +#define MAX77620_SD_CFG1_FSRADE_SD_ENABLE BIT(0) + +/* LDO_CNFG2 */ +#define MAX77620_LDO_POWER_MODE_MASK 0xC0 +#define MAX77620_LDO_POWER_MODE_SHIFT 6 +#define MAX77620_LDO_CFG2_ADE_MASK BIT(1) +#define MAX77620_LDO_CFG2_ADE_DISABLE 0 +#define MAX77620_LDO_CFG2_ADE_ENABLE BIT(1) +#define MAX77620_LDO_CFG2_SS_MASK BIT(0) +#define MAX77620_LDO_CFG2_SS_FAST BIT(0) +#define MAX77620_LDO_CFG2_SS_SLOW 0 + +#define MAX77620_IRQ_TOP_GLBL_MASK BIT(7) +#define MAX77620_IRQ_TOP_SD_MASK BIT(6) +#define MAX77620_IRQ_TOP_LDO_MASK BIT(5) +#define MAX77620_IRQ_TOP_GPIO_MASK BIT(4) +#define MAX77620_IRQ_TOP_RTC_MASK BIT(3) +#define MAX77620_IRQ_TOP_32K_MASK BIT(2) +#define MAX77620_IRQ_TOP_ONOFF_MASK BIT(1) + +#define MAX77620_IRQ_LBM_MASK BIT(3) +#define MAX77620_IRQ_TJALRM1_MASK BIT(2) +#define MAX77620_IRQ_TJALRM2_MASK BIT(1) + +#define MAX77620_PWR_I2C_ADDR 0x3c +#define MAX77620_RTC_I2C_ADDR 0x68 + +#define MAX77620_CNFG_GPIO_DRV_MASK BIT(0) +#define MAX77620_CNFG_GPIO_DRV_PUSHPULL BIT(0) +#define MAX77620_CNFG_GPIO_DRV_OPENDRAIN 0 +#define MAX77620_CNFG_GPIO_DIR_MASK BIT(1) +#define MAX77620_CNFG_GPIO_DIR_INPUT BIT(1) +#define MAX77620_CNFG_GPIO_DIR_OUTPUT 0 +#define MAX77620_CNFG_GPIO_INPUT_VAL_MASK BIT(2) +#define MAX77620_CNFG_GPIO_OUTPUT_VAL_MASK BIT(3) +#define MAX77620_CNFG_GPIO_OUTPUT_VAL_HIGH BIT(3) +#define MAX77620_CNFG_GPIO_OUTPUT_VAL_LOW 0 +#define MAX77620_CNFG_GPIO_INT_MASK (0x3 << 4) +#define MAX77620_CNFG_GPIO_INT_FALLING BIT(4) +#define MAX77620_CNFG_GPIO_INT_RISING BIT(5) +#define MAX77620_CNFG_GPIO_DBNC_MASK (0x3 << 6) +#define MAX77620_CNFG_GPIO_DBNC_None (0x0 << 6) +#define MAX77620_CNFG_GPIO_DBNC_8ms (0x1 << 6) +#define MAX77620_CNFG_GPIO_DBNC_16ms (0x2 << 6) +#define MAX77620_CNFG_GPIO_DBNC_32ms (0x3 << 6) + +#define MAX77620_IRQ_LVL2_GPIO_EDGE0 BIT(0) +#define MAX77620_IRQ_LVL2_GPIO_EDGE1 BIT(1) +#define MAX77620_IRQ_LVL2_GPIO_EDGE2 BIT(2) +#define MAX77620_IRQ_LVL2_GPIO_EDGE3 BIT(3) +#define MAX77620_IRQ_LVL2_GPIO_EDGE4 BIT(4) +#define MAX77620_IRQ_LVL2_GPIO_EDGE5 BIT(5) +#define MAX77620_IRQ_LVL2_GPIO_EDGE6 BIT(6) +#define MAX77620_IRQ_LVL2_GPIO_EDGE7 BIT(7) + +#define MAX77620_CNFG1_32K_OUT0_EN BIT(2) + +#define MAX77620_ONOFFCNFG1_SFT_RST BIT(7) +#define MAX77620_ONOFFCNFG1_MRT_MASK 0x38 +#define MAX77620_ONOFFCNFG1_MRT_SHIFT 0x3 +#define MAX77620_ONOFFCNFG1_SLPEN BIT(2) +#define MAX77620_ONOFFCNFG1_PWR_OFF BIT(1) +#define MAX20024_ONOFFCNFG1_CLRSE 0x18 + +#define MAX77620_ONOFFCNFG2_SFT_RST_WK BIT(7) +#define MAX77620_ONOFFCNFG2_WD_RST_WK BIT(6) +#define MAX77620_ONOFFCNFG2_SLP_LPM_MSK BIT(5) +#define MAX77620_ONOFFCNFG2_WK_ALARM1 BIT(2) +#define MAX77620_ONOFFCNFG2_WK_EN0 BIT(0) + +#define MAX77620_GLBLM_MASK BIT(0) + +#define MAX77620_WDTC_MASK 0x3 +#define MAX77620_WDTOFFC BIT(4) +#define MAX77620_WDTSLPC BIT(3) +#define MAX77620_WDTEN BIT(2) + +#define MAX77620_TWD_MASK 0x3 +#define MAX77620_TWD_2s 0x0 +#define MAX77620_TWD_16s 0x1 +#define MAX77620_TWD_64s 0x2 +#define MAX77620_TWD_128s 0x3 + +#define MAX77620_CNFGGLBL1_LBDAC_EN BIT(7) +#define MAX77620_CNFGGLBL1_MPPLD BIT(6) +#define MAX77620_CNFGGLBL1_LBHYST (BIT(5) | BIT(4)) +#define MAX77620_CNFGGLBL1_LBDAC 0x0E +#define MAX77620_CNFGGLBL1_LBRSTEN BIT(0) + +/* CNFG BBC registers */ +#define MAX77620_CNFGBBC_ENABLE BIT(0) +#define MAX77620_CNFGBBC_CURRENT_MASK 0x06 +#define MAX77620_CNFGBBC_CURRENT_SHIFT 1 +#define MAX77620_CNFGBBC_VOLTAGE_MASK 0x18 +#define MAX77620_CNFGBBC_VOLTAGE_SHIFT 3 +#define MAX77620_CNFGBBC_LOW_CURRENT_DISABLE BIT(5) +#define MAX77620_CNFGBBC_RESISTOR_MASK 0xC0 +#define MAX77620_CNFGBBC_RESISTOR_SHIFT 6 + +#define MAX77620_FPS_COUNT 3 + +/* Interrupts */ +enum { + MAX77620_IRQ_TOP_GLBL, /* Low-Battery */ + MAX77620_IRQ_TOP_SD, /* SD power fail */ + MAX77620_IRQ_TOP_LDO, /* LDO power fail */ + MAX77620_IRQ_TOP_GPIO, /* TOP GPIO internal int to MAX77620 */ + MAX77620_IRQ_TOP_RTC, /* RTC */ + MAX77620_IRQ_TOP_32K, /* 32kHz oscillator */ + MAX77620_IRQ_TOP_ONOFF, /* ON/OFF oscillator */ + MAX77620_IRQ_LBT_MBATLOW, /* Thermal alarm status, > 120C */ + MAX77620_IRQ_LBT_TJALRM1, /* Thermal alarm status, > 120C */ + MAX77620_IRQ_LBT_TJALRM2, /* Thermal alarm status, > 140C */ +}; + +/* GPIOs */ +enum { + MAX77620_GPIO0, + MAX77620_GPIO1, + MAX77620_GPIO2, + MAX77620_GPIO3, + MAX77620_GPIO4, + MAX77620_GPIO5, + MAX77620_GPIO6, + MAX77620_GPIO7, + MAX77620_GPIO_NR, +}; + +/* FPS Source */ +enum max77620_fps_src { + MAX77620_FPS_SRC_0, + MAX77620_FPS_SRC_1, + MAX77620_FPS_SRC_2, + MAX77620_FPS_SRC_NONE, + MAX77620_FPS_SRC_DEF, +}; + +enum max77620_chip_id { + MAX77620, + MAX20024, +}; + +struct max77620_chip { + struct device *dev; + struct regmap *rmap; + + int chip_irq; + int irq_base; + + /* chip id */ + enum max77620_chip_id chip_id; + + bool sleep_enable; + bool enable_global_lpm; + int shutdown_fps_period[MAX77620_FPS_COUNT]; + int suspend_fps_period[MAX77620_FPS_COUNT]; + + struct regmap_irq_chip_data *top_irq_data; + struct regmap_irq_chip_data *gpio_irq_data; +}; + +#endif /* _LINUX_MFD_MAX77620_H_ */ From 1f5bbeeefda8299d0ca414973cc3dd4633531980 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Wed, 30 Mar 2016 19:59:43 +0530 Subject: [PATCH 17/19] mfd: add device-tree binding doc for PMIC max77620/max20024 The MAXIM PMIC MAX77620 and MAX20024 are power management IC which supports RTC, GPIO, DCDC/LDO regulators, interrupt, watchdog etc. Add DT binding document for the different functionality of this device. Signed-off-by: Laxman Dewangan Acked-by: Rob Herring --- .../devicetree/bindings/mfd/max77620.txt | 143 ++++++++++++++++++ include/dt-bindings/mfd/max77620.h | 39 +++++ 2 files changed, 182 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/max77620.txt create mode 100644 include/dt-bindings/mfd/max77620.h diff --git a/Documentation/devicetree/bindings/mfd/max77620.txt b/Documentation/devicetree/bindings/mfd/max77620.txt new file mode 100644 index 00000000000000..2ad44f7e488015 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/max77620.txt @@ -0,0 +1,143 @@ +MAX77620 Power management IC from Maxim Semiconductor. + +Required properties: +------------------- +- compatible: Must be one of + "maxim,max77620" + "maxim,max20024". +- reg: I2C device address. + +Optional properties: +------------------- +- interrupts: The interrupt on the parent the controller is + connected to. +- interrupt-controller: Marks the device node as an interrupt controller. +- #interrupt-cells: is <2> and their usage is compliant to the 2 cells + variant of <../interrupt-controller/interrupts.txt> + IRQ numbers for different interrupt source of MAX77620 + are defined at dt-bindings/mfd/max77620.h. + +Optional subnodes and their properties: +======================================= + +Flexible power sequence configurations: +-------------------------------------- +The Flexible Power Sequencer (FPS) allows each regulator to power up under +hardware or software control. Additionally, each regulator can power on +independently or among a group of other regulators with an adjustable power-up +and power-down delays (sequencing). GPIO1, GPIO2, and GPIO3 can be programmed +to be part of a sequence allowing external regulators to be sequenced along +with internal regulators. 32KHz clock can be programmed to be part of a +sequence. + +The flexible sequencing structure consists of two hardware enable inputs +(EN0, EN1), and 3 master sequencing timers called FPS0, FPS1 and FPS2. +Each master sequencing timer is programmable through its configuration +register to have a hardware enable source (EN1 or EN2) or a software enable +source (SW). When enabled/disabled, the master sequencing timer generates +eight sequencing events on different time periods called slots. The time +period between each event is programmable within the configuration register. +Each regulator, GPIO1, GPIO2, GPIO3, and 32KHz clock has a flexible power +sequence slave register which allows its enable source to be specified as +a flexible power sequencer timer or a software bit. When a FPS source of +regulators, GPIOs and clocks specifies the enable source to be a flexible +power sequencer, the power up and power down delays can be specified in +the regulators, GPIOs and clocks flexible power sequencer configuration +registers. + +When FPS event cleared (set to LOW), regulators, GPIOs and 32KHz +clock are set into following state at the sequencing event that +corresponds to its flexible sequencer configuration register. + Sleep state: In this state, regulators, GPIOs + and 32KHz clock get disabled at + the sequencing event. + Global Low Power Mode (GLPM): In this state, regulators are set in + low power mode at the sequencing event. + +The configuration parameters of FPS is provided through sub-node "fps" +and their child for FPS specific. The child node name for FPS are "fps0", +"fps1", and "fps2" for FPS0, FPS1 and FPS2 respectively. + +The FPS configurations like FPS source, power up and power down slots for +regulators, GPIOs and 32kHz clocks are provided in their respective +configuration nodes which is explained in respective sub-system DT +binding document. + +There is need for different FPS configuration parameters based on system +state like when system state changed from active to suspend or active to +power off (shutdown). + +Optional properties: +------------------- +-maxim,fps-event-source: u32, FPS event source like external + hardware input to PMIC i.e. EN0, EN1 or + software (SW). + The macros are defined on + dt-bindings/mfd/max77620.h + for different control source. + - MAX77620_FPS_EVENT_SRC_EN0 + for hardware input pin EN0. + - MAX77620_FPS_EVENT_SRC_EN1 + for hardware input pin EN1. + - MAX77620_FPS_EVENT_SRC_SW + for software control. + +-maxim,shutdown-fps-time-period-us: u32, FPS time period in microseconds + when system enters in to shutdown + state. + +-maxim,suspend-fps-time-period-us: u32, FPS time period in microseconds + when system enters in to suspend state. + +-maxim,device-state-on-disabled-event: u32, describe the PMIC state when FPS + event cleared (set to LOW) whether it + should go to sleep state or low-power + state. Following are valid values: + - MAX77620_FPS_INACTIVE_STATE_SLEEP + to set the PMIC state to sleep. + - MAX77620_FPS_INACTIVE_STATE_LOW_POWER + to set the PMIC state to low + power. + Absence of this property or other value + will not change device state when FPS + event get cleared. + +Here supported time periods by device in microseconds are as follows: +MAX77620 supports 40, 80, 160, 320, 640, 1280, 2560 and 5120 microseconds. +MAX20024 supports 20, 40, 80, 160, 320, 640, 1280 and 2540 microseconds. + +For DT binding details of different sub modules like GPIO, pincontrol, +regulator, power, please refer respective device-tree binding document +under their respective sub-system directories. + +Example: +-------- +#include + +max77620@3c { + compatible = "maxim,max77620"; + reg = <0x3c>; + + interrupt-parent = <&intc>; + interrupts = <0 86 IRQ_TYPE_NONE>; + + interrupt-controller; + #interrupt-cells = <2>; + + fps { + fps0 { + maxim,shutdown-fps-time-period-us = <1280>; + maxim,fps-event-source = ; + }; + + fps1 { + maxim,shutdown-fps-time-period-us = <1280>; + maxim,fps-event-source = ; + }; + + fps2 { + maxim,shutdown-fps-time-period-us = <1280>; + maxim,fps-event-source = ; + }; + }; +}; diff --git a/include/dt-bindings/mfd/max77620.h b/include/dt-bindings/mfd/max77620.h new file mode 100644 index 00000000000000..b911a0720ccd49 --- /dev/null +++ b/include/dt-bindings/mfd/max77620.h @@ -0,0 +1,39 @@ +/* + * This header provides macros for MAXIM MAX77620 device bindings. + * + * Copyright (c) 2016, NVIDIA Corporation. + * Author: Laxman Dewangan + */ + +#ifndef _DT_BINDINGS_MFD_MAX77620_H +#define _DT_BINDINGS_MFD_MAX77620_H + +/* MAX77620 interrupts */ +#define MAX77620_IRQ_TOP_GLBL 0 /* Low-Battery */ +#define MAX77620_IRQ_TOP_SD 1 /* SD power fail */ +#define MAX77620_IRQ_TOP_LDO 2 /* LDO power fail */ +#define MAX77620_IRQ_TOP_GPIO 3 /* GPIO internal int to MAX77620 */ +#define MAX77620_IRQ_TOP_RTC 4 /* RTC */ +#define MAX77620_IRQ_TOP_32K 5 /* 32kHz oscillator */ +#define MAX77620_IRQ_TOP_ONOFF 6 /* ON/OFF oscillator */ +#define MAX77620_IRQ_LBT_MBATLOW 7 /* Thermal alarm status, > 120C */ +#define MAX77620_IRQ_LBT_TJALRM1 8 /* Thermal alarm status, > 120C */ +#define MAX77620_IRQ_LBT_TJALRM2 9 /* Thermal alarm status, > 140C */ + +/* FPS event source */ +#define MAX77620_FPS_EVENT_SRC_EN0 0 +#define MAX77620_FPS_EVENT_SRC_EN1 1 +#define MAX77620_FPS_EVENT_SRC_SW 2 + +/* Device state when FPS event LOW */ +#define MAX77620_FPS_INACTIVE_STATE_SLEEP 0 +#define MAX77620_FPS_INACTIVE_STATE_LOW_POWER 1 + +/* FPS source */ +#define MAX77620_FPS_SRC_0 0 +#define MAX77620_FPS_SRC_1 1 +#define MAX77620_FPS_SRC_2 2 +#define MAX77620_FPS_SRC_NONE 3 +#define MAX77620_FPS_SRC_DEF 4 + +#endif From 50c9216a177647d024a1b9c7b83e52675192bab4 Mon Sep 17 00:00:00 2001 From: Shardar Shariff Md Date: Mon, 14 Mar 2016 18:52:18 +0530 Subject: [PATCH 18/19] i2c: tegra: enable multi master mode Enable multi-master mode in I2C_CNFG reg based on hw features. Using single/multi-master mode bit introduced for Tegra210, whereas multi-master mode is enabled by default in HW for T124 and earlier Tegra SOC. Enabling this bit doesn't explicitly start treating the bus has having multiple masters, but will start checking for arbitration lost and reporting when it occurs. The Tegra210 I2C controller supports single/multi master mode. Add chipdata for Tegra210 and its compatibility string so that Tegra210 will select data that enables multi master mode correctly. Do below prerequisites for multi-master bus if "multi-master" dt property entry is added. 1. Enable 1st level clock always set. 2. Disable 2nd level clock gating (slcg which is supported from T124 SOC and later chips) Signed-off-by: Shardar Shariff Md --- drivers/i2c/busses/i2c-tegra.c | 74 +++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 929185a7296c0e..d764d64e9d2c8f 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -38,6 +38,7 @@ #define I2C_CNFG_DEBOUNCE_CNT_SHIFT 12 #define I2C_CNFG_PACKET_MODE_EN (1<<10) #define I2C_CNFG_NEW_MASTER_FSM (1<<11) +#define I2C_CNFG_MULTI_MASTER_MODE (1<<17) #define I2C_STATUS 0x01C #define I2C_SL_CNFG 0x020 #define I2C_SL_CNFG_NACK (1<<1) @@ -106,6 +107,9 @@ #define I2C_SLV_CONFIG_LOAD (1 << 1) #define I2C_TIMEOUT_CONFIG_LOAD (1 << 2) +#define I2C_CLKEN_OVERRIDE 0x090 +#define I2C_MST_CORE_CLKEN_OVR (1 << 0) + /* * msg_end_type: The bus control which need to be send at end of transfer. * @MSG_END_STOP: Send stop pulse at end of transfer. @@ -143,6 +147,8 @@ struct tegra_i2c_hw_feature { int clk_divisor_hs_mode; int clk_divisor_std_fast_mode; u16 clk_divisor_fast_plus_mode; + bool has_multi_master_mode; + bool has_slcg_override_reg; }; /** @@ -184,6 +190,7 @@ struct tegra_i2c_dev { u32 bus_clk_rate; u16 clk_divisor_non_hs_mode; bool is_suspended; + bool is_multimaster_mode; }; static void dvc_writel(struct tegra_i2c_dev *i2c_dev, u32 val, unsigned long reg) @@ -438,6 +445,10 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) val = I2C_CNFG_NEW_MASTER_FSM | I2C_CNFG_PACKET_MODE_EN | (0x2 << I2C_CNFG_DEBOUNCE_CNT_SHIFT); + + if (i2c_dev->hw->has_multi_master_mode) + val |= I2C_CNFG_MULTI_MASTER_MODE; + i2c_writel(i2c_dev, val, I2C_CNFG); i2c_writel(i2c_dev, 0, I2C_INT_MASK); @@ -463,6 +474,9 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) if (tegra_i2c_flush_fifos(i2c_dev)) err = -ETIMEDOUT; + if (i2c_dev->is_multimaster_mode && i2c_dev->hw->has_slcg_override_reg) + i2c_writel(i2c_dev, I2C_MST_CORE_CLKEN_OVR, I2C_CLKEN_OVERRIDE); + if (i2c_dev->hw->has_config_load_reg) { i2c_writel(i2c_dev, I2C_MSTR_CONFIG_LOAD, I2C_CONFIG_LOAD); while (i2c_readl(i2c_dev, I2C_CONFIG_LOAD) != 0) { @@ -688,6 +702,20 @@ static u32 tegra_i2c_func(struct i2c_adapter *adap) return ret; } +static void tegra_i2c_parse_dt(struct tegra_i2c_dev *i2c_dev) +{ + struct device_node *np = i2c_dev->dev->of_node; + int ret; + + ret = of_property_read_u32(np, "clock-frequency", + &i2c_dev->bus_clk_rate); + if (ret) + i2c_dev->bus_clk_rate = 100000; /* default clock rate */ + + i2c_dev->is_multimaster_mode = of_property_read_bool(np, + "multi-master"); +} + static const struct i2c_algorithm tegra_i2c_algo = { .master_xfer = tegra_i2c_xfer, .functionality = tegra_i2c_func, @@ -707,6 +735,8 @@ static const struct tegra_i2c_hw_feature tegra20_i2c_hw = { .clk_divisor_std_fast_mode = 0, .clk_divisor_fast_plus_mode = 0, .has_config_load_reg = false, + .has_multi_master_mode = false, + .has_slcg_override_reg = false, }; static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { @@ -717,6 +747,8 @@ static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { .clk_divisor_std_fast_mode = 0, .clk_divisor_fast_plus_mode = 0, .has_config_load_reg = false, + .has_multi_master_mode = false, + .has_slcg_override_reg = false, }; static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { @@ -727,6 +759,8 @@ static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { .clk_divisor_std_fast_mode = 0x19, .clk_divisor_fast_plus_mode = 0x10, .has_config_load_reg = false, + .has_multi_master_mode = false, + .has_slcg_override_reg = false, }; static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { @@ -737,10 +771,25 @@ static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { .clk_divisor_std_fast_mode = 0x19, .clk_divisor_fast_plus_mode = 0x10, .has_config_load_reg = true, + .has_multi_master_mode = false, + .has_slcg_override_reg = true, +}; + +static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { + .has_continue_xfer_support = true, + .has_per_pkt_xfer_complete_irq = true, + .has_single_clk_source = true, + .clk_divisor_hs_mode = 1, + .clk_divisor_std_fast_mode = 0x19, + .clk_divisor_fast_plus_mode = 0x10, + .has_config_load_reg = true, + .has_multi_master_mode = true, + .has_slcg_override_reg = true, }; /* Match table for of_platform binding */ static const struct of_device_id tegra_i2c_of_match[] = { + { .compatible = "nvidia,tegra210-i2c", .data = &tegra210_i2c_hw, }, { .compatible = "nvidia,tegra124-i2c", .data = &tegra124_i2c_hw, }, { .compatible = "nvidia,tegra114-i2c", .data = &tegra114_i2c_hw, }, { .compatible = "nvidia,tegra30-i2c", .data = &tegra30_i2c_hw, }, @@ -797,10 +846,7 @@ static int tegra_i2c_probe(struct platform_device *pdev) return PTR_ERR(i2c_dev->rst); } - ret = of_property_read_u32(i2c_dev->dev->of_node, "clock-frequency", - &i2c_dev->bus_clk_rate); - if (ret) - i2c_dev->bus_clk_rate = 100000; /* default clock rate */ + tegra_i2c_parse_dt(i2c_dev); i2c_dev->hw = &tegra20_i2c_hw; @@ -853,6 +899,15 @@ static int tegra_i2c_probe(struct platform_device *pdev) goto unprepare_fast_clk; } + if (i2c_dev->is_multimaster_mode) { + ret = clk_enable(i2c_dev->div_clk); + if (ret < 0) { + dev_err(i2c_dev->dev, "div_clk enable failed %d\n", + ret); + goto unprepare_div_clk; + } + } + ret = tegra_i2c_init(i2c_dev); if (ret) { dev_err(&pdev->dev, "Failed to initialize i2c controller"); @@ -863,7 +918,7 @@ static int tegra_i2c_probe(struct platform_device *pdev) tegra_i2c_isr, 0, dev_name(&pdev->dev), i2c_dev); if (ret) { dev_err(&pdev->dev, "Failed to request irq %i\n", i2c_dev->irq); - goto unprepare_div_clk; + goto disable_div_clk; } i2c_set_adapdata(&i2c_dev->adapter, i2c_dev); @@ -878,11 +933,15 @@ static int tegra_i2c_probe(struct platform_device *pdev) ret = i2c_add_numbered_adapter(&i2c_dev->adapter); if (ret) { dev_err(&pdev->dev, "Failed to add I2C adapter\n"); - goto unprepare_div_clk; + goto disable_div_clk; } return 0; +disable_div_clk: + if (i2c_dev->is_multimaster_mode) + clk_disable(i2c_dev->div_clk); + unprepare_div_clk: clk_unprepare(i2c_dev->div_clk); @@ -898,6 +957,9 @@ static int tegra_i2c_remove(struct platform_device *pdev) struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev); i2c_del_adapter(&i2c_dev->adapter); + if (i2c_dev->is_multimaster_mode) + clk_disable(i2c_dev->div_clk); + clk_unprepare(i2c_dev->div_clk); if (!i2c_dev->hw->has_single_clk_source) clk_unprepare(i2c_dev->fast_clk); From 758e95157712b79d851847b696765056007fabea Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 16 Oct 2015 08:35:19 +0100 Subject: [PATCH 19/19] dmaengine: tegra-adma: Add support for Tegra210 ADMA Add support for the Tegra210 Audio DMA controller that is used for transferring data between system memory and the Audio sub-system. The driver only supports cyclic transfers because this is being solely used for audio. This driver is based upon the work by Dara Ramesh . Signed-off-by: Jon Hunter --- drivers/dma/Kconfig | 13 + drivers/dma/Makefile | 1 + drivers/dma/tegra210-adma.c | 909 ++++++++++++++++++++++++++++++++++++ 3 files changed, 923 insertions(+) create mode 100644 drivers/dma/tegra210-adma.c diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index d96d87c56f2e13..d75567c6598529 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -467,6 +467,19 @@ config TEGRA20_APB_DMA This DMA controller transfers data from memory to peripheral fifo or vice versa. It does not support memory to memory data transfer. +config TEGRA210_ADMA + bool "NVIDIA Tegra210 ADMA support" + depends on ARCH_TEGRA + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + help + Support for the NVIDIA Tegra210 ADMA controller driver. The + DMA controller has multiple DMA channels and is used to service + various audio clients in the Tegra210 audio processing engine + (APE). This DMA controller transfers data from memory to + peripheral and vice versa. It does not support memory to + memory data transfer. + config TIMB_DMA tristate "Timberdale FPGA DMA support" depends on MFD_TIMBERDALE diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 6084127c14866c..614f28b0b739de 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_STM32_DMA) += stm32-dma.o obj-$(CONFIG_S3C24XX_DMAC) += s3c24xx-dma.o obj-$(CONFIG_TXX9_DMAC) += txx9dmac.o obj-$(CONFIG_TEGRA20_APB_DMA) += tegra20-apb-dma.o +obj-$(CONFIG_TEGRA210_ADMA) += tegra210-adma.o obj-$(CONFIG_TIMB_DMA) += timb_dma.o obj-$(CONFIG_TI_CPPI41) += cppi41.o obj-$(CONFIG_TI_DMA_CROSSBAR) += ti-dma-crossbar.o diff --git a/drivers/dma/tegra210-adma.c b/drivers/dma/tegra210-adma.c new file mode 100644 index 00000000000000..92957465042103 --- /dev/null +++ b/drivers/dma/tegra210-adma.c @@ -0,0 +1,909 @@ +/* + * ADMA driver for Nvidia's Tegra210 ADMA controller. + * + * Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dmaengine.h" +#include "virt-dma.h" + +#define ADMA_CH_CMD 0x00 +#define ADMA_CH_STATUS 0x0c +#define ADMA_CH_STATUS_XFER_EN BIT(0) + +#define ADMA_CH_INT_STATUS 0x10 +#define ADMA_CH_INT_STATUS_XFER_DONE BIT(0) + +#define ADMA_CH_INT_CLEAR 0x1c +#define ADMA_CH_CTRL 0x24 +#define ADMA_CH_CTRL_TX_REQ(val) (((val) & 0xf) << 28) +#define ADMA_CH_CTRL_TX_REQ_MAX 10 +#define ADMA_CH_CTRL_RX_REQ(val) (((val) & 0xf) << 24) +#define ADMA_CH_CTRL_RX_REQ_MAX 10 +#define ADMA_CH_CTRL_DIR(val) (((val) & 0xf) << 12) +#define ADMA_CH_CTRL_DIR_AHUB2MEM 2 +#define ADMA_CH_CTRL_DIR_MEM2AHUB 4 +#define ADMA_CH_CTRL_MODE_CONTINUOUS (2 << 8) +#define ADMA_CH_CTRL_FLOWCTRL_EN BIT(1) + +#define ADMA_CH_CONFIG 0x28 +#define ADMA_CH_CONFIG_SRC_BUF(val) (((val) & 0x7) << 28) +#define ADMA_CH_CONFIG_TRG_BUF(val) (((val) & 0x7) << 24) +#define ADMA_CH_CONFIG_BURST_SIZE(val) (((val) & 0x7) << 20) +#define ADMA_CH_CONFIG_BURST_16 5 +#define ADMA_CH_CONFIG_WEIGHT_FOR_WRR(val) ((val) & 0xf) +#define ADMA_CH_CONFIG_MAX_BUFS 8 + +#define ADMA_CH_FIFO_CTRL 0x2c +#define ADMA_CH_FIFO_CTRL_OVRFW_THRES(val) (((val) & 0xf) << 24) +#define ADMA_CH_FIFO_CTRL_STARV_THRES(val) (((val) & 0xf) << 16) +#define ADMA_CH_FIFO_CTRL_TX_SIZE(val) (((val) & 0xf) << 8) +#define ADMA_CH_FIFO_CTRL_RX_SIZE(val) ((val) & 0xf) + +#define ADMA_CH_TC_STATUS 0x30 +#define ADMA_CH_LOWER_SRC_ADDR 0x34 +#define ADMA_CH_LOWER_TRG_ADDR 0x3c +#define ADMA_CH_TC 0x44 +#define ADMA_CH_TC_COUNT_MASK 0x3ffffffc + +#define ADMA_CH_XFER_STATUS 0x54 +#define ADMA_CH_XFER_STATUS_COUNT_MASK 0xffff + +#define ADMA_GLOBAL_CMD 0xc00 +#define ADMA_GLOBAL_SOFT_RESET 0xc04 +#define ADMA_GLOBAL_INT_CLEAR 0xc20 +#define ADMA_GLOBAL_CTRL 0xc24 + +#define ADMA_CH_REG_OFFSET(a) (a * 0x80) + +#define ADMA_CH_FIFO_CTRL_DEFAULT (ADMA_CH_FIFO_CTRL_OVRFW_THRES(1) | \ + ADMA_CH_FIFO_CTRL_STARV_THRES(1) | \ + ADMA_CH_FIFO_CTRL_TX_SIZE(3) | \ + ADMA_CH_FIFO_CTRL_RX_SIZE(3)) +struct tegra_adma; + +/* + * struct tegra_adma_chip_data - Tegra chip specific data + * @nr_channels: Number of DMA channels available. + */ +struct tegra_adma_chip_data { + int nr_channels; +}; + +/* + * struct tegra_adma_chan_regs - Tegra ADMA channel registers + */ +struct tegra_adma_chan_regs { + unsigned int ctrl; + unsigned int config; + unsigned int src_addr; + unsigned int trg_addr; + unsigned int fifo_ctrl; + unsigned int tc; +}; + +/* + * struct tegra_adma_desc - Tegra ADMA descriptor to manage transfer requests. + */ +struct tegra_adma_desc { + struct virt_dma_desc vd; + struct tegra_adma_chan_regs ch_regs; + unsigned long bytes_requested; + unsigned long bytes_transferred; +}; + +/* + * struct tegra_adma_chan - Tegra ADMA channel information + */ +struct tegra_adma_chan { + struct virt_dma_chan vc; + struct tegra_adma_desc *desc; + struct tegra_adma *tdma; + char name[30]; + int irq; + void __iomem *chan_addr; + spinlock_t lock; + + /* Slave channel configuration info */ + struct dma_slave_config sconfig; + bool sconfig_valid; + unsigned int sreq_dir; + unsigned int sreq_index; + bool sreq_reserved; + + /* Transfer count and position info */ + unsigned int tx_buf_count; + unsigned int tx_buf_pos; +}; + +/* + * struct tegra_adma - Tegra ADMA controller information + */ +struct tegra_adma { + struct dma_device dma_dev; + struct device *dev; + struct clk *adma_clk; + void __iomem *base_addr; + unsigned int nr_channels; + unsigned long rx_requests_reserved; + unsigned long tx_requests_reserved; + + /* Used to store global command register state when suspending */ + unsigned int global_cmd; + + /* Last member of the structure */ + struct tegra_adma_chan channels[0]; +}; + +static inline void tdma_write(struct tegra_adma *tdma, u32 reg, u32 val) +{ + writel(val, tdma->base_addr + reg); +} + +static inline u32 tdma_read(struct tegra_adma *tdma, u32 reg) +{ + return readl(tdma->base_addr + reg); +} + +static inline void tdma_ch_write(struct tegra_adma_chan *tdc, + u32 reg, u32 val) +{ + writel(val, tdc->chan_addr + reg); +} + +static inline u32 tdma_ch_read(struct tegra_adma_chan *tdc, u32 reg) +{ + return readl(tdc->chan_addr + reg); +} + +static inline struct tegra_adma_chan *to_tegra_adma_chan(struct dma_chan *dc) +{ + return container_of(dc, struct tegra_adma_chan, vc.chan); +} + +static inline struct tegra_adma_desc *to_tegra_adma_desc( + struct dma_async_tx_descriptor *td) +{ + return container_of(td, struct tegra_adma_desc, vd.tx); +} + +static inline struct device *tdc2dev(struct tegra_adma_chan *tdc) +{ + return tdc->tdma->dev; +} + +static void tegra_adma_desc_free(struct virt_dma_desc *vd) +{ + kfree(container_of(vd, struct tegra_adma_desc, vd)); +} + +static int tegra_adma_slave_config(struct dma_chan *dc, + struct dma_slave_config *sconfig) +{ + struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); + + memcpy(&tdc->sconfig, sconfig, sizeof(*sconfig)); + tdc->sconfig_valid = true; + + return 0; +} + +static int tegra_adma_init(struct tegra_adma *tdma) +{ + u32 status; + int ret; + + /* Clear any interrupts */ + tdma_write(tdma, ADMA_GLOBAL_INT_CLEAR, 0x1); + + /* Assert soft reset */ + tdma_write(tdma, ADMA_GLOBAL_SOFT_RESET, 0x1); + + /* Wait for reset to clear */ + ret = readx_poll_timeout(readl, + tdma->base_addr + ADMA_GLOBAL_SOFT_RESET, + status, status == 0, 20, 10000); + if (ret) + return ret; + + /* Enable global ADMA registers */ + tdma_write(tdma, ADMA_GLOBAL_CMD, 1); + + return 0; +} + +static int tegra_adma_request_alloc(struct tegra_adma_chan *tdc, + unsigned int sreq_dir) +{ + struct tegra_adma *tdma = tdc->tdma; + unsigned int sreq_index = tdc->sreq_index; + + if (tdc->sreq_reserved) + return tdc->sreq_dir == sreq_dir ? 0 : -EINVAL; + + switch (sreq_dir) { + case ADMA_CH_CTRL_DIR_MEM2AHUB: + if (sreq_index > ADMA_CH_CTRL_TX_REQ_MAX) { + dev_err(tdma->dev, "invalid DMA request\n"); + return -EINVAL; + } + + if (test_and_set_bit(sreq_index, &tdma->tx_requests_reserved)) { + dev_err(tdma->dev, "DMA request reserved\n"); + return -EINVAL; + } + break; + + case ADMA_CH_CTRL_DIR_AHUB2MEM: + if (sreq_index > ADMA_CH_CTRL_RX_REQ_MAX) { + dev_err(tdma->dev, "invalid DMA request\n"); + return -EINVAL; + } + + if (test_and_set_bit(sreq_index, &tdma->rx_requests_reserved)) { + dev_err(tdma->dev, "DMA request reserved\n"); + return -EINVAL; + } + break; + + default: + dev_WARN(tdma->dev, "channel %s has invalid transfer type\n", + tdc->name); + return -EINVAL; + } + + tdc->sreq_dir = sreq_dir; + tdc->sreq_reserved = true; + + return 0; +} + +static void tegra_adma_request_free(struct tegra_adma_chan *tdc) +{ + struct tegra_adma *tdma = tdc->tdma; + + if (!tdc->sreq_reserved) + return; + + switch (tdc->sreq_dir) { + case ADMA_CH_CTRL_DIR_MEM2AHUB: + clear_bit(tdc->sreq_index, &tdma->tx_requests_reserved); + break; + case ADMA_CH_CTRL_DIR_AHUB2MEM: + clear_bit(tdc->sreq_index, &tdma->rx_requests_reserved); + break; + default: + dev_WARN(tdma->dev, "channel %s has invalid transfer type\n", + tdc->name); + return; + } + + tdc->sreq_reserved = false; +} + +static u32 tegra_adma_irq_status(struct tegra_adma_chan *tdc) +{ + u32 status = tdma_ch_read(tdc, ADMA_CH_INT_STATUS); + + return status & ADMA_CH_INT_STATUS_XFER_DONE; +} + +static u32 tegra_adma_irq_clear(struct tegra_adma_chan *tdc) +{ + u32 status = tegra_adma_irq_status(tdc); + + if (status) + tdma_ch_write(tdc, ADMA_CH_INT_CLEAR, status); + + return status; +} + +static void tegra_adma_stop(struct tegra_adma_chan *tdc) +{ + unsigned int status; + + /* Disable ADMA */ + tdma_ch_write(tdc, ADMA_CH_CMD, 0); + + /* Clear interrupt status */ + tegra_adma_irq_clear(tdc); + + if (readx_poll_timeout_atomic(readl, tdc->chan_addr + ADMA_CH_STATUS, + status, !(status & ADMA_CH_STATUS_XFER_EN), + 20, 10000)) { + dev_err(tdc2dev(tdc), "unable to stop DMA channel\n"); + return; + } + + tdc->desc = NULL; +} + +static void tegra_adma_start(struct tegra_adma_chan *tdc) +{ + struct virt_dma_desc *vd = vchan_next_desc(&tdc->vc); + struct tegra_adma_chan_regs *ch_regs; + struct tegra_adma_desc *desc; + + if (!vd) + return; + + list_del(&vd->node); + + desc = to_tegra_adma_desc(&vd->tx); + + if (!desc) { + dev_warn(tdc2dev(tdc), "unable to start DMA, no descriptor\n"); + return; + } + + ch_regs = &desc->ch_regs; + + tdc->tx_buf_pos = 0; + tdc->tx_buf_count = 0; + tdma_ch_write(tdc, ADMA_CH_TC, ch_regs->tc); + tdma_ch_write(tdc, ADMA_CH_CTRL, ch_regs->ctrl); + tdma_ch_write(tdc, ADMA_CH_LOWER_SRC_ADDR, ch_regs->src_addr); + tdma_ch_write(tdc, ADMA_CH_LOWER_TRG_ADDR, ch_regs->trg_addr); + tdma_ch_write(tdc, ADMA_CH_FIFO_CTRL, ch_regs->fifo_ctrl); + tdma_ch_write(tdc, ADMA_CH_CONFIG, ch_regs->config); + + /* Start ADMA */ + tdma_ch_write(tdc, ADMA_CH_CMD, 1); + + tdc->desc = desc; +} + +static void tegra_adma_update_position(struct tegra_adma_chan *tdc) +{ + struct tegra_adma_desc *desc = tdc->desc; + unsigned int max = ADMA_CH_XFER_STATUS_COUNT_MASK + 1; + unsigned int pos = tdma_ch_read(tdc, ADMA_CH_XFER_STATUS); + + /* + * Handle wrap around of buffer count register + */ + if (pos < tdc->tx_buf_pos) + tdc->tx_buf_count += pos + (max - tdc->tx_buf_pos); + else + tdc->tx_buf_count += pos - tdc->tx_buf_pos; + + tdc->tx_buf_pos = pos; + + desc->bytes_transferred = tdc->tx_buf_count * desc->ch_regs.tc; + + /* + * If we are not currently active, then it is safe to read the + * remaining words from the TC_STATUS register and add the partial + * buffer to the total transferred. + */ + if (!tdc->desc) + desc->bytes_transferred += desc->ch_regs.tc - + tdma_ch_read(tdc, ADMA_CH_TC_STATUS); +} + +static unsigned int tegra_adma_get_residue(struct tegra_adma_desc *desc) +{ + return desc->bytes_requested - (desc->bytes_transferred % + desc->bytes_requested); +} + +static irqreturn_t tegra_adma_isr(int irq, void *dev_id) +{ + struct tegra_adma_chan *tdc = dev_id; + unsigned long status; + unsigned long flags; + + spin_lock_irqsave(&tdc->lock, flags); + + status = tegra_adma_irq_clear(tdc); + if (status == 0 || !tdc->desc) { + spin_unlock_irqrestore(&tdc->lock, flags); + return IRQ_NONE; + } + + vchan_cyclic_callback(&tdc->desc->vd); + + spin_unlock_irqrestore(&tdc->lock, flags); + + return IRQ_HANDLED; +} + +static void tegra_adma_issue_pending(struct dma_chan *dc) +{ + struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); + unsigned long flags; + + spin_lock_irqsave(&tdc->lock, flags); + + if (vchan_issue_pending(&tdc->vc)) { + if (tdc->desc) + dev_warn(tdc2dev(tdc), "DMA already running\n"); + else + tegra_adma_start(tdc); + } + + spin_unlock_irqrestore(&tdc->lock, flags); +} + +static int tegra_adma_terminate_all(struct dma_chan *dc) +{ + struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); + unsigned long flags; + LIST_HEAD(head); + + spin_lock_irqsave(&tdc->lock, flags); + + if (tdc->desc) + tegra_adma_stop(tdc); + + tegra_adma_request_free(tdc); + vchan_get_all_descriptors(&tdc->vc, &head); + spin_unlock_irqrestore(&tdc->lock, flags); + vchan_dma_desc_free_list(&tdc->vc, &head); + + return 0; +} + +static enum dma_status tegra_adma_tx_status(struct dma_chan *dc, + dma_cookie_t cookie, + struct dma_tx_state *txstate) +{ + struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); + struct tegra_adma_desc *desc; + struct virt_dma_desc *vd; + enum dma_status ret; + unsigned long flags; + unsigned int residual; + + spin_lock_irqsave(&tdc->lock, flags); + + ret = dma_cookie_status(dc, cookie, txstate); + if (ret == DMA_COMPLETE || !txstate) { + spin_unlock_irqrestore(&tdc->lock, flags); + return ret; + } + + vd = vchan_find_desc(&tdc->vc, cookie); + if (vd) { + desc = to_tegra_adma_desc(&vd->tx); + residual = desc->ch_regs.tc; + } else if (tdc->desc && tdc->desc->vd.tx.cookie == cookie) { + tegra_adma_update_position(tdc); + residual = tegra_adma_get_residue(tdc->desc); + } else { + residual = 0; + } + + dma_set_residue(txstate, residual); + + spin_unlock_irqrestore(&tdc->lock, flags); + + return ret; +} + +static int tegra_adma_set_xfer_params(struct tegra_adma_chan *tdc, + struct tegra_adma_desc *desc, + dma_addr_t buf_addr, size_t buf_len, + size_t period_len, + enum dma_transfer_direction direction) +{ + struct tegra_adma_chan_regs *ch_regs = &desc->ch_regs; + unsigned int burst_size, num_bufs, sreq_dir; + + num_bufs = buf_len / period_len; + + if (num_bufs > ADMA_CH_CONFIG_MAX_BUFS) + return -EINVAL; + + switch (direction) { + case DMA_MEM_TO_DEV: + sreq_dir = ADMA_CH_CTRL_DIR_MEM2AHUB; + burst_size = fls(tdc->sconfig.dst_maxburst); + ch_regs->config = ADMA_CH_CONFIG_SRC_BUF(num_bufs - 1); + ch_regs->ctrl = ADMA_CH_CTRL_TX_REQ(tdc->sreq_index); + ch_regs->src_addr = buf_addr; + break; + + case DMA_DEV_TO_MEM: + sreq_dir = ADMA_CH_CTRL_DIR_AHUB2MEM; + burst_size = fls(tdc->sconfig.src_maxburst); + ch_regs->config = ADMA_CH_CONFIG_TRG_BUF(num_bufs - 1); + ch_regs->ctrl = ADMA_CH_CTRL_RX_REQ(tdc->sreq_index); + ch_regs->trg_addr = buf_addr; + break; + + default: + dev_err(tdc2dev(tdc), "DMA direction is not supported\n"); + return -EINVAL; + } + + if (!burst_size || burst_size > ADMA_CH_CONFIG_BURST_16) + burst_size = ADMA_CH_CONFIG_BURST_16; + + ch_regs->ctrl |= ADMA_CH_CTRL_DIR(sreq_dir) | + ADMA_CH_CTRL_MODE_CONTINUOUS | + ADMA_CH_CTRL_FLOWCTRL_EN; + ch_regs->config |= ADMA_CH_CONFIG_BURST_SIZE(burst_size); + ch_regs->config |= ADMA_CH_CONFIG_WEIGHT_FOR_WRR(1); + ch_regs->fifo_ctrl = ADMA_CH_FIFO_CTRL_DEFAULT; + ch_regs->tc = period_len & ADMA_CH_TC_COUNT_MASK; + + return tegra_adma_request_alloc(tdc, sreq_dir); +} + +static struct dma_async_tx_descriptor *tegra_adma_prep_slave_sg( + struct dma_chan *dc, struct scatterlist *sgl, unsigned int sg_len, + enum dma_transfer_direction direction, unsigned long flags, + void *context) +{ + struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); + + dev_warn(tdc2dev(tdc), "scatter-gather transfers are not supported\n"); + + return NULL; +} + +static struct dma_async_tx_descriptor *tegra_adma_prep_dma_cyclic( + struct dma_chan *dc, dma_addr_t buf_addr, size_t buf_len, + size_t period_len, enum dma_transfer_direction direction, + unsigned long flags) +{ + struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); + struct tegra_adma_desc *desc = NULL; + + if (!tdc->sconfig_valid) { + dev_err(tdc2dev(tdc), "ADMA slave configuration not set\n"); + return NULL; + } + + if (!buf_len || !period_len || period_len > ADMA_CH_TC_COUNT_MASK) { + dev_err(tdc2dev(tdc), "invalid buffer/period len\n"); + return NULL; + } + + if (buf_len % period_len) { + dev_err(tdc2dev(tdc), "buf_len not a multiple of period_len\n"); + return NULL; + } + + if (!IS_ALIGNED(buf_addr, 4)) { + dev_err(tdc2dev(tdc), "invalid buffer alignment\n"); + return NULL; + } + + desc = kzalloc(sizeof(*desc), GFP_NOWAIT); + if (!desc) + return NULL; + + desc->bytes_transferred = 0; + desc->bytes_requested = buf_len; + + if (tegra_adma_set_xfer_params(tdc, desc, buf_addr, buf_len, period_len, + direction)) { + kfree(desc); + return NULL; + } + + return vchan_tx_prep(&tdc->vc, &desc->vd, flags); +} + +static int tegra_adma_alloc_chan_resources(struct dma_chan *dc) +{ + struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); + int ret; + + ret = pm_runtime_get_sync(tdc2dev(tdc)); + if (ret) + return ret; + + dma_cookie_init(&tdc->vc.chan); + tdc->sconfig_valid = false; + + return 0; +} + +static void tegra_adma_free_chan_resources(struct dma_chan *dc) +{ + struct tegra_adma_chan *tdc = to_tegra_adma_chan(dc); + + if (tdc->desc) + tegra_adma_terminate_all(dc); + + tdc->sconfig_valid = false; + vchan_free_chan_resources(&tdc->vc); + + pm_runtime_put(tdc2dev(tdc)); + + tegra_adma_request_free(tdc); + + tdc->sreq_index = 0; + tdc->sreq_dir = 0; +} + +static struct dma_chan *tegra_dma_of_xlate(struct of_phandle_args *dma_spec, + struct of_dma *ofdma) +{ + struct tegra_adma *tdma = ofdma->of_dma_data; + struct tegra_adma_chan *tdc; + struct dma_chan *chan; + unsigned int sreq_index; + + if (dma_spec->args_count != 1) + return NULL; + + sreq_index = dma_spec->args[0]; + + if (sreq_index == 0) { + dev_err(tdma->dev, "DMA request must not be 0\n"); + return NULL; + } + + chan = dma_get_any_slave_channel(&tdma->dma_dev); + if (!chan) + return NULL; + + tdc = to_tegra_adma_chan(chan); + tdc->sreq_index = sreq_index; + + return chan; +} + +static int tegra_adma_runtime_suspend(struct device *dev) +{ + struct tegra_adma *tdma = dev_get_drvdata(dev); + + tdma->global_cmd = tdma_read(tdma, ADMA_GLOBAL_CMD); + + clk_disable_unprepare(tdma->adma_clk); + + return 0; +} + +static int tegra_adma_runtime_resume(struct device *dev) +{ + struct tegra_adma *tdma = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(tdma->adma_clk); + if (ret < 0) { + dev_err(dev, "failed to enable ADMA clock: %d\n", ret); + return ret; + } + + tdma_write(tdma, ADMA_GLOBAL_CMD, tdma->global_cmd); + + return 0; +} + +static const struct tegra_adma_chip_data tegra210_chip_data = { + .nr_channels = 22, +}; + +static const struct of_device_id tegra_adma_of_match[] = { + { .compatible = "nvidia,tegra210-adma", .data = &tegra210_chip_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_adma_of_match); + +static int tegra_adma_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + const struct tegra_adma_chip_data *cdata; + struct tegra_adma *tdma; + struct resource *res; + int ret, i; + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "no device tree node for ADMA\n"); + return -ENODEV; + } + + match = of_match_device(tegra_adma_of_match, &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "no device match found\n"); + return -ENODEV; + } + cdata = match->data; + + tdma = devm_kzalloc(&pdev->dev, sizeof(*tdma) + cdata->nr_channels * + sizeof(struct tegra_adma_chan), GFP_KERNEL); + if (!tdma) + return -ENOMEM; + + tdma->dev = &pdev->dev; + tdma->nr_channels = cdata->nr_channels; + platform_set_drvdata(pdev, tdma); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + tdma->base_addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tdma->base_addr)) + return PTR_ERR(tdma->base_addr); + + tdma->adma_clk = devm_clk_get(&pdev->dev, "adma_ape"); + if (IS_ERR(tdma->adma_clk)) { + dev_err(&pdev->dev, "ADMA clock not found\n"); + return PTR_ERR(tdma->adma_clk); + } + + pm_runtime_enable(&pdev->dev); + if (pm_runtime_enabled(&pdev->dev)) + ret = pm_runtime_get_sync(&pdev->dev); + else + ret = tegra_adma_runtime_resume(&pdev->dev); + + if (ret) { + pm_runtime_disable(&pdev->dev); + return ret; + } + + ret = tegra_adma_init(tdma); + if (ret) + goto err_pm_disable; + + INIT_LIST_HEAD(&tdma->dma_dev.channels); + for (i = 0; i < tdma->nr_channels; i++) { + struct tegra_adma_chan *tdc = &tdma->channels[i]; + + tdc->chan_addr = tdma->base_addr + ADMA_CH_REG_OFFSET(i); + + snprintf(tdc->name, sizeof(tdc->name), "adma.%d", i); + + tdc->irq = platform_get_irq(pdev, i); + if (tdc->irq < 0) { + ret = -EPROBE_DEFER; + goto err_irq; + } + + ret = devm_request_irq(&pdev->dev, tdc->irq, tegra_adma_isr, 0, + tdc->name, tdc); + if (ret) { + dev_err(&pdev->dev, + "failed to get interrupt for channel %d\n", i); + goto err_irq; + } + + spin_lock_init(&tdc->lock); + vchan_init(&tdc->vc, &tdma->dma_dev); + tdc->vc.desc_free = tegra_adma_desc_free; + tdc->tdma = tdma; + } + + dma_cap_set(DMA_SLAVE, tdma->dma_dev.cap_mask); + dma_cap_set(DMA_PRIVATE, tdma->dma_dev.cap_mask); + dma_cap_set(DMA_CYCLIC, tdma->dma_dev.cap_mask); + + tdma->dma_dev.dev = &pdev->dev; + tdma->dma_dev.device_alloc_chan_resources = + tegra_adma_alloc_chan_resources; + tdma->dma_dev.device_free_chan_resources = + tegra_adma_free_chan_resources; + tdma->dma_dev.device_issue_pending = tegra_adma_issue_pending; + tdma->dma_dev.device_prep_slave_sg = tegra_adma_prep_slave_sg; + tdma->dma_dev.device_prep_dma_cyclic = tegra_adma_prep_dma_cyclic; + tdma->dma_dev.device_config = tegra_adma_slave_config; + tdma->dma_dev.device_tx_status = tegra_adma_tx_status; + tdma->dma_dev.device_terminate_all = tegra_adma_terminate_all; + tdma->dma_dev.src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + tdma->dma_dev.dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + + ret = dma_async_device_register(&tdma->dma_dev); + if (ret < 0) { + dev_err(&pdev->dev, "ADMA registration failed: %d\n", ret); + goto err_irq; + } + + ret = of_dma_controller_register(pdev->dev.of_node, + tegra_dma_of_xlate, tdma); + if (ret < 0) { + dev_err(&pdev->dev, "ADMA OF registration failed %d\n", ret); + goto err_unregister_dma_dev; + } + + pm_runtime_put(&pdev->dev); + + dev_info(&pdev->dev, "Tegra210 ADMA driver registered %d channels\n", + tdma->nr_channels); + + return 0; + +err_unregister_dma_dev: + dma_async_device_unregister(&tdma->dma_dev); +err_irq: + while (--i >= 0) { + struct tegra_adma_chan *tdc = &tdma->channels[i]; + + tasklet_kill(&tdc->vc.task); + } +err_pm_disable: + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra_adma_runtime_suspend(&pdev->dev); + + return ret; +} + +static int tegra_adma_remove(struct platform_device *pdev) +{ + struct tegra_adma *tdma = platform_get_drvdata(pdev); + struct tegra_adma_chan *tdc; + int i; + + dma_async_device_unregister(&tdma->dma_dev); + + for (i = 0; i < tdma->nr_channels; ++i) { + tdc = &tdma->channels[i]; + disable_irq(tdc->irq); + tasklet_kill(&tdc->vc.task); + } + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + tegra_adma_runtime_suspend(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra_adma_pm_suspend(struct device *dev) +{ + return pm_runtime_suspended(dev); +} +#endif + +static const struct dev_pm_ops tegra_adma_dev_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_adma_runtime_suspend, + tegra_adma_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(tegra_adma_pm_suspend, NULL) +}; + +static struct platform_driver tegra_admac_driver = { + .driver = { + .name = "tegra-adma", + .pm = &tegra_adma_dev_pm_ops, + .of_match_table = tegra_adma_of_match, + }, + .probe = tegra_adma_probe, + .remove = tegra_adma_remove, +}; + +module_platform_driver(tegra_admac_driver); + +MODULE_ALIAS("platform:tegra210-adma"); +MODULE_DESCRIPTION("NVIDIA Tegra ADMA driver"); +MODULE_AUTHOR("Dara Ramesh "); +MODULE_AUTHOR("Jon Hunter "); +MODULE_LICENSE("GPL v2");