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>; +}; 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/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..8cbfeb60f864fd --- /dev/null +++ b/Documentation/devicetree/bindings/phy/nvidia,tegra124-xusb-padctl.txt @@ -0,0 +1,703 @@ +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" + - 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: + - "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. + +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: +========== + +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" + +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: +=========== + +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 + - 0-3: for Tegra210 + +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 + +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: +========= + +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"; + }; + }; + }; + +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>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt index 30676ded85bb3a..793c3549c37717 100644 --- a/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt +++ b/Documentation/devicetree/bindings/pinctrl/nvidia,tegra124-xusb-padctl.txt @@ -1,6 +1,12 @@ 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 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 associated PHY that must be powered up before the pad can be used. @@ -23,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: ------------ @@ -52,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, hsic-0, hsic-1: + - ulpi-0: Valid functions for this group are: "snps", "xusb". - The nvidia,iddq property does not apply to this group. + - hsic-0, hsic-1: + + Valid functions for this group are: "snps", "xusb". + + 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: ======== @@ -84,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>; }; @@ -102,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/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 = ; + }; + }; +}; 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..d28295a3e55f14 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra124-xusb.txt @@ -0,0 +1,120 @@ +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" + - 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: + - "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. + +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. + 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 + - Tegra210: usb2-0, usb2-1, usb2-2, usb2-3, hsic-0, usb3-0, usb3-1, usb3-2, + usb3-3 + +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>; + }; 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/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"); 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"); 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); 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/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..898589238fd94c --- /dev/null +++ b/drivers/phy/tegra/Makefile @@ -0,0 +1,6 @@ +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-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-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/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/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"); 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/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..426e8d922cf465 --- /dev/null +++ b/drivers/usb/host/xhci-tegra.c @@ -0,0 +1,1375 @@ +/* + * 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; + + bool scale_ss_clock; +}; + +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: + 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; + } + + 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; + + 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; + +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, }, + }, + .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); + +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"); 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 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 */ 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_ */ 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_ */ 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 */ 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__