From 867a05a91cd0480aff1e7389b9af76696915967e Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 22 Oct 2014 12:58:44 +0300 Subject: pinctrl: Device tree bindings for Qualcomm PMIC GPIO block This introduced the device tree bindings for the GPIO block found in PMIC's from Qualcomm. Signed-off-by: Bjorn Andersson Signed-off-by: Ivan T. Ivanov --- .../devicetree/bindings/pinctrl/qcom,pmic-gpio.txt | 215 +++++++++++++++++++++ include/dt-bindings/pinctrl/qcom,pmic-gpio.h | 142 ++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 Documentation/devicetree/bindings/pinctrl/qcom,pmic-gpio.txt create mode 100644 include/dt-bindings/pinctrl/qcom,pmic-gpio.h diff --git a/Documentation/devicetree/bindings/pinctrl/qcom,pmic-gpio.txt b/Documentation/devicetree/bindings/pinctrl/qcom,pmic-gpio.txt new file mode 100644 index 000000000000..7ed08048516a --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/qcom,pmic-gpio.txt @@ -0,0 +1,215 @@ +Qualcomm PMIC GPIO block + +This binding describes the GPIO block(s) found in the 8xxx series of +PMIC's from Qualcomm. + +- compatible: + Usage: required + Value type: + Definition: must be one of: + "qcom,pm8018-gpio" + "qcom,pm8038-gpio" + "qcom,pm8058-gpio" + "qcom,pm8917-gpio" + "qcom,pm8921-gpio" + "qcom,pm8941-gpio" + "qcom,pma8084-gpio" + +- reg: + Usage: required + Value type: + Definition: Register base of the GPIO block and length. + +- interrupts: + Usage: required + Value type: + Definition: Must contain an array of encoded interrupt specifiers for + each available GPIO + +- gpio-controller: + Usage: required + Value type: + Definition: Mark the device node as a GPIO controller + +- #gpio-cells: + Usage: required + Value type: + Definition: Must be 2; + the first cell will be used to define gpio number and the + second denotes the flags for this gpio + +Please refer to ../gpio/gpio.txt and ../interrupt-controller/interrupts.txt for +a general description of GPIO and interrupt bindings. + +Please refer to pinctrl-bindings.txt in this directory for details of the +common pinctrl bindings used by client devices, including the meaning of the +phrase "pin configuration node". + +The pin configuration nodes act as a container for an arbitrary number of +subnodes. Each of these subnodes represents some desired configuration for a +pin or a list of pins. This configuration can include the +mux function to select on those pin(s), and various pin configuration +parameters, as listed below. + + +SUBNODES: + +The name of each subnode is not important; all subnodes should be enumerated +and processed purely based on their content. + +Each subnode only affects those parameters that are explicitly listed. In +other words, a subnode that lists a mux function but no pin configuration +parameters implies no information about any pin configuration parameters. +Similarly, a pin subnode that describes a pullup parameter implies no +information about e.g. the mux function. + +The following generic properties as defined in pinctrl-bindings.txt are valid +to specify in a pin configuration subnode: + +- pins: + Usage: required + Value type: + Definition: List of gpio pins affected by the properties specified in + this subnode. Valid pins are: + gpio1-gpio6 for pm8018 + gpio1-gpio12 for pm8038 + gpio1-gpio40 for pm8058 + gpio1-gpio38 for pm8917 + gpio1-gpio44 for pm8921 + gpio1-gpio36 for pm8941 + gpio1-gpio22 for pma8084 + +- function: + Usage: required + Value type: + Definition: Specify the alternative function to be configured for the + specified pins. Valid values are: + "normal", + "paired", + "func1", + "func2", + "dtest1", + "dtest2", + "dtest3", + "dtest4" + +- bias-disable: + Usage: optional + Value type: + Definition: The specified pins should be configured as no pull. + +- bias-pull-down: + Usage: optional + Value type: + Definition: The specified pins should be configured as pull down. + +- bias-pull-up: + Usage: optional + Value type: + Definition: The specified pins should be configured as pull up. + +- qcom,pull-up-strength: + Usage: optional + Value type: + Definition: Specifies the strength to use for pull up, if selected. + Valid values are; as defined in + : + 1: 30uA (PMIC_GPIO_PULL_UP_30) + 2: 1.5uA (PMIC_GPIO_PULL_UP_1P5) + 3: 31.5uA (PMIC_GPIO_PULL_UP_31P5) + 4: 1.5uA + 30uA boost (PMIC_GPIO_PULL_UP_1P5_30) + If this property is ommited 30uA strength will be used if + pull up is selected + +- bias-high-impedance: + Usage: optional + Value type: + Definition: The specified pins will put in high-Z mode and disabled. + +- input-enable: + Usage: optional + Value type: + Definition: The specified pins are put in input mode. + +- output-high: + Usage: optional + Value type: + Definition: The specified pins are configured in output mode, driven + high. + +- output-low: + Usage: optional + Value type: + Definition: The specified pins are configured in output mode, driven + low. + +- power-source: + Usage: optional + Value type: + Definition: Selects the power source for the specified pins. Valid + power sources are defined per chip in + + +- qcom,drive-strength: + Usage: optional + Value type: + Definition: Selects the drive strength for the specified pins. Value + drive strengths are: + 0: no (PMIC_GPIO_STRENGTH_NO) + 1: high (PMIC_GPIO_STRENGTH_HIGH) 0.9mA @ 1.8V - 1.9mA @ 2.6V + 2: medium (PMIC_GPIO_STRENGTH_MED) 0.6mA @ 1.8V - 1.25mA @ 2.6V + 3: low (PMIC_GPIO_STRENGTH_LOW) 0.15mA @ 1.8V - 0.3mA @ 2.6V + as defined in + +- drive-push-pull: + Usage: optional + Value type: + Definition: The specified pins are configured in push-pull mode. + +- drive-open-drain: + Usage: optional + Value type: + Definition: The specified pins are configured in open-drain mode. + +- drive-open-source: + Usage: optional + Value type: + Definition: The specified pins are configured in open-source mode. + +Example: + + pm8921_gpio: gpio@150 { + compatible = "qcom,pm8921-gpio"; + reg = <0x150 0x160>; + interrupts = <192 1>, <193 1>, <194 1>, + <195 1>, <196 1>, <197 1>, + <198 1>, <199 1>, <200 1>, + <201 1>, <202 1>, <203 1>, + <204 1>, <205 1>, <206 1>, + <207 1>, <208 1>, <209 1>, + <210 1>, <211 1>, <212 1>, + <213 1>, <214 1>, <215 1>, + <216 1>, <217 1>, <218 1>, + <219 1>, <220 1>, <221 1>, + <222 1>, <223 1>, <224 1>, + <225 1>, <226 1>, <227 1>, + <228 1>, <229 1>, <230 1>, + <231 1>, <232 1>, <233 1>, + <234 1>, <235 1>; + + gpio-controller; + #gpio-cells = <2>; + + pm8921_gpio_keys: gpio-keys { + volume-keys { + pins = "gpio20", "gpio21"; + function = "normal"; + + input-enable; + bias-pull-up; + drive-push-pull; + qcom,drive-strength = ; + power-source = ; + }; + }; + }; diff --git a/include/dt-bindings/pinctrl/qcom,pmic-gpio.h b/include/dt-bindings/pinctrl/qcom,pmic-gpio.h new file mode 100644 index 000000000000..fa74d7cc960c --- /dev/null +++ b/include/dt-bindings/pinctrl/qcom,pmic-gpio.h @@ -0,0 +1,142 @@ +/* + * This header provides constants for the Qualcomm PMIC GPIO binding. + */ + +#ifndef _DT_BINDINGS_PINCTRL_QCOM_PMIC_GPIO_H +#define _DT_BINDINGS_PINCTRL_QCOM_PMIC_GPIO_H + +#define PMIC_GPIO_PULL_UP_30 0 +#define PMIC_GPIO_PULL_UP_1P5 1 +#define PMIC_GPIO_PULL_UP_31P5 2 +#define PMIC_GPIO_PULL_UP_1P5_30 3 + +#define PMIC_GPIO_STRENGTH_NO 0 +#define PMIC_GPIO_STRENGTH_HIGH 1 +#define PMIC_GPIO_STRENGTH_MED 2 +#define PMIC_GPIO_STRENGTH_LOW 3 + +/* + * Note: PM8018 GPIO3 and GPIO4 are supporting + * only S3 and L2 options (1.8V) + */ +#define PM8018_GPIO_L6 0 +#define PM8018_GPIO_L5 1 +#define PM8018_GPIO_S3 2 +#define PM8018_GPIO_L14 3 +#define PM8018_GPIO_L2 4 +#define PM8018_GPIO_L4 5 +#define PM8018_GPIO_VDD 6 + +/* + * Note: PM8038 GPIO7 and GPIO8 are supporting + * only L11 and L4 options (1.8V) + */ +#define PM8038_GPIO_VPH 0 +#define PM8038_GPIO_BB 1 +#define PM8038_GPIO_L11 2 +#define PM8038_GPIO_L15 3 +#define PM8038_GPIO_L4 4 +#define PM8038_GPIO_L3 5 +#define PM8038_GPIO_L17 6 + +#define PM8058_GPIO_VPH 0 +#define PM8058_GPIO_BB 1 +#define PM8058_GPIO_S3 2 +#define PM8058_GPIO_L3 3 +#define PM8058_GPIO_L7 4 +#define PM8058_GPIO_L6 5 +#define PM8058_GPIO_L5 6 +#define PM8058_GPIO_L2 7 + +#define PM8917_GPIO_VPH 0 +#define PM8917_GPIO_S4 2 +#define PM8917_GPIO_L15 3 +#define PM8917_GPIO_L4 4 +#define PM8917_GPIO_L3 5 +#define PM8917_GPIO_L17 6 + +#define PM8921_GPIO_VPH 0 +#define PM8921_GPIO_BB 1 +#define PM8921_GPIO_S4 2 +#define PM8921_GPIO_L15 3 +#define PM8921_GPIO_L4 4 +#define PM8921_GPIO_L3 5 +#define PM8921_GPIO_L17 6 + +/* + * Note: PM8941 gpios from 15 to 18 are supporting + * only S3 and L6 options (1.8V) + */ +#define PM8941_GPIO_VPH 0 +#define PM8941_GPIO_L1 1 +#define PM8941_GPIO_S3 2 +#define PM8941_GPIO_L6 3 + +/* + * Note: PMA8084 gpios from 15 to 18 are supporting + * only S4 and L6 options (1.8V) + */ +#define PMA8084_GPIO_VPH 0 +#define PMA8084_GPIO_L1 1 +#define PMA8084_GPIO_S4 2 +#define PMA8084_GPIO_L6 3 + +/* To be used with "function" */ +#define PMIC_GPIO_FUNC_NORMAL "normal" +#define PMIC_GPIO_FUNC_PAIRED "paired" +#define PMIC_GPIO_FUNC_FUNC1 "func1" +#define PMIC_GPIO_FUNC_FUNC2 "func2" +#define PMIC_GPIO_FUNC_DTEST1 "dtest1" +#define PMIC_GPIO_FUNC_DTEST2 "dtest2" +#define PMIC_GPIO_FUNC_DTEST3 "dtest3" +#define PMIC_GPIO_FUNC_DTEST4 "dtest4" + +#define PM8038_GPIO1_2_LPG_DRV PMIC_GPIO_FUNC_FUNC1 +#define PM8038_GPIO3_5V_BOOST_EN PMIC_GPIO_FUNC_FUNC1 +#define PM8038_GPIO4_SSBI_ALT_CLK PMIC_GPIO_FUNC_FUNC1 +#define PM8038_GPIO5_6_EXT_REG_EN PMIC_GPIO_FUNC_FUNC1 +#define PM8038_GPIO10_11_EXT_REG_EN PMIC_GPIO_FUNC_FUNC1 +#define PM8038_GPIO6_7_CLK PMIC_GPIO_FUNC_FUNC1 +#define PM8038_GPIO9_BAT_ALRM_OUT PMIC_GPIO_FUNC_FUNC1 +#define PM8038_GPIO6_12_KYPD_DRV PMIC_GPIO_FUNC_FUNC2 + +#define PM8058_GPIO7_8_MP3_CLK PMIC_GPIO_FUNC_FUNC1 +#define PM8058_GPIO7_8_BCLK_19P2MHZ PMIC_GPIO_FUNC_FUNC2 +#define PM8058_GPIO9_26_KYPD_DRV PMIC_GPIO_FUNC_FUNC1 +#define PM8058_GPIO21_23_UART_TX PMIC_GPIO_FUNC_FUNC2 +#define PM8058_GPIO24_26_LPG_DRV PMIC_GPIO_FUNC_FUNC2 +#define PM8058_GPIO33_BCLK_19P2MHZ PMIC_GPIO_FUNC_FUNC1 +#define PM8058_GPIO34_35_MP3_CLK PMIC_GPIO_FUNC_FUNC1 +#define PM8058_GPIO36_BCLK_19P2MHZ PMIC_GPIO_FUNC_FUNC1 +#define PM8058_GPIO37_UPL_OUT PMIC_GPIO_FUNC_FUNC1 +#define PM8058_GPIO37_UART_M_RX PMIC_GPIO_FUNC_FUNC2 +#define PM8058_GPIO38_XO_SLEEP_CLK PMIC_GPIO_FUNC_FUNC1 +#define PM8058_GPIO38_39_CLK_32KHZ PMIC_GPIO_FUNC_FUNC2 +#define PM8058_GPIO39_MP3_CLK PMIC_GPIO_FUNC_FUNC1 +#define PM8058_GPIO40_EXT_BB_EN PMIC_GPIO_FUNC_FUNC1 + +#define PM8917_GPIO9_18_KEYP_DRV PMIC_GPIO_FUNC_FUNC1 +#define PM8917_GPIO20_BAT_ALRM_OUT PMIC_GPIO_FUNC_FUNC1 +#define PM8917_GPIO21_23_UART_TX PMIC_GPIO_FUNC_FUNC2 +#define PM8917_GPIO25_26_EXT_REG_EN PMIC_GPIO_FUNC_FUNC1 +#define PM8917_GPIO37_38_XO_SLEEP_CLK PMIC_GPIO_FUNC_FUNC1 +#define PM8917_GPIO37_38_MP3_CLK PMIC_GPIO_FUNC_FUNC2 + +#define PM8941_GPIO9_14_KYPD_DRV PMIC_GPIO_FUNC_FUNC1 +#define PM8941_GPIO15_18_DIV_CLK PMIC_GPIO_FUNC_FUNC1 +#define PM8941_GPIO15_18_SLEEP_CLK PMIC_GPIO_FUNC_FUNC2 +#define PM8941_GPIO23_26_KYPD_DRV PMIC_GPIO_FUNC_FUNC1 +#define PM8941_GPIO23_26_LPG_DRV_HI PMIC_GPIO_FUNC_FUNC2 +#define PM8941_GPIO31_BAT_ALRM_OUT PMIC_GPIO_FUNC_FUNC1 +#define PM8941_GPIO33_36_LPG_DRV_3D PMIC_GPIO_FUNC_FUNC1 +#define PM8941_GPIO33_36_LPG_DRV_HI PMIC_GPIO_FUNC_FUNC2 + +#define PMA8084_GPIO4_5_LPG_DRV PMIC_GPIO_FUNC_FUNC1 +#define PMA8084_GPIO7_10_LPG_DRV PMIC_GPIO_FUNC_FUNC1 +#define PMA8084_GPIO5_14_KEYP_DRV PMIC_GPIO_FUNC_FUNC2 +#define PMA8084_GPIO19_21_KEYP_DRV PMIC_GPIO_FUNC_FUNC2 +#define PMA8084_GPIO15_18_DIV_CLK PMIC_GPIO_FUNC_FUNC1 +#define PMA8084_GPIO15_18_SLEEP_CLK PMIC_GPIO_FUNC_FUNC2 +#define PMA8084_GPIO22_BAT_ALRM_OUT PMIC_GPIO_FUNC_FUNC1 + +#endif -- cgit v1.2.3 From 7224c60a650a0b533dd01f9ddb8c56df9d4615c8 Mon Sep 17 00:00:00 2001 From: "Ivan T. Ivanov" Date: Wed, 22 Oct 2014 12:58:45 +0300 Subject: pinctrl: Device tree bindings for Qualcomm PMIC MPP block DeviceTree binding documentation for Qualcomm SPMI PMIC MPP pinctrl drivers. Signed-off-by: Ivan T. Ivanov Acked-by: Bjorn Andersson --- .../devicetree/bindings/pinctrl/qcom,pmic-mpp.txt | 162 +++++++++++++++++++++ include/dt-bindings/pinctrl/qcom,pmic-mpp.h | 44 ++++++ 2 files changed, 206 insertions(+) create mode 100644 Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt create mode 100644 include/dt-bindings/pinctrl/qcom,pmic-mpp.h diff --git a/Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt b/Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt new file mode 100644 index 000000000000..854774b194ed --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt @@ -0,0 +1,162 @@ +Qualcomm PMIC Multi-Purpose Pin (MPP) block + +This binding describes the MPP block(s) found in the 8xxx series +of PMIC's from Qualcomm. + +- compatible: + Usage: required + Value type: + Definition: Should contain one of: + "qcom,pm8841-mpp", + "qcom,pm8941-mpp", + "qcom,pma8084-mpp", + +- reg: + Usage: required + Value type: + Definition: Register base of the MPP block and length. + +- interrupts: + Usage: required + Value type: + Definition: Must contain an array of encoded interrupt specifiers for + each available MPP + +- gpio-controller: + Usage: required + Value type: + Definition: Mark the device node as a GPIO controller + +- #gpio-cells: + Usage: required + Value type: + Definition: Must be 2; + the first cell will be used to define MPP number and the + second denotes the flags for this MPP + +Please refer to ../gpio/gpio.txt and ../interrupt-controller/interrupts.txt for +a general description of GPIO and interrupt bindings. + +Please refer to pinctrl-bindings.txt in this directory for details of the +common pinctrl bindings used by client devices, including the meaning of the +phrase "pin configuration node". + +The pin configuration nodes act as a container for an arbitrary number of +subnodes. Each of these subnodes represents some desired configuration for a +pin or a list of pins. This configuration can include the +mux function to select on those pin(s), and various pin configuration +parameters, as listed below. + +SUBNODES: + +The name of each subnode is not important; all subnodes should be enumerated +and processed purely based on their content. + +Each subnode only affects those parameters that are explicitly listed. In +other words, a subnode that lists a mux function but no pin configuration +parameters implies no information about any pin configuration parameters. +Similarly, a pin subnode that describes a pullup parameter implies no +information about e.g. the mux function. + +The following generic properties as defined in pinctrl-bindings.txt are valid +to specify in a pin configuration subnode: + +- pins: + Usage: required + Value type: + Definition: List of MPP pins affected by the properties specified in + this subnode. Valid pins are: + mpp1-mpp4 for pm8841 + mpp1-mpp8 for pm8941 + mpp1-mpp4 for pma8084 + +- function: + Usage: required + Value type: + Definition: Specify the alternative function to be configured for the + specified pins. Valid values are: + "normal", + "paired", + "dtest1", + "dtest2", + "dtest3", + "dtest4" + +- bias-disable: + Usage: optional + Value type: + Definition: The specified pins should be configured as no pull. + +- bias-pull-up: + Usage: optional + Value type: + Definition: The specified pins should be configured as pull up. + Valid values are 600, 10000 and 30000 in bidirectional mode + only, i.e. when operating in qcom,analog-mode and input and + outputs are enabled. The hardware ignores the configuration + when operating in other modes. + +- bias-high-impedance: + Usage: optional + Value type: + Definition: The specified pins will put in high-Z mode and disabled. + +- input-enable: + Usage: optional + Value type: + Definition: The specified pins are put in input mode, i.e. their input + buffer is enabled + +- output-high: + Usage: optional + Value type: + Definition: The specified pins are configured in output mode, driven + high. + +- output-low: + Usage: optional + Value type: + Definition: The specified pins are configured in output mode, driven + low. + +- power-source: + Usage: optional + Value type: + Definition: Selects the power source for the specified pins. Valid power + sources are defined in + +- qcom,analog-mode: + Usage: optional + Value type: + Definition: Selects Analog mode of operation: combined with input-enable + and/or output-high, output-low MPP could operate as + Bidirectional Logic, Analog Input, Analog Output. + +- qcom,amux-route: + Usage: optional + Value type: + Definition: Selects the source for analog input. Valid values are + defined in + PMIC_MPP_AMUX_ROUTE_CH5, PMIC_MPP_AMUX_ROUTE_CH6... + +Example: + + mpps@a000 { + compatible = "qcom,pm8841-mpp"; + reg = <0xa000>; + gpio-controller; + #gpio-cells = <2>; + interrupts = <4 0xa0 0 0>, <4 0xa1 0 0>, <4 0xa2 0 0>, <4 0xa3 0 0>; + + pinctrl-names = "default"; + pinctrl-0 = <&pm8841_default>; + + pm8841_default: default { + gpio { + pins = "mpp1", "mpp2", "mpp3", "mpp4"; + function = "normal"; + input-enable; + power-source = ; + }; + }; + }; diff --git a/include/dt-bindings/pinctrl/qcom,pmic-mpp.h b/include/dt-bindings/pinctrl/qcom,pmic-mpp.h new file mode 100644 index 000000000000..d2c7dabe3223 --- /dev/null +++ b/include/dt-bindings/pinctrl/qcom,pmic-mpp.h @@ -0,0 +1,44 @@ +/* + * This header provides constants for the Qualcomm PMIC's + * Multi-Purpose Pin binding. + */ + +#ifndef _DT_BINDINGS_PINCTRL_QCOM_PMIC_MPP_H +#define _DT_BINDINGS_PINCTRL_QCOM_PMIC_MPP_H + +/* power-source */ +#define PM8841_MPP_VPH 0 +#define PM8841_MPP_S3 2 + +#define PM8941_MPP_VPH 0 +#define PM8941_MPP_L1 1 +#define PM8941_MPP_S3 2 +#define PM8941_MPP_L6 3 + +#define PMA8084_MPP_VPH 0 +#define PMA8084_MPP_L1 1 +#define PMA8084_MPP_S4 2 +#define PMA8084_MPP_L6 3 + +/* + * Analog Input - Set the source for analog input. + * To be used with "qcom,amux-route" property + */ +#define PMIC_MPP_AMUX_ROUTE_CH5 0 +#define PMIC_MPP_AMUX_ROUTE_CH6 1 +#define PMIC_MPP_AMUX_ROUTE_CH7 2 +#define PMIC_MPP_AMUX_ROUTE_CH8 3 +#define PMIC_MPP_AMUX_ROUTE_ABUS1 4 +#define PMIC_MPP_AMUX_ROUTE_ABUS2 5 +#define PMIC_MPP_AMUX_ROUTE_ABUS3 6 +#define PMIC_MPP_AMUX_ROUTE_ABUS4 7 + +/* To be used with "function" */ +#define PMIC_MPP_FUNC_NORMAL "normal" +#define PMIC_MPP_FUNC_PAIRED "paired" +#define PMIC_MPP_FUNC_DTEST1 "dtest1" +#define PMIC_MPP_FUNC_DTEST2 "dtest2" +#define PMIC_MPP_FUNC_DTEST3 "dtest3" +#define PMIC_MPP_FUNC_DTEST4 "dtest4" + +#endif -- cgit v1.2.3 From 93b9dbc8f7207f581dd4e646082fdd2e382fe656 Mon Sep 17 00:00:00 2001 From: "Ivan T. Ivanov" Date: Wed, 22 Oct 2014 12:58:46 +0300 Subject: pinctrl: Qualcomm SPMI PMIC GPIO pin controller driver This is the pinctrl, pinmux, pinconf and gpiolib driver for the Qualcomm GPIO sub-function blocks found in the PMIC chips. Signed-off-by: Ivan T. Ivanov Acked-by: Bjorn Andersson --- drivers/pinctrl/qcom/Kconfig | 13 + drivers/pinctrl/qcom/Makefile | 1 + drivers/pinctrl/qcom/pinctrl-spmi-gpio.c | 933 +++++++++++++++++++++++++++++++ 3 files changed, 947 insertions(+) create mode 100644 drivers/pinctrl/qcom/pinctrl-spmi-gpio.c diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig index 81275af9638b..3cd243c26b7d 100644 --- a/drivers/pinctrl/qcom/Kconfig +++ b/drivers/pinctrl/qcom/Kconfig @@ -47,4 +47,17 @@ config PINCTRL_MSM8X74 This is the pinctrl, pinmux, pinconf and gpiolib driver for the Qualcomm TLMM block found in the Qualcomm 8974 platform. +config PINCTRL_QCOM_SPMI_PMIC + tristate "Qualcomm SPMI PMIC pin controller driver" + depends on GPIOLIB && OF && SPMI + select REGMAP_SPMI + select PINMUX + select PINCONF + select GENERIC_PINCONF + help + This is the pinctrl, pinmux, pinconf and gpiolib driver for the + Qualcomm GPIO and MPP blocks found in the Qualcomm PMIC's chips, + which are using SPMI for communication with SoC. Example PMIC's + devices are pm8841, pm8941 and pma8084. + endif diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile index ba8519fcd8d3..9b49c65a1cab 100644 --- a/drivers/pinctrl/qcom/Makefile +++ b/drivers/pinctrl/qcom/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_PINCTRL_APQ8084) += pinctrl-apq8084.o obj-$(CONFIG_PINCTRL_IPQ8064) += pinctrl-ipq8064.o obj-$(CONFIG_PINCTRL_MSM8960) += pinctrl-msm8960.o obj-$(CONFIG_PINCTRL_MSM8X74) += pinctrl-msm8x74.o +obj-$(CONFIG_PINCTRL_QCOM_SPMI_PMIC) += pinctrl-spmi-gpio.o diff --git a/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c b/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c new file mode 100644 index 000000000000..b863b5080890 --- /dev/null +++ b/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c @@ -0,0 +1,933 @@ +/* + * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../core.h" +#include "../pinctrl-utils.h" + +#define PMIC_GPIO_ADDRESS_RANGE 0x100 + +/* type and subtype registers base address offsets */ +#define PMIC_GPIO_REG_TYPE 0x4 +#define PMIC_GPIO_REG_SUBTYPE 0x5 + +/* GPIO peripheral type and subtype out_values */ +#define PMIC_GPIO_TYPE 0x10 +#define PMIC_GPIO_SUBTYPE_GPIO_4CH 0x1 +#define PMIC_GPIO_SUBTYPE_GPIOC_4CH 0x5 +#define PMIC_GPIO_SUBTYPE_GPIO_8CH 0x9 +#define PMIC_GPIO_SUBTYPE_GPIOC_8CH 0xd + +#define PMIC_MPP_REG_RT_STS 0x10 +#define PMIC_MPP_REG_RT_STS_VAL_MASK 0x1 + +/* control register base address offsets */ +#define PMIC_GPIO_REG_MODE_CTL 0x40 +#define PMIC_GPIO_REG_DIG_VIN_CTL 0x41 +#define PMIC_GPIO_REG_DIG_PULL_CTL 0x42 +#define PMIC_GPIO_REG_DIG_OUT_CTL 0x45 +#define PMIC_GPIO_REG_EN_CTL 0x46 + +/* PMIC_GPIO_REG_MODE_CTL */ +#define PMIC_GPIO_REG_MODE_VALUE_SHIFT 0x1 +#define PMIC_GPIO_REG_MODE_FUNCTION_SHIFT 1 +#define PMIC_GPIO_REG_MODE_FUNCTION_MASK 0x7 +#define PMIC_GPIO_REG_MODE_DIR_SHIFT 4 +#define PMIC_GPIO_REG_MODE_DIR_MASK 0x7 + +/* PMIC_GPIO_REG_DIG_VIN_CTL */ +#define PMIC_GPIO_REG_VIN_SHIFT 0 +#define PMIC_GPIO_REG_VIN_MASK 0x7 + +/* PMIC_GPIO_REG_DIG_PULL_CTL */ +#define PMIC_GPIO_REG_PULL_SHIFT 0 +#define PMIC_GPIO_REG_PULL_MASK 0x7 + +#define PMIC_GPIO_PULL_DOWN 4 +#define PMIC_GPIO_PULL_DISABLE 5 + +/* PMIC_GPIO_REG_DIG_OUT_CTL */ +#define PMIC_GPIO_REG_OUT_STRENGTH_SHIFT 0 +#define PMIC_GPIO_REG_OUT_STRENGTH_MASK 0x3 +#define PMIC_GPIO_REG_OUT_TYPE_SHIFT 4 +#define PMIC_GPIO_REG_OUT_TYPE_MASK 0x3 + +/* + * Output type - indicates pin should be configured as push-pull, + * open drain or open source. + */ +#define PMIC_GPIO_OUT_BUF_CMOS 0 +#define PMIC_GPIO_OUT_BUF_OPEN_DRAIN_NMOS 1 +#define PMIC_GPIO_OUT_BUF_OPEN_DRAIN_PMOS 2 + +/* PMIC_GPIO_REG_EN_CTL */ +#define PMIC_GPIO_REG_MASTER_EN_SHIFT 7 + +#define PMIC_GPIO_PHYSICAL_OFFSET 1 + +/* Qualcomm specific pin configurations */ +#define PMIC_GPIO_CONF_PULL_UP (PIN_CONFIG_END + 1) +#define PMIC_GPIO_CONF_STRENGTH (PIN_CONFIG_END + 2) + +/** + * struct pmic_gpio_pad - keep current GPIO settings + * @base: Address base in SPMI device. + * @irq: IRQ number which this GPIO generate. + * @is_enabled: Set to false when GPIO should be put in high Z state. + * @out_value: Cached pin output value + * @have_buffer: Set to true if GPIO output could be configured in push-pull, + * open-drain or open-source mode. + * @output_enabled: Set to true if GPIO output logic is enabled. + * @input_enabled: Set to true if GPIO input buffer logic is enabled. + * @num_sources: Number of power-sources supported by this GPIO. + * @power_source: Current power-source used. + * @buffer_type: Push-pull, open-drain or open-source. + * @pullup: Constant current which flow trough GPIO output buffer. + * @strength: No, Low, Medium, High + * @function: See pmic_gpio_functions[] + */ +struct pmic_gpio_pad { + u16 base; + int irq; + bool is_enabled; + bool out_value; + bool have_buffer; + bool output_enabled; + bool input_enabled; + unsigned int num_sources; + unsigned int power_source; + unsigned int buffer_type; + unsigned int pullup; + unsigned int strength; + unsigned int function; +}; + +struct pmic_gpio_state { + struct device *dev; + struct regmap *map; + struct pinctrl_dev *ctrl; + struct gpio_chip chip; +}; + +struct pmic_gpio_bindings { + const char *property; + unsigned param; +}; + +static struct pmic_gpio_bindings pmic_gpio_bindings[] = { + {"qcom,pull-up-strength", PMIC_GPIO_CONF_PULL_UP}, + {"qcom,drive-strength", PMIC_GPIO_CONF_STRENGTH}, +}; + +static const char *const pmic_gpio_groups[] = { + "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7", "gpio8", + "gpio9", "gpio10", "gpio11", "gpio12", "gpio13", "gpio14", "gpio15", + "gpio16", "gpio17", "gpio18", "gpio19", "gpio20", "gpio21", "gpio22", + "gpio23", "gpio24", "gpio25", "gpio26", "gpio27", "gpio28", "gpio29", + "gpio30", "gpio31", "gpio32", "gpio33", "gpio34", "gpio35", "gpio36", +}; + +static const char *const pmic_gpio_functions[] = { + PMIC_GPIO_FUNC_NORMAL, PMIC_GPIO_FUNC_PAIRED, + PMIC_GPIO_FUNC_FUNC1, PMIC_GPIO_FUNC_FUNC2, + PMIC_GPIO_FUNC_DTEST1, PMIC_GPIO_FUNC_DTEST2, + PMIC_GPIO_FUNC_DTEST3, PMIC_GPIO_FUNC_DTEST4, +}; + +static inline struct pmic_gpio_state *to_gpio_state(struct gpio_chip *chip) +{ + return container_of(chip, struct pmic_gpio_state, chip); +}; + +static int pmic_gpio_read(struct pmic_gpio_state *state, + struct pmic_gpio_pad *pad, unsigned int addr) +{ + unsigned int val; + int ret; + + ret = regmap_read(state->map, pad->base + addr, &val); + if (ret < 0) + dev_err(state->dev, "read 0x%x failed\n", addr); + else + ret = val; + + return ret; +} + +static int pmic_gpio_write(struct pmic_gpio_state *state, + struct pmic_gpio_pad *pad, unsigned int addr, + unsigned int val) +{ + int ret; + + ret = regmap_write(state->map, pad->base + addr, val); + if (ret < 0) + dev_err(state->dev, "write 0x%x failed\n", addr); + + return ret; +} + +static int pmic_gpio_get_groups_count(struct pinctrl_dev *pctldev) +{ + /* Every PIN is a group */ + return pctldev->desc->npins; +} + +static const char *pmic_gpio_get_group_name(struct pinctrl_dev *pctldev, + unsigned pin) +{ + return pctldev->desc->pins[pin].name; +} + +static int pmic_gpio_get_group_pins(struct pinctrl_dev *pctldev, unsigned pin, + const unsigned **pins, unsigned *num_pins) +{ + *pins = &pctldev->desc->pins[pin].number; + *num_pins = 1; + return 0; +} + +static int pmic_gpio_parse_dt_config(struct device_node *np, + struct pinctrl_dev *pctldev, + unsigned long **configs, + unsigned int *nconfs) +{ + struct pmic_gpio_bindings *par; + unsigned long cfg; + int ret, i; + u32 val; + + for (i = 0; i < ARRAY_SIZE(pmic_gpio_bindings); i++) { + par = &pmic_gpio_bindings[i]; + ret = of_property_read_u32(np, par->property, &val); + + /* property not found */ + if (ret == -EINVAL) + continue; + + /* use zero as default value */ + if (ret) + val = 0; + + dev_dbg(pctldev->dev, "found %s with value %u\n", + par->property, val); + + cfg = pinconf_to_config_packed(par->param, val); + + ret = pinctrl_utils_add_config(pctldev, configs, nconfs, cfg); + if (ret) + return ret; + } + + return 0; +} + +static int pmic_gpio_dt_subnode_to_map(struct pinctrl_dev *pctldev, + struct device_node *np, + struct pinctrl_map **map, + unsigned *reserv, unsigned *nmaps, + enum pinctrl_map_type type) +{ + unsigned long *configs = NULL; + unsigned nconfs = 0; + struct property *prop; + const char *group; + int ret; + + ret = pmic_gpio_parse_dt_config(np, pctldev, &configs, &nconfs); + if (ret < 0) + return ret; + + if (!nconfs) + return 0; + + ret = of_property_count_strings(np, "pins"); + if (ret < 0) + goto exit; + + ret = pinctrl_utils_reserve_map(pctldev, map, reserv, nmaps, ret); + if (ret < 0) + goto exit; + + of_property_for_each_string(np, "pins", prop, group) { + ret = pinctrl_utils_add_map_configs(pctldev, map, + reserv, nmaps, group, + configs, nconfs, type); + if (ret < 0) + break; + } +exit: + kfree(configs); + return ret; +} + +static int pmic_gpio_dt_node_to_map(struct pinctrl_dev *pctldev, + struct device_node *np_config, + struct pinctrl_map **map, unsigned *nmaps) +{ + enum pinctrl_map_type type; + struct device_node *np; + unsigned reserv; + int ret; + + ret = 0; + *map = NULL; + *nmaps = 0; + reserv = 0; + type = PIN_MAP_TYPE_CONFIGS_GROUP; + + for_each_child_of_node(np_config, np) { + ret = pinconf_generic_dt_subnode_to_map(pctldev, np, map, + &reserv, nmaps, type); + if (ret) + break; + + ret = pmic_gpio_dt_subnode_to_map(pctldev, np, map, &reserv, + nmaps, type); + if (ret) + break; + } + + if (ret < 0) + pinctrl_utils_dt_free_map(pctldev, *map, *nmaps); + + return ret; +} + +static const struct pinctrl_ops pmic_gpio_pinctrl_ops = { + .get_groups_count = pmic_gpio_get_groups_count, + .get_group_name = pmic_gpio_get_group_name, + .get_group_pins = pmic_gpio_get_group_pins, + .dt_node_to_map = pmic_gpio_dt_node_to_map, + .dt_free_map = pinctrl_utils_dt_free_map, +}; + +static int pmic_gpio_get_functions_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(pmic_gpio_functions); +} + +static const char *pmic_gpio_get_function_name(struct pinctrl_dev *pctldev, + unsigned function) +{ + return pmic_gpio_functions[function]; +} + +static int pmic_gpio_get_function_groups(struct pinctrl_dev *pctldev, + unsigned function, + const char *const **groups, + unsigned *const num_qgroups) +{ + *groups = pmic_gpio_groups; + *num_qgroups = pctldev->desc->npins; + return 0; +} + +static int pmic_gpio_set_mux(struct pinctrl_dev *pctldev, unsigned function, + unsigned pin) +{ + struct pmic_gpio_state *state = pinctrl_dev_get_drvdata(pctldev); + struct pmic_gpio_pad *pad; + unsigned int val; + int ret; + + pad = pctldev->desc->pins[pin].drv_data; + + pad->function = function; + + val = 0; + if (pad->output_enabled) { + if (pad->input_enabled) + val = 2; + else + val = 1; + } + + val |= pad->function << PMIC_GPIO_REG_MODE_FUNCTION_SHIFT; + val |= pad->out_value & PMIC_GPIO_REG_MODE_VALUE_SHIFT; + + ret = pmic_gpio_write(state, pad, PMIC_GPIO_REG_MODE_CTL, val); + if (ret < 0) + return ret; + + val = pad->is_enabled << PMIC_GPIO_REG_MASTER_EN_SHIFT; + + return pmic_gpio_write(state, pad, PMIC_GPIO_REG_EN_CTL, val); +} + +static const struct pinmux_ops pmic_gpio_pinmux_ops = { + .get_functions_count = pmic_gpio_get_functions_count, + .get_function_name = pmic_gpio_get_function_name, + .get_function_groups = pmic_gpio_get_function_groups, + .set_mux = pmic_gpio_set_mux, +}; + +static int pmic_gpio_config_get(struct pinctrl_dev *pctldev, + unsigned int pin, unsigned long *config) +{ + unsigned param = pinconf_to_config_param(*config); + struct pmic_gpio_pad *pad; + unsigned arg; + + pad = pctldev->desc->pins[pin].drv_data; + + switch (param) { + case PIN_CONFIG_DRIVE_PUSH_PULL: + arg = pad->buffer_type == PMIC_GPIO_OUT_BUF_CMOS; + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + arg = pad->buffer_type == PMIC_GPIO_OUT_BUF_OPEN_DRAIN_NMOS; + break; + case PIN_CONFIG_DRIVE_OPEN_SOURCE: + arg = pad->buffer_type == PMIC_GPIO_OUT_BUF_OPEN_DRAIN_PMOS; + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + arg = pad->pullup == PMIC_GPIO_PULL_DOWN; + break; + case PIN_CONFIG_BIAS_DISABLE: + arg = pad->pullup = PMIC_GPIO_PULL_DISABLE; + break; + case PIN_CONFIG_BIAS_PULL_UP: + arg = pad->pullup == PMIC_GPIO_PULL_UP_30; + break; + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + arg = !pad->is_enabled; + break; + case PIN_CONFIG_POWER_SOURCE: + arg = pad->power_source; + break; + case PIN_CONFIG_INPUT_ENABLE: + arg = pad->input_enabled; + break; + case PIN_CONFIG_OUTPUT: + arg = pad->out_value; + break; + case PMIC_GPIO_CONF_PULL_UP: + arg = pad->pullup; + break; + case PMIC_GPIO_CONF_STRENGTH: + arg = pad->strength; + break; + default: + return -EINVAL; + } + + *config = pinconf_to_config_packed(param, arg); + return 0; +} + +static int pmic_gpio_config_set(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *configs, unsigned nconfs) +{ + struct pmic_gpio_state *state = pinctrl_dev_get_drvdata(pctldev); + struct pmic_gpio_pad *pad; + unsigned param, arg; + unsigned int val; + int i, ret; + + pad = pctldev->desc->pins[pin].drv_data; + + for (i = 0; i < nconfs; i++) { + param = pinconf_to_config_param(configs[i]); + arg = pinconf_to_config_argument(configs[i]); + + switch (param) { + case PIN_CONFIG_DRIVE_PUSH_PULL: + pad->buffer_type = PMIC_GPIO_OUT_BUF_CMOS; + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + if (!pad->have_buffer) + return -EINVAL; + pad->buffer_type = PMIC_GPIO_OUT_BUF_OPEN_DRAIN_NMOS; + break; + case PIN_CONFIG_DRIVE_OPEN_SOURCE: + if (!pad->have_buffer) + return -EINVAL; + pad->buffer_type = PMIC_GPIO_OUT_BUF_OPEN_DRAIN_PMOS; + break; + case PIN_CONFIG_BIAS_DISABLE: + pad->pullup = PMIC_GPIO_PULL_DISABLE; + break; + case PIN_CONFIG_BIAS_PULL_UP: + pad->pullup = PMIC_GPIO_PULL_UP_30; + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + if (arg) + pad->pullup = PMIC_GPIO_PULL_DOWN; + else + pad->pullup = PMIC_GPIO_PULL_DISABLE; + break; + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + pad->is_enabled = false; + break; + case PIN_CONFIG_POWER_SOURCE: + if (arg > pad->num_sources) + return -EINVAL; + pad->power_source = arg; + break; + case PIN_CONFIG_INPUT_ENABLE: + pad->input_enabled = arg ? true : false; + break; + case PIN_CONFIG_OUTPUT: + pad->output_enabled = true; + pad->out_value = arg; + break; + case PMIC_GPIO_CONF_PULL_UP: + if (arg > PMIC_GPIO_PULL_UP_1P5_30) + return -EINVAL; + pad->pullup = arg; + break; + case PMIC_GPIO_CONF_STRENGTH: + if (arg > PMIC_GPIO_STRENGTH_LOW) + return -EINVAL; + pad->strength = arg; + break; + default: + return -EINVAL; + } + } + + val = pad->power_source << PMIC_GPIO_REG_VIN_SHIFT; + + ret = pmic_gpio_write(state, pad, PMIC_GPIO_REG_DIG_VIN_CTL, val); + if (ret < 0) + return ret; + + val = pad->pullup << PMIC_GPIO_REG_PULL_SHIFT; + + ret = pmic_gpio_write(state, pad, PMIC_GPIO_REG_DIG_PULL_CTL, val); + if (ret < 0) + return ret; + + val = pad->buffer_type << PMIC_GPIO_REG_OUT_TYPE_SHIFT; + val = pad->strength << PMIC_GPIO_REG_OUT_STRENGTH_SHIFT; + + ret = pmic_gpio_write(state, pad, PMIC_GPIO_REG_DIG_OUT_CTL, val); + if (ret < 0) + return ret; + + val = 0; + if (pad->output_enabled) { + if (pad->input_enabled) + val = 2; + else + val = 1; + } + + val = val << PMIC_GPIO_REG_MODE_DIR_SHIFT; + val |= pad->function << PMIC_GPIO_REG_MODE_FUNCTION_SHIFT; + val |= pad->out_value & PMIC_GPIO_REG_MODE_VALUE_SHIFT; + + return pmic_gpio_write(state, pad, PMIC_GPIO_REG_MODE_CTL, val); +} + +static void pmic_gpio_config_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, unsigned pin) +{ + struct pmic_gpio_state *state = pinctrl_dev_get_drvdata(pctldev); + struct pmic_gpio_pad *pad; + int ret, val; + + static const char *const biases[] = { + "pull-up 30uA", "pull-up 1.5uA", "pull-up 31.5uA", + "pull-up 1.5uA + 30uA boost", "pull-down 10uA", "no pull" + }; + static const char *const buffer_types[] = { + "push-pull", "open-drain", "open-source" + }; + static const char *const strengths[] = { + "no", "high", "medium", "low" + }; + + pad = pctldev->desc->pins[pin].drv_data; + + seq_printf(s, " gpio%-2d:", pin + PMIC_GPIO_PHYSICAL_OFFSET); + + val = pmic_gpio_read(state, pad, PMIC_GPIO_REG_EN_CTL); + + if (val < 0 || !(val >> PMIC_GPIO_REG_MASTER_EN_SHIFT)) { + seq_puts(s, " ---"); + } else { + + if (!pad->input_enabled) { + ret = pmic_gpio_read(state, pad, PMIC_MPP_REG_RT_STS); + if (!ret) { + ret &= PMIC_MPP_REG_RT_STS_VAL_MASK; + pad->out_value = ret; + } + } + + seq_printf(s, " %-4s", pad->output_enabled ? "out" : "in"); + seq_printf(s, " %-7s", pmic_gpio_functions[pad->function]); + seq_printf(s, " vin-%d", pad->power_source); + seq_printf(s, " %-27s", biases[pad->pullup]); + seq_printf(s, " %-10s", buffer_types[pad->buffer_type]); + seq_printf(s, " %-4s", pad->out_value ? "high" : "low"); + seq_printf(s, " %-7s", strengths[pad->strength]); + } +} + +static const struct pinconf_ops pmic_gpio_pinconf_ops = { + .pin_config_group_get = pmic_gpio_config_get, + .pin_config_group_set = pmic_gpio_config_set, + .pin_config_group_dbg_show = pmic_gpio_config_dbg_show, +}; + +static int pmic_gpio_direction_input(struct gpio_chip *chip, unsigned pin) +{ + struct pmic_gpio_state *state = to_gpio_state(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1); + + return pmic_gpio_config_set(state->ctrl, pin, &config, 1); +} + +static int pmic_gpio_direction_output(struct gpio_chip *chip, + unsigned pin, int val) +{ + struct pmic_gpio_state *state = to_gpio_state(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, val); + + return pmic_gpio_config_set(state->ctrl, pin, &config, 1); +} + +static int pmic_gpio_get(struct gpio_chip *chip, unsigned pin) +{ + struct pmic_gpio_state *state = to_gpio_state(chip); + struct pmic_gpio_pad *pad; + int ret; + + pad = state->ctrl->desc->pins[pin].drv_data; + + if (!pad->is_enabled) + return -EINVAL; + + if (pad->input_enabled) { + ret = pmic_gpio_read(state, pad, PMIC_MPP_REG_RT_STS); + if (ret < 0) + return ret; + + pad->out_value = ret & PMIC_MPP_REG_RT_STS_VAL_MASK; + } + + return pad->out_value; +} + +static void pmic_gpio_set(struct gpio_chip *chip, unsigned pin, int value) +{ + struct pmic_gpio_state *state = to_gpio_state(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value); + + pmic_gpio_config_set(state->ctrl, pin, &config, 1); +} + +static int pmic_gpio_request(struct gpio_chip *chip, unsigned base) +{ + return pinctrl_request_gpio(chip->base + base); +} + +static void pmic_gpio_free(struct gpio_chip *chip, unsigned base) +{ + pinctrl_free_gpio(chip->base + base); +} + +static int pmic_gpio_of_xlate(struct gpio_chip *chip, + const struct of_phandle_args *gpio_desc, + u32 *flags) +{ + if (chip->of_gpio_n_cells < 2) + return -EINVAL; + + if (flags) + *flags = gpio_desc->args[1]; + + return gpio_desc->args[0] - PMIC_GPIO_PHYSICAL_OFFSET; +} + +static int pmic_gpio_to_irq(struct gpio_chip *chip, unsigned pin) +{ + struct pmic_gpio_state *state = to_gpio_state(chip); + struct pmic_gpio_pad *pad; + + pad = state->ctrl->desc->pins[pin].drv_data; + + return pad->irq; +} + +static void pmic_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + struct pmic_gpio_state *state = to_gpio_state(chip); + unsigned i; + + for (i = 0; i < chip->ngpio; i++) { + pmic_gpio_config_dbg_show(state->ctrl, s, i); + seq_puts(s, "\n"); + } +} + +static const struct gpio_chip pmic_gpio_gpio_template = { + .direction_input = pmic_gpio_direction_input, + .direction_output = pmic_gpio_direction_output, + .get = pmic_gpio_get, + .set = pmic_gpio_set, + .request = pmic_gpio_request, + .free = pmic_gpio_free, + .of_xlate = pmic_gpio_of_xlate, + .to_irq = pmic_gpio_to_irq, + .dbg_show = pmic_gpio_dbg_show, +}; + +static int pmic_gpio_populate(struct pmic_gpio_state *state, + struct pmic_gpio_pad *pad) +{ + int type, subtype, val, dir; + + type = pmic_gpio_read(state, pad, PMIC_GPIO_REG_TYPE); + if (type < 0) + return type; + + if (type != PMIC_GPIO_TYPE) { + dev_err(state->dev, "incorrect block type 0x%x at 0x%x\n", + type, pad->base); + return -ENODEV; + } + + subtype = pmic_gpio_read(state, pad, PMIC_GPIO_REG_SUBTYPE); + if (subtype < 0) + return subtype; + + switch (subtype) { + case PMIC_GPIO_SUBTYPE_GPIO_4CH: + pad->have_buffer = true; + case PMIC_GPIO_SUBTYPE_GPIOC_4CH: + pad->num_sources = 4; + break; + case PMIC_GPIO_SUBTYPE_GPIO_8CH: + pad->have_buffer = true; + case PMIC_GPIO_SUBTYPE_GPIOC_8CH: + pad->num_sources = 8; + break; + default: + dev_err(state->dev, "unknown GPIO type 0x%x\n", subtype); + return -ENODEV; + } + + val = pmic_gpio_read(state, pad, PMIC_GPIO_REG_MODE_CTL); + if (val < 0) + return val; + + pad->out_value = val & PMIC_GPIO_REG_MODE_VALUE_SHIFT; + + dir = val >> PMIC_GPIO_REG_MODE_DIR_SHIFT; + dir &= PMIC_GPIO_REG_MODE_DIR_MASK; + switch (dir) { + case 0: + pad->input_enabled = true; + pad->output_enabled = false; + break; + case 1: + pad->input_enabled = false; + pad->output_enabled = true; + break; + case 2: + pad->input_enabled = true; + pad->output_enabled = true; + break; + default: + dev_err(state->dev, "unknown GPIO direction\n"); + return -ENODEV; + } + + pad->function = val >> PMIC_GPIO_REG_MODE_FUNCTION_SHIFT; + pad->function &= PMIC_GPIO_REG_MODE_FUNCTION_MASK; + + val = pmic_gpio_read(state, pad, PMIC_GPIO_REG_DIG_VIN_CTL); + if (val < 0) + return val; + + pad->power_source = val >> PMIC_GPIO_REG_VIN_SHIFT; + pad->power_source &= PMIC_GPIO_REG_VIN_MASK; + + val = pmic_gpio_read(state, pad, PMIC_GPIO_REG_DIG_PULL_CTL); + if (val < 0) + return val; + + pad->pullup = val >> PMIC_GPIO_REG_PULL_SHIFT; + pad->pullup &= PMIC_GPIO_REG_PULL_MASK; + + val = pmic_gpio_read(state, pad, PMIC_GPIO_REG_DIG_OUT_CTL); + if (val < 0) + return val; + + pad->strength = val >> PMIC_GPIO_REG_OUT_STRENGTH_SHIFT; + pad->strength &= PMIC_GPIO_REG_OUT_STRENGTH_MASK; + + pad->buffer_type = val >> PMIC_GPIO_REG_OUT_TYPE_SHIFT; + pad->buffer_type &= PMIC_GPIO_REG_OUT_TYPE_MASK; + + /* Pin could be disabled with PIN_CONFIG_BIAS_HIGH_IMPEDANCE */ + pad->is_enabled = true; + return 0; +} + +static int pmic_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pinctrl_pin_desc *pindesc; + struct pinctrl_desc *pctrldesc; + struct pmic_gpio_pad *pad, *pads; + struct pmic_gpio_state *state; + int ret, npins, i; + u32 res[2]; + + ret = of_property_read_u32_array(dev->of_node, "reg", res, 2); + if (ret < 0) { + dev_err(dev, "missing base address and/or range"); + return ret; + } + + npins = res[1] / PMIC_GPIO_ADDRESS_RANGE; + + if (!npins) + return -EINVAL; + + BUG_ON(npins > ARRAY_SIZE(pmic_gpio_groups)); + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + platform_set_drvdata(pdev, state); + + state->dev = &pdev->dev; + state->map = dev_get_regmap(dev->parent, NULL); + + pindesc = devm_kcalloc(dev, npins, sizeof(*pindesc), GFP_KERNEL); + if (!pindesc) + return -ENOMEM; + + pads = devm_kcalloc(dev, npins, sizeof(*pads), GFP_KERNEL); + if (!pads) + return -ENOMEM; + + pctrldesc = devm_kzalloc(dev, sizeof(*pctrldesc), GFP_KERNEL); + if (!pctrldesc) + return -ENOMEM; + + pctrldesc->pctlops = &pmic_gpio_pinctrl_ops; + pctrldesc->pmxops = &pmic_gpio_pinmux_ops; + pctrldesc->confops = &pmic_gpio_pinconf_ops; + pctrldesc->owner = THIS_MODULE; + pctrldesc->name = dev_name(dev); + pctrldesc->pins = pindesc; + pctrldesc->npins = npins; + + for (i = 0; i < npins; i++, pindesc++) { + pad = &pads[i]; + pindesc->drv_data = pad; + pindesc->number = i; + pindesc->name = pmic_gpio_groups[i]; + + pad->irq = platform_get_irq(pdev, i); + if (pad->irq < 0) + return pad->irq; + + pad->base = res[0] + i * PMIC_GPIO_ADDRESS_RANGE; + + ret = pmic_gpio_populate(state, pad); + if (ret < 0) + return ret; + } + + state->chip = pmic_gpio_gpio_template; + state->chip.dev = dev; + state->chip.base = -1; + state->chip.ngpio = npins; + state->chip.label = dev_name(dev); + state->chip.of_gpio_n_cells = 2; + state->chip.can_sleep = false; + + state->ctrl = pinctrl_register(pctrldesc, dev, state); + if (!state->ctrl) + return -ENODEV; + + ret = gpiochip_add(&state->chip); + if (ret) { + dev_err(state->dev, "can't add gpio chip\n"); + goto err_chip; + } + + ret = gpiochip_add_pin_range(&state->chip, dev_name(dev), 0, 0, npins); + if (ret) { + dev_err(dev, "failed to add pin range\n"); + goto err_range; + } + + return 0; + +err_range: + gpiochip_remove(&state->chip); +err_chip: + pinctrl_unregister(state->ctrl); + return ret; +} + +static int pmic_gpio_remove(struct platform_device *pdev) +{ + struct pmic_gpio_state *state = platform_get_drvdata(pdev); + + gpiochip_remove(&state->chip); + pinctrl_unregister(state->ctrl); + return 0; +} + +static const struct of_device_id pmic_gpio_of_match[] = { + { .compatible = "qcom,pm8941-gpio" }, /* 36 GPIO's */ + { .compatible = "qcom,pma8084-gpio" }, /* 22 GPIO's */ + { }, +}; + +MODULE_DEVICE_TABLE(of, pmic_gpio_of_match); + +static struct platform_driver pmic_gpio_driver = { + .driver = { + .name = "qcom-spmi-gpio", + .of_match_table = pmic_gpio_of_match, + }, + .probe = pmic_gpio_probe, + .remove = pmic_gpio_remove, +}; + +module_platform_driver(pmic_gpio_driver); + +MODULE_AUTHOR("Ivan T. Ivanov "); +MODULE_DESCRIPTION("Qualcomm SPMI PMIC GPIO pin control driver"); +MODULE_ALIAS("platform:qcom-spmi-gpio"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From f4c706df2470f144028a21e13b8cd3c6d8af9578 Mon Sep 17 00:00:00 2001 From: "Ivan T. Ivanov" Date: Wed, 22 Oct 2014 12:58:47 +0300 Subject: pinctrl: Qualcomm SPMI PMIC MPP pin controller driver This is the pinctrl, pinmux, pinconf and gpiolib driver for the Qualcomm MPP sub-function blocks found in the PMIC chips. Signed-off-by: Ivan T. Ivanov Acked-by: Bjorn Andersson --- drivers/pinctrl/qcom/Makefile | 1 + drivers/pinctrl/qcom/pinctrl-spmi-mpp.c | 949 ++++++++++++++++++++++++++++++++ 2 files changed, 950 insertions(+) create mode 100644 drivers/pinctrl/qcom/pinctrl-spmi-mpp.c diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile index 9b49c65a1cab..bfd79af5f982 100644 --- a/drivers/pinctrl/qcom/Makefile +++ b/drivers/pinctrl/qcom/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_PINCTRL_IPQ8064) += pinctrl-ipq8064.o obj-$(CONFIG_PINCTRL_MSM8960) += pinctrl-msm8960.o obj-$(CONFIG_PINCTRL_MSM8X74) += pinctrl-msm8x74.o obj-$(CONFIG_PINCTRL_QCOM_SPMI_PMIC) += pinctrl-spmi-gpio.o +obj-$(CONFIG_PINCTRL_QCOM_SPMI_PMIC) += pinctrl-spmi-mpp.o diff --git a/drivers/pinctrl/qcom/pinctrl-spmi-mpp.c b/drivers/pinctrl/qcom/pinctrl-spmi-mpp.c new file mode 100644 index 000000000000..a8924dba335e --- /dev/null +++ b/drivers/pinctrl/qcom/pinctrl-spmi-mpp.c @@ -0,0 +1,949 @@ +/* + * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../core.h" +#include "../pinctrl-utils.h" + +#define PMIC_MPP_ADDRESS_RANGE 0x100 + +/* + * Pull Up Values - it indicates whether a pull-up should be + * applied for bidirectional mode only. The hardware ignores the + * configuration when operating in other modes. + */ +#define PMIC_MPP_PULL_UP_0P6KOHM 0 +#define PMIC_MPP_PULL_UP_10KOHM 1 +#define PMIC_MPP_PULL_UP_30KOHM 2 +#define PMIC_MPP_PULL_UP_OPEN 3 + +/* type registers base address bases */ +#define PMIC_MPP_REG_TYPE 0x4 +#define PMIC_MPP_REG_SUBTYPE 0x5 + +/* mpp peripheral type and subtype values */ +#define PMIC_MPP_TYPE 0x11 +#define PMIC_MPP_SUBTYPE_4CH_NO_ANA_OUT 0x3 +#define PMIC_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT 0x4 +#define PMIC_MPP_SUBTYPE_4CH_NO_SINK 0x5 +#define PMIC_MPP_SUBTYPE_ULT_4CH_NO_SINK 0x6 +#define PMIC_MPP_SUBTYPE_4CH_FULL_FUNC 0x7 +#define PMIC_MPP_SUBTYPE_8CH_FULL_FUNC 0xf + +#define PMIC_MPP_REG_RT_STS 0x10 +#define PMIC_MPP_REG_RT_STS_VAL_MASK 0x1 + +/* control register base address bases */ +#define PMIC_MPP_REG_MODE_CTL 0x40 +#define PMIC_MPP_REG_DIG_VIN_CTL 0x41 +#define PMIC_MPP_REG_DIG_PULL_CTL 0x42 +#define PMIC_MPP_REG_DIG_IN_CTL 0x43 +#define PMIC_MPP_REG_EN_CTL 0x46 +#define PMIC_MPP_REG_AIN_CTL 0x4a + +/* PMIC_MPP_REG_MODE_CTL */ +#define PMIC_MPP_REG_MODE_VALUE_MASK 0x1 +#define PMIC_MPP_REG_MODE_FUNCTION_SHIFT 1 +#define PMIC_MPP_REG_MODE_FUNCTION_MASK 0x7 +#define PMIC_MPP_REG_MODE_DIR_SHIFT 4 +#define PMIC_MPP_REG_MODE_DIR_MASK 0x7 + +/* PMIC_MPP_REG_DIG_VIN_CTL */ +#define PMIC_MPP_REG_VIN_SHIFT 0 +#define PMIC_MPP_REG_VIN_MASK 0x7 + +/* PMIC_MPP_REG_DIG_PULL_CTL */ +#define PMIC_MPP_REG_PULL_SHIFT 0 +#define PMIC_MPP_REG_PULL_MASK 0x7 + +/* PMIC_MPP_REG_EN_CTL */ +#define PMIC_MPP_REG_MASTER_EN_SHIFT 7 + +/* PMIC_MPP_REG_AIN_CTL */ +#define PMIC_MPP_REG_AIN_ROUTE_SHIFT 0 +#define PMIC_MPP_REG_AIN_ROUTE_MASK 0x7 + +#define PMIC_MPP_PHYSICAL_OFFSET 1 + +/* Qualcomm specific pin configurations */ +#define PMIC_MPP_CONF_AMUX_ROUTE (PIN_CONFIG_END + 1) +#define PMIC_MPP_CONF_ANALOG_MODE (PIN_CONFIG_END + 2) + +/** + * struct pmic_mpp_pad - keep current MPP settings + * @base: Address base in SPMI device. + * @irq: IRQ number which this MPP generate. + * @is_enabled: Set to false when MPP should be put in high Z state. + * @out_value: Cached pin output value. + * @output_enabled: Set to true if MPP output logic is enabled. + * @input_enabled: Set to true if MPP input buffer logic is enabled. + * @analog_mode: Set to true when MPP should operate in Analog Input, Analog + * Output or Bidirectional Analog mode. + * @num_sources: Number of power-sources supported by this MPP. + * @power_source: Current power-source used. + * @amux_input: Set the source for analog input. + * @pullup: Pullup resistor value. Valid in Bidirectional mode only. + * @function: See pmic_mpp_functions[]. + */ +struct pmic_mpp_pad { + u16 base; + int irq; + bool is_enabled; + bool out_value; + bool output_enabled; + bool input_enabled; + bool analog_mode; + unsigned int num_sources; + unsigned int power_source; + unsigned int amux_input; + unsigned int pullup; + unsigned int function; +}; + +struct pmic_mpp_state { + struct device *dev; + struct regmap *map; + struct pinctrl_dev *ctrl; + struct gpio_chip chip; +}; + +struct pmic_mpp_bindings { + const char *property; + unsigned param; +}; + +static struct pmic_mpp_bindings pmic_mpp_bindings[] = { + {"qcom,amux-route", PMIC_MPP_CONF_AMUX_ROUTE}, + {"qcom,analog-mode", PMIC_MPP_CONF_ANALOG_MODE}, +}; + +static const char *const pmic_mpp_groups[] = { + "mpp1", "mpp2", "mpp3", "mpp4", "mpp5", "mpp6", "mpp7", "mpp8", +}; + +static const char *const pmic_mpp_functions[] = { + PMIC_MPP_FUNC_NORMAL, PMIC_MPP_FUNC_PAIRED, + "reserved1", "reserved2", + PMIC_MPP_FUNC_DTEST1, PMIC_MPP_FUNC_DTEST2, + PMIC_MPP_FUNC_DTEST3, PMIC_MPP_FUNC_DTEST4, +}; + +static inline struct pmic_mpp_state *to_mpp_state(struct gpio_chip *chip) +{ + return container_of(chip, struct pmic_mpp_state, chip); +}; + +static int pmic_mpp_read(struct pmic_mpp_state *state, + struct pmic_mpp_pad *pad, unsigned int addr) +{ + unsigned int val; + int ret; + + ret = regmap_read(state->map, pad->base + addr, &val); + if (ret < 0) + dev_err(state->dev, "read 0x%x failed\n", addr); + else + ret = val; + + return ret; +} + +static int pmic_mpp_write(struct pmic_mpp_state *state, + struct pmic_mpp_pad *pad, unsigned int addr, + unsigned int val) +{ + int ret; + + ret = regmap_write(state->map, pad->base + addr, val); + if (ret < 0) + dev_err(state->dev, "write 0x%x failed\n", addr); + + return ret; +} + +static int pmic_mpp_get_groups_count(struct pinctrl_dev *pctldev) +{ + /* Every PIN is a group */ + return pctldev->desc->npins; +} + +static const char *pmic_mpp_get_group_name(struct pinctrl_dev *pctldev, + unsigned pin) +{ + return pctldev->desc->pins[pin].name; +} + +static int pmic_mpp_get_group_pins(struct pinctrl_dev *pctldev, + unsigned pin, + const unsigned **pins, unsigned *num_pins) +{ + *pins = &pctldev->desc->pins[pin].number; + *num_pins = 1; + return 0; +} + +static int pmic_mpp_parse_dt_config(struct device_node *np, + struct pinctrl_dev *pctldev, + unsigned long **configs, + unsigned int *nconfs) +{ + struct pmic_mpp_bindings *par; + unsigned long cfg; + int ret, i; + u32 val; + + for (i = 0; i < ARRAY_SIZE(pmic_mpp_bindings); i++) { + par = &pmic_mpp_bindings[i]; + ret = of_property_read_u32(np, par->property, &val); + + /* property not found */ + if (ret == -EINVAL) + continue; + + /* use zero as default value, when no value is specified */ + if (ret) + val = 0; + + dev_dbg(pctldev->dev, "found %s with value %u\n", + par->property, val); + + cfg = pinconf_to_config_packed(par->param, val); + + ret = pinctrl_utils_add_config(pctldev, configs, nconfs, cfg); + if (ret) + return ret; + } + + return 0; +} + +static int pmic_mpp_dt_subnode_to_map(struct pinctrl_dev *pctldev, + struct device_node *np, + struct pinctrl_map **map, + unsigned *reserv, unsigned *nmaps, + enum pinctrl_map_type type) +{ + unsigned long *configs = NULL; + unsigned nconfs = 0; + struct property *prop; + const char *group; + int ret; + + ret = pmic_mpp_parse_dt_config(np, pctldev, &configs, &nconfs); + if (ret < 0) + return ret; + + if (!nconfs) + return 0; + + ret = of_property_count_strings(np, "pins"); + if (ret < 0) + goto exit; + + ret = pinctrl_utils_reserve_map(pctldev, map, reserv, nmaps, ret); + if (ret < 0) + goto exit; + + of_property_for_each_string(np, "pins", prop, group) { + ret = pinctrl_utils_add_map_configs(pctldev, map, + reserv, nmaps, group, + configs, nconfs, type); + if (ret < 0) + break; + } +exit: + kfree(configs); + return ret; +} + +static int pmic_mpp_dt_node_to_map(struct pinctrl_dev *pctldev, + struct device_node *np_config, + struct pinctrl_map **map, unsigned *nmaps) +{ + struct device_node *np; + enum pinctrl_map_type type; + unsigned reserv; + int ret; + + ret = 0; + *map = NULL; + *nmaps = 0; + reserv = 0; + type = PIN_MAP_TYPE_CONFIGS_GROUP; + + for_each_child_of_node(np_config, np) { + ret = pinconf_generic_dt_subnode_to_map(pctldev, np, map, + &reserv, nmaps, type); + if (ret) + break; + + ret = pmic_mpp_dt_subnode_to_map(pctldev, np, map, &reserv, + nmaps, type); + if (ret) + break; + } + + if (ret < 0) + pinctrl_utils_dt_free_map(pctldev, *map, *nmaps); + + return ret; +} + +static const struct pinctrl_ops pmic_mpp_pinctrl_ops = { + .get_groups_count = pmic_mpp_get_groups_count, + .get_group_name = pmic_mpp_get_group_name, + .get_group_pins = pmic_mpp_get_group_pins, + .dt_node_to_map = pmic_mpp_dt_node_to_map, + .dt_free_map = pinctrl_utils_dt_free_map, +}; + +static int pmic_mpp_get_functions_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(pmic_mpp_functions); +} + +static const char *pmic_mpp_get_function_name(struct pinctrl_dev *pctldev, + unsigned function) +{ + return pmic_mpp_functions[function]; +} + +static int pmic_mpp_get_function_groups(struct pinctrl_dev *pctldev, + unsigned function, + const char *const **groups, + unsigned *const num_qgroups) +{ + *groups = pmic_mpp_groups; + *num_qgroups = pctldev->desc->npins; + return 0; +} + +static int pmic_mpp_set_mux(struct pinctrl_dev *pctldev, unsigned function, + unsigned pin) +{ + struct pmic_mpp_state *state = pinctrl_dev_get_drvdata(pctldev); + struct pmic_mpp_pad *pad; + unsigned int val; + int ret; + + pad = pctldev->desc->pins[pin].drv_data; + + pad->function = function; + + if (!pad->analog_mode) { + val = 0; /* just digital input */ + if (pad->output_enabled) { + if (pad->input_enabled) + val = 2; /* digital input and output */ + else + val = 1; /* just digital output */ + } + } else { + val = 4; /* just analog input */ + if (pad->output_enabled) { + if (pad->input_enabled) + val = 3; /* analog input and output */ + else + val = 5; /* just analog output */ + } + } + + val |= pad->function << PMIC_MPP_REG_MODE_FUNCTION_SHIFT; + val |= pad->out_value & PMIC_MPP_REG_MODE_VALUE_MASK; + + ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_MODE_CTL, val); + if (ret < 0) + return ret; + + val = pad->is_enabled << PMIC_MPP_REG_MASTER_EN_SHIFT; + + return pmic_mpp_write(state, pad, PMIC_MPP_REG_EN_CTL, val); +} + +static const struct pinmux_ops pmic_mpp_pinmux_ops = { + .get_functions_count = pmic_mpp_get_functions_count, + .get_function_name = pmic_mpp_get_function_name, + .get_function_groups = pmic_mpp_get_function_groups, + .set_mux = pmic_mpp_set_mux, +}; + +static int pmic_mpp_config_get(struct pinctrl_dev *pctldev, + unsigned int pin, unsigned long *config) +{ + unsigned param = pinconf_to_config_param(*config); + struct pmic_mpp_pad *pad; + unsigned arg = 0; + + pad = pctldev->desc->pins[pin].drv_data; + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + arg = pad->pullup == PMIC_MPP_PULL_UP_OPEN; + break; + case PIN_CONFIG_BIAS_PULL_UP: + switch (pad->pullup) { + case PMIC_MPP_PULL_UP_OPEN: + arg = 0; + break; + case PMIC_MPP_PULL_UP_0P6KOHM: + arg = 600; + break; + case PMIC_MPP_PULL_UP_10KOHM: + arg = 10000; + break; + case PMIC_MPP_PULL_UP_30KOHM: + arg = 30000; + break; + default: + return -EINVAL; + } + break; + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + arg = !pad->is_enabled; + break; + case PIN_CONFIG_POWER_SOURCE: + arg = pad->power_source; + break; + case PIN_CONFIG_INPUT_ENABLE: + arg = pad->input_enabled; + break; + case PIN_CONFIG_OUTPUT: + arg = pad->out_value; + break; + case PMIC_MPP_CONF_AMUX_ROUTE: + arg = pad->amux_input; + break; + case PMIC_MPP_CONF_ANALOG_MODE: + arg = pad->analog_mode; + break; + default: + return -EINVAL; + } + + /* Convert register value to pinconf value */ + *config = pinconf_to_config_packed(param, arg); + return 0; +} + +static int pmic_mpp_config_set(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *configs, unsigned nconfs) +{ + struct pmic_mpp_state *state = pinctrl_dev_get_drvdata(pctldev); + struct pmic_mpp_pad *pad; + unsigned param, arg; + unsigned int val; + int i, ret; + + pad = pctldev->desc->pins[pin].drv_data; + + for (i = 0; i < nconfs; i++) { + param = pinconf_to_config_param(configs[i]); + arg = pinconf_to_config_argument(configs[i]); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + pad->pullup = PMIC_MPP_PULL_UP_OPEN; + break; + case PIN_CONFIG_BIAS_PULL_UP: + switch (arg) { + case 600: + pad->pullup = PMIC_MPP_PULL_UP_0P6KOHM; + break; + case 10000: + pad->pullup = PMIC_MPP_PULL_UP_10KOHM; + break; + case 30000: + pad->pullup = PMIC_MPP_PULL_UP_30KOHM; + break; + default: + return -EINVAL; + } + break; + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + pad->is_enabled = false; + break; + case PIN_CONFIG_POWER_SOURCE: + if (arg >= pad->num_sources) + return -EINVAL; + pad->power_source = arg; + break; + case PIN_CONFIG_INPUT_ENABLE: + pad->input_enabled = arg ? true : false; + break; + case PIN_CONFIG_OUTPUT: + pad->output_enabled = true; + pad->out_value = arg; + break; + case PMIC_MPP_CONF_AMUX_ROUTE: + if (arg >= PMIC_MPP_AMUX_ROUTE_ABUS4) + return -EINVAL; + pad->amux_input = arg; + break; + case PMIC_MPP_CONF_ANALOG_MODE: + pad->analog_mode = true; + break; + default: + return -EINVAL; + } + } + + val = pad->power_source << PMIC_MPP_REG_VIN_SHIFT; + + ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_DIG_VIN_CTL, val); + if (ret < 0) + return ret; + + val = pad->pullup << PMIC_MPP_REG_PULL_SHIFT; + + ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_DIG_PULL_CTL, val); + if (ret < 0) + return ret; + + val = pad->amux_input & PMIC_MPP_REG_AIN_ROUTE_MASK; + + ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_AIN_CTL, val); + if (ret < 0) + return ret; + + if (!pad->analog_mode) { + val = 0; /* just digital input */ + if (pad->output_enabled) { + if (pad->input_enabled) + val = 2; /* digital input and output */ + else + val = 1; /* just digital output */ + } + } else { + val = 4; /* just analog input */ + if (pad->output_enabled) { + if (pad->input_enabled) + val = 3; /* analog input and output */ + else + val = 5; /* just analog output */ + } + } + + val = val << PMIC_MPP_REG_MODE_DIR_SHIFT; + val |= pad->function << PMIC_MPP_REG_MODE_FUNCTION_SHIFT; + val |= pad->out_value & PMIC_MPP_REG_MODE_VALUE_MASK; + + return pmic_mpp_write(state, pad, PMIC_MPP_REG_MODE_CTL, val); +} + +static void pmic_mpp_config_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, unsigned pin) +{ + struct pmic_mpp_state *state = pinctrl_dev_get_drvdata(pctldev); + struct pmic_mpp_pad *pad; + int ret, val; + + static const char *const biases[] = { + "0.6kOhm", "10kOhm", "30kOhm", "Disabled" + }; + + + pad = pctldev->desc->pins[pin].drv_data; + + seq_printf(s, " mpp%-2d:", pin + PMIC_MPP_PHYSICAL_OFFSET); + + val = pmic_mpp_read(state, pad, PMIC_MPP_REG_EN_CTL); + + if (val < 0 || !(val >> PMIC_MPP_REG_MASTER_EN_SHIFT)) { + seq_puts(s, " ---"); + } else { + + if (pad->input_enabled) { + ret = pmic_mpp_read(state, pad, PMIC_MPP_REG_RT_STS); + if (!ret) { + ret &= PMIC_MPP_REG_RT_STS_VAL_MASK; + pad->out_value = ret; + } + } + + seq_printf(s, " %-4s", pad->output_enabled ? "out" : "in"); + seq_printf(s, " %-4s", pad->analog_mode ? "ana" : "dig"); + seq_printf(s, " %-7s", pmic_mpp_functions[pad->function]); + seq_printf(s, " vin-%d", pad->power_source); + seq_printf(s, " %-8s", biases[pad->pullup]); + seq_printf(s, " %-4s", pad->out_value ? "high" : "low"); + } +} + +static const struct pinconf_ops pmic_mpp_pinconf_ops = { + .pin_config_group_get = pmic_mpp_config_get, + .pin_config_group_set = pmic_mpp_config_set, + .pin_config_group_dbg_show = pmic_mpp_config_dbg_show, +}; + +static int pmic_mpp_direction_input(struct gpio_chip *chip, unsigned pin) +{ + struct pmic_mpp_state *state = to_mpp_state(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1); + + return pmic_mpp_config_set(state->ctrl, pin, &config, 1); +} + +static int pmic_mpp_direction_output(struct gpio_chip *chip, + unsigned pin, int val) +{ + struct pmic_mpp_state *state = to_mpp_state(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, val); + + return pmic_mpp_config_set(state->ctrl, pin, &config, 1); +} + +static int pmic_mpp_get(struct gpio_chip *chip, unsigned pin) +{ + struct pmic_mpp_state *state = to_mpp_state(chip); + struct pmic_mpp_pad *pad; + int ret; + + pad = state->ctrl->desc->pins[pin].drv_data; + + if (pad->input_enabled) { + ret = pmic_mpp_read(state, pad, PMIC_MPP_REG_RT_STS); + if (ret < 0) + return ret; + + pad->out_value = ret & PMIC_MPP_REG_RT_STS_VAL_MASK; + } + + return pad->out_value; +} + +static void pmic_mpp_set(struct gpio_chip *chip, unsigned pin, int value) +{ + struct pmic_mpp_state *state = to_mpp_state(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value); + + pmic_mpp_config_set(state->ctrl, pin, &config, 1); +} + +static int pmic_mpp_request(struct gpio_chip *chip, unsigned base) +{ + return pinctrl_request_gpio(chip->base + base); +} + +static void pmic_mpp_free(struct gpio_chip *chip, unsigned base) +{ + pinctrl_free_gpio(chip->base + base); +} + +static int pmic_mpp_of_xlate(struct gpio_chip *chip, + const struct of_phandle_args *gpio_desc, + u32 *flags) +{ + if (chip->of_gpio_n_cells < 2) + return -EINVAL; + + if (flags) + *flags = gpio_desc->args[1]; + + return gpio_desc->args[0] - PMIC_MPP_PHYSICAL_OFFSET; +} + +static int pmic_mpp_to_irq(struct gpio_chip *chip, unsigned pin) +{ + struct pmic_mpp_state *state = to_mpp_state(chip); + struct pmic_mpp_pad *pad; + + pad = state->ctrl->desc->pins[pin].drv_data; + + return pad->irq; +} + +static void pmic_mpp_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + struct pmic_mpp_state *state = to_mpp_state(chip); + unsigned i; + + for (i = 0; i < chip->ngpio; i++) { + pmic_mpp_config_dbg_show(state->ctrl, s, i); + seq_puts(s, "\n"); + } +} + +static const struct gpio_chip pmic_mpp_gpio_template = { + .direction_input = pmic_mpp_direction_input, + .direction_output = pmic_mpp_direction_output, + .get = pmic_mpp_get, + .set = pmic_mpp_set, + .request = pmic_mpp_request, + .free = pmic_mpp_free, + .of_xlate = pmic_mpp_of_xlate, + .to_irq = pmic_mpp_to_irq, + .dbg_show = pmic_mpp_dbg_show, +}; + +static int pmic_mpp_populate(struct pmic_mpp_state *state, + struct pmic_mpp_pad *pad) +{ + int type, subtype, val, dir; + + type = pmic_mpp_read(state, pad, PMIC_MPP_REG_TYPE); + if (type < 0) + return type; + + if (type != PMIC_MPP_TYPE) { + dev_err(state->dev, "incorrect block type 0x%x at 0x%x\n", + type, pad->base); + return -ENODEV; + } + + subtype = pmic_mpp_read(state, pad, PMIC_MPP_REG_SUBTYPE); + if (subtype < 0) + return subtype; + + switch (subtype) { + case PMIC_MPP_SUBTYPE_4CH_NO_ANA_OUT: + case PMIC_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT: + case PMIC_MPP_SUBTYPE_4CH_NO_SINK: + case PMIC_MPP_SUBTYPE_ULT_4CH_NO_SINK: + case PMIC_MPP_SUBTYPE_4CH_FULL_FUNC: + pad->num_sources = 4; + break; + case PMIC_MPP_SUBTYPE_8CH_FULL_FUNC: + pad->num_sources = 8; + break; + default: + dev_err(state->dev, "unknown MPP type 0x%x at 0x%x\n", + subtype, pad->base); + return -ENODEV; + } + + val = pmic_mpp_read(state, pad, PMIC_MPP_REG_MODE_CTL); + if (val < 0) + return val; + + pad->out_value = val & PMIC_MPP_REG_MODE_VALUE_MASK; + + dir = val >> PMIC_MPP_REG_MODE_DIR_SHIFT; + dir &= PMIC_MPP_REG_MODE_DIR_MASK; + + switch (dir) { + case 0: + pad->input_enabled = true; + pad->output_enabled = false; + pad->analog_mode = false; + break; + case 1: + pad->input_enabled = false; + pad->output_enabled = true; + pad->analog_mode = false; + break; + case 2: + pad->input_enabled = true; + pad->output_enabled = true; + pad->analog_mode = false; + break; + case 3: + pad->input_enabled = true; + pad->output_enabled = true; + pad->analog_mode = true; + break; + case 4: + pad->input_enabled = true; + pad->output_enabled = false; + pad->analog_mode = true; + break; + case 5: + pad->input_enabled = false; + pad->output_enabled = true; + pad->analog_mode = true; + break; + default: + dev_err(state->dev, "unknown MPP direction\n"); + return -ENODEV; + } + + pad->function = val >> PMIC_MPP_REG_MODE_FUNCTION_SHIFT; + pad->function &= PMIC_MPP_REG_MODE_FUNCTION_MASK; + + val = pmic_mpp_read(state, pad, PMIC_MPP_REG_DIG_VIN_CTL); + if (val < 0) + return val; + + pad->power_source = val >> PMIC_MPP_REG_VIN_SHIFT; + pad->power_source &= PMIC_MPP_REG_VIN_MASK; + + val = pmic_mpp_read(state, pad, PMIC_MPP_REG_DIG_PULL_CTL); + if (val < 0) + return val; + + pad->pullup = val >> PMIC_MPP_REG_PULL_SHIFT; + pad->pullup &= PMIC_MPP_REG_PULL_MASK; + + val = pmic_mpp_read(state, pad, PMIC_MPP_REG_AIN_CTL); + if (val < 0) + return val; + + pad->amux_input = val >> PMIC_MPP_REG_AIN_ROUTE_SHIFT; + pad->amux_input &= PMIC_MPP_REG_AIN_ROUTE_MASK; + + /* Pin could be disabled with PIN_CONFIG_BIAS_HIGH_IMPEDANCE */ + pad->is_enabled = true; + return 0; +} + +static int pmic_mpp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pinctrl_pin_desc *pindesc; + struct pinctrl_desc *pctrldesc; + struct pmic_mpp_pad *pad, *pads; + struct pmic_mpp_state *state; + int ret, npins, i; + u32 res[2]; + + ret = of_property_read_u32_array(dev->of_node, "reg", res, 2); + if (ret < 0) { + dev_err(dev, "missing base address and/or range"); + return ret; + } + + npins = res[1] / PMIC_MPP_ADDRESS_RANGE; + if (!npins) + return -EINVAL; + + BUG_ON(npins > ARRAY_SIZE(pmic_mpp_groups)); + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + platform_set_drvdata(pdev, state); + + state->dev = &pdev->dev; + state->map = dev_get_regmap(dev->parent, NULL); + + pindesc = devm_kcalloc(dev, npins, sizeof(*pindesc), GFP_KERNEL); + if (!pindesc) + return -ENOMEM; + + pads = devm_kcalloc(dev, npins, sizeof(*pads), GFP_KERNEL); + if (!pads) + return -ENOMEM; + + pctrldesc = devm_kzalloc(dev, sizeof(*pctrldesc), GFP_KERNEL); + if (!pctrldesc) + return -ENOMEM; + + pctrldesc->pctlops = &pmic_mpp_pinctrl_ops; + pctrldesc->pmxops = &pmic_mpp_pinmux_ops; + pctrldesc->confops = &pmic_mpp_pinconf_ops; + pctrldesc->owner = THIS_MODULE; + pctrldesc->name = dev_name(dev); + pctrldesc->pins = pindesc; + pctrldesc->npins = npins; + + for (i = 0; i < npins; i++, pindesc++) { + pad = &pads[i]; + pindesc->drv_data = pad; + pindesc->number = i; + pindesc->name = pmic_mpp_groups[i]; + + pad->irq = platform_get_irq(pdev, i); + if (pad->irq < 0) + return pad->irq; + + pad->base = res[0] + i * PMIC_MPP_ADDRESS_RANGE; + + ret = pmic_mpp_populate(state, pad); + if (ret < 0) + return ret; + } + + state->chip = pmic_mpp_gpio_template; + state->chip.dev = dev; + state->chip.base = -1; + state->chip.ngpio = npins; + state->chip.label = dev_name(dev); + state->chip.of_gpio_n_cells = 2; + state->chip.can_sleep = false; + + state->ctrl = pinctrl_register(pctrldesc, dev, state); + if (!state->ctrl) + return -ENODEV; + + ret = gpiochip_add(&state->chip); + if (ret) { + dev_err(state->dev, "can't add gpio chip\n"); + goto err_chip; + } + + ret = gpiochip_add_pin_range(&state->chip, dev_name(dev), 0, 0, npins); + if (ret) { + dev_err(dev, "failed to add pin range\n"); + goto err_range; + } + + return 0; + +err_range: + gpiochip_remove(&state->chip); +err_chip: + pinctrl_unregister(state->ctrl); + return ret; +} + +static int pmic_mpp_remove(struct platform_device *pdev) +{ + struct pmic_mpp_state *state = platform_get_drvdata(pdev); + + gpiochip_remove(&state->chip); + pinctrl_unregister(state->ctrl); + return 0; +} + +static const struct of_device_id pmic_mpp_of_match[] = { + { .compatible = "qcom,pm8841-mpp" }, /* 4 MPP's */ + { .compatible = "qcom,pm8941-mpp" }, /* 8 MPP's */ + { .compatible = "qcom,pma8084-mpp" }, /* 8 MPP's */ + { }, +}; + +MODULE_DEVICE_TABLE(of, pmic_mpp_of_match); + +static struct platform_driver pmic_mpp_driver = { + .driver = { + .name = "qcom-spmi-mpp", + .of_match_table = pmic_mpp_of_match, + }, + .probe = pmic_mpp_probe, + .remove = pmic_mpp_remove, +}; + +module_platform_driver(pmic_mpp_driver); + +MODULE_AUTHOR("Ivan T. Ivanov "); +MODULE_DESCRIPTION("Qualcomm SPMI PMIC MPP pin control driver"); +MODULE_ALIAS("platform:qcom-spmi-mpp"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 8ef41e1496a151226aea134052b105033a2f0b25 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 7 Jul 2014 18:26:23 -0700 Subject: mfd: pm8921: Expose pm8xxx_read_irq_status Most status bits, e.g. for GPIO and MPP input, is retrieved by reading the interrupt status registers, so this needs to be exposed to clients. Signed-off-by: Bjorn Andersson --- drivers/mfd/pm8921-core.c | 36 ++++++++++++++++++++++++++++++++++++ include/linux/mfd/pm8921-core.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 include/linux/mfd/pm8921-core.h diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c index 39904f77c049..96b60db0dac9 100644 --- a/drivers/mfd/pm8921-core.c +++ b/drivers/mfd/pm8921-core.c @@ -26,6 +26,7 @@ #include #include #include +#include #define SSBI_REG_ADDR_IRQ_BASE 0x1BB @@ -65,6 +66,41 @@ struct pm_irq_chip { u8 config[0]; }; +int pm8xxx_read_irq_status(int irq) +{ + struct irq_data *d = irq_get_irq_data(irq); + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + unsigned int pmirq = irqd_to_hwirq(d); + unsigned int bits; + int irq_bit; + u8 block; + int rc; + + if (!chip) { + pr_err("Failed to resolve pm_irq_chip\n"); + return -EINVAL; + } + + block = pmirq / 8; + irq_bit = pmirq % 8; + + spin_lock(&chip->pm_irq_lock); + rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_BLK_SEL, block); + if (rc) { + pr_err("Failed Selecting Block %d rc=%d\n", block, rc); + goto bail; + } + + rc = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_RT_STATUS, &bits); + if (rc) + pr_err("Failed Reading Status rc=%d\n", rc); +bail: + spin_unlock(&chip->pm_irq_lock); + + return rc ? rc : !!(bits & BIT(irq_bit)); +} +EXPORT_SYMBOL(pm8xxx_read_irq_status); + static int pm8xxx_read_block_irq(struct pm_irq_chip *chip, unsigned int bp, unsigned int *ip) { diff --git a/include/linux/mfd/pm8921-core.h b/include/linux/mfd/pm8921-core.h new file mode 100644 index 000000000000..b31d785761f4 --- /dev/null +++ b/include/linux/mfd/pm8921-core.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2014, Sony Mobile Communications AB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MFD_PM8921_CORE_H +#define __MFD_PM8921_CORE_H + +#include + +#if IS_ENABLED(CONFIG_MFD_PM8921_CORE) + +int pm8xxx_read_irq_status(int irq); + +#else +static inline int pm8xxx_read_irq_status(int irq) +{ + return -ENOSYS; +} + +#endif + +#endif -- cgit v1.2.3 From 8aa89d263a146c9525590614b0ce34e20740a0ec Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 11 Aug 2014 18:40:30 +0300 Subject: pinctrl: Introduce pinctrl driver for Qualcomm SSBI PMIC's This introduces a pinctrl, pinconf, pinmux and gpio driver for the gpio block found in pm8018, pm8038, pm8058, pm8917 and pm8921 pmics from Qualcomm. Signed-off-by: Bjorn Andersson Signed-off-by: Ivan T. Ivanov --- drivers/pinctrl/qcom/Kconfig | 11 + drivers/pinctrl/qcom/Makefile | 1 + drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c | 870 +++++++++++++++++++++++++++++++ 3 files changed, 882 insertions(+) create mode 100644 drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig index 3cd243c26b7d..295b2af4369b 100644 --- a/drivers/pinctrl/qcom/Kconfig +++ b/drivers/pinctrl/qcom/Kconfig @@ -60,4 +60,15 @@ config PINCTRL_QCOM_SPMI_PMIC which are using SPMI for communication with SoC. Example PMIC's devices are pm8841, pm8941 and pma8084. +config PINCTRL_SSBI_PMIC + tristate "Qualcomm SSBI PMIC pin controller driver" + depends on GPIOLIB && OF && MFD_PM8921_CORE + select PINCONF + select PINMUX + select GENERIC_PINCONF + help + This is the pinctrl, pinmux, pinconf and gpiolib driver for the + Qualcomm GPIO blocks found in the pm8018, pm8038, pm8058, pm8917 and + pm8921 pmics from Qualcomm. + endif diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile index bfd79af5f982..27485f41a225 100644 --- a/drivers/pinctrl/qcom/Makefile +++ b/drivers/pinctrl/qcom/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_PINCTRL_MSM8960) += pinctrl-msm8960.o obj-$(CONFIG_PINCTRL_MSM8X74) += pinctrl-msm8x74.o obj-$(CONFIG_PINCTRL_QCOM_SPMI_PMIC) += pinctrl-spmi-gpio.o obj-$(CONFIG_PINCTRL_QCOM_SPMI_PMIC) += pinctrl-spmi-mpp.o +obj-$(CONFIG_PINCTRL_SSBI_PMIC) += pinctrl-ssbi-pmic.o diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c new file mode 100644 index 000000000000..9a1b44352fb0 --- /dev/null +++ b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c @@ -0,0 +1,870 @@ +/* + * Copyright (c) 2014, Sony Mobile Communications AB. + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../core.h" +#include "../pinconf.h" +#include "../pinctrl-utils.h" + +/* direction */ +#define PM8XXX_GPIO_DIR_OUT BIT(0) +#define PM8XXX_GPIO_DIR_IN BIT(1) + +/* output buffer */ +#define PM8XXX_GPIO_PUSH_PULL 0 +#define PM8XXX_GPIO_OPEN_DRAIN 1 + +/* bias */ +#define PM8XXX_GPIO_BIAS_PU_30 0 +#define PM8XXX_GPIO_BIAS_PU_1P5 1 +#define PM8XXX_GPIO_BIAS_PU_31P5 2 +#define PM8XXX_GPIO_BIAS_PU_1P5_30 3 +#define PM8XXX_GPIO_BIAS_PD 4 +#define PM8XXX_GPIO_BIAS_NP 5 + +/* GPIO registers */ +#define SSBI_REG_ADDR_GPIO_BASE 0x150 +#define SSBI_REG_ADDR_GPIO(n) (SSBI_REG_ADDR_GPIO_BASE + n) + +#define PM8XXX_GPIO_WRITE BIT(7) + +#define PM8XXX_MAX_GPIOS 44 + +/* Qualcomm specific pin configurations */ +#define PM8XXX_PINCONF_PULL_UP (PIN_CONFIG_END + 1) +#define PM8XXX_PINCONF_STRENGTH (PIN_CONFIG_END + 2) + +struct pm8xxx_pinbindings { + const char *property; + unsigned param; + u32 default_value; +}; +static struct pm8xxx_pinbindings pm8xxx_pinbindings[] = { + /* PMIC_GPIO_PULL_UP_30... */ + {"qcom,pull-up-strength", PM8XXX_PINCONF_PULL_UP, 0}, + /* PMIC_GPIO_STRENGTH_NO... */ + {"qcom,drive-strength", PM8XXX_PINCONF_STRENGTH, 0}, +}; + +struct pm8xxx_gpio_pin { + int irq; + + u8 power_source; + u8 direction; + u8 output_buffer; + u8 output_value; + u8 bias; + u8 output_strength; + u8 disable; + u8 function; + u8 non_inverted; +}; + +struct pm8xxx_gpio_data { + int ngpio; +}; + +struct pm8xxx_gpio { + struct device *dev; + struct regmap *regmap; + struct pinctrl_dev *pctrl; + struct gpio_chip chip; + + const struct pm8xxx_gpio_data *data; + + struct pm8xxx_gpio_pin pins[PM8XXX_MAX_GPIOS]; +}; + +static inline struct pm8xxx_gpio *to_pm8xxx_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct pm8xxx_gpio, chip); +}; + +static const char * const pm8xxx_gpio_groups[PM8XXX_MAX_GPIOS] = { + "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7", "gpio8", + "gpio9", "gpio10", "gpio11", "gpio12", "gpio13", "gpio14", "gpio15", + "gpio16", "gpio17", "gpio18", "gpio19", "gpio20", "gpio21", "gpio22", + "gpio23", "gpio24", "gpio25", "gpio26", "gpio27", "gpio28", "gpio29", + "gpio30", "gpio31", "gpio32", "gpio33", "gpio34", "gpio35", "gpio36", + "gpio37", "gpio38", "gpio39", "gpio40", "gpio41", "gpio42", "gpio43", + "gpio44", +}; + +static const char * const pm8xxx_gpio_functions[] = { + PMIC_GPIO_FUNC_NORMAL, PMIC_GPIO_FUNC_PAIRED, + PMIC_GPIO_FUNC_FUNC1, PMIC_GPIO_FUNC_FUNC2, + PMIC_GPIO_FUNC_DTEST1, PMIC_GPIO_FUNC_DTEST2, + PMIC_GPIO_FUNC_DTEST3, PMIC_GPIO_FUNC_DTEST4, +}; + +static int pm8xxx_gpio_read(struct pm8xxx_gpio *pctrl, int pin, int bank) +{ + int reg = SSBI_REG_ADDR_GPIO(pin); + unsigned int val = bank << 4; + int ret; + + ret = regmap_write(pctrl->regmap, reg, val); + if (ret) { + dev_err(pctrl->dev, + "failed to select bank %d of pin %d\n", bank, pin); + return ret; + } + + ret = regmap_read(pctrl->regmap, reg, &val); + if (ret) { + dev_err(pctrl->dev, + "failed to read register %d of pin %d\n", bank, pin); + return ret; + } + + return val; +} + +static int pm8xxx_gpio_write(struct pm8xxx_gpio *pctrl, + int pin, int bank, u8 val) +{ + int ret; + + val |= PM8XXX_GPIO_WRITE; + val |= bank << 4; + + ret = regmap_write(pctrl->regmap, SSBI_REG_ADDR_GPIO(pin), val); + if (ret) + dev_err(pctrl->dev, "failed to write register\n"); + + return ret; +} + +static int pm8xxx_gpio_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev); + + return pctrl->data->ngpio; +} + +static const char *pm8xxx_gpio_get_group_name(struct pinctrl_dev *pctldev, + unsigned group) +{ + return pm8xxx_gpio_groups[group]; +} + +static int pm8xxx_parse_dt_config(struct device *dev, struct device_node *np, + unsigned long **configs, unsigned int *nconfigs) +{ + struct pm8xxx_pinbindings *par; + unsigned long cfg[ARRAY_SIZE(pm8xxx_pinbindings)]; + unsigned int ncfg = 0; + int ret, idx; + u32 val; + + if (!np) + return -EINVAL; + + for (idx = 0; idx < ARRAY_SIZE(pm8xxx_pinbindings); idx++) { + par = &pm8xxx_pinbindings[idx]; + ret = of_property_read_u32(np, par->property, &val); + + /* property not found */ + if (ret == -EINVAL) + continue; + + /* use default value, when no value is specified */ + if (ret) + val = par->default_value; + + dev_dbg(dev, "found %s with value %u\n", par->property, val); + cfg[ncfg] = pinconf_to_config_packed(par->param, val); + ncfg++; + } + + ret = 0; + + /* no configs found at qchip->npads */ + if (ncfg == 0) { + *configs = NULL; + *nconfigs = 0; + goto out; + } + + /* + * Now limit the number of configs to the real number of + * found properties. + */ + *configs = kcalloc(ncfg, sizeof(unsigned long), GFP_KERNEL); + if (!*configs) { + ret = -ENOMEM; + goto out; + } + + memcpy(*configs, cfg, ncfg * sizeof(unsigned long)); + *nconfigs = ncfg; + +out: + return ret; +} + +static int pm8xxx_dt_subnode_to_map(struct pinctrl_dev *pctldev, + struct device_node *np, + struct pinctrl_map **map, + unsigned *reserv, unsigned *nmaps, + enum pinctrl_map_type type) +{ + unsigned long *configs = NULL; + unsigned num_configs = 0; + struct property *prop; + const char *group; + int ret; + + ret = pm8xxx_parse_dt_config(pctldev->dev, np, &configs, &num_configs); + if (ret < 0) + return ret; + + if (!num_configs) + return 0; + + ret = of_property_count_strings(np, "pins"); + if (ret < 0) + goto exit; + + ret = pinctrl_utils_reserve_map(pctldev, map, reserv, + nmaps, ret); + if (ret < 0) + goto exit; + + of_property_for_each_string(np, "pins", prop, group) { + ret = pinctrl_utils_add_map_configs(pctldev, map, + reserv, nmaps, group, configs, + num_configs, type); + if (ret < 0) + break; + } +exit: + kfree(configs); + return ret; +} + +static int pm8xxx_dt_node_to_map(struct pinctrl_dev *pctldev, + struct device_node *np_config, + struct pinctrl_map **map, + unsigned *nmaps) +{ + struct device_node *np; + enum pinctrl_map_type type; + unsigned reserv; + int ret; + + ret = 0; + *map = NULL; + *nmaps = 0; + reserv = 0; + type = PIN_MAP_TYPE_CONFIGS_GROUP; + + for_each_child_of_node(np_config, np) { + + ret = pinconf_generic_dt_subnode_to_map(pctldev, np, map, + &reserv, nmaps, type); + if (ret) + break; + + ret = pm8xxx_dt_subnode_to_map(pctldev, np, map, &reserv, + nmaps, type); + if (ret) + break; + } + + if (ret < 0) + pinctrl_utils_dt_free_map(pctldev, *map, *nmaps); + + return ret; +} + +static const struct pinctrl_ops pm8xxx_gpio_pinctrl_ops = { + .get_groups_count = pm8xxx_gpio_get_groups_count, + .get_group_name = pm8xxx_gpio_get_group_name, + .dt_node_to_map = pm8xxx_dt_node_to_map, + .dt_free_map = pinctrl_utils_dt_free_map, +}; + +static int pm8xxx_get_functions_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(pm8xxx_gpio_functions); +} + +static const char *pm8xxx_get_function_name(struct pinctrl_dev *pctldev, + unsigned function) +{ + return pm8xxx_gpio_functions[function]; +} + +static int pm8xxx_get_function_groups(struct pinctrl_dev *pctldev, + unsigned function, + const char * const **groups, + unsigned * const num_groups) +{ + struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev); + + *groups = pm8xxx_gpio_groups; + *num_groups = pctrl->data->ngpio; + return 0; +} + +static int pm8xxx_pinmux_enable(struct pinctrl_dev *pctldev, + unsigned function, + unsigned group) +{ + struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev); + struct pm8xxx_gpio_pin *pin = &pctrl->pins[group]; + u8 val; + + pin->function = function; + val = pin->function << 1; + + pm8xxx_gpio_write(pctrl, group, 4, val); + + return 0; +} + +static const struct pinmux_ops pm8xxx_pinmux_ops = { + .get_functions_count = pm8xxx_get_functions_count, + .get_function_name = pm8xxx_get_function_name, + .get_function_groups = pm8xxx_get_function_groups, + .enable = pm8xxx_pinmux_enable, +}; + +static int pm8xxx_gpio_config_get(struct pinctrl_dev *pctldev, + unsigned int offset, + unsigned long *config) +{ + struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev); + struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset]; + unsigned param = pinconf_to_config_param(*config); + unsigned arg; + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + arg = pin->bias == PM8XXX_GPIO_BIAS_NP; + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + arg = pin->bias == PM8XXX_GPIO_BIAS_PD; + break; + case PM8XXX_PINCONF_PULL_UP: + if (pin->bias >= PM8XXX_GPIO_BIAS_PU_30 && + pin->bias <= PM8XXX_GPIO_BIAS_PU_1P5_30) + arg = PMIC_GPIO_PULL_UP_30 + pin->bias; + else + arg = 0; + break; + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + arg = pin->disable; + break; + case PIN_CONFIG_INPUT_ENABLE: + arg = pin->direction == PM8XXX_GPIO_DIR_IN; + break; + case PIN_CONFIG_OUTPUT: + arg = pin->output_value; + break; + case PIN_CONFIG_POWER_SOURCE: + arg = pin->power_source; + break; + case PM8XXX_PINCONF_STRENGTH: + arg = pin->output_strength; + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + arg = pin->output_buffer == PM8XXX_GPIO_PUSH_PULL; + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + arg = pin->output_buffer == PM8XXX_GPIO_OPEN_DRAIN; + break; + default: + dev_err(pctrl->dev, + "unsupported config parameter: %x\n", + param); + return -EINVAL; + } + + *config = pinconf_to_config_packed(param, arg); + + return 0; +} + +static int pm8xxx_gpio_config_set(struct pinctrl_dev *pctldev, + unsigned int offset, + unsigned long *configs, + unsigned num_configs) +{ + struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev); + struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset]; + unsigned param; + unsigned arg; + unsigned i; + u8 banks = 0; + u8 val; + + for (i = 0; i < num_configs; i++) { + param = pinconf_to_config_param(configs[i]); + arg = pinconf_to_config_argument(configs[i]); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + pin->bias = PM8XXX_GPIO_BIAS_NP; + banks |= BIT(2); + pin->disable = 0; + banks |= BIT(3); + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + pin->bias = PM8XXX_GPIO_BIAS_PD; + banks |= BIT(2); + pin->disable = 0; + banks |= BIT(3); + break; + case PM8XXX_PINCONF_PULL_UP: + if (arg < PMIC_GPIO_PULL_UP_30 || + arg > PMIC_GPIO_PULL_UP_1P5_30) { + dev_err(pctrl->dev, "invalid pull-up level\n"); + return -EINVAL; + } + pin->bias = arg - PM8XXX_GPIO_BIAS_PU_30; + banks |= BIT(2); + pin->disable = 0; + banks |= BIT(3); + break; + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + pin->disable = 1; + banks |= BIT(3); + break; + case PIN_CONFIG_INPUT_ENABLE: + pin->direction = PM8XXX_GPIO_DIR_IN; + banks |= BIT(1); + break; + case PIN_CONFIG_OUTPUT: + pin->direction = PM8XXX_GPIO_DIR_OUT; + pin->output_value = !!arg; + banks |= BIT(1); + break; + case PIN_CONFIG_POWER_SOURCE: + pin->power_source = arg; + banks |= BIT(0); + break; + case PM8XXX_PINCONF_STRENGTH: + if (arg > PMIC_GPIO_STRENGTH_LOW) { + dev_err(pctrl->dev, "invalid drive strength\n"); + return -EINVAL; + } + pin->output_strength = arg; + banks |= BIT(3); + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + pin->output_buffer = PM8XXX_GPIO_PUSH_PULL; + banks |= BIT(1); + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + pin->output_buffer = PM8XXX_GPIO_OPEN_DRAIN; + banks |= BIT(1); + break; + default: + dev_err(pctrl->dev, + "unsupported config parameter: %x\n", + param); + return -EINVAL; + } + } + + if (banks & BIT(0)) + pm8xxx_gpio_write(pctrl, offset, 0, pin->power_source << 1); + + if (banks & BIT(1)) { + val = pin->direction << 2; + val |= pin->output_buffer << 1; + val |= pin->output_value; + pm8xxx_gpio_write(pctrl, offset, 1, val); + } + + if (banks & BIT(2)) { + val = pin->bias << 1; + pm8xxx_gpio_write(pctrl, offset, 2, val); + } + + if (banks & BIT(3)) { + val = pin->output_strength << 2; + val |= pin->disable; + pm8xxx_gpio_write(pctrl, offset, 3, val); + } + + if (banks & BIT(4)) { + val = pin->function << 1; + pm8xxx_gpio_write(pctrl, offset, 4, val); + } + + if (banks & BIT(5)) { + val = 0; + if (pin->non_inverted) + val |= BIT(3); + pm8xxx_gpio_write(pctrl, offset, 5, val); + } + + return 0; +} + +static const struct pinconf_ops pm8xxx_gpio_pinconf_ops = { + .pin_config_group_get = pm8xxx_gpio_config_get, + .pin_config_group_set = pm8xxx_gpio_config_set, +}; + +static struct pinctrl_desc pm8xxx_gpio_desc = { + .pctlops = &pm8xxx_gpio_pinctrl_ops, + .pmxops = &pm8xxx_pinmux_ops, + .confops = &pm8xxx_gpio_pinconf_ops, + .owner = THIS_MODULE, +}; + +static int pm8xxx_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip); + struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1]; + u8 val; + + pin->direction = PM8XXX_GPIO_DIR_IN; + val = pin->direction << 2; + + pm8xxx_gpio_write(pctrl, offset, 1, val); + + return 0; +} + +static int pm8xxx_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, + int value) +{ + struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip); + struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1]; + u8 val; + + pin->direction = PM8XXX_GPIO_DIR_OUT; + pin->output_value = !!value; + + val = pin->direction << 2; + val |= pin->output_buffer << 1; + val |= pin->output_value; + + pm8xxx_gpio_write(pctrl, offset, 1, val); + + return 0; +} + +static int pm8xxx_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip); + struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1]; + + if (pin->direction == PM8XXX_GPIO_DIR_OUT) + return pin->output_value; + + return pm8xxx_read_irq_status(pin->irq); +} + +static void pm8xxx_gpio_set(struct gpio_chip *gc, unsigned offset, int value) +{ + struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(gc); + struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1]; + u8 val; + + pin->output_value = !!value; + + val = pin->direction << 2; + val |= pin->output_buffer << 1; + val |= pin->output_value; + + pm8xxx_gpio_write(pctrl, offset, 1, val); +} + +static int pm8xxx_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip); + struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1]; + + return pin->irq; +} + +#ifdef CONFIG_DEBUG_FS +#include + +static void pm8xxx_gpio_dbg_show_one(struct seq_file *s, + struct pinctrl_dev *pctldev, + struct gpio_chip *chip, + unsigned offset, + unsigned gpio) +{ + struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip); + struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset]; + + static const char * const directions[] = { + "off", "out", "in", "both" + }; + static const char * const biases[] = { + "pull-up 30uA", "pull-up 1.5uA", "pull-up 31.5uA", + "pull-up 1.5uA + 30uA boost", "pull-down 10uA", "no pull" + }; + static const char * const buffer_types[] = { + "push-pull", "open-drain" + }; + static const char * const strengths[] = { + "no", "high", "medium", "low" + }; + + seq_printf(s, " gpio%-2d:", offset + 1); + if (pin->disable) { + seq_puts(s, " ---"); + } else { + seq_printf(s, " %-4s", directions[pin->direction]); + seq_printf(s, " %-7s", pm8xxx_gpio_functions[pin->function]); + seq_printf(s, " VIN%d", pin->power_source); + seq_printf(s, " %-27s", biases[pin->bias]); + seq_printf(s, " %-10s", buffer_types[pin->output_buffer]); + seq_printf(s, " %-4s", pin->output_value ? "high" : "low"); + seq_printf(s, " %-7s", strengths[pin->output_strength]); + if (!pin->non_inverted) + seq_puts(s, " inverted"); + } +} + +static void pm8xxx_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + unsigned gpio = chip->base; + unsigned i; + + for (i = 0; i < chip->ngpio; i++, gpio++) { + pm8xxx_gpio_dbg_show_one(s, NULL, chip, i, gpio); + seq_puts(s, "\n"); + } +} + +#else +#define msm_gpio_dbg_show NULL +#endif + +static struct gpio_chip pm8xxx_gpio_template = { + .direction_input = pm8xxx_gpio_direction_input, + .direction_output = pm8xxx_gpio_direction_output, + .get = pm8xxx_gpio_get, + .set = pm8xxx_gpio_set, + .to_irq = pm8xxx_gpio_to_irq, + .dbg_show = pm8xxx_gpio_dbg_show, + .owner = THIS_MODULE, +}; + +static int pm8xxx_gpio_populate(struct pm8xxx_gpio *pctrl) +{ + struct pm8xxx_gpio_pin *pin; + int val; + int i; + + for (i = 0; i < pctrl->data->ngpio; i++) { + pin = &pctrl->pins[i]; + + val = pm8xxx_gpio_read(pctrl, i, 0); + if (val < 0) + return val; + + pin->power_source = (val >> 1) & 0x7; + + val = pm8xxx_gpio_read(pctrl, i, 1); + if (val < 0) + return val; + + pin->direction = (val >> 2) & 0x3; + pin->output_buffer = !!(val & BIT(1)); + pin->output_value = val & BIT(0); + + val = pm8xxx_gpio_read(pctrl, i, 2); + if (val < 0) + return val; + + pin->bias = (val >> 1) & 0x7; + + val = pm8xxx_gpio_read(pctrl, i, 3); + if (val < 0) + return val; + + pin->output_strength = (val >> 2) & 0x3; + pin->disable = val & BIT(0); + + val = pm8xxx_gpio_read(pctrl, i, 4); + if (val < 0) + return val; + + pin->function = (val >> 1) & 0x7; + + val = pm8xxx_gpio_read(pctrl, i, 5); + if (val < 0) + return val; + + pin->non_inverted = !!(val & BIT(3)); + } + + return 0; +} + +static const struct pm8xxx_gpio_data pm8018_gpio_data = { + .ngpio = 6, +}; + +static const struct pm8xxx_gpio_data pm8038_gpio_data = { + .ngpio = 12, +}; + +static const struct pm8xxx_gpio_data pm8058_gpio_data = { + .ngpio = 40, +}; +static const struct pm8xxx_gpio_data pm8917_gpio_data = { + .ngpio = 38, +}; + +static const struct pm8xxx_gpio_data pm8921_gpio_data = { + .ngpio = 44, +}; + +static const struct of_device_id pm8xxx_gpio_of_match[] = { + { .compatible = "qcom,pm8018-gpio", .data = &pm8018_gpio_data }, + { .compatible = "qcom,pm8038-gpio", .data = &pm8038_gpio_data }, + { .compatible = "qcom,pm8058-gpio", .data = &pm8058_gpio_data }, + { .compatible = "qcom,pm8917-gpio", .data = &pm8917_gpio_data }, + { .compatible = "qcom,pm8921-gpio", .data = &pm8921_gpio_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, pm8xxx_gpio_of_match); + +static int pm8xxx_gpio_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct pm8xxx_gpio *pctrl; + int ret; + int i; + + match = of_match_node(pm8xxx_gpio_of_match, pdev->dev.of_node); + if (!match) + return -ENXIO; + + pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL); + if (!pctrl) + return -ENOMEM; + + pctrl->dev = &pdev->dev; + pctrl->data = match->data; + + BUG_ON(pctrl->data->ngpio > PM8XXX_MAX_GPIOS); + + pctrl->chip = pm8xxx_gpio_template; + pctrl->chip.base = -1; + pctrl->chip.dev = &pdev->dev; + pctrl->chip.of_node = pdev->dev.of_node; + pctrl->chip.label = dev_name(pctrl->dev); + pctrl->chip.ngpio = pctrl->data->ngpio; + + pctrl->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!pctrl->regmap) { + dev_err(&pdev->dev, "parent regmap unavailable\n"); + return -ENXIO; + } + + for (i = 0; i < pctrl->data->ngpio; i++) { + ret = platform_get_irq(pdev, i); + if (ret < 0) { + dev_err(&pdev->dev, + "missing interrupts for pin %d\n", i); + return ret; + } + + pctrl->pins[i].irq = ret; + } + + ret = pm8xxx_gpio_populate(pctrl); + if (ret) + return ret; + + pm8xxx_gpio_desc.name = dev_name(&pdev->dev); + pctrl->pctrl = pinctrl_register(&pm8xxx_gpio_desc, &pdev->dev, pctrl); + if (!pctrl->pctrl) { + dev_err(&pdev->dev, "couldn't register pm8xxx gpio driver\n"); + return -ENODEV; + } + + ret = gpiochip_add(&pctrl->chip); + if (ret) { + dev_err(&pdev->dev, "failed register gpiochip\n"); + goto unregister_pinctrl; + } + + ret = gpiochip_add_pin_range(&pctrl->chip, + dev_name(pctrl->dev), + 1, 0, pctrl->data->ngpio); + if (ret) { + dev_err(pctrl->dev, "failed to add pin range\n"); + goto unregister_gpiochip; + } + + platform_set_drvdata(pdev, pctrl); + + dev_dbg(&pdev->dev, "Qualcomm pm8xxx gpio driver probed\n"); + + return 0; + +unregister_pinctrl: + pinctrl_unregister(pctrl->pctrl); + +unregister_gpiochip: + if (gpiochip_remove(&pctrl->chip)) + dev_err(&pdev->dev, "unable to unregister gpiochip\n"); + + return ret; +} + +static int pm8xxx_gpio_remove(struct platform_device *pdev) +{ + struct pm8xxx_gpio *pctrl = platform_get_drvdata(pdev); + + gpiochip_remove(&pctrl->chip); + + pinctrl_unregister(pctrl->pctrl); + + return 0; +} + +static struct platform_driver pm8xxx_gpio_driver = { + .driver = { + .name = "ssbi-pmic-gpio", + .owner = THIS_MODULE, + .of_match_table = pm8xxx_gpio_of_match, + }, + .probe = pm8xxx_gpio_probe, + .remove = pm8xxx_gpio_remove, +}; + +module_platform_driver(pm8xxx_gpio_driver); + +MODULE_AUTHOR("Bjorn Andersson "); +MODULE_DESCRIPTION("Qualcomm SSBI PMIC GPIO driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 7f6c07645ec46f9fde0291a1e3d148bccde588da Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Wed, 20 Aug 2014 07:09:34 +0100 Subject: pinctrl: qcom: ssbi-pmic: promote driver to sub system level This patch promotes the driver to subsystem level as the pins will be used by many drivers which come up a the same level. So moving this driver to subsystem level would make sure that there are not many deffered probes. Signed-off-by: Srinivas Kandagatla --- drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c index 9a1b44352fb0..27e5ec9063e0 100644 --- a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c +++ b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c @@ -863,7 +863,11 @@ static struct platform_driver pm8xxx_gpio_driver = { .remove = pm8xxx_gpio_remove, }; -module_platform_driver(pm8xxx_gpio_driver); +static int pm8xxx_gpio_init(void) +{ + return platform_driver_register(&pm8xxx_gpio_driver); +} +subsys_initcall(pm8xxx_gpio_init); MODULE_AUTHOR("Bjorn Andersson "); MODULE_DESCRIPTION("Qualcomm SSBI PMIC GPIO driver"); -- cgit v1.2.3 From f69d3215a36590e125680b4bd6c656295d216a5e Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Wed, 20 Aug 2014 07:18:03 +0100 Subject: pinctrl:qcom:ssbi: Enable gpio mode Looking back at 3.4 kernel driver, it looks like the gpio enable bit is missing in this driver. This patch is just a hack to get the wlan reset line working. Signed-off-by: Srinivas Kandagatla --- drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c index 27e5ec9063e0..2633ea18799a 100644 --- a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c +++ b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c @@ -49,6 +49,7 @@ #define SSBI_REG_ADDR_GPIO_BASE 0x150 #define SSBI_REG_ADDR_GPIO(n) (SSBI_REG_ADDR_GPIO_BASE + n) +#define PM8XXX_GPIO_MODE_ENABLE BIT(0) #define PM8XXX_GPIO_WRITE BIT(7) #define PM8XXX_MAX_GPIOS 44 @@ -493,7 +494,8 @@ static int pm8xxx_gpio_config_set(struct pinctrl_dev *pctldev, } if (banks & BIT(0)) - pm8xxx_gpio_write(pctrl, offset, 0, pin->power_source << 1); + pm8xxx_gpio_write(pctrl, offset, 0, pin->power_source << 1 | + PM8XXX_GPIO_MODE_ENABLE); if (banks & BIT(1)) { val = pin->direction << 2; -- cgit v1.2.3 From eb0043282fdd5f15b0f99c1a6b0ff36e666cd9b7 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 21 Oct 2014 16:19:02 +0100 Subject: pinctrl: qcom: fix compiler errors. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These patches are not in mainline yet, so recent changes in the apis introduced below compiler errors. drivers/pinctrl/qcom/pinctrl-spmi-pmic-gpio.c:345:12: warning: ‘pmic_gpio_pinmux_enable’ defined but not used [-Wunused-function] drivers/pinctrl/qcom/pinctrl-spmi-pmic-gpio.c: In function ‘pmic_gpio_remove’: rivers/pinctrl/qcom/pinctrl-spmi-pmic-gpio.c:913:5: warning: ‘ret’ is used uninitialized in this function [-Wuninitialized] This patch fixes those errors in various files. Signed-off-by: Srinivas Kandagatla --- drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c index 2633ea18799a..e02145e11baf 100644 --- a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c +++ b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c @@ -352,7 +352,7 @@ static const struct pinmux_ops pm8xxx_pinmux_ops = { .get_functions_count = pm8xxx_get_functions_count, .get_function_name = pm8xxx_get_function_name, .get_function_groups = pm8xxx_get_function_groups, - .enable = pm8xxx_pinmux_enable, + .set_mux = pm8xxx_pinmux_enable, }; static int pm8xxx_gpio_config_get(struct pinctrl_dev *pctldev, @@ -838,8 +838,7 @@ unregister_pinctrl: pinctrl_unregister(pctrl->pctrl); unregister_gpiochip: - if (gpiochip_remove(&pctrl->chip)) - dev_err(&pdev->dev, "unable to unregister gpiochip\n"); + gpiochip_remove(&pctrl->chip); return ret; } -- cgit v1.2.3