From patchwork Mon Oct 8 21:11:59 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Zapolskiy X-Patchwork-Id: 980820 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=mleia.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42TY3y5mQKz9sD2 for ; Tue, 9 Oct 2018 08:13:02 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726754AbeJIEZw (ORCPT ); Tue, 9 Oct 2018 00:25:52 -0400 Received: from mleia.com ([178.79.152.223]:35432 "EHLO mail.mleia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725835AbeJIEZv (ORCPT ); Tue, 9 Oct 2018 00:25:51 -0400 Received: from mail.mleia.com (localhost [127.0.0.1]) by mail.mleia.com (Postfix) with ESMTP id 97D7B3CB479; Mon, 8 Oct 2018 22:12:09 +0100 (BST) From: Vladimir Zapolskiy To: Lee Jones , Linus Walleij , Rob Herring Cc: Marek Vasut , Laurent Pinchart , Wolfram Sang , devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, Sandeep Jain , Vladimir Zapolskiy Subject: [PATCH 1/7] dt-bindings: mfd: ds90ux9xx: add description of TI DS90Ux9xx ICs Date: Tue, 9 Oct 2018 00:11:59 +0300 Message-Id: <20181008211205.2900-2-vz@mleia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181008211205.2900-1-vz@mleia.com> References: <20181008211205.2900-1-vz@mleia.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-49551924 X-CRM114-CacheID: sfid-20181008_221209_646909_16AEEB1B X-CRM114-Status: GOOD ( 15.18 ) Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org From: Sandeep Jain The change adds device tree binding description of TI DS90Ux9xx series of serializer and deserializer controllers which support video, audio and control data transmission over FPD-III Link connection. Signed-off-by: Sandeep Jain [vzapolskiy: various updates and corrections of secondary importance] Signed-off-by: Vladimir Zapolskiy --- .../devicetree/bindings/mfd/ti,ds90ux9xx.txt | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/ti,ds90ux9xx.txt diff --git a/Documentation/devicetree/bindings/mfd/ti,ds90ux9xx.txt b/Documentation/devicetree/bindings/mfd/ti,ds90ux9xx.txt new file mode 100644 index 000000000000..0733da88f7ef --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/ti,ds90ux9xx.txt @@ -0,0 +1,66 @@ +Texas Instruments DS90Ux9xx de-/serializer controllers + +Required properties: +- compatible: Must contain a generic "ti,ds90ux9xx" value and + may contain one more specific value from the list: + "ti,ds90ub925q", + "ti,ds90uh925q", + "ti,ds90ub927q", + "ti,ds90uh927q", + "ti,ds90ub926q", + "ti,ds90uh926q", + "ti,ds90ub928q", + "ti,ds90uh928q", + "ti,ds90ub940q", + "ti,ds90uh940q". + +Optional properties: +- reg : Specifies the I2C slave address of a local de-/serializer. +- power-gpios : GPIO line to control supplied power to the device. +- ti,backward-compatible-mode : Overrides backward compatibility mode. + Possible values are "<1>" or "<0>". + If "ti,backward-compatible-mode" is not mentioned, the backward + compatibility mode is not touched and given by hardware pin strapping. +- ti,low-frequency-mode : Overrides low frequency mode. + Possible values are "<1>" or "<0>". + If "ti,low-frequency-mode" is not mentioned, the low frequency mode + is not touched and given by hardware pin strapping. +- ti,video-map-select-msb: Sets video bridge pins to MSB mode, if it is set + MAPSEL pin value is ignored. +- ti,video-map-select-lsb: Sets video bridge pins to LSB mode, if it is set + MAPSEL pin value is ignored. +- ti,pixel-clock-edge : Selects Pixel Clock Edge. + Possible values are "<1>" or "<0>". + If "ti,pixel-clock-edge" is High <1>, output data is strobed on the + Rising edge of the PCLK. If ti,pixel-clock-edge is Low <0>, data is + strobed on the Falling edge of the PCLK. + If "ti,pixel-clock-edge" is not mentioned, the pixel clock edge + value is not touched and given by hardware pin strapping. +- ti,spread-spectrum-clock-generation : Spread Sprectrum Clock Generation. + Possible values are from "<0>" to "<7>". The same value will be + written to SSC register. If "ti,spread-spectrum-clock-gen" is not + found, then SSCG will be disabled. + +TI DS90Ux9xx serializers and deserializer device nodes may contain a number +of children device nodes to describe and enable particular subcomponents +found on ICs. + +Example: + +serializer: serializer@c { + compatible = "ti,ds90ub927q", "ti,ds90ux9xx"; + reg = <0xc>; + power-gpios = <&gpio5 12 GPIO_ACTIVE_HIGH>; + ti,backward-compatible-mode = <0>; + ti,low-frequency-mode = <0>; + ti,pixel-clock-edge = <0>; + ... +} + +deserializer: deserializer@3c { + compatible = "ti,ds90ub940q", "ti,ds90ux9xx"; + reg = <0x3c>; + power-gpios = <&gpio6 31 GPIO_ACTIVE_HIGH>; + ... +} + From patchwork Mon Oct 8 21:12:00 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Zapolskiy X-Patchwork-Id: 980816 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=mleia.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42TY3k0gtcz9sD2 for ; Tue, 9 Oct 2018 08:12:50 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726966AbeJIEZx (ORCPT ); Tue, 9 Oct 2018 00:25:53 -0400 Received: from mleia.com ([178.79.152.223]:35456 "EHLO mail.mleia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726656AbeJIEZx (ORCPT ); Tue, 9 Oct 2018 00:25:53 -0400 Received: from mail.mleia.com (localhost [127.0.0.1]) by mail.mleia.com (Postfix) with ESMTP id 6B49341E2BE; Mon, 8 Oct 2018 22:12:10 +0100 (BST) From: Vladimir Zapolskiy To: Lee Jones , Linus Walleij , Rob Herring Cc: Marek Vasut , Laurent Pinchart , Wolfram Sang , devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, Vladimir Zapolskiy Subject: [PATCH 2/7] dt-bindings: mfd: ds90ux9xx: add description of TI DS90Ux9xx I2C bridge Date: Tue, 9 Oct 2018 00:12:00 +0300 Message-Id: <20181008211205.2900-3-vz@mleia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181008211205.2900-1-vz@mleia.com> References: <20181008211205.2900-1-vz@mleia.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-49551924 X-CRM114-CacheID: sfid-20181008_221210_462796_E865BBDF X-CRM114-Status: GOOD ( 17.92 ) Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org From: Vladimir Zapolskiy TI DS90Ux9xx de-/serializers are capable to route I2C messages to I2C slave devices connected to a remote de-/serializer in a pair, the change adds description of device tree bindings of the subcontroller to configure and enable this functionality. Signed-off-by: Vladimir Zapolskiy --- .../bindings/mfd/ti,ds90ux9xx-i2c-bridge.txt | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/ti,ds90ux9xx-i2c-bridge.txt diff --git a/Documentation/devicetree/bindings/mfd/ti,ds90ux9xx-i2c-bridge.txt b/Documentation/devicetree/bindings/mfd/ti,ds90ux9xx-i2c-bridge.txt new file mode 100644 index 000000000000..4169e382073a --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/ti,ds90ux9xx-i2c-bridge.txt @@ -0,0 +1,61 @@ +TI DS90Ux9xx de-/serializer I2C bridge subcontroller + +Required properties: +- compatible: Must contain a generic "ti,ds90ux9xx-i2c-bridge" value and + may contain one more specific value from the list: + "ti,ds90ux925-i2c-bridge", + "ti,ds90ux926-i2c-bridge", + "ti,ds90ux927-i2c-bridge", + "ti,ds90ux928-i2c-bridge", + "ti,ds90ux940-i2c-bridge". + +Required properties of a de-/serializer device connected to a local I2C bus: +- ti,i2c-bridges: List of phandles to remote de-/serializer devices with + two arguments: id of a local de-/serializer FPD link and an assigned + I2C address of a remote de-/serializer to be accessed on a local + I2C bus. + +Optional properties of a de-/serializer device connected to a local I2C bus: +- ti,i2c-bridge-maps: List of 3-cell values: + - the first argument is id of a local de-/serializer FPD link, + - the second argument is an I2C address of a device connected to + a remote de-/serializer IC, + - the third argument is an I2C address of the remote I2C device + for access on a local I2C bus. +- ti,i2c-bridge-auto-ack: Enables AUTO ACK mode. +- ti,i2c-bridge-pass-all: Enables PASS ALL mode, remote I2C slave devices + are accessible on a local (host) I2C bus without I2C address + remappings. + +Remote de-/serializer device may contain a list of device nodes, each +one represents an I2C device connected to that remote de-/serializer IC. + +Example (remote device is a deserializer with Atmel MXT touchscreen): + +serializer: serializer@c { + compatible = "ti,ds90ub927q", "ti,ds90ux9xx"; + reg = <0xc>; + + i2c-bridge { + compatible = "ti,ds90ux927-i2c-bridge", + "ti,ds90ux9xx-i2c-bridge"; + ti,i2c-bridges = <&deserializer 0 0x3b>; + ti,i2c-bridge-maps = <0 0x4b 0x64>; + }; +}; + +deserializer: deserializer { + compatible = "ti,ds90ub928q", "ti,ds90ux9xx"; + + i2c-bridge { + compatible = "ti,ds90ux928-i2c-bridge", + "ti,ds90ux9xx-i2c-bridge"; + #address-cells = <1>; + #size-cells = <0>; + + touchscreen@4b { + compatible = "atmel,maxtouch"; + reg = <0x4b>; + }; + }; +}; From patchwork Mon Oct 8 21:12:01 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Zapolskiy X-Patchwork-Id: 980818 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=mleia.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42TY3p57Ynz9s9J for ; Tue, 9 Oct 2018 08:12:54 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726920AbeJIEZx (ORCPT ); Tue, 9 Oct 2018 00:25:53 -0400 Received: from mleia.com ([178.79.152.223]:35472 "EHLO mail.mleia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725794AbeJIEZx (ORCPT ); Tue, 9 Oct 2018 00:25:53 -0400 Received: from mail.mleia.com (localhost [127.0.0.1]) by mail.mleia.com (Postfix) with ESMTP id 4411341E2C2; Mon, 8 Oct 2018 22:12:11 +0100 (BST) From: Vladimir Zapolskiy To: Lee Jones , Linus Walleij , Rob Herring Cc: Marek Vasut , Laurent Pinchart , Wolfram Sang , devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, Vladimir Zapolskiy Subject: [PATCH 3/7] dt-bindings: pinctrl: ds90ux9xx: add description of TI DS90Ux9xx pinmux Date: Tue, 9 Oct 2018 00:12:01 +0300 Message-Id: <20181008211205.2900-4-vz@mleia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181008211205.2900-1-vz@mleia.com> References: <20181008211205.2900-1-vz@mleia.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-49551924 X-CRM114-CacheID: sfid-20181008_221211_311768_E333BD88 X-CRM114-Status: GOOD ( 15.25 ) Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org From: Vladimir Zapolskiy TI DS90Ux9xx de-/serializers have a capability to multiplex pin functions, in particular a pin may have selectable functions of GPIO, GPIO line transmitter, one of I2S lines, one of RGB24 video signal lines and so on. The change adds a description of DS90Ux9xx pin multiplexers and GPIO controllers. Signed-off-by: Vladimir Zapolskiy --- .../bindings/pinctrl/ti,ds90ux9xx-pinctrl.txt | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Documentation/devicetree/bindings/pinctrl/ti,ds90ux9xx-pinctrl.txt diff --git a/Documentation/devicetree/bindings/pinctrl/ti,ds90ux9xx-pinctrl.txt b/Documentation/devicetree/bindings/pinctrl/ti,ds90ux9xx-pinctrl.txt new file mode 100644 index 000000000000..fbfa1a3cdf9f --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/ti,ds90ux9xx-pinctrl.txt @@ -0,0 +1,83 @@ +TI DS90Ux9xx de-/serializer pinmux and GPIO subcontroller + +Required properties: +- compatible: Must contain a generic "ti,ds90ux9xx-pinctrl" value and + may contain one more specific value from the list: + "ti,ds90ux925-pinctrl", + "ti,ds90ux926-pinctrl", + "ti,ds90ux927-pinctrl", + "ti,ds90ux928-pinctrl", + "ti,ds90ux940-pinctrl". + +- gpio-controller: Marks the device node as a GPIO controller. + +- #gpio-cells: Must be set to 2, + - the first cell is the GPIO offset number within the controller, + - the second cell is used to specify the GPIO line polarity. + +- gpio-ranges: Mapping to pin controller pins (as described in + Documentation/devicetree/bindings/gpio/gpio.txt) + +Optional properties: +- ti,video-depth-18bit: Sets video bridge pins to RGB 18-bit mode. + +Available pins, groups and functions (reference to device datasheets): + +function: "gpio" ("gpio4" is on DS90Ux925 and DS90Ux926 only, + "gpio9" is on DS90Ux940 only) + - pins: "gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", + "gpio7", "gpio8", "gpio9" + +function: "gpio-remote" + - pins: "gpio0", "gpio1", "gpio2", "gpio3" + +function: "pass" (DS90Ux940 specific only) + - pins: "gpio0", "gpio3" + +function: "i2s-1" + - group: "i2s-1" + +function: "i2s-2" + - group: "i2s-2" + +function: "i2s-3" (DS90Ux927, DS90Ux928 and DS90Ux940 specific only) + - group: "i2s-3" + +function: "i2s-4" (DS90Ux927, DS90Ux928 and DS90Ux940 specific only) + - group: "i2s-4" + +function: "i2s-m" (DS90Ux928 and DS90Ux940 specific only) + - group: "i2s-m" + +function: "parallel" (DS90Ux925 and DS90Ux926 specific only) + - group: "parallel" + +Example (deserializer with pins GPIO[3:0] set to bridged output + function and pin GPIO4 in standard hogged GPIO function): + +deserializer { + compatible = "ti,ds90ub928q", "ti,ds90ux9xx"; + + ds90ux928_pctrl: pin-controller { + compatible = "ti,ds90ux928-pinctrl", "ti,ds90ux9xx-pinctrl"; + gpio-controller; + #gpio-cells = <2>; + gpio-ranges = <&ds90ux928_pctrl 0 0 8>; + + pinctrl-names = "default"; + pinctrl-0 = <&ds90ux928_pins>; + + ds90ux928_pins: pinmux { + gpio-remote { + pins = "gpio0", "gpio1", "gpio2", "gpio3"; + function = "gpio-remote"; + }; + }; + + rst { + gpio-hog; + gpios = <4 GPIO_ACTIVE_HIGH>; + output-high; + }; + }; +}; From patchwork Mon Oct 8 21:12:02 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Zapolskiy X-Patchwork-Id: 980814 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=mleia.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42TY3V6yXKz9s9J for ; Tue, 9 Oct 2018 08:12:38 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726656AbeJIEZ4 (ORCPT ); Tue, 9 Oct 2018 00:25:56 -0400 Received: from mleia.com ([178.79.152.223]:35500 "EHLO mail.mleia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725835AbeJIEZz (ORCPT ); Tue, 9 Oct 2018 00:25:55 -0400 Received: from mail.mleia.com (localhost [127.0.0.1]) by mail.mleia.com (Postfix) with ESMTP id 2BA3E41E2C1; Mon, 8 Oct 2018 22:12:12 +0100 (BST) From: Vladimir Zapolskiy To: Lee Jones , Linus Walleij , Rob Herring Cc: Marek Vasut , Laurent Pinchart , Wolfram Sang , devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, Vladimir Zapolskiy Subject: [PATCH 4/7] mfd: ds90ux9xx: add TI DS90Ux9xx de-/serializer MFD driver Date: Tue, 9 Oct 2018 00:12:02 +0300 Message-Id: <20181008211205.2900-5-vz@mleia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181008211205.2900-1-vz@mleia.com> References: <20181008211205.2900-1-vz@mleia.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-49551924 X-CRM114-CacheID: sfid-20181008_221212_203935_FD671C5E X-CRM114-Status: GOOD ( 33.71 ) Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org From: Vladimir Zapolskiy The change adds I2C device driver for TI DS90Ux9xx de-/serializers, support of subdevice controllers is done in separate drivers, because not all IC functionality may be needed in particular situations, and this can be fine grained controlled in device tree. The development of the driver was a collaborative work, the contribution done by Balasubramani Vivekanandan includes: * original implementation of the driver based on a reference driver, * regmap powered interrupt controller support on serializers, * support of implicitly or improperly specified in device tree ICs, * support of device properties and attributes: backward compatible mode, low frequency operation mode, spread spectrum clock generator. Contribution by Steve Longerbeam: * added ds90ux9xx_read_indirect() function, * moved number of links property and added ds90ux9xx_num_fpd_links(), * moved and updated ds90ux9xx_get_link_status() function to core driver, * added fpd_link_show device attribute. Sandeep Jain added support of pixel clock edge configuration. Signed-off-by: Vladimir Zapolskiy --- drivers/mfd/Kconfig | 14 + drivers/mfd/Makefile | 1 + drivers/mfd/ds90ux9xx-core.c | 879 ++++++++++++++++++++++++++++++++++ include/linux/mfd/ds90ux9xx.h | 42 ++ 4 files changed, 936 insertions(+) create mode 100644 drivers/mfd/ds90ux9xx-core.c create mode 100644 include/linux/mfd/ds90ux9xx.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 8c5dfdce4326..a969fa123f64 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1280,6 +1280,20 @@ config MFD_DM355EVM_MSP boards. MSP430 firmware manages resets and power sequencing, inputs from buttons and the IR remote, LEDs, an RTC, and more. +config MFD_DS90UX9XX + tristate "TI DS90Ux9xx FPD-Link de-/serializer driver" + depends on I2C && OF + select MFD_CORE + select REGMAP_I2C + help + Say yes here to enable support for TI DS90UX9XX de-/serializer ICs. + + This driver provides basic support for setting up the de-/serializer + chips. Additional functionalities like connection handling to + remote de-/serializers, I2C bridging, pin multiplexing, GPIO + controller and so on are provided by separate drivers and should + enabled individually. + config MFD_LP3943 tristate "TI/National Semiconductor LP3943 MFD Driver" depends on I2C diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 12980a4ad460..cc92bf5394b7 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -224,6 +224,7 @@ obj-$(CONFIG_MFD_HI655X_PMIC) += hi655x-pmic.o obj-$(CONFIG_MFD_DLN2) += dln2.o obj-$(CONFIG_MFD_RT5033) += rt5033.o obj-$(CONFIG_MFD_SKY81452) += sky81452.o +obj-$(CONFIG_MFD_DS90UX9XX) += ds90ux9xx-core.o intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o diff --git a/drivers/mfd/ds90ux9xx-core.c b/drivers/mfd/ds90ux9xx-core.c new file mode 100644 index 000000000000..ad96c109a451 --- /dev/null +++ b/drivers/mfd/ds90ux9xx-core.c @@ -0,0 +1,879 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * TI DS90Ux9xx MFD driver + * + * Copyright (c) 2017-2018 Mentor Graphics Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Serializer Registers */ +#define SER_REG_MODE_SELECT 0x04 +#define SER_REG_GEN_STATUS 0x0C +#define SER_REG_CTRL_STATUS 0x13 + +/* Deserializer Registers */ +#define DES_REG_GEN_CONFIG0 0x02 +#define DES_REG_GEN_STATUS 0x1C +#define DES_REG_MODE_STATUS 0x23 +#define DES_REG_SSCG_CTRL 0x2C +#define DES_REG_DUAL_RX_CTRL 0x34 +#define DES_REG_MAPSEL 0x49 +#define DES_REG_INDIRECT_ADDR 0x6C +#define DES_REG_INDIRECT_DATA 0x6D + +#define DES_FPD_MODE_MASK (BIT(4) | BIT(3)) +#define DES_FPD_HW_MODE_AUTO 0x0 +#define DES_FPD_HW_MODE_PRIMARY BIT(4) +#define DES_FPD_HW_MODE_SECONDARY (BIT(4) | BIT(3)) + +#define SSCG_REG_MASK 0x0F +#define SSCG_MAX_VALUE 0x7 +#define SSCG_ENABLE BIT(3) + +/* Common Registers and bitfields */ +#define SER_DES_REG_CONFIG 0x03 + +#define SER_DE_GATE_RGB BIT(4) +#define DES_DE_GATE_RGB BIT(1) + +#define PIXEL_CLK_EDGE_RISING BIT(0) + +#define MAPSEL_MASK (BIT(6) | BIT(5)) +#define MAPSEL_SET BIT(6) +#define MAPSEL_MSB BIT(5) + +#define BKWD_MODE_OVERRIDE BIT(3) +#define BKWD_MODE_ENABLED BIT(2) +#define LF_MODE_OVERRIDE BIT(1) +#define LF_MODE_ENABLED BIT(0) + +#define LF_MODE_PIN_STATUS BIT(3) +#define BKWD_MODE_PIN_STATUS BIT(1) + +#define GEN_STATUS_LOCK BIT(0) + +#define SER_DES_DEVICE_ID_REG 0xF0 +#define DEVICE_ID_LEN 6 + +enum ds90ux9xx_capability { + DS90UX9XX_CAP_SERIALIZER, + DS90UX9XX_CAP_HDCP, + DS90UX9XX_CAP_MODES, + DS90UX9XX_CAP_SSCG, + DS90UX9XX_CAP_PIXEL_CLK_EDGE, + DS90UX9XX_CAP_MAPSEL, + DS90UX9XX_CAP_DE_GATE, +}; + +struct ds90ux9xx_device_property { + const char id[DEVICE_ID_LEN]; + unsigned int num_links; + const u32 caps; +}; + +struct ds90ux9xx { + struct device *dev; + struct regmap *regmap; + struct mutex indirect; /* serializes access to indirect registers */ + struct gpio_desc *power_gpio; + enum ds90ux9xx_device_id dev_id; + const struct ds90ux9xx_device_property *property; +}; + +#define TO_CAP(_cap, _enabled) (_enabled ? BIT(DS90UX9XX_CAP_ ## _cap) : 0x0) + +#define DS90UX9XX_DEVICE(_id, _links, _ser, _hdcp, _modes, _sscg, \ + _pixel, _mapsel, _de_gate) \ + [TI_DS90 ## _id] = { \ + .id = "_" __stringify(_id), \ + .num_links = _links, \ + .caps = \ + TO_CAP(SERIALIZER, _ser) | \ + TO_CAP(HDCP, _hdcp) | \ + TO_CAP(MODES, _modes) | \ + TO_CAP(SSCG, _sscg) | \ + TO_CAP(PIXEL_CLK_EDGE, _pixel) | \ + TO_CAP(MAPSEL, _mapsel) | \ + TO_CAP(DE_GATE, _de_gate) \ + } + +static const struct ds90ux9xx_device_property ds90ux9xx_devices[] = { + /* + * List of TI DS90Ux9xx properties: + * # FPD-links, serializer, HDCP, BW/LF modes, SSCG, + * pixel clock edge, mapsel, RGB DE Gate + */ + DS90UX9XX_DEVICE(UB925, 1, 1, 0, 1, 0, 1, 0, 1), + DS90UX9XX_DEVICE(UH925, 1, 1, 1, 1, 0, 1, 0, 1), + DS90UX9XX_DEVICE(UB927, 1, 1, 0, 1, 0, 0, 1, 1), + DS90UX9XX_DEVICE(UH927, 1, 1, 1, 1, 0, 0, 1, 1), + DS90UX9XX_DEVICE(UB926, 1, 0, 0, 1, 1, 1, 0, 0), + DS90UX9XX_DEVICE(UH926, 1, 0, 1, 1, 1, 1, 0, 0), + DS90UX9XX_DEVICE(UB928, 1, 0, 0, 1, 0, 0, 1, 0), + DS90UX9XX_DEVICE(UH928, 1, 0, 1, 1, 0, 0, 1, 0), + DS90UX9XX_DEVICE(UB940, 2, 0, 0, 0, 0, 0, 0, 1), + DS90UX9XX_DEVICE(UH940, 2, 0, 1, 0, 0, 0, 0, 1), +}; + +static const struct regmap_config ds90ux9xx_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xFF, + .cache_type = REGCACHE_NONE, +}; + +static bool ds90ux9xx_has_capability(struct ds90ux9xx *ds90ux9xx, + enum ds90ux9xx_capability cap) +{ + return ds90ux9xx->property->caps & BIT(cap); +} + +bool ds90ux9xx_is_serializer(struct device *dev) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + + return ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_SERIALIZER); +} +EXPORT_SYMBOL_GPL(ds90ux9xx_is_serializer); + +unsigned int ds90ux9xx_num_fpd_links(struct device *dev) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + + return ds90ux9xx->property->num_links; +} +EXPORT_SYMBOL_GPL(ds90ux9xx_num_fpd_links); + +int ds90ux9xx_update_bits_indirect(struct device *dev, u8 reg, u8 mask, u8 val) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + int ret; + + mutex_lock(&ds90ux9xx->indirect); + + ret = regmap_write(ds90ux9xx->regmap, DES_REG_INDIRECT_ADDR, reg); + if (ret) { + mutex_unlock(&ds90ux9xx->indirect); + return ret; + } + + ret = regmap_update_bits(ds90ux9xx->regmap, DES_REG_INDIRECT_DATA, + mask, val); + + mutex_unlock(&ds90ux9xx->indirect); + + return ret; +} +EXPORT_SYMBOL_GPL(ds90ux9xx_update_bits_indirect); + +int ds90ux9xx_write_indirect(struct device *dev, u8 reg, u8 val) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + int ret; + + mutex_lock(&ds90ux9xx->indirect); + + ret = regmap_write(ds90ux9xx->regmap, DES_REG_INDIRECT_ADDR, reg); + if (ret) { + mutex_unlock(&ds90ux9xx->indirect); + return ret; + } + + ret = regmap_write(ds90ux9xx->regmap, DES_REG_INDIRECT_DATA, val); + + mutex_unlock(&ds90ux9xx->indirect); + + return ret; +} +EXPORT_SYMBOL_GPL(ds90ux9xx_write_indirect); + +int ds90ux9xx_read_indirect(struct device *dev, u8 reg, u8 *val) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + unsigned int data; + int ret; + + mutex_lock(&ds90ux9xx->indirect); + + ret = regmap_write(ds90ux9xx->regmap, DES_REG_INDIRECT_ADDR, reg); + if (ret) { + mutex_unlock(&ds90ux9xx->indirect); + return ret; + } + + ret = regmap_read(ds90ux9xx->regmap, DES_REG_INDIRECT_DATA, &data); + + mutex_unlock(&ds90ux9xx->indirect); + + if (!ret) + *val = data; + + return ret; +} +EXPORT_SYMBOL_GPL(ds90ux9xx_read_indirect); + +static int ds90ux9xx_read_active_link(struct ds90ux9xx *ds90ux9xx, + unsigned int *link) +{ + unsigned int val; + int ret; + + if (ds90ux9xx->property->num_links == 1) { + *link = 0; + return 0; + } + + ret = regmap_read(ds90ux9xx->regmap, DES_REG_DUAL_RX_CTRL, &val); + if (ret) { + dev_err(ds90ux9xx->dev, "Failed to read FPD mode\n"); + return ret; + } + + switch (val & DES_FPD_MODE_MASK) { + case DES_FPD_HW_MODE_AUTO: + case DES_FPD_HW_MODE_PRIMARY: + *link = 0; + break; + case DES_FPD_HW_MODE_SECONDARY: + *link = 1; + break; + default: + dev_err(ds90ux9xx->dev, "Unhandled FPD mode\n"); + return -EINVAL; + } + + return 0; +} + +/* Note that lock status is reported if video pattern generator is enabled */ +int ds90ux9xx_get_link_status(struct device *dev, unsigned int *link, + bool *locked) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + unsigned int reg, status; + int ret; + + ret = ds90ux9xx_read_active_link(ds90ux9xx, link); + if (ret) + return ret; + + if (ds90ux9xx_is_serializer(dev)) + reg = SER_REG_GEN_STATUS; + else + reg = DES_REG_GEN_STATUS; + + ret = regmap_read(ds90ux9xx->regmap, reg, &status); + if (ret) { + dev_err(dev, "Unable to get lock status\n"); + return ret; + } + + *locked = status & GEN_STATUS_LOCK ? true : false; + + return 0; +} +EXPORT_SYMBOL_GPL(ds90ux9xx_get_link_status); + +enum ds90ux9xx_device_id ds90ux9xx_get_ic_type(struct device *dev) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + + return ds90ux9xx->dev_id; +} +EXPORT_SYMBOL_GPL(ds90ux9xx_get_ic_type); + +static int ds90ux9xx_set_de_gate(struct ds90ux9xx *ds90ux9xx, bool enable) +{ + unsigned int reg, val; + + if (!ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_DE_GATE)) + return 0; + + if (ds90ux9xx_is_serializer(ds90ux9xx->dev)) { + reg = SER_REG_MODE_SELECT; + val = SER_DE_GATE_RGB; + } else { + reg = SER_DES_REG_CONFIG; + val = DES_DE_GATE_RGB; + } + + return regmap_update_bits(ds90ux9xx->regmap, reg, val, + enable ? val : 0x0); +} + +static int ds90ux9xx_set_bcmode(struct ds90ux9xx *ds90ux9xx, bool enable) +{ + unsigned int reg, val; + int ret; + + if (ds90ux9xx_is_serializer(ds90ux9xx->dev)) + reg = SER_REG_MODE_SELECT; + else + reg = DES_REG_GEN_CONFIG0; + + val = BKWD_MODE_OVERRIDE; + if (enable) + val |= BKWD_MODE_ENABLED; + + ret = regmap_update_bits(ds90ux9xx->regmap, reg, + BKWD_MODE_OVERRIDE | BKWD_MODE_ENABLED, val); + if (ret) + return ret; + + return ds90ux9xx_set_de_gate(ds90ux9xx, !enable); +} + +static int ds90ux9xx_set_lfmode(struct ds90ux9xx *ds90ux9xx, bool enable) +{ + unsigned int reg, val; + + if (ds90ux9xx_is_serializer(ds90ux9xx->dev)) + reg = SER_REG_MODE_SELECT; + else + reg = DES_REG_GEN_CONFIG0; + + val = LF_MODE_OVERRIDE; + if (enable) + val |= LF_MODE_ENABLED; + + return regmap_update_bits(ds90ux9xx->regmap, reg, + LF_MODE_OVERRIDE | LF_MODE_ENABLED, val); +} + +static int ds90ux9xx_set_sscg(struct ds90ux9xx *ds90ux9xx, unsigned int val) +{ + if ((val & ~SSCG_ENABLE) > SSCG_MAX_VALUE) { + dev_err(ds90ux9xx->dev, "Invalid SSCG value: %#x", val); + return -EINVAL; + } + + return regmap_write(ds90ux9xx->regmap, DES_REG_SSCG_CTRL, val); +} + +static int ds90ux9xx_set_pixel_clk_edge(struct ds90ux9xx *ds90ux9xx, + bool rising) +{ + return regmap_update_bits(ds90ux9xx->regmap, SER_DES_REG_CONFIG, + PIXEL_CLK_EDGE_RISING, + rising ? PIXEL_CLK_EDGE_RISING : 0x0); +} + +static ssize_t backward_compatible_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + unsigned int cfg_reg, sts_reg, val; + int ret, bcmode; + + if (ds90ux9xx_is_serializer(ds90ux9xx->dev)) { + cfg_reg = SER_REG_MODE_SELECT; + sts_reg = SER_REG_CTRL_STATUS; + } else { + cfg_reg = DES_REG_GEN_CONFIG0; + sts_reg = DES_REG_MODE_STATUS; + } + + ret = regmap_read(ds90ux9xx->regmap, cfg_reg, &val); + if (ret) + return ret; + + if (val & BKWD_MODE_OVERRIDE) { + bcmode = val & BKWD_MODE_ENABLED; + } else { + /* Read mode from pin status */ + ret = regmap_read(ds90ux9xx->regmap, sts_reg, &val); + if (ret) + return ret; + + bcmode = val & BKWD_MODE_PIN_STATUS; + } + + return sprintf(buf, "%d\n", bcmode ? 1 : 0); +} + +static ssize_t backward_compatible_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + + ret = ds90ux9xx_set_bcmode(ds90ux9xx, val); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_RW(backward_compatible_mode); + +static ssize_t low_frequency_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + unsigned int cfg_reg, sts_reg, val; + int ret, lfmode; + + if (ds90ux9xx_is_serializer(ds90ux9xx->dev)) { + cfg_reg = SER_REG_MODE_SELECT; + sts_reg = SER_REG_CTRL_STATUS; + } else { + cfg_reg = DES_REG_GEN_CONFIG0; + sts_reg = DES_REG_MODE_STATUS; + } + + ret = regmap_read(ds90ux9xx->regmap, cfg_reg, &val); + if (ret) + return ret; + + if (val & LF_MODE_OVERRIDE) { + lfmode = val & LF_MODE_ENABLED; + } else { + /* Read mode from pin status */ + ret = regmap_read(ds90ux9xx->regmap, sts_reg, &val); + if (ret) + return ret; + + lfmode = val & LF_MODE_PIN_STATUS; + } + + return sprintf(buf, "%d\n", lfmode ? 1 : 0); +} + +static ssize_t low_frequency_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + + ret = ds90ux9xx_set_lfmode(ds90ux9xx, val); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_RW(low_frequency_mode); + +static ssize_t sscg_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = regmap_read(ds90ux9xx->regmap, DES_REG_SSCG_CTRL, &val); + if (ret) + return ret; + + return sprintf(buf, "%d\n", val & SSCG_REG_MASK); +} + +static ssize_t sscg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + int ret, val; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + ret = ds90ux9xx_set_sscg(ds90ux9xx, val); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_RW(sscg); + +static ssize_t pixel_clock_edge_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = regmap_read(ds90ux9xx->regmap, SER_DES_REG_CONFIG, &val); + if (ret) + return ret; + + return sprintf(buf, "%s\n", + (val & PIXEL_CLK_EDGE_RISING) ? "rising" : "falling"); +} + +static ssize_t pixel_clock_edge_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + bool rising; + int ret; + + if (sysfs_streq(buf, "rising") || sysfs_streq(buf, "1")) + rising = true; + else if (sysfs_streq(buf, "falling") || sysfs_streq(buf, "0")) + rising = false; + else + return -EINVAL; + + ret = ds90ux9xx_set_pixel_clk_edge(ds90ux9xx, rising); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_RW(pixel_clock_edge); + +static ssize_t link_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int link; + bool locked; + int ret; + + ret = ds90ux9xx_get_link_status(dev, &link, &locked); + if (ret) + return ret; + + return sprintf(buf, "%u: %s\n", link, locked ? "locked" : "unlocked"); +} +static DEVICE_ATTR_RO(link); + +static struct attribute *ds90ux9xx_attributes[] = { + &dev_attr_backward_compatible_mode.attr, + &dev_attr_low_frequency_mode.attr, + &dev_attr_sscg.attr, + &dev_attr_pixel_clock_edge.attr, + &dev_attr_link.attr, + NULL, +}; + +static umode_t ds90ux9xx_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct ds90ux9xx *ds90ux9xx = dev_get_drvdata(dev); + enum ds90ux9xx_capability cap; + + /* Attribute indices match the ds90ux9xx_attributes[] array indices */ + switch (index) { + case 0: /* Backward compatible mode */ + case 1: /* Low frequency mode */ + cap = DS90UX9XX_CAP_MODES; + break; + case 2: /* Spread spectrum clock generator */ + cap = DS90UX9XX_CAP_SSCG; + break; + case 3: /* Pixel clock edge */ + cap = DS90UX9XX_CAP_PIXEL_CLK_EDGE; + break; + case 4: /* FPD-III link lock status - visible for all chips */ + return attr->mode; + default: + return 0; + } + + return ds90ux9xx_has_capability(ds90ux9xx, cap) ? attr->mode : 0; +} + +static const struct attribute_group ds90ux9xx_attr_group = { + .attrs = ds90ux9xx_attributes, + .is_visible = ds90ux9xx_attr_is_visible, +}; +__ATTRIBUTE_GROUPS(ds90ux9xx_attr); + +static int ds90ux9xx_config_modes(struct ds90ux9xx *ds90ux9xx) +{ + struct device_node *np = ds90ux9xx->dev->of_node; + u32 mode; + int ret; + + if (!ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_MODES)) + return 0; + + ret = of_property_read_u32(np, "ti,backward-compatible-mode", &mode); + if (!ret) { + ret = ds90ux9xx_set_bcmode(ds90ux9xx, mode); + if (ret) + return ret; + } else { + ret = ds90ux9xx_set_de_gate(ds90ux9xx, true); + if (ret) + return ret; + } + + ret = of_property_read_u32(np, "ti,low-frequency-mode", &mode); + if (!ret) { + ret = ds90ux9xx_set_lfmode(ds90ux9xx, mode); + if (ret) + return ret; + } + + return 0; +} + +static int ds90ux9xx_config_sscg(struct ds90ux9xx *ds90ux9xx) +{ + struct device_node *np = ds90ux9xx->dev->of_node; + u32 sscg; + int ret; + + if (!ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_SSCG)) + return 0; + + ret = of_property_read_u32(np, "ti,spread-spectrum-clock-generation", + &sscg); + if (ret) + return 0; + + return ds90ux9xx_set_sscg(ds90ux9xx, sscg); +} + +static int ds90ux9xx_config_pixel_clk_edge(struct ds90ux9xx *ds90ux9xx) +{ + struct device_node *np = ds90ux9xx->dev->of_node; + const char *pixel; + bool rising; + + if (!ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_PIXEL_CLK_EDGE)) + return 0; + + if (of_property_read_string(np, "ti,pixel-clock-edge", &pixel)) + return 0; + + if (!strcmp(pixel, "rising")) + rising = true; + else if (!strcmp(pixel, "falling")) + rising = false; + else + return -EINVAL; + + return ds90ux9xx_set_pixel_clk_edge(ds90ux9xx, rising); +} + +static int ds90ux9xx_config_map_select(struct ds90ux9xx *ds90ux9xx) +{ + struct device_node *np = ds90ux9xx->dev->of_node; + unsigned int reg, val; + + if (!ds90ux9xx_has_capability(ds90ux9xx, DS90UX9XX_CAP_MAPSEL)) + return 0; + + if (ds90ux9xx_is_serializer(ds90ux9xx->dev)) + reg = SER_REG_CTRL_STATUS; + else + reg = DES_REG_MAPSEL; + + if (of_property_read_bool(np, "ti,video-map-select-msb")) + val = MAPSEL_SET | MAPSEL_MSB; + else if (of_property_read_bool(np, "ti,video-map-select-lsb")) + val = MAPSEL_SET; + else + val = 0; + + return regmap_update_bits(ds90ux9xx->regmap, reg, MAPSEL_MASK, val); +} + +static int ds90ux9xx_config_properties(struct ds90ux9xx *ds90ux9xx) +{ + int ret; + + ret = ds90ux9xx_config_modes(ds90ux9xx); + if (ret) + return ret; + + ret = ds90ux9xx_config_sscg(ds90ux9xx); + if (ret) + return ret; + + ret = ds90ux9xx_config_pixel_clk_edge(ds90ux9xx); + if (ret) + return ret; + + return ds90ux9xx_config_map_select(ds90ux9xx); +} + +static const struct i2c_device_id ds90ux9xx_ids[] = { + /* Supported serializers */ + { "ds90ub925q", TI_DS90UB925 }, + { "ds90uh925q", TI_DS90UH925 }, + { "ds90ub927q", TI_DS90UB927 }, + { "ds90uh927q", TI_DS90UH927 }, + + /* Supported deserializers */ + { "ds90ub926q", TI_DS90UB926 }, + { "ds90uh926q", TI_DS90UH926 }, + { "ds90ub928q", TI_DS90UB928 }, + { "ds90uh928q", TI_DS90UH928 }, + { "ds90ub940q", TI_DS90UB940 }, + { "ds90uh940q", TI_DS90UH940 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, ds90ux9xx_ids); + +static const struct of_device_id ds90ux9xx_dt_ids[] = { + { .compatible = "ti,ds90ux9xx", }, + { .compatible = "ti,ds90ub925q", }, + { .compatible = "ti,ds90uh925q", }, + { .compatible = "ti,ds90ub927q", }, + { .compatible = "ti,ds90uh927q", }, + { .compatible = "ti,ds90ub926q", }, + { .compatible = "ti,ds90uh926q", }, + { .compatible = "ti,ds90ub928q", }, + { .compatible = "ti,ds90uh928q", }, + { .compatible = "ti,ds90ub940q", }, + { .compatible = "ti,ds90uh940q", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ds90ux9xx_dt_ids); + +static int ds90ux9xx_read_ic_type(struct i2c_client *client, + struct ds90ux9xx *ds90ux9xx) +{ + u8 device_id[DEVICE_ID_LEN + 1] = { 0 }; + const struct i2c_device_id *id; + const char *id_code; + unsigned int i; + int ret; + + ret = regmap_raw_read(ds90ux9xx->regmap, SER_DES_DEVICE_ID_REG, + device_id, DEVICE_ID_LEN); + if (ret < 0) { + dev_err(ds90ux9xx->dev, "Failed to read device id: %d\n", ret); + return ret; + } + + id = i2c_match_id(ds90ux9xx_ids, client); + if (id) { + id_code = ds90ux9xx_devices[id->driver_data].id; + if (!strncmp(device_id, id_code, DEVICE_ID_LEN)) { + ds90ux9xx->dev_id = id->driver_data; + return 0; + } + } + + /* DS90UH925 device quirk */ + if (!memcmp(device_id, "\0\0\0\0\0\0", DEVICE_ID_LEN)) { + if (id && id->driver_data != TI_DS90UH925) + dev_err(ds90ux9xx->dev, + "Device ID returned all zeroes, assuming device is DS90UH925\n"); + ds90ux9xx->dev_id = TI_DS90UH925; + return 0; + } + + for (i = 0; i < ARRAY_SIZE(ds90ux9xx_devices); i++) { + if (strncmp(device_id, ds90ux9xx_devices[i].id, DEVICE_ID_LEN)) + continue; + + if (id) + dev_err(ds90ux9xx->dev, + "Mismatch between probed device ID [%s] and HW device ID [%s]\n", + id_code, device_id); + + ds90ux9xx->dev_id = i; + return 0; + } + + dev_err(ds90ux9xx->dev, "Device ID [%s] is not supported\n", device_id); + + return -ENODEV; +} + +static int ds90ux9xx_probe(struct i2c_client *client) +{ + struct ds90ux9xx *ds90ux9xx; + int ret; + + ds90ux9xx = devm_kzalloc(&client->dev, sizeof(*ds90ux9xx), GFP_KERNEL); + if (!ds90ux9xx) + return -ENOMEM; + + /* Get optional GPIO connected to PDB pin */ + ds90ux9xx->power_gpio = devm_gpiod_get_optional(&client->dev, "power", + GPIOD_OUT_HIGH); + if (IS_ERR(ds90ux9xx->power_gpio)) + return PTR_ERR(ds90ux9xx->power_gpio); + + /* Give time to the controller to initialize itself after power-up */ + if (ds90ux9xx->power_gpio) + usleep_range(2000, 2500); + + ds90ux9xx->dev = &client->dev; + ds90ux9xx->regmap = devm_regmap_init_i2c(client, + &ds90ux9xx_regmap_config); + if (IS_ERR(ds90ux9xx->regmap)) + return PTR_ERR(ds90ux9xx->regmap); + + mutex_init(&ds90ux9xx->indirect); + + ret = ds90ux9xx_read_ic_type(client, ds90ux9xx); + if (ret) + return ret; + + ds90ux9xx->property = &ds90ux9xx_devices[ds90ux9xx->dev_id]; + + i2c_set_clientdata(client, ds90ux9xx); + + ret = ds90ux9xx_config_properties(ds90ux9xx); + if (ret) + return ret; + + ret = sysfs_create_groups(&ds90ux9xx->dev->kobj, ds90ux9xx_attr_groups); + if (ret) + return ret; + + ret = devm_of_platform_populate(ds90ux9xx->dev); + if (ret) + sysfs_remove_groups(&ds90ux9xx->dev->kobj, + ds90ux9xx_attr_groups); + + return ret; +} + +static int ds90ux9xx_remove(struct i2c_client *client) +{ + struct ds90ux9xx *ds90ux9xx = i2c_get_clientdata(client); + + sysfs_remove_groups(&ds90ux9xx->dev->kobj, ds90ux9xx_attr_groups); + + if (ds90ux9xx->power_gpio) + gpiod_set_value_cansleep(ds90ux9xx->power_gpio, 0); + + return 0; +} + +static struct i2c_driver ds90ux9xx_driver = { + .driver = { + .name = "ds90ux9xx", + .of_match_table = ds90ux9xx_dt_ids, + }, + .probe_new = ds90ux9xx_probe, + .remove = ds90ux9xx_remove, + .id_table = ds90ux9xx_ids, +}; +module_i2c_driver(ds90ux9xx_driver); + +MODULE_AUTHOR("Balasubramani Vivekanandan "); +MODULE_AUTHOR("Vladimir Zapolskiy "); +MODULE_DESCRIPTION("TI DS90Ux9xx MFD driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/ds90ux9xx.h b/include/linux/mfd/ds90ux9xx.h new file mode 100644 index 000000000000..72a5928b6ad1 --- /dev/null +++ b/include/linux/mfd/ds90ux9xx.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * TI DS90Ux9xx MFD driver + * + * Copyright (c) 2017-2018 Mentor Graphics Inc. + */ + +#ifndef __LINUX_MFD_DS90UX9XX_H +#define __LINUX_MFD_DS90UX9XX_H + +#include + +enum ds90ux9xx_device_id { + /* Supported serializers */ + TI_DS90UB925, + TI_DS90UH925, + TI_DS90UB927, + TI_DS90UH927, + + /* Supported deserializers */ + TI_DS90UB926, + TI_DS90UH926, + TI_DS90UB928, + TI_DS90UH928, + TI_DS90UB940, + TI_DS90UH940, +}; + +struct device; + +bool ds90ux9xx_is_serializer(struct device *dev); +enum ds90ux9xx_device_id ds90ux9xx_get_ic_type(struct device *dev); +unsigned int ds90ux9xx_num_fpd_links(struct device *dev); + +int ds90ux9xx_get_link_status(struct device *dev, unsigned int *link, + bool *locked); + +int ds90ux9xx_update_bits_indirect(struct device *dev, u8 reg, u8 mask, u8 val); +int ds90ux9xx_write_indirect(struct device *dev, unsigned char reg, u8 val); +int ds90ux9xx_read_indirect(struct device *dev, u8 reg, u8 *val); + +#endif /*__LINUX_MFD_DS90UX9XX_H */ From patchwork Mon Oct 8 21:12:03 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Zapolskiy X-Patchwork-Id: 980812 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=mleia.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42TY3F6ly1zB3sq for ; Tue, 9 Oct 2018 08:12:25 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1725835AbeJIEZ5 (ORCPT ); Tue, 9 Oct 2018 00:25:57 -0400 Received: from mleia.com ([178.79.152.223]:35518 "EHLO mail.mleia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726896AbeJIEZ5 (ORCPT ); Tue, 9 Oct 2018 00:25:57 -0400 Received: from mail.mleia.com (localhost [127.0.0.1]) by mail.mleia.com (Postfix) with ESMTP id EA34741E2C4; Mon, 8 Oct 2018 22:12:12 +0100 (BST) From: Vladimir Zapolskiy To: Lee Jones , Linus Walleij , Rob Herring Cc: Marek Vasut , Laurent Pinchart , Wolfram Sang , devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, Vladimir Zapolskiy Subject: [PATCH 5/7] mfd: ds90ux9xx: add I2C bridge/alias and link connection driver Date: Tue, 9 Oct 2018 00:12:03 +0300 Message-Id: <20181008211205.2900-6-vz@mleia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181008211205.2900-1-vz@mleia.com> References: <20181008211205.2900-1-vz@mleia.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-49551924 X-CRM114-CacheID: sfid-20181008_221212_987446_3B960AD5 X-CRM114-Status: GOOD ( 37.21 ) Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org From: Vladimir Zapolskiy The change adds TI DS90Ux9xx I2C bridge/alias subdevice driver and FPD Link connection handling mechanism. Access to I2C devices connected to a remote de-/serializer is done in a transparent way, on established link detection event such devices are registered on an I2C bus, which serves a local de-/serializer IC. The development of the driver was a collaborative work, the contribution done by Balasubramani Vivekanandan includes: * original simplistic implementation of the driver, * support of implicitly specified devices in device tree, * support of multiple FPD links for TI DS90Ux9xx, * other kind of valuable review comments, clean-ups and fixes. Also Steve Longerbeam made the following changes: * clear address maps after linked device removal, * disable pass-through in disconnection, * qualify locked status with non-zero remote address. Signed-off-by: Vladimir Zapolskiy --- drivers/mfd/Kconfig | 8 + drivers/mfd/Makefile | 1 + drivers/mfd/ds90ux9xx-i2c-bridge.c | 764 +++++++++++++++++++++++++++++ 3 files changed, 773 insertions(+) create mode 100644 drivers/mfd/ds90ux9xx-i2c-bridge.c diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a969fa123f64..d97f652046d9 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1294,6 +1294,14 @@ config MFD_DS90UX9XX controller and so on are provided by separate drivers and should enabled individually. +config MFD_DS90UX9XX_I2C + tristate "TI DS90Ux9xx I2C bridge/alias driver" + default MFD_DS90UX9XX + depends on MFD_DS90UX9XX + help + Select this option to enable I2C bridge/alias and link connection + handling driver for the TI DS90Ux9xx FPD Link de-/serializer ICs. + config MFD_LP3943 tristate "TI/National Semiconductor LP3943 MFD Driver" depends on I2C diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index cc92bf5394b7..5414d0cc0898 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -225,6 +225,7 @@ obj-$(CONFIG_MFD_DLN2) += dln2.o obj-$(CONFIG_MFD_RT5033) += rt5033.o obj-$(CONFIG_MFD_SKY81452) += sky81452.o obj-$(CONFIG_MFD_DS90UX9XX) += ds90ux9xx-core.o +obj-$(CONFIG_MFD_DS90UX9XX_I2C) += ds90ux9xx-i2c-bridge.o intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o diff --git a/drivers/mfd/ds90ux9xx-i2c-bridge.c b/drivers/mfd/ds90ux9xx-i2c-bridge.c new file mode 100644 index 000000000000..f35af0f238c8 --- /dev/null +++ b/drivers/mfd/ds90ux9xx-i2c-bridge.c @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * TI DS90Ux9xx I2C bridge/alias controller driver + * + * Copyright (c) 2017-2018 Mentor Graphics Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Serializer Registers */ +#define SER_REG_REMOTE_ID 0x06 +#define SER_REG_I2C_CTRL 0x17 + +/* Deserializer Registers */ +#define DES_REG_I2C_CTRL 0x05 +#define DES_REG_REMOTE_ID 0x07 + +/* Common Register address */ +#define SER_DES_REG_DEVICE_ID 0x00 +#define DEVICE_ID_OVERRIDE BIT(0) + +#define SER_DES_REG_CONFIG 0x03 +#define SER_CONFIG_I2C_AUTO_ACK BIT(5) +#define CONFIG_I2C_PASS_THROUGH BIT(3) +#define DES_CONFIG_I2C_AUTO_ACK BIT(2) + +#define I2C_CTRL_PASS_ALL BIT(7) + +#define DS90UX9XX_MAX_LINKED_DEVICES 2 +#define DS90UX9XX_MAX_SLAVE_DEVICES 8 + +/* Chosen link connection timings */ +#define CONN_MIN_TIME_MSEC 400U +#define CONN_STEP_TIME_MSEC 50U +#define CONN_MAX_TIME_MSEC 1000U + +struct ds90ux9xx_i2c_data { + const u8 slave_reg[DS90UX9XX_MAX_SLAVE_DEVICES]; + const u8 alias_reg[DS90UX9XX_MAX_SLAVE_DEVICES]; + const unsigned int num_slaves; +}; + +static const struct ds90ux9xx_i2c_data ds90ux925_i2c = { + .slave_reg = { 0x07, }, + .alias_reg = { 0x08, }, + .num_slaves = 1, +}; + +static const struct ds90ux9xx_i2c_data ds90ux926_i2c = { + .slave_reg = { 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, }, + .alias_reg = { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, }, + .num_slaves = 8, +}; + +static const struct ds90ux9xx_i2c_data ds90ux927_i2c = { + .slave_reg = { 0x07, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, }, + .alias_reg = { 0x08, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, }, + .num_slaves = 8, +}; + +struct ds90ux9xx_i2c_bridged { + struct device_node *np; + struct i2c_client *i2c; + u8 addr; +}; + +struct ds90ux9xx_i2c_map { + u8 real_addr; + u8 alias_addr; +}; + +struct ds90ux9xx_i2c_linked { + struct ds90ux9xx_i2c_bridged remote; + struct ds90ux9xx_i2c_bridged slave[DS90UX9XX_MAX_SLAVE_DEVICES]; + struct ds90ux9xx_i2c_map map[DS90UX9XX_MAX_SLAVE_DEVICES]; + unsigned int num_slaves; + unsigned int num_maps; + bool is_bridged; +}; + +struct ds90ux9xx_i2c { + struct device *dev; + struct regmap *regmap; + struct i2c_adapter *adapter; + const struct ds90ux9xx_i2c_data *i2c_data; + bool remote; + struct task_struct *conn_monitor; + struct ds90ux9xx_i2c_linked linked[DS90UX9XX_MAX_LINKED_DEVICES]; + unsigned int link; + bool pass_all; +}; + +static int ds90ux9xx_setup_i2c_pass_through(struct ds90ux9xx_i2c *i2c_bridge, + bool enable) +{ + int ret; + + ret = regmap_update_bits(i2c_bridge->regmap, SER_DES_REG_CONFIG, + CONFIG_I2C_PASS_THROUGH, + enable ? CONFIG_I2C_PASS_THROUGH : 0x0); + if (ret) + dev_err(i2c_bridge->dev, "Failed to setup pass through\n"); + + return ret; +} + +static int ds90ux9xx_setup_i2c_pass_all(struct ds90ux9xx_i2c *i2c_bridge, + bool enable) +{ + unsigned int reg; + int ret; + + if (ds90ux9xx_is_serializer(i2c_bridge->dev->parent)) + reg = SER_REG_I2C_CTRL; + else + reg = DES_REG_I2C_CTRL; + + ret = regmap_update_bits(i2c_bridge->regmap, reg, I2C_CTRL_PASS_ALL, + enable ? I2C_CTRL_PASS_ALL : 0x0); + if (ret) + dev_err(i2c_bridge->dev, "Failed to setup pass all mode\n"); + + return ret; +} + +static int ds90ux9xx_setup_auto_ack(struct ds90ux9xx_i2c *i2c_bridge, + bool enable) +{ + unsigned int val; + int ret; + + if (ds90ux9xx_is_serializer(i2c_bridge->dev->parent)) + val = SER_CONFIG_I2C_AUTO_ACK; + else + val = DES_CONFIG_I2C_AUTO_ACK; + + ret = regmap_update_bits(i2c_bridge->regmap, SER_DES_REG_CONFIG, val, + enable ? val : 0x0); + if (ret) + dev_err(i2c_bridge->dev, "Failed to setup auto ack mode\n"); + + return ret; +} + +static int ds90ux9xx_setup_address_mapping(struct ds90ux9xx_i2c *i2c_bridge, + unsigned int i, u8 slave, u8 alias) +{ + const struct ds90ux9xx_i2c_data *i2c_data = i2c_bridge->i2c_data; + int ret; + + if (i >= i2c_data->num_slaves) + return -EINVAL; + + ret = regmap_write(i2c_bridge->regmap, i2c_data->slave_reg[i], + slave << 1); + if (ret) + return ret; + + return regmap_write(i2c_bridge->regmap, i2c_data->alias_reg[i], + alias << 1); +} + +static void ds90ux9xx_clear_address_mappings(struct ds90ux9xx_i2c *i2c_bridge) +{ + unsigned int i; + + for (i = 0; i < i2c_bridge->i2c_data->num_slaves; i++) + ds90ux9xx_setup_address_mapping(i2c_bridge, i, 0, 0); +} + +static int ds90ux9xx_setup_address_mappings(struct ds90ux9xx_i2c *i2c_bridge, + struct ds90ux9xx_i2c_linked *linked) +{ + struct ds90ux9xx_i2c_map *map; + unsigned int i; + int ret; + + /* To avoid address collisions disable the remaining slave/aliases */ + for (i = 0; i < i2c_bridge->i2c_data->num_slaves; i++) { + map = &linked->map[i]; + + if (i < linked->num_maps) + dev_dbg(i2c_bridge->dev, "Mapping remote slave %#x to %#x\n", + map->real_addr, map->alias_addr); + + ret = ds90ux9xx_setup_address_mapping(i2c_bridge, i, + map->real_addr, + map->alias_addr); + if (ret) + return ret; + } + + return 0; +} + +static int ds90ux9xx_get_remote_addr(struct ds90ux9xx_i2c *i2c_bridge, u8 *addr) +{ + unsigned int reg, val; + int ret; + + if (ds90ux9xx_is_serializer(i2c_bridge->dev->parent)) + reg = SER_REG_REMOTE_ID; + else + reg = DES_REG_REMOTE_ID; + + ret = regmap_read(i2c_bridge->regmap, reg, &val); + if (ret) + dev_err(i2c_bridge->dev, "Failed to get remote addr: %d\n", + ret); + else + *addr = val >> 1; + + return ret; +} + +static int ds90ux9xx_set_remote_addr(struct ds90ux9xx_i2c *i2c_bridge, u8 addr) +{ + u8 remote_addr, data[2] = { SER_DES_REG_DEVICE_ID, + (addr << 1) | DEVICE_ID_OVERRIDE }; + struct i2c_msg msg = { + .addr = addr, + .flags = 0x00, + .len = 2, + .buf = data, + }; + int ret; + + ret = ds90ux9xx_get_remote_addr(i2c_bridge, &remote_addr); + if (ret) + return ret; + + if (remote_addr == addr) + return 0; + + ret = ds90ux9xx_setup_address_mapping(i2c_bridge, 0, remote_addr, addr); + if (ret) + return ret; + + ret = i2c_transfer(i2c_bridge->adapter, &msg, 1); + if (ret != 1) + return ret ? ret : -EIO; + + dev_dbg(i2c_bridge->dev, "New address set for remote %#x\n", addr); + + return 0; +} + +static void ds90ux9xx_remove_slave_devices(struct ds90ux9xx_i2c_linked *linked, + bool drop_reference) +{ + struct ds90ux9xx_i2c_bridged *slave; + unsigned int i; + + if (!linked->is_bridged) + return; + + for (i = 0; i < linked->num_slaves; i++) { + slave = &linked->slave[i]; + + if (slave->i2c) { + i2c_unregister_device(slave->i2c); + slave->i2c = NULL; + } + + if (drop_reference && slave->np) { + of_node_put(slave->np); + slave->np = NULL; + } + } +} + +static void ds90ux9xx_remove_linked_device(struct ds90ux9xx_i2c *i2c_bridge, + struct ds90ux9xx_i2c_linked *linked, + bool drop_reference) +{ + struct ds90ux9xx_i2c_bridged *remote = &linked->remote; + + ds90ux9xx_remove_slave_devices(linked, drop_reference); + + if (remote->i2c) { + if (linked->is_bridged) + i2c_unregister_device(remote->i2c); + else + put_device(&remote->i2c->dev); + + remote->i2c = NULL; + } + + if (drop_reference && remote->np) { + of_node_put(remote->np); + remote->np = NULL; + } + + ds90ux9xx_clear_address_mappings(i2c_bridge); +} + +static void ds90ux9xx_remove_linked_devices(struct ds90ux9xx_i2c *i2c_bridge) +{ + struct ds90ux9xx_i2c_linked *linked; + unsigned int i; + + for (i = 0; i < ds90ux9xx_num_fpd_links(i2c_bridge->dev->parent); i++) { + linked = &i2c_bridge->linked[i]; + ds90ux9xx_remove_linked_device(i2c_bridge, linked, true); + } +} + +static void ds90ux9xx_add_bridged_device(struct ds90ux9xx_i2c *i2c_bridge, + struct ds90ux9xx_i2c_bridged *bridged) +{ + struct i2c_board_info info = {}; + int ret; + + dev_dbg(i2c_bridge->dev, "Add I2C device '%s'\n", bridged->np->name); + + info.addr = bridged->addr; + info.of_node = bridged->np; + + /* Non-critical, in case of the problem report it and fallback */ + ret = of_modalias_node(bridged->np, info.type, sizeof(info.type)); + if (ret) + dev_err(i2c_bridge->dev, "Cannot get module alias for '%s'\n", + bridged->np->full_name); + + bridged->i2c = i2c_new_device(i2c_bridge->adapter, &info); + if (!bridged->i2c) + dev_err(i2c_bridge->dev, "Cannot add new I2C device\n"); +} + +static int ds90ux9xx_configure_remote_devices(struct ds90ux9xx_i2c *i2c_bridge, + struct ds90ux9xx_i2c_linked *linked) +{ + struct ds90ux9xx_i2c_bridged *remote = &linked->remote, *slave; + unsigned int i; + int ret; + + ret = ds90ux9xx_setup_address_mappings(i2c_bridge, linked); + if (ret) + return ret; + + ds90ux9xx_add_bridged_device(i2c_bridge, remote); + if (!remote->i2c) + return -ENODEV; + + for (i = 0; i < linked->num_slaves; i++) { + slave = &linked->slave[i]; + + ds90ux9xx_add_bridged_device(i2c_bridge, slave); + if (!slave->i2c) { + ds90ux9xx_remove_linked_device(i2c_bridge, + linked, false); + return -ENODEV; + } + } + + return 0; +} + +static void ds90ux9xx_disconnect_remotes(struct ds90ux9xx_i2c *i2c_bridge) +{ + unsigned int link = i2c_bridge->link; + + dev_dbg(i2c_bridge->dev, "Link %d is disconnected\n", link); + + ds90ux9xx_remove_linked_device(i2c_bridge, + &i2c_bridge->linked[link], false); + + ds90ux9xx_setup_i2c_pass_through(i2c_bridge, false); +} + +static int ds90ux9xx_connect_remotes(struct ds90ux9xx_i2c *i2c_bridge, + unsigned int link) +{ + struct ds90ux9xx_i2c_linked *linked = &i2c_bridge->linked[link]; + struct ds90ux9xx_i2c_bridged *remote = &linked->remote; + int ret; + + dev_dbg(i2c_bridge->dev, "Link %d is connected\n", link); + + i2c_bridge->link = link; + + ret = ds90ux9xx_setup_i2c_pass_through(i2c_bridge, linked->is_bridged); + if (ret) + return ret; + + if (!linked->is_bridged) { + remote->i2c = of_find_i2c_device_by_node(remote->np); + return remote->i2c ? 0 : -ENODEV; + } + + ret = ds90ux9xx_set_remote_addr(i2c_bridge, remote->addr); + if (ret) + return ret; + + return ds90ux9xx_configure_remote_devices(i2c_bridge, linked); +} + +static int ds90ux9xx_conn_monitor(void *data) +{ + unsigned int link, sleep_time = CONN_MIN_TIME_MSEC; + struct ds90ux9xx_i2c *i2c_bridge = data; + struct ds90ux9xx_i2c_bridged *remote; + struct ds90ux9xx_i2c_linked *linked; + bool lock; + u8 addr; + int ret; + + while (!kthread_should_stop()) { + ret = ds90ux9xx_get_link_status(i2c_bridge->dev->parent, + &link, &lock); + if (ret) + goto sleep; + + linked = &i2c_bridge->linked[i2c_bridge->link]; + remote = &linked->remote; + + ret = ds90ux9xx_get_remote_addr(i2c_bridge, &addr); + if (ret < 0) + goto sleep; + + lock = lock && (addr != 0); + + if (lock) + sleep_time = CONN_MAX_TIME_MSEC; + else if (remote->i2c) + sleep_time = CONN_MIN_TIME_MSEC; + else + sleep_time = min_t(unsigned int, CONN_MAX_TIME_MSEC, + sleep_time + CONN_STEP_TIME_MSEC); + + if (remote->i2c && lock && i2c_bridge->link == link) { + if (!linked->is_bridged) + goto sleep; + + if (remote->addr == addr) + goto sleep; + } + + if (remote->i2c) + ds90ux9xx_disconnect_remotes(i2c_bridge); + + if (!remote->i2c && lock) { + ret = ds90ux9xx_connect_remotes(i2c_bridge, link); + if (ret < 0) + dev_err(i2c_bridge->dev, + "Can't establish connection\n"); + } +sleep: + msleep(sleep_time); + } + + return 0; +} + +static int ds90ux9xx_parse_address_mappings(struct ds90ux9xx_i2c *i2c_bridge) +{ + const struct ds90ux9xx_i2c_data *i2c_data = i2c_bridge->i2c_data; + struct ds90ux9xx_i2c_linked *remote; + u32 link, real_addr, alias_addr; + unsigned int size, i; + const __be32 *list; + + list = of_get_property(i2c_bridge->dev->of_node, "ti,i2c-bridge-maps", + &size); + if (!list) + return 0; + + if (!size || size % 12) { + dev_err(i2c_bridge->dev, "Failed to get valid alias maps\n"); + return -EINVAL; + } + + for (i = 0; i < size / 12; i++) { + link = be32_to_cpu(*list++); + real_addr = be32_to_cpu(*list++); + alias_addr = be32_to_cpu(*list++); + + if (link >= ds90ux9xx_num_fpd_links(i2c_bridge->dev->parent)) { + dev_info(i2c_bridge->dev, "Invalid link id %d\n", i); + continue; + } + + remote = &i2c_bridge->linked[link]; + if (remote->num_maps >= i2c_data->num_slaves) { + dev_info(i2c_bridge->dev, "Too many aliases\n"); + break; + } + + remote->map[remote->num_maps].real_addr = real_addr; + remote->map[remote->num_maps].alias_addr = alias_addr; + remote->num_maps++; + } + + return 0; +} + +static u8 ds90ux9xx_get_mapped_address(struct ds90ux9xx_i2c_linked *linked, + u8 remote_addr) +{ + unsigned int i; + + for (i = 0; i < linked->num_maps; i++) { + if (linked->map[i].real_addr == remote_addr) + return linked->map[i].alias_addr; + } + + /* Fallback to preset address, remote device may be inaccessible */ + return remote_addr; +} + +static int ds90ux9xx_parse_remote_slaves(struct ds90ux9xx_i2c *i2c_bridge, + struct ds90ux9xx_i2c_linked *linked) +{ + struct ds90ux9xx_i2c_bridged *slave; + struct device_node *child, *np; + u32 addr; + + np = of_get_child_by_name(linked->remote.np, "i2c-bridge"); + if (!np) { + dev_info(i2c_bridge->dev, "I2C bridge device node not found\n"); + return 0; + } + + if (of_get_child_count(np) > DS90UX9XX_MAX_SLAVE_DEVICES) { + dev_err(i2c_bridge->dev, "Too many aliased I2C devices\n"); + of_node_put(np); + return -EINVAL; + } + + for_each_child_of_node(np, child) { + if (of_property_read_u32(child, "reg", &addr)) { + dev_err(i2c_bridge->dev, "No I2C device address '%s'\n", + child->full_name); + /* Try the next one */ + continue; + } + + slave = &linked->slave[linked->num_slaves]; + slave->np = of_node_get(child); + if (i2c_bridge->pass_all) + slave->addr = addr; + else + slave->addr = ds90ux9xx_get_mapped_address(linked, + addr); + + linked->num_slaves++; + } + + of_node_put(np); + + return 0; +} + +static int ds90ux9xx_configure_link(struct ds90ux9xx_i2c *i2c_bridge) +{ + struct device_node *np = i2c_bridge->dev->parent->of_node; + struct i2c_client *client; + int ret; + + /* The link value is updated when established connection is detected */ + i2c_bridge->link = 0; + + client = of_find_i2c_device_by_node(np); + if (!client || !client->adapter) + return -ENODEV; + + i2c_bridge->adapter = client->adapter; + put_device(&client->dev); + + ret = ds90ux9xx_setup_i2c_pass_all(i2c_bridge, i2c_bridge->pass_all); + if (ret) + return ret; + + i2c_bridge->conn_monitor = kthread_run(ds90ux9xx_conn_monitor, + i2c_bridge, + "ds90ux9xx_conn_monitor"); + if (IS_ERR(i2c_bridge->conn_monitor)) + return PTR_ERR(i2c_bridge->conn_monitor); + + return 0; +} + +static int ds90ux9xx_set_link_attributes(struct ds90ux9xx_i2c *i2c_bridge) +{ + struct device_node *np = i2c_bridge->dev->of_node; + struct ds90ux9xx_i2c_linked *linked; + struct of_phandle_args remote; + unsigned int link, num_links, i; + bool auto_ack; + int ret; + + if (of_property_read_bool(np, "ti,i2c-bridge-pass-all")) + i2c_bridge->pass_all = true; + + auto_ack = of_property_read_bool(np, "ti,i2c-bridge-auto-ack"); + ret = ds90ux9xx_setup_auto_ack(i2c_bridge, auto_ack); + if (ret) + return ret; + + ret = ds90ux9xx_parse_address_mappings(i2c_bridge); + if (ret) + return ret; + + num_links = ds90ux9xx_num_fpd_links(i2c_bridge->dev->parent); + + for (i = 0; i < num_links; i++) { + ret = of_parse_phandle_with_fixed_args(np, "ti,i2c-bridges", + 2, i, &remote); + if (ret) { + if (ret == -ENOENT) + break; + goto drop_nodes; + } + + link = remote.args[0]; + if (link >= num_links) { + ret = -EINVAL; + goto drop_nodes; + } + + linked = &i2c_bridge->linked[link]; + if (linked->remote.np) { + ret = -EINVAL; + goto drop_nodes; + } + + linked->remote.np = remote.np; + linked->remote.addr = remote.args[1]; + + /* + * Don't open I2C access over the FPD-link bidirectional channel + * to the remote's slave devices, if the remote is an I2C slave + * attached to a local bus, because the remote's slaves would + * also necessarily have to hang off the same local bus. + * Enabling pass-through in this case will cause I2C collisions + * due to multiple routes to the same device. + */ + if (of_property_read_bool(linked->remote.np, "reg")) { + linked->is_bridged = false; + continue; + } + + linked->is_bridged = true; + + ret = ds90ux9xx_parse_remote_slaves(i2c_bridge, linked); + if (ret) + goto drop_nodes; + } + + ret = ds90ux9xx_configure_link(i2c_bridge); + if (ret) + goto drop_nodes; + + return 0; + +drop_nodes: + ds90ux9xx_remove_linked_devices(i2c_bridge); + + return ret; +} + +static void ds90ux9xx_get_i2c_data(struct ds90ux9xx_i2c *i2c_bridge) +{ + enum ds90ux9xx_device_id id = + ds90ux9xx_get_ic_type(i2c_bridge->dev->parent); + + switch (id) { + case TI_DS90UB925: + case TI_DS90UH925: + i2c_bridge->i2c_data = &ds90ux925_i2c; + break; + case TI_DS90UB927: + case TI_DS90UH927: + i2c_bridge->i2c_data = &ds90ux927_i2c; + break; + case TI_DS90UB926: + case TI_DS90UH926: + case TI_DS90UB928: + case TI_DS90UH928: + case TI_DS90UB940: + case TI_DS90UH940: + i2c_bridge->i2c_data = &ds90ux926_i2c; + break; + default: + dev_err(i2c_bridge->dev, "Not supported hardware: [%d]\n", id); + } +} + +static int ds90ux9xx_i2c_bridge_probe(struct platform_device *pdev) +{ + struct ds90ux9xx_i2c *i2c_bridge; + struct device *dev = &pdev->dev; + + i2c_bridge = devm_kzalloc(dev, sizeof(*i2c_bridge), GFP_KERNEL); + if (!i2c_bridge) + return -ENOMEM; + + i2c_bridge->dev = dev; + i2c_bridge->regmap = dev_get_regmap(dev->parent, NULL); + if (!i2c_bridge->regmap) + return -ENODEV; + + i2c_bridge->i2c_data = of_device_get_match_data(dev); + if (!i2c_bridge->i2c_data) + ds90ux9xx_get_i2c_data(i2c_bridge); + + if (!i2c_bridge->i2c_data) + return -ENODEV; + + platform_set_drvdata(pdev, i2c_bridge); + + if (of_property_read_bool(dev->of_node, "ti,i2c-bridges")) + return ds90ux9xx_set_link_attributes(i2c_bridge); + + i2c_bridge->remote = true; + + return ds90ux9xx_setup_i2c_pass_through(i2c_bridge, false); +} + +static int ds90ux9xx_i2c_bridge_remove(struct platform_device *pdev) +{ + struct ds90ux9xx_i2c *i2c_bridge = platform_get_drvdata(pdev); + + if (i2c_bridge->remote) + return 0; + + kthread_stop(i2c_bridge->conn_monitor); + ds90ux9xx_remove_linked_devices(i2c_bridge); + + return 0; +} + +static const struct of_device_id ds90ux9xx_i2c_dt_ids[] = { + { .compatible = "ti,ds90ux9xx-i2c-bridge", }, + { .compatible = "ti,ds90ux925-i2c-bridge", .data = &ds90ux925_i2c, }, + { .compatible = "ti,ds90ux926-i2c-bridge", .data = &ds90ux926_i2c, }, + { .compatible = "ti,ds90ux927-i2c-bridge", .data = &ds90ux927_i2c, }, + { .compatible = "ti,ds90ux928-i2c-bridge", .data = &ds90ux926_i2c, }, + { .compatible = "ti,ds90ux940-i2c-bridge", .data = &ds90ux926_i2c, }, + { }, +}; +MODULE_DEVICE_TABLE(of, ds90ux9xx_i2c_dt_ids); + +static struct platform_driver ds90ux9xx_i2c_bridge_driver = { + .probe = ds90ux9xx_i2c_bridge_probe, + .remove = ds90ux9xx_i2c_bridge_remove, + .driver = { + .name = "ds90ux9xx-i2c-bridge", + .of_match_table = ds90ux9xx_i2c_dt_ids, + }, +}; +module_platform_driver(ds90ux9xx_i2c_bridge_driver); + +MODULE_AUTHOR("Vladimir Zapolskiy "); +MODULE_AUTHOR("Balasubramani Vivekanandan "); +MODULE_DESCRIPTION("TI DS90Ux9xx I2C bridge/alias controller driver"); +MODULE_LICENSE("GPL"); From patchwork Mon Oct 8 21:12:04 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Zapolskiy X-Patchwork-Id: 980813 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=mleia.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42TY3L5CPhz9sD2 for ; Tue, 9 Oct 2018 08:12:30 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727195AbeJIE0F (ORCPT ); Tue, 9 Oct 2018 00:26:05 -0400 Received: from mleia.com ([178.79.152.223]:35472 "EHLO mail.mleia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726969AbeJIEZ5 (ORCPT ); Tue, 9 Oct 2018 00:25:57 -0400 Received: from mail.mleia.com (localhost [127.0.0.1]) by mail.mleia.com (Postfix) with ESMTP id CDDE141E2C7; Mon, 8 Oct 2018 22:12:13 +0100 (BST) From: Vladimir Zapolskiy To: Lee Jones , Linus Walleij , Rob Herring Cc: Marek Vasut , Laurent Pinchart , Wolfram Sang , devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, Vladimir Zapolskiy Subject: [PATCH 6/7] pinctrl: ds90ux9xx: add TI DS90Ux9xx pinmux and GPIO controller driver Date: Tue, 9 Oct 2018 00:12:04 +0300 Message-Id: <20181008211205.2900-7-vz@mleia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181008211205.2900-1-vz@mleia.com> References: <20181008211205.2900-1-vz@mleia.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-49551924 X-CRM114-CacheID: sfid-20181008_221213_870949_5A2A5CAE X-CRM114-Status: GOOD ( 25.23 ) Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org From: Vladimir Zapolskiy The change adds an MFD cell driver for managing pin functions on TI DS90Ux9xx de-/serializers. Signed-off-by: Vladimir Zapolskiy --- drivers/pinctrl/Kconfig | 11 + drivers/pinctrl/Makefile | 1 + drivers/pinctrl/pinctrl-ds90ux9xx.c | 970 ++++++++++++++++++++++++++++ 3 files changed, 982 insertions(+) create mode 100644 drivers/pinctrl/pinctrl-ds90ux9xx.c diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 978b2ed4d014..9350263cac4b 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -123,6 +123,17 @@ config PINCTRL_DIGICOLOR select PINMUX select GENERIC_PINCONF +config PINCTRL_DS90UX9XX + tristate "TI DS90Ux9xx pin multiplexer and GPIO driver" + depends on MFD_DS90UX9XX + default MFD_DS90UX9XX + select GPIOLIB + select PINMUX + select GENERIC_PINCONF + help + Select this option to enable pinmux and GPIO bridge/controller + driver for the TI DS90Ux9xx de-/serializer chip family. + config PINCTRL_LANTIQ bool depends on LANTIQ diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 8e127bd8610f..34fc2dbfb9c1 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_PINCTRL_AT91PIO4) += pinctrl-at91-pio4.o obj-$(CONFIG_PINCTRL_AMD) += pinctrl-amd.o obj-$(CONFIG_PINCTRL_DA850_PUPD) += pinctrl-da850-pupd.o obj-$(CONFIG_PINCTRL_DIGICOLOR) += pinctrl-digicolor.o +obj-$(CONFIG_PINCTRL_DS90UX9XX) += pinctrl-ds90ux9xx.o obj-$(CONFIG_PINCTRL_FALCON) += pinctrl-falcon.o obj-$(CONFIG_PINCTRL_GEMINI) += pinctrl-gemini.o obj-$(CONFIG_PINCTRL_MAX77620) += pinctrl-max77620.o diff --git a/drivers/pinctrl/pinctrl-ds90ux9xx.c b/drivers/pinctrl/pinctrl-ds90ux9xx.c new file mode 100644 index 000000000000..7fdb5c15743a --- /dev/null +++ b/drivers/pinctrl/pinctrl-ds90ux9xx.c @@ -0,0 +1,970 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Pinmux and GPIO controller driver for TI DS90Ux9xx De-/Serializer hardware + * + * Copyright (c) 2017-2018 Mentor Graphics Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pinctrl-utils.h" + +#define SER_REG_PIN_CTRL 0x12 +#define PIN_CTRL_RGB18 BIT(2) +#define PIN_CTRL_I2S_DATA_ISLAND BIT(1) +#define PIN_CTRL_I2S_CHANNEL_B (BIT(0) | BIT(3)) + +#define SER_REG_I2S_SURROUND 0x1A +#define PIN_CTRL_I2S_SURR_BIT BIT(0) + +#define DES_REG_INDIRECT_PASS 0x16 + +#define OUTPUT_HIGH BIT(3) +#define REMOTE_CONTROL BIT(2) +#define DIR_INPUT BIT(1) +#define ENABLE_GPIO BIT(0) + +#define GPIO_AS_INPUT (ENABLE_GPIO | DIR_INPUT) +#define GPIO_AS_OUTPUT ENABLE_GPIO +#define GPIO_OUTPUT_HIGH (GPIO_AS_OUTPUT | OUTPUT_HIGH) +#define GPIO_OUTPUT_LOW GPIO_AS_OUTPUT +#define GPIO_OUTPUT_REMOTE (GPIO_AS_OUTPUT | REMOTE_CONTROL) + +#define DS90UX9XX_MAX_GROUP_PINS 6 + +struct ds90ux9xx_gpio { + const u8 cfg_reg; + const u8 cfg_mask; + const u8 stat_reg; + const u8 stat_bit; + const bool input; +}; + +#define DS90UX9XX_PIN_GPIO_SIMPLE(_cfg, _high, _input) \ + { \ + .cfg_reg = _cfg, \ + .cfg_mask = _high ? 0xf0 : 0x0f, \ + .stat_reg = 0x0, \ + .stat_bit = 0x0, \ + .input = _input ? true : false, \ + } + +#define DS90UX9XX_PIN_GPIO(_cfg, _high, _stat, _bit) \ + { \ + .cfg_reg = _cfg, \ + .cfg_mask = _high ? 0xf0 : 0x0f, \ + .stat_reg = _stat, \ + .stat_bit = BIT(_bit), \ + .input = true, \ + } + +enum ds90ux9xx_function { + DS90UX9XX_FUNC_NONE, + DS90UX9XX_FUNC_GPIO, + DS90UX9XX_FUNC_REMOTE, + DS90UX9XX_FUNC_PASS, + DS90UX9XX_FUNC_I2S_1, + DS90UX9XX_FUNC_I2S_2, + DS90UX9XX_FUNC_I2S_3, + DS90UX9XX_FUNC_I2S_4, + DS90UX9XX_FUNC_I2S_M, + DS90UX9XX_FUNC_PARALLEL, +}; + +struct ds90ux9xx_pin { + const unsigned int id; + const char *name; + const u32 func_mask; + const u8 pass_bit; + const struct ds90ux9xx_gpio gpio; +}; + +#define TO_BIT(_f) (DS90UX9XX_FUNC_##_f ? BIT(DS90UX9XX_FUNC_##_f) : 0) +#define DS90UX9XX_GPIO(_pin, _name, _f1, _f2, _f3) \ + [_pin] = { \ + .id = _pin, \ + .name = _name, \ + .func_mask = TO_BIT(GPIO) | TO_BIT(_f2) | TO_BIT(_f3), \ + .gpio = DS90UX9XX_PIN_##_f1, \ + } + +#define DS90UX940_GPIO(_pin, _name, _f1, _f2, _f3, _pass) \ + [_pin] = { \ + .id = _pin, \ + .name = _name, \ + .func_mask = TO_BIT(GPIO) | TO_BIT(PASS) | \ + TO_BIT(_f2) | TO_BIT(_f3), \ + .pass_bit = BIT(_pass), \ + .gpio = DS90UX9XX_PIN_##_f1, \ + } + +struct ds90ux9xx_pinctrl_data { + const char *name; + const struct ds90ux9xx_pin *pins; + const struct pinctrl_pin_desc *pins_desc; + const unsigned int npins; + const enum ds90ux9xx_function *functions; + const unsigned int nfunctions; +}; + +static const struct pinctrl_pin_desc ds90ux925_926_pins_desc[] = { + PINCTRL_PIN(0, "gpio0"), /* DS90Ux925 pin 25, DS90Ux926 pin 41 */ + PINCTRL_PIN(1, "gpio1"), /* DS90Ux925 pin 26, DS90Ux926 pin 40 */ + PINCTRL_PIN(2, "gpio2"), /* DS90Ux925 pin 35, DS90Ux926 pin 28 */ + PINCTRL_PIN(3, "gpio3"), /* DS90Ux925 pin 36, DS90Ux926 pin 27 */ + PINCTRL_PIN(4, "gpio4"), /* DS90Ux925 pin 43, DS90Ux926 pin 19 */ + PINCTRL_PIN(5, "gpio5"), /* DS90Ux925 pin 44, DS90Ux926 pin 18 */ + PINCTRL_PIN(6, "gpio6"), /* DS90Ux925 pin 11, DS90Ux926 pin 45 */ + PINCTRL_PIN(7, "gpio7"), /* DS90Ux925 pin 12, DS90Ux926 pin 30 */ + PINCTRL_PIN(8, "gpio8"), /* DS90Ux925 pin 13, DS90Ux926 pin 1 */ +}; + +static const struct pinctrl_pin_desc ds90ux927_928_pins_desc[] = { + PINCTRL_PIN(0, "gpio0"), /* DS90Ux927 pin 39, DS90Ux928 pin 14 */ + PINCTRL_PIN(1, "gpio1"), /* DS90Ux927 pin 40, DS90Ux928 pin 13 */ + PINCTRL_PIN(2, "gpio2"), /* DS90Ux927 pin 5, DS90Ux928 pin 37 */ + PINCTRL_PIN(3, "gpio3"), /* DS90Ux927 pin 6, DS90Ux928 pin 36 */ + PINCTRL_PIN(4, "gpio5"), /* DS90Ux927 pin 4, DS90Ux928 pin 3 */ + PINCTRL_PIN(5, "gpio6"), /* DS90Ux927 pin 3, DS90Ux928 pin 7 */ + PINCTRL_PIN(6, "gpio7"), /* DS90Ux927 pin 2, DS90Ux928 pin 10 */ + PINCTRL_PIN(7, "gpio8"), /* DS90Ux927 pin 1, DS90Ux928 pin 8 */ +}; + +static const struct pinctrl_pin_desc ds90ux940_pins_desc[] = { + PINCTRL_PIN(0, "gpio0"), /* DS90Ux940 pin 7 */ + PINCTRL_PIN(1, "gpio1"), /* DS90Ux940 pin 8 */ + PINCTRL_PIN(2, "gpio2"), /* DS90Ux940 pin 10 */ + PINCTRL_PIN(3, "gpio3"), /* DS90Ux940 pin 9 */ + PINCTRL_PIN(4, "gpio5"), /* DS90Ux940 pin 11 */ + PINCTRL_PIN(5, "gpio6"), /* DS90Ux940 pin 12 */ + PINCTRL_PIN(6, "gpio7"), /* DS90Ux940 pin 14 */ + PINCTRL_PIN(7, "gpio8"), /* DS90Ux940 pin 13 */ + PINCTRL_PIN(8, "gpio9"), /* DS90Ux940 pin 15 */ +}; + +static const struct ds90ux9xx_pin ds90ux925_pins[] = { + DS90UX9XX_GPIO(0, "gpio0", GPIO_SIMPLE(0x0d, 0, 1), REMOTE, PARALLEL), + DS90UX9XX_GPIO(1, "gpio1", GPIO_SIMPLE(0x0e, 0, 1), REMOTE, PARALLEL), + DS90UX9XX_GPIO(2, "gpio2", GPIO_SIMPLE(0x0e, 1, 1), REMOTE, PARALLEL), + DS90UX9XX_GPIO(3, "gpio3", GPIO_SIMPLE(0x0f, 0, 1), REMOTE, PARALLEL), + DS90UX9XX_GPIO(4, "gpio4", GPIO_SIMPLE(0x0f, 1, 0), NONE, PARALLEL), + DS90UX9XX_GPIO(5, "gpio5", GPIO_SIMPLE(0x10, 0, 0), I2S_2, PARALLEL), + DS90UX9XX_GPIO(6, "gpio6", GPIO_SIMPLE(0x10, 1, 0), I2S_1, NONE), + DS90UX9XX_GPIO(7, "gpio7", GPIO_SIMPLE(0x11, 0, 0), I2S_1, NONE), + DS90UX9XX_GPIO(8, "gpio8", GPIO_SIMPLE(0x11, 1, 0), I2S_1, NONE), +}; + +static const struct ds90ux9xx_pin ds90ux926_pins[] = { + DS90UX9XX_GPIO(0, "gpio0", GPIO_SIMPLE(0x1d, 0, 1), REMOTE, PARALLEL), + DS90UX9XX_GPIO(1, "gpio1", GPIO_SIMPLE(0x1e, 0, 1), REMOTE, PARALLEL), + DS90UX9XX_GPIO(2, "gpio2", GPIO_SIMPLE(0x1e, 1, 1), REMOTE, PARALLEL), + DS90UX9XX_GPIO(3, "gpio3", GPIO_SIMPLE(0x1f, 0, 1), REMOTE, PARALLEL), + DS90UX9XX_GPIO(4, "gpio4", GPIO_SIMPLE(0x1f, 1, 0), NONE, PARALLEL), + DS90UX9XX_GPIO(5, "gpio5", GPIO_SIMPLE(0x20, 0, 0), I2S_2, PARALLEL), + DS90UX9XX_GPIO(6, "gpio6", GPIO_SIMPLE(0x20, 1, 0), I2S_1, NONE), + DS90UX9XX_GPIO(7, "gpio7", GPIO_SIMPLE(0x21, 0, 0), I2S_1, NONE), + DS90UX9XX_GPIO(8, "gpio8", GPIO_SIMPLE(0x21, 1, 0), I2S_1, NONE), +}; + +static const struct ds90ux9xx_pin ds90ux927_pins[] = { + DS90UX9XX_GPIO(0, "gpio0", GPIO(0x0d, 0, 0x1c, 0), REMOTE, NONE), + DS90UX9XX_GPIO(1, "gpio1", GPIO(0x0e, 0, 0x1c, 1), REMOTE, NONE), + DS90UX9XX_GPIO(2, "gpio2", GPIO(0x0e, 1, 0x1c, 2), REMOTE, I2S_3), + DS90UX9XX_GPIO(3, "gpio3", GPIO(0x0f, 0, 0x1c, 3), REMOTE, I2S_4), + DS90UX9XX_GPIO(4, "gpio5", GPIO(0x10, 0, 0x1c, 5), NONE, I2S_2), + DS90UX9XX_GPIO(5, "gpio6", GPIO(0x10, 1, 0x1c, 6), NONE, I2S_1), + DS90UX9XX_GPIO(6, "gpio7", GPIO(0x11, 0, 0x1c, 7), NONE, I2S_1), + DS90UX9XX_GPIO(7, "gpio8", GPIO(0x11, 1, 0x1d, 0), NONE, I2S_1), +}; + +static const struct ds90ux9xx_pin ds90ux928_pins[] = { + DS90UX9XX_GPIO(0, "gpio0", GPIO(0x1d, 0, 0x6e, 0), REMOTE, I2S_M), + DS90UX9XX_GPIO(1, "gpio1", GPIO(0x1e, 0, 0x6e, 1), REMOTE, I2S_M), + DS90UX9XX_GPIO(2, "gpio2", GPIO(0x1e, 1, 0x6e, 2), REMOTE, I2S_3), + DS90UX9XX_GPIO(3, "gpio3", GPIO(0x1f, 0, 0x6e, 3), REMOTE, I2S_4), + DS90UX9XX_GPIO(4, "gpio5", GPIO(0x20, 0, 0x6e, 5), NONE, I2S_2), + DS90UX9XX_GPIO(5, "gpio6", GPIO(0x20, 1, 0x6e, 6), NONE, I2S_1), + DS90UX9XX_GPIO(6, "gpio7", GPIO(0x21, 0, 0x6e, 7), NONE, I2S_1), + DS90UX9XX_GPIO(7, "gpio8", GPIO(0x21, 1, 0x6f, 0), NONE, I2S_1), +}; + +static const struct ds90ux9xx_pin ds90ux940_pins[] = { + DS90UX940_GPIO(0, "gpio0", GPIO(0x1d, 0, 0x6e, 0), REMOTE, I2S_M, 1), + DS90UX9XX_GPIO(1, "gpio1", GPIO(0x1e, 0, 0x6e, 1), REMOTE, I2S_M), + DS90UX9XX_GPIO(2, "gpio2", GPIO(0x1e, 1, 0x6e, 2), REMOTE, I2S_3), + DS90UX940_GPIO(3, "gpio3", GPIO(0x1f, 0, 0x6e, 3), REMOTE, I2S_4, 2), + DS90UX9XX_GPIO(4, "gpio5", GPIO(0x20, 0, 0x6e, 5), NONE, I2S_2), + DS90UX9XX_GPIO(5, "gpio6", GPIO(0x20, 1, 0x6e, 6), NONE, I2S_1), + DS90UX9XX_GPIO(6, "gpio7", GPIO(0x21, 0, 0x6e, 7), NONE, I2S_1), + DS90UX9XX_GPIO(7, "gpio8", GPIO(0x21, 1, 0x6f, 0), NONE, I2S_1), + DS90UX9XX_GPIO(8, "gpio9", GPIO_SIMPLE(0x1a, 0, 1), NONE, I2S_M), +}; + +static const enum ds90ux9xx_function ds90ux925_926_pin_functions[] = { + DS90UX9XX_FUNC_GPIO, + DS90UX9XX_FUNC_REMOTE, + DS90UX9XX_FUNC_I2S_1, + DS90UX9XX_FUNC_I2S_2, + DS90UX9XX_FUNC_PARALLEL, +}; + +static const enum ds90ux9xx_function ds90ux927_pin_functions[] = { + DS90UX9XX_FUNC_GPIO, + DS90UX9XX_FUNC_REMOTE, + DS90UX9XX_FUNC_I2S_1, + DS90UX9XX_FUNC_I2S_2, + DS90UX9XX_FUNC_I2S_3, + DS90UX9XX_FUNC_I2S_4, +}; + +static const enum ds90ux9xx_function ds90ux928_pin_functions[] = { + DS90UX9XX_FUNC_GPIO, + DS90UX9XX_FUNC_REMOTE, + DS90UX9XX_FUNC_I2S_1, + DS90UX9XX_FUNC_I2S_2, + DS90UX9XX_FUNC_I2S_3, + DS90UX9XX_FUNC_I2S_4, + DS90UX9XX_FUNC_I2S_M, +}; + +static const enum ds90ux9xx_function ds90ux940_pin_functions[] = { + DS90UX9XX_FUNC_GPIO, + DS90UX9XX_FUNC_REMOTE, + DS90UX9XX_FUNC_PASS, + DS90UX9XX_FUNC_I2S_1, + DS90UX9XX_FUNC_I2S_2, + DS90UX9XX_FUNC_I2S_3, + DS90UX9XX_FUNC_I2S_4, + DS90UX9XX_FUNC_I2S_M, +}; + +static const struct ds90ux9xx_pinctrl_data ds90ux925_pinctrl = { + .name = "ds90ux925", + .pins_desc = ds90ux925_926_pins_desc, + .pins = ds90ux925_pins, + .npins = ARRAY_SIZE(ds90ux925_pins), + .functions = ds90ux925_926_pin_functions, + .nfunctions = ARRAY_SIZE(ds90ux925_926_pin_functions), +}; + +static const struct ds90ux9xx_pinctrl_data ds90ux926_pinctrl = { + .name = "ds90ux926", + .pins_desc = ds90ux925_926_pins_desc, + .pins = ds90ux926_pins, + .npins = ARRAY_SIZE(ds90ux926_pins), + .functions = ds90ux925_926_pin_functions, + .nfunctions = ARRAY_SIZE(ds90ux925_926_pin_functions), +}; + +static const struct ds90ux9xx_pinctrl_data ds90ux927_pinctrl = { + .name = "ds90ux927", + .pins_desc = ds90ux927_928_pins_desc, + .pins = ds90ux927_pins, + .npins = ARRAY_SIZE(ds90ux927_pins), + .functions = ds90ux927_pin_functions, + .nfunctions = ARRAY_SIZE(ds90ux927_pin_functions), +}; + +static const struct ds90ux9xx_pinctrl_data ds90ux928_pinctrl = { + .name = "ds90ux928", + .pins_desc = ds90ux927_928_pins_desc, + .pins = ds90ux928_pins, + .npins = ARRAY_SIZE(ds90ux928_pins), + .functions = ds90ux928_pin_functions, + .nfunctions = ARRAY_SIZE(ds90ux928_pin_functions), +}; + +static const struct ds90ux9xx_pinctrl_data ds90ux940_pinctrl = { + .name = "ds90ux940", + .pins_desc = ds90ux940_pins_desc, + .pins = ds90ux940_pins, + .npins = ARRAY_SIZE(ds90ux940_pins), + .functions = ds90ux940_pin_functions, + .nfunctions = ARRAY_SIZE(ds90ux940_pin_functions), +}; + +struct ds90ux9xx_pin_function { + enum ds90ux9xx_function id; + const char **groups; + unsigned int ngroups; +}; + +struct ds90ux9xx_pin_group { + const char *name; + unsigned int pins[DS90UX9XX_MAX_GROUP_PINS]; + unsigned int npins; +}; + +struct ds90ux9xx_pinctrl { + struct device *dev; + struct regmap *regmap; + + struct pinctrl_dev *pctrl; + struct pinctrl_desc desc; + + struct ds90ux9xx_pin_function *functions; + unsigned int nfunctions; + + struct ds90ux9xx_pin_group *groups; + unsigned int ngroups; + + const struct ds90ux9xx_pin *pins; + unsigned int npins; + + struct gpio_chip gpiochip; + unsigned int ngpios; +}; + +static const char *const ds90ux9xx_func_names[] = { + [DS90UX9XX_FUNC_NONE] = "none", + [DS90UX9XX_FUNC_GPIO] = "gpio", + [DS90UX9XX_FUNC_REMOTE] = "gpio-remote", + [DS90UX9XX_FUNC_PASS] = "pass", + [DS90UX9XX_FUNC_I2S_1] = "i2s-1", + [DS90UX9XX_FUNC_I2S_2] = "i2s-2", + [DS90UX9XX_FUNC_I2S_3] = "i2s-3", + [DS90UX9XX_FUNC_I2S_4] = "i2s-4", + [DS90UX9XX_FUNC_I2S_M] = "i2s-m", + [DS90UX9XX_FUNC_PARALLEL] = "parallel", +}; + +static bool ds90ux9xx_func_in_group(u32 func_mask, enum ds90ux9xx_function id) +{ + u32 mask = BIT(id); + + if (id == DS90UX9XX_FUNC_I2S_4) { + mask |= BIT(DS90UX9XX_FUNC_I2S_3); + id = DS90UX9XX_FUNC_I2S_3; + } + + if (id == DS90UX9XX_FUNC_I2S_3) { + mask |= BIT(DS90UX9XX_FUNC_I2S_2); + id = DS90UX9XX_FUNC_I2S_2; + } + + if (id == DS90UX9XX_FUNC_I2S_2) + mask |= BIT(DS90UX9XX_FUNC_I2S_1); + + return func_mask & mask; +} + +static bool ds90ux9xx_pin_function(enum ds90ux9xx_function id) +{ + if (id == DS90UX9XX_FUNC_GPIO || id == DS90UX9XX_FUNC_REMOTE || + id == DS90UX9XX_FUNC_PASS) + return true; + + return false; +} + +static int ds90ux9xx_populate_groups(struct ds90ux9xx_pinctrl *pctrl, + const struct ds90ux9xx_pinctrl_data *cfg) +{ + struct ds90ux9xx_pin_function *func; + struct ds90ux9xx_pin_group *group; + enum ds90ux9xx_function func_id; + unsigned int i, j, n; + + pctrl->pins = cfg->pins; + pctrl->npins = cfg->npins; + + /* Assert that only GPIO pins are muxed, it may be changed in future */ + for (j = 0; j < pctrl->npins; j++) + if (!(pctrl->pins[j].func_mask & BIT(DS90UX9XX_FUNC_GPIO))) + return -EINVAL; + + pctrl->ngpios = pctrl->npins; + + pctrl->nfunctions = cfg->nfunctions; + pctrl->functions = devm_kcalloc(pctrl->dev, pctrl->nfunctions, + sizeof(*pctrl->functions), GFP_KERNEL); + if (!pctrl->functions) + return -ENOMEM; + + for (i = 0; i < pctrl->nfunctions; i++) { + func = &pctrl->functions[i]; + func->id = cfg->functions[i]; + + /* + * Number of pin groups is a sum of pins and pin group functions + */ + if (ds90ux9xx_pin_function(func->id)) { + for (j = 0; j < pctrl->npins; j++) { + if (func->id == DS90UX9XX_FUNC_GPIO) + pctrl->ngroups++; + + if (pctrl->pins[j].func_mask & BIT(func->id)) + func->ngroups++; + } + } else { + pctrl->ngroups++; + func->ngroups = 1; + } + + func->groups = devm_kcalloc(pctrl->dev, func->ngroups, + sizeof(*func->groups), GFP_KERNEL); + if (!func->groups) + return -ENOMEM; + + if (ds90ux9xx_pin_function(func->id)) { + n = 0; + for (j = 0; j < pctrl->npins; j++) + if (pctrl->pins[j].func_mask & BIT(func->id)) + func->groups[n++] = pctrl->pins[j].name; + } else { + /* Group name matches function name */ + func->groups[0] = ds90ux9xx_func_names[func->id]; + } + } + + pctrl->groups = devm_kcalloc(pctrl->dev, pctrl->ngroups, + sizeof(*pctrl->groups), GFP_KERNEL); + if (!pctrl->groups) + return -ENOMEM; + + /* Firstly scan for GPIOs as individual pin groups */ + group = pctrl->groups; + for (i = 0; i < pctrl->npins; i++) { + group->name = pctrl->pins[i].name; + group->pins[0] = pctrl->pins[i].id; + group->npins = 1; + group++; + } + + /* Now scan for the rest of pin groups, which has own functions */ + for (i = 0; i < pctrl->nfunctions; i++) { + func_id = pctrl->functions[i].id; + + /* Those pin groups were accounted above as pin functions */ + if (ds90ux9xx_pin_function(func_id)) + continue; + + group->name = ds90ux9xx_func_names[func_id]; + for (j = 0; j < pctrl->npins; j++) { + if (ds90ux9xx_func_in_group(pctrl->pins[j].func_mask, + func_id)) { + group->pins[group->npins] = pctrl->pins[j].id; + group->npins++; + } + } + + group++; + } + + return 0; +} + +static int ds90ux9xx_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct ds90ux9xx_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + + return pctrl->ngroups; +} + +static const char *ds90ux9xx_get_group_name(struct pinctrl_dev *pctldev, + unsigned int group) +{ + struct ds90ux9xx_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + + return pctrl->groups[group].name; +} + +static int ds90ux9xx_get_group_pins(struct pinctrl_dev *pctldev, + unsigned int group, + const unsigned int **pins, + unsigned int *num_pins) +{ + struct ds90ux9xx_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + + *pins = pctrl->groups[group].pins; + *num_pins = pctrl->groups[group].npins; + + return 0; +} + +static const struct pinctrl_ops ds90ux9xx_pinctrl_ops = { + .get_groups_count = ds90ux9xx_get_groups_count, + .get_group_name = ds90ux9xx_get_group_name, + .get_group_pins = ds90ux9xx_get_group_pins, + .dt_node_to_map = pinconf_generic_dt_node_to_map_all, + .dt_free_map = pinctrl_utils_free_map, +}; + +static int ds90ux9xx_get_funcs_count(struct pinctrl_dev *pctldev) +{ + struct ds90ux9xx_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + + return pctrl->nfunctions; +} + +static const char *ds90ux9xx_get_func_name(struct pinctrl_dev *pctldev, + unsigned int function) +{ + struct ds90ux9xx_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + + return ds90ux9xx_func_names[pctrl->functions[function].id]; +} + +static int ds90ux9xx_get_func_groups(struct pinctrl_dev *pctldev, + unsigned int function, + const char * const **groups, + unsigned int * const num_groups) +{ + struct ds90ux9xx_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + + *groups = pctrl->functions[function].groups; + *num_groups = pctrl->functions[function].ngroups; + + return 0; +} + +static int ds90ux9xx_gpio_read(struct ds90ux9xx_pinctrl *pctrl, + unsigned int offset, u8 *value) +{ + const struct ds90ux9xx_gpio *gpio; + unsigned int val; + int ret; + + gpio = &pctrl->pins[offset].gpio; + + ret = regmap_read(pctrl->regmap, gpio->cfg_reg, &val); + if (ret) { + dev_err(pctrl->dev, "Failed to read register %#x, gpio %d\n", + gpio->cfg_reg, offset); + return ret; + } + + *value = val & gpio->cfg_mask; + if (gpio->cfg_mask == 0xf0) + *value >>= 4; + + return 0; +} + +static int ds90ux9xx_gpio_read_stat(struct ds90ux9xx_pinctrl *pctrl, + unsigned int offset, u8 *value) +{ + const struct ds90ux9xx_gpio *gpio; + unsigned int val; + int ret; + + gpio = &pctrl->pins[offset].gpio; + + if (!gpio->stat_bit) + return -EINVAL; + + ret = regmap_read(pctrl->regmap, gpio->stat_reg, &val); + if (ret) { + dev_err(pctrl->dev, "Failed to read register %#x, gpio %d\n", + gpio->stat_reg, offset); + return ret; + } + + *value = val & gpio->stat_bit; + + return 0; +} + +static int ds90ux9xx_gpio_write(struct ds90ux9xx_pinctrl *pctrl, + unsigned int offset, u8 value) +{ + const struct ds90ux9xx_gpio *gpio; + int ret; + + gpio = &pctrl->pins[offset].gpio; + + if (value & DIR_INPUT && !gpio->input) + return -EINVAL; + + if (gpio->cfg_mask == 0xf0) + value <<= 4; + + ret = regmap_update_bits(pctrl->regmap, gpio->cfg_reg, + gpio->cfg_mask, value); + if (ret) + dev_err(pctrl->dev, "Failed to modify register %#x, gpio %d\n", + gpio->cfg_reg, offset); + + return ret; +} + +static int ds90ux940_set_pass(struct ds90ux9xx_pinctrl *pctrl, + unsigned int pin, bool enable) +{ + u8 bit = pctrl->pins[pin].pass_bit; + + return ds90ux9xx_update_bits_indirect(pctrl->dev->parent, + DES_REG_INDIRECT_PASS, bit, enable ? bit : 0x0); +} + +static int ds90ux9xx_pinctrl_group_disable(struct ds90ux9xx_pinctrl *pctrl, + enum ds90ux9xx_function function, + unsigned int pin) +{ + int ret; + + switch (function) { + case DS90UX9XX_FUNC_GPIO: + case DS90UX9XX_FUNC_REMOTE: + return ds90ux9xx_gpio_write(pctrl, pin, 0x0); + case DS90UX9XX_FUNC_PASS: + return ds90ux940_set_pass(pctrl, pin, false); + default: + break; + } + + if (!ds90ux9xx_is_serializer(pctrl->dev->parent)) + return 0; + + switch (function) { + case DS90UX9XX_FUNC_PARALLEL: + return regmap_update_bits(pctrl->regmap, SER_REG_PIN_CTRL, + PIN_CTRL_RGB18, PIN_CTRL_RGB18); + case DS90UX9XX_FUNC_I2S_4: + case DS90UX9XX_FUNC_I2S_3: + ret = regmap_update_bits(pctrl->regmap, SER_REG_I2S_SURROUND, + PIN_CTRL_I2S_SURR_BIT, 0x0); + if (ret) + return ret; + /* Fall through */ + case DS90UX9XX_FUNC_I2S_2: + return regmap_update_bits(pctrl->regmap, SER_REG_PIN_CTRL, + PIN_CTRL_I2S_CHANNEL_B, 0x0); + default: + break; + } + + return 0; +} + +static int ds90ux9xx_pinctrl_group_enable(struct ds90ux9xx_pinctrl *pctrl, + enum ds90ux9xx_function function, + struct ds90ux9xx_pin_group *group) +{ + unsigned int pin = group->pins[0]; + int ret; + + switch (function) { + case DS90UX9XX_FUNC_GPIO: + /* Not all GPIOs can be set to input, fallback to output low */ + ret = ds90ux9xx_gpio_write(pctrl, pin, GPIO_AS_INPUT); + if (ret < 0) + ret = ds90ux9xx_gpio_write(pctrl, pin, GPIO_OUTPUT_LOW); + return ret; + case DS90UX9XX_FUNC_REMOTE: + return ds90ux9xx_gpio_write(pctrl, pin, GPIO_OUTPUT_REMOTE); + case DS90UX9XX_FUNC_PASS: + return ds90ux940_set_pass(pctrl, pin, true); + default: + break; + } + + if (!ds90ux9xx_is_serializer(pctrl->dev->parent)) + return 0; + + switch (function) { + case DS90UX9XX_FUNC_PARALLEL: + return regmap_update_bits(pctrl->regmap, SER_REG_PIN_CTRL, + PIN_CTRL_RGB18, 0x0); + case DS90UX9XX_FUNC_I2S_4: + case DS90UX9XX_FUNC_I2S_3: + ret = regmap_update_bits(pctrl->regmap, SER_REG_I2S_SURROUND, + PIN_CTRL_I2S_SURR_BIT, + PIN_CTRL_I2S_SURR_BIT); + if (ret) + return ret; + /* Fall through */ + case DS90UX9XX_FUNC_I2S_2: + return regmap_update_bits(pctrl->regmap, SER_REG_PIN_CTRL, + PIN_CTRL_I2S_CHANNEL_B | PIN_CTRL_I2S_DATA_ISLAND, + PIN_CTRL_I2S_CHANNEL_B); + default: + break; + } + + return 0; +} + +static int ds90ux9xx_pinctrl_func_enable(struct ds90ux9xx_pinctrl *pctrl, + enum ds90ux9xx_function function, + struct ds90ux9xx_pin_group *group) +{ + enum ds90ux9xx_function func; + unsigned int i, pin; + u32 func_mask; + int ret; + + /* Disable probably enabled pin functions with pin resource conflicts */ + for (i = 0; i < group->npins; i++) { + pin = group->pins[i]; + + func_mask = pctrl->pins[pin].func_mask & ~BIT(function); + + /* Abandon remote GPIOs which are covered by GPIO function */ + if (function == DS90UX9XX_FUNC_REMOTE) + func_mask &= ~BIT(DS90UX9XX_FUNC_GPIO); + else + func_mask &= ~BIT(DS90UX9XX_FUNC_REMOTE); + + /* Zero to three possible conflicting pin functions */ + while (func_mask) { + func = __ffs(func_mask); + func_mask &= ~BIT(func); + ret = ds90ux9xx_pinctrl_group_disable(pctrl, func, pin); + if (ret) + return ret; + } + } + + return ds90ux9xx_pinctrl_group_enable(pctrl, function, group); +} + +static int ds90ux9xx_set_mux(struct pinctrl_dev *pctldev, + unsigned int function, unsigned int group) +{ + struct ds90ux9xx_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + enum ds90ux9xx_function func = pctrl->functions[function].id; + struct ds90ux9xx_pin_group *grp = &pctrl->groups[group]; + + return ds90ux9xx_pinctrl_func_enable(pctrl, func, grp); +} + +static int ds90ux9xx_gpio_request_enable(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned int offset) +{ + struct ds90ux9xx_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev); + + return ds90ux9xx_pinctrl_func_enable(pctrl, DS90UX9XX_FUNC_GPIO, + &pctrl->groups[offset]); +} + +static const struct pinmux_ops ds90ux9xx_pinmux_ops = { + .get_functions_count = ds90ux9xx_get_funcs_count, + .get_function_name = ds90ux9xx_get_func_name, + .get_function_groups = ds90ux9xx_get_func_groups, + .set_mux = ds90ux9xx_set_mux, + .gpio_request_enable = ds90ux9xx_gpio_request_enable, + .strict = true, +}; + +static const struct pinctrl_desc ds90ux9xx_pinctrl_desc = { + .pctlops = &ds90ux9xx_pinctrl_ops, + .pmxops = &ds90ux9xx_pinmux_ops, +}; + +static int ds90ux9xx_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ds90ux9xx_pinctrl *pctrl = gpiochip_get_data(chip); + int ret; + u8 val; + + ret = ds90ux9xx_gpio_read(pctrl, offset, &val); + if (ret) + return ret; + + if (!(val & DIR_INPUT)) + return !!(val & OUTPUT_HIGH); + + ret = ds90ux9xx_gpio_read_stat(pctrl, offset, &val); + if (ret) + return ret; + + return !!val; +} + +static void ds90ux9xx_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct ds90ux9xx_pinctrl *pctrl = gpiochip_get_data(chip); + u8 val = value ? GPIO_OUTPUT_HIGH : GPIO_OUTPUT_LOW; + + ds90ux9xx_gpio_write(pctrl, offset, val); +} + +static int ds90ux9xx_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + struct ds90ux9xx_pinctrl *pctrl = gpiochip_get_data(chip); + int ret; + u8 val; + + ret = ds90ux9xx_gpio_read(pctrl, offset, &val); + if (ret) + return ret; + + return !!(val & DIR_INPUT); +} + +static int ds90ux9xx_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct ds90ux9xx_pinctrl *pctrl = gpiochip_get_data(chip); + + return ds90ux9xx_gpio_write(pctrl, offset, GPIO_AS_INPUT); +} + +static int ds90ux9xx_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct ds90ux9xx_pinctrl *pctrl = gpiochip_get_data(chip); + u8 val = value ? GPIO_OUTPUT_HIGH : GPIO_OUTPUT_LOW; + + return ds90ux9xx_gpio_write(pctrl, offset, val); +} + +static const struct gpio_chip ds90ux9xx_gpio_chip = { + .owner = THIS_MODULE, + .get = ds90ux9xx_gpio_get, + .set = ds90ux9xx_gpio_set, + .get_direction = ds90ux9xx_gpio_get_direction, + .direction_input = ds90ux9xx_gpio_direction_input, + .direction_output = ds90ux9xx_gpio_direction_output, + .base = -1, + .can_sleep = 1, + .of_gpio_n_cells = 2, + .of_xlate = of_gpio_simple_xlate, +}; + +static int ds90ux9xx_parse_dt_properties(struct ds90ux9xx_pinctrl *pctrl) +{ + struct device_node *np = pctrl->dev->of_node; + unsigned int val; + + if (!ds90ux9xx_is_serializer(pctrl->dev->parent)) + return 0; + + /* + * Optionally set "Video Color Depth Mode" to RGB18 mode, it may be + * needed if DS90Ux927 serializer is paired with DS90Ux926 deserializer + * or if there is no pins conflicting with the "parallel" pin group + * to disable RGB24 mode implicitly. + */ + if (of_property_read_bool(np, "ti,video-depth-18bit")) + val = PIN_CTRL_RGB18; + else + val = 0; + + return regmap_update_bits(pctrl->regmap, SER_REG_PIN_CTRL, + PIN_CTRL_RGB18, val); +} + +static void ds90ux9xx_get_data(struct ds90ux9xx_pinctrl *pctrl, + const struct ds90ux9xx_pinctrl_data **pctrl_data) +{ + enum ds90ux9xx_device_id id = ds90ux9xx_get_ic_type(pctrl->dev->parent); + + switch (id) { + case TI_DS90UB925: + case TI_DS90UH925: + *pctrl_data = &ds90ux925_pinctrl; + break; + case TI_DS90UB927: + case TI_DS90UH927: + *pctrl_data = &ds90ux927_pinctrl; + break; + case TI_DS90UB926: + case TI_DS90UH926: + *pctrl_data = &ds90ux926_pinctrl; + break; + case TI_DS90UB928: + case TI_DS90UH928: + *pctrl_data = &ds90ux928_pinctrl; + break; + case TI_DS90UB940: + case TI_DS90UH940: + *pctrl_data = &ds90ux940_pinctrl; + break; + default: + dev_err(pctrl->dev, "Unsupported hardware id %d\n", id); + } +} + +static int ds90ux9xx_pinctrl_probe(struct platform_device *pdev) +{ + const struct ds90ux9xx_pinctrl_data *pctrl_data; + struct ds90ux9xx_pinctrl *pctrl; + struct device *dev = &pdev->dev; + int ret; + + pctrl = devm_kzalloc(dev, sizeof(*pctrl), GFP_KERNEL); + if (!pctrl) + return -ENOMEM; + + pctrl->dev = dev; + pctrl->regmap = dev_get_regmap(dev->parent, NULL); + if (!pctrl->regmap) + return -ENODEV; + + pctrl_data = of_device_get_match_data(dev); + if (!pctrl_data) + ds90ux9xx_get_data(pctrl, &pctrl_data); + + if (!pctrl_data) + return -ENODEV; + + ret = ds90ux9xx_populate_groups(pctrl, pctrl_data); + if (ret) + return ret; + + ret = ds90ux9xx_parse_dt_properties(pctrl); + if (ret) + return ret; + + pctrl->desc = ds90ux9xx_pinctrl_desc; + pctrl->desc.name = pctrl_data->name; + pctrl->desc.pins = pctrl_data->pins_desc; + pctrl->desc.npins = pctrl_data->npins; + + pctrl->pctrl = devm_pinctrl_register(dev, &pctrl->desc, pctrl); + if (IS_ERR(pctrl->pctrl)) + return PTR_ERR(pctrl->pctrl); + + platform_set_drvdata(pdev, pctrl); + + pctrl->gpiochip = ds90ux9xx_gpio_chip; + pctrl->gpiochip.parent = dev; + pctrl->gpiochip.label = pctrl_data->name; + pctrl->gpiochip.ngpio = pctrl->ngpios; + + if (of_property_read_bool(dev->of_node, "gpio-ranges")) { + pctrl->gpiochip.request = gpiochip_generic_request; + pctrl->gpiochip.free = gpiochip_generic_free; + } + + return devm_gpiochip_add_data(dev, &pctrl->gpiochip, pctrl); +} + +static const struct of_device_id ds90ux9xx_pinctrl_dt_ids[] = { + { .compatible = "ti,ds90ux9xx-pinctrl", }, + { .compatible = "ti,ds90ux925-pinctrl", .data = &ds90ux925_pinctrl, }, + { .compatible = "ti,ds90ux926-pinctrl", .data = &ds90ux926_pinctrl, }, + { .compatible = "ti,ds90ux927-pinctrl", .data = &ds90ux927_pinctrl, }, + { .compatible = "ti,ds90ux928-pinctrl", .data = &ds90ux928_pinctrl, }, + { .compatible = "ti,ds90ux940-pinctrl", .data = &ds90ux940_pinctrl, }, + { }, +}; +MODULE_DEVICE_TABLE(of, ds90ux9xx_pinctrl_dt_ids); + +static struct platform_driver ds90ux9xx_pinctrl_driver = { + .probe = ds90ux9xx_pinctrl_probe, + .driver = { + .name = "ds90ux9xx-pinctrl", + .of_match_table = ds90ux9xx_pinctrl_dt_ids, + }, +}; +module_platform_driver(ds90ux9xx_pinctrl_driver); + +MODULE_AUTHOR("Vladimir Zapolskiy "); +MODULE_DESCRIPTION("TI DS90Ux9xx pinmux and GPIO controller driver"); +MODULE_LICENSE("GPL"); From patchwork Mon Oct 8 21:12:05 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Zapolskiy X-Patchwork-Id: 980815 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=mleia.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 42TY3d2yffz9sD2 for ; Tue, 9 Oct 2018 08:12:45 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727248AbeJIE0S (ORCPT ); Tue, 9 Oct 2018 00:26:18 -0400 Received: from mleia.com ([178.79.152.223]:35560 "EHLO mail.mleia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726540AbeJIEZ4 (ORCPT ); Tue, 9 Oct 2018 00:25:56 -0400 Received: from mail.mleia.com (localhost [127.0.0.1]) by mail.mleia.com (Postfix) with ESMTP id 8EF2341E2C8; Mon, 8 Oct 2018 22:12:14 +0100 (BST) From: Vladimir Zapolskiy To: Lee Jones , Linus Walleij , Rob Herring Cc: Marek Vasut , Laurent Pinchart , Wolfram Sang , devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-media@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 7/7] MAINTAINERS: add entry for TI DS90Ux9xx FPD-Link III drivers Date: Tue, 9 Oct 2018 00:12:05 +0300 Message-Id: <20181008211205.2900-8-vz@mleia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181008211205.2900-1-vz@mleia.com> References: <20181008211205.2900-1-vz@mleia.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-49551924 X-CRM114-CacheID: sfid-20181008_221214_607095_C4CB41BB X-CRM114-Status: UNSURE ( 9.03 ) X-CRM114-Notice: Please train this message. Sender: linux-gpio-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org Record TI DS90Ux9xx series of serializer and deserializer ICs and IC subcontrollers as maintained. Signed-off-by: Vladimir Zapolskiy --- MAINTAINERS | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 29c08106bd22..3952035b6b71 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14762,6 +14762,16 @@ S: Maintained F: drivers/media/platform/davinci/ F: include/media/davinci/ +TI DS90UX9XX FPD-LINK III SERDES DRIVERS +M: Vladimir Zapolskiy +L: linux-media@vger.kernel.org +S: Maintained +F: drivers/mfd/ds90ux9xx-core.c +F: drivers/mfd/ds90ux9xx-i2c-bridge.c +F: drivers/pinctrl/pinctrl-ds90ux9xx.c +F: include/linux/mfd/ds90ux9xx.h +N: ds90u[bhx]9* + TI ETHERNET SWITCH DRIVER (CPSW) R: Grygorii Strashko L: linux-omap@vger.kernel.org