From 00f153a5b10897acd1a0b302c29635adc750d4ec Mon Sep 17 00:00:00 2001 From: Yeleswarapu Nagaradhesh Date: Wed, 22 Jun 2016 00:50:34 +0530 Subject: ARM: dts: msm: add sound node for msmcobalt Add sound node and lnbbclk2 node for msmcobalt to enable audio functionality. CRs-Fixed: 1041199 Change-Id: If6066a42b8aa5a820263a88627e2405df5e227b7 Signed-off-by: Yeleswarapu Nagaradhesh --- arch/arm/boot/dts/qcom/msmcobalt-audio.dtsi | 129 ++++++++++++++++++++++++++ arch/arm/boot/dts/qcom/msmcobalt-wsa881x.dtsi | 32 +++++++ 2 files changed, 161 insertions(+) diff --git a/arch/arm/boot/dts/qcom/msmcobalt-audio.dtsi b/arch/arm/boot/dts/qcom/msmcobalt-audio.dtsi index b414a215cbaa..25f3be5fff09 100644 --- a/arch/arm/boot/dts/qcom/msmcobalt-audio.dtsi +++ b/arch/arm/boot/dts/qcom/msmcobalt-audio.dtsi @@ -109,6 +109,74 @@ "SpkrLeft", "SpkrRight"; }; + sound-tavil { + compatible = "qcom,msmcobalt-asoc-snd-tavil"; + qcom,model = "msmcobalt-tavil-snd-card"; + + qcom,audio-routing = + "RX_BIAS", "MCLK", + "AMIC2", "MIC BIAS2", + "MIC BIAS2", "Headset Mic", + "AMIC3", "MIC BIAS2", + "MIC BIAS2", "ANCRight Headset Mic", + "AMIC4", "MIC BIAS2", + "MIC BIAS2", "ANCLeft Headset Mic", + "AMIC5", "MIC BIAS3", + "MIC BIAS3", "Handset Mic", + "DMIC0", "MIC BIAS1", + "MIC BIAS1", "Digital Mic0", + "DMIC1", "MIC BIAS1", + "MIC BIAS1", "Digital Mic1", + "DMIC2", "MIC BIAS3", + "MIC BIAS3", "Digital Mic2", + "DMIC3", "MIC BIAS3", + "MIC BIAS3", "Digital Mic3", + "DMIC4", "MIC BIAS4", + "MIC BIAS4", "Digital Mic4", + "DMIC5", "MIC BIAS4", + "MIC BIAS4", "Digital Mic5", + "SpkrLeft IN", "SPK1 OUT", + "SpkrRight IN", "SPK2 OUT"; + + qcom,msm-mbhc-hphl-swh = <0>; + qcom,msm-mbhc-gnd-swh = <0>; + qcom,tavil-mclk-clk-freq = <9600000>; + asoc-platform = <&pcm0>, <&pcm1>, <&pcm2>, <&voip>, <&voice>, + <&loopback>, <&compress>, <&hostless>, + <&afe>, <&lsm>, <&routing>, <&cpe>, <&compr>; + asoc-platform-names = "msm-pcm-dsp.0", "msm-pcm-dsp.1", + "msm-pcm-dsp.2", "msm-voip-dsp", + "msm-pcm-voice", "msm-pcm-loopback", + "msm-compress-dsp", "msm-pcm-hostless", + "msm-pcm-afe", "msm-lsm-client", + "msm-pcm-routing", "msm-cpe-lsm", + "msm-compr-dsp"; + asoc-cpu = <&dai_hdmi>, + <&sb_0_rx>, <&sb_0_tx>, <&sb_1_rx>, <&sb_1_tx>, + <&sb_2_rx>, <&sb_2_tx>, <&sb_3_rx>, <&sb_3_tx>, + <&sb_4_rx>, <&sb_4_tx>, <&sb_5_tx>, + <&afe_pcm_rx>, <&afe_pcm_tx>, <&afe_proxy_rx>, + <&afe_proxy_tx>, <&incall_record_rx>, + <&incall_record_tx>, <&incall_music_rx>, + <&incall_music_2_rx>, <&sb_5_rx>, + <&usb_audio_rx>, <&usb_audio_tx>; + asoc-cpu-names = "msm-dai-q6-hdmi.8", + "msm-dai-q6-dev.16384", "msm-dai-q6-dev.16385", + "msm-dai-q6-dev.16386", "msm-dai-q6-dev.16387", + "msm-dai-q6-dev.16388", "msm-dai-q6-dev.16389", + "msm-dai-q6-dev.16390", "msm-dai-q6-dev.16391", + "msm-dai-q6-dev.16392", "msm-dai-q6-dev.16393", + "msm-dai-q6-dev.16395", "msm-dai-q6-dev.224", + "msm-dai-q6-dev.225", "msm-dai-q6-dev.241", + "msm-dai-q6-dev.240", "msm-dai-q6-dev.32771", + "msm-dai-q6-dev.32772", "msm-dai-q6-dev.32773", + "msm-dai-q6-dev.32770", "msm-dai-q6-dev.16394", + "msm-dai-q6-dev.28672", "msm-dai-q6-dev.28673"; + asoc-codec = <&stub_codec>; + asoc-codec-names = "msm-stub-codec.1"; + qcom,wsa-max-devs = <0>; + }; + cpe: qcom,msm-cpe-lsm { compatible = "qcom,msm-cpe-lsm"; }; @@ -141,6 +209,15 @@ #clock-cells = <1>; }; + clock_audio_lnbb: audio_ext_clk_lnbb { + status = "ok"; + compatible = "qcom,audio-ref-clk"; + clock-names = "osr_clk"; + clocks = <&clock_gcc clk_ln_bb_clk2>; + qcom,node_has_rpm_clock; + #clock-cells = <1>; + }; + wcd_rst_gpio: msm_cdc_pinctrl@64 { compatible = "qcom,msm-cdc-pinctrl"; qcom,cdc-rst-n-gpio = <&tlmm 64 0>; @@ -208,4 +285,56 @@ qcom,cdc-dmic-sample-rate = <4800000>; qcom,cdc-mad-dmic-rate = <600000>; }; + + tavil_codec { + compatible = "qcom,tavil-slim-pgd"; + elemental-addr = [00 01 50 02 17 02]; + + interrupt-parent = <&wcd9xxx_intc>; + interrupts = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + 17 18 19 20 21 22 23 24 25 26 27 28 29 + 30 31>; + + qcom,wcd-rst-gpio-node = <&wcd_rst_gpio>; + + clock-names = "wcd_clk"; + clocks = <&clock_audio_lnbb clk_audio_pmi_lnbb_clk>; + + cdc-vdd-buck-supply = <&pmcobalt_s4>; + qcom,cdc-vdd-buck-voltage = <1800000 1800000>; + qcom,cdc-vdd-buck-current = <650000>; + + cdc-buck-sido-supply = <&pmcobalt_s4>; + qcom,cdc-buck-sido-voltage = <1800000 1800000>; + qcom,cdc-buck-sido-current = <250000>; + + cdc-vdd-tx-h-supply = <&pmcobalt_s4>; + qcom,cdc-vdd-tx-h-voltage = <1800000 1800000>; + qcom,cdc-vdd-tx-h-current = <25000>; + + cdc-vdd-rx-h-supply = <&pmcobalt_s4>; + qcom,cdc-vdd-rx-h-voltage = <1800000 1800000>; + qcom,cdc-vdd-rx-h-current = <25000>; + + cdc-vddpx-1-supply = <&pmcobalt_s4>; + qcom,cdc-vddpx-1-voltage = <1800000 1800000>; + qcom,cdc-vddpx-1-current = <10000>; + + qcom,cdc-static-supplies = "cdc-vdd-buck", + "cdc-buck-sido", + "cdc-vdd-tx-h", + "cdc-vdd-rx-h", + "cdc-vddpx-1"; + + qcom,cdc-micbias1-mv = <1800>; + qcom,cdc-micbias2-mv = <1800>; + qcom,cdc-micbias3-mv = <1800>; + qcom,cdc-micbias4-mv = <1800>; + + qcom,cdc-mclk-clk-rate = <9600000>; + qcom,cdc-slim-ifd = "tavil-slim-ifd"; + qcom,cdc-slim-ifd-elemental-addr = [00 00 50 02 17 02]; + qcom,cdc-dmic-sample-rate = <4800000>; + qcom,cdc-mad-dmic-rate = <600000>; + }; }; diff --git a/arch/arm/boot/dts/qcom/msmcobalt-wsa881x.dtsi b/arch/arm/boot/dts/qcom/msmcobalt-wsa881x.dtsi index 8f1f699cfc1f..f9bb0e0d9dc3 100644 --- a/arch/arm/boot/dts/qcom/msmcobalt-wsa881x.dtsi +++ b/arch/arm/boot/dts/qcom/msmcobalt-wsa881x.dtsi @@ -60,4 +60,36 @@ }; }; }; + + tavil_codec { + swr_master { + compatible = "qcom,swr-wcd"; + #address-cells = <2>; + #size-cells = <0>; + + wsa881x_0211: wsa881x@20170211 { + compatible = "qcom,wsa881x"; + reg = <0x00 0x20170211>; + qcom,spkr-sd-n-node = <&wsa_spkr_sd1>; + }; + + wsa881x_0212: wsa881x@20170212 { + compatible = "qcom,wsa881x"; + reg = <0x00 0x20170212>; + qcom,spkr-sd-n-node = <&wsa_spkr_sd2>; + }; + + wsa881x_0213: wsa881x@21170213 { + compatible = "qcom,wsa881x"; + reg = <0x00 0x21170213>; + qcom,spkr-sd-n-node = <&wsa_spkr_sd1>; + }; + + wsa881x_0214: wsa881x@21170214 { + compatible = "qcom,wsa881x"; + reg = <0x00 0x21170214>; + qcom,spkr-sd-n-node = <&wsa_spkr_sd2>; + }; + }; + }; }; -- cgit v1.2.3 From 8aa9aba2221380022cf3a6afe1f77cea84b6245f Mon Sep 17 00:00:00 2001 From: Yeleswarapu Nagaradhesh Date: Tue, 19 Jul 2016 05:57:18 +0530 Subject: qcom: wcd934x: add pinctrl driver for wcd934x WCD934X audio codec has a GPIO controller which can support 5 GPIO's, add pinctrl driver to handle GPIO's of wcd934x. CRs-Fixed: 1041199 Change-Id: I0489f9149cfd6ec7af056d074cb1869a705f9eff Signed-off-by: Yeleswarapu Nagaradhesh --- .../bindings/pinctrl/qcom,wcd-pinctrl.txt | 138 +++++++ drivers/mfd/Kconfig | 1 + drivers/pinctrl/qcom/Kconfig | 7 + drivers/pinctrl/qcom/Makefile | 1 + drivers/pinctrl/qcom/pinctrl-wcd.c | 443 +++++++++++++++++++++ 5 files changed, 590 insertions(+) create mode 100644 Documentation/devicetree/bindings/pinctrl/qcom,wcd-pinctrl.txt create mode 100644 drivers/pinctrl/qcom/pinctrl-wcd.c diff --git a/Documentation/devicetree/bindings/pinctrl/qcom,wcd-pinctrl.txt b/Documentation/devicetree/bindings/pinctrl/qcom,wcd-pinctrl.txt new file mode 100644 index 000000000000..add8b7d688a8 --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/qcom,wcd-pinctrl.txt @@ -0,0 +1,138 @@ +Qualcomm Technologies, Inc. WCD GPIO block + +This binding describes the GPIO block found in the WCD934X series of +audio codec's from QTI. + +- compatible: + Usage: required + Value type: + Definition: must be "qcom,wcd-pinctrl" + +- qcom,num-gpios: + Usage: required + Value type: + Definition: Number of GPIO's supported by the controller + +- 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 for a general description of GPIO 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-gpio5 for wcd9340 + +- 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. + +- 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. + +- qcom,drive-strength: + Usage: optional + Value type: + Definition: Selects the drive strength for the specified pins. + +Example: + + wcd: wcd_pinctrl@5 { + compatible = "qcom,wcd-pinctl"; + qcom,num-gpios = <5> + gpio-controller; + #gpio-cells = <2>; + + spkr_1_wcd_en_active: spkr_1_wcd_en_active { + mux { + pins = "gpio2"; + }; + + config { + pins = "gpio2"; + output-high; + }; + }; + + spkr_1_wcd_en_sleep: spkr_1_wcd_en_sleep { + mux { + pins = "gpio2"; + }; + + config { + pins = "gpio2"; + input-enable; + }; + }; + }; diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d27918647b5c..a97f5df7a7db 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1572,6 +1572,7 @@ config WCD934X_CODEC select MSM_CDC_SUPPLY select MSM_CDC_PINCTRL select REGMAP_ALLOW_WRITE_DEBUGFS + select PINCTRL_WCD help Enables the WCD9xxx codec core driver. The core driver provides read/write capability to registers which are part of the diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig index b9819b929a91..4805c4feac74 100644 --- a/drivers/pinctrl/qcom/Kconfig +++ b/drivers/pinctrl/qcom/Kconfig @@ -120,4 +120,11 @@ config PINCTRL_MSMFALCON This is the pinctrl, pinmux, pinconf and gpiolib driver for the Qualcomm TLMM block found in the Qualcomm MSMFALCON platform. +config PINCTRL_WCD + tristate "Qualcomm Technologies, Inc WCD pin controller driver" + depends on WCD934X_CODEC + help + This is the pinctrl, pinmux, pinconf and gpiolib driver for the + WCD gpio controller block. + endif diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile index 0d390906ea00..bddc21431eeb 100644 --- a/drivers/pinctrl/qcom/Makefile +++ b/drivers/pinctrl/qcom/Makefile @@ -15,3 +15,4 @@ obj-$(CONFIG_PINCTRL_QCOM_SSBI_PMIC) += pinctrl-ssbi-mpp.o obj-$(CONFIG_PINCTRL_MSM8996) += pinctrl-msm8996.o obj-$(CONFIG_PINCTRL_MSMCOBALT) += pinctrl-msmcobalt.o obj-$(CONFIG_PINCTRL_MSMFALCON) += pinctrl-msmfalcon.o +obj-$(CONFIG_PINCTRL_WCD) += pinctrl-wcd.o diff --git a/drivers/pinctrl/qcom/pinctrl-wcd.c b/drivers/pinctrl/qcom/pinctrl-wcd.c new file mode 100644 index 000000000000..08d87f7452eb --- /dev/null +++ b/drivers/pinctrl/qcom/pinctrl-wcd.c @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2016, 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 WCD_REG_DIR_CTL WCD934X_CHIP_TIER_CTRL_GPIO_CTL_OE +#define WCD_REG_VAL_CTL WCD934X_CHIP_TIER_CTRL_GPIO_CTL_DATA +#define WCD_GPIO_PULL_UP 1 +#define WCD_GPIO_PULL_DOWN 2 +#define WCD_GPIO_BIAS_DISABLE 3 +#define WCD_GPIO_STRING_LEN 20 + +/** + * struct wcd_gpio_pad - keep current GPIO settings + * @offset: offset of gpio. + * @is_valid: Set to false, when GPIO in high Z state. + * @value: value of a pin + * @output_enabled: Set to true if GPIO is output and false if it is input + * @pullup: Constant current which flow through GPIO output buffer. + * @strength: Drive strength of a pin + */ +struct wcd_gpio_pad { + u16 offset; + bool is_valid; + bool value; + bool output_enabled; + unsigned int pullup; + unsigned int strength; +}; + +struct wcd_gpio_priv { + struct device *dev; + struct regmap *map; + struct pinctrl_dev *ctrl; + struct gpio_chip chip; +}; + +static inline struct wcd_gpio_priv *to_gpio_state(struct gpio_chip *chip) +{ + return container_of(chip, struct wcd_gpio_priv, chip); +}; + +static int wcd_gpio_read(struct wcd_gpio_priv *priv_data, + struct wcd_gpio_pad *pad, unsigned int addr) +{ + unsigned int val; + int ret; + + ret = regmap_read(priv_data->map, addr, &val); + if (ret < 0) + dev_err(priv_data->dev, "%s: read 0x%x failed\n", + __func__, addr); + else + ret = (val >> pad->offset); + + return ret; +} + +static int wcd_gpio_write(struct wcd_gpio_priv *priv_data, + struct wcd_gpio_pad *pad, unsigned int addr, + unsigned int val) +{ + int ret; + + ret = regmap_update_bits(priv_data->map, addr, (1 << pad->offset), + val << pad->offset); + if (ret < 0) + dev_err(priv_data->dev, "write 0x%x failed\n", addr); + + return ret; +} + +static int wcd_get_groups_count(struct pinctrl_dev *pctldev) +{ + return pctldev->desc->npins; +} + +static const char *wcd_get_group_name(struct pinctrl_dev *pctldev, + unsigned pin) +{ + return pctldev->desc->pins[pin].name; +} + +static int wcd_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 const struct pinctrl_ops wcd_pinctrl_ops = { + .get_groups_count = wcd_get_groups_count, + .get_group_name = wcd_get_group_name, + .get_group_pins = wcd_get_group_pins, + .dt_node_to_map = pinconf_generic_dt_node_to_map_group, + .dt_free_map = pinctrl_utils_dt_free_map, +}; + +static int wcd_config_get(struct pinctrl_dev *pctldev, + unsigned int pin, unsigned long *config) +{ + unsigned param = pinconf_to_config_param(*config); + struct wcd_gpio_pad *pad; + unsigned arg; + + pad = pctldev->desc->pins[pin].drv_data; + + switch (param) { + case PIN_CONFIG_BIAS_PULL_DOWN: + arg = pad->pullup == WCD_GPIO_PULL_DOWN; + break; + case PIN_CONFIG_BIAS_DISABLE: + arg = pad->pullup = WCD_GPIO_BIAS_DISABLE; + break; + case PIN_CONFIG_BIAS_PULL_UP: + arg = pad->pullup == WCD_GPIO_PULL_UP; + break; + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + arg = !pad->is_valid; + break; + case PIN_CONFIG_INPUT_ENABLE: + arg = pad->output_enabled; + break; + case PIN_CONFIG_OUTPUT: + arg = pad->value; + break; + default: + return -EINVAL; + } + + *config = pinconf_to_config_packed(param, arg); + return 0; +} + +static int wcd_config_set(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *configs, unsigned nconfs) +{ + struct wcd_gpio_priv *priv_data = pinctrl_dev_get_drvdata(pctldev); + struct wcd_gpio_pad *pad; + unsigned param, arg; + 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]); + + dev_dbg(priv_data->dev, "%s: param: %d arg: %d", + __func__, param, arg); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + pad->pullup = WCD_GPIO_BIAS_DISABLE; + break; + case PIN_CONFIG_BIAS_PULL_UP: + pad->pullup = WCD_GPIO_PULL_UP; + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + pad->pullup = WCD_GPIO_PULL_DOWN; + break; + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + pad->is_valid = false; + break; + case PIN_CONFIG_INPUT_ENABLE: + pad->output_enabled = false; + break; + case PIN_CONFIG_OUTPUT: + pad->output_enabled = true; + pad->value = arg; + break; + case PIN_CONFIG_DRIVE_STRENGTH: + pad->strength = arg; + break; + default: + ret = -EINVAL; + goto done; + } + } + + if (pad->output_enabled) { + ret = wcd_gpio_write(priv_data, pad, WCD_REG_DIR_CTL, + pad->output_enabled); + if (ret < 0) + goto done; + ret = wcd_gpio_write(priv_data, pad, WCD_REG_VAL_CTL, + pad->value); + } else + ret = wcd_gpio_write(priv_data, pad, WCD_REG_DIR_CTL, + pad->output_enabled); +done: + return ret; +} + +static const struct pinconf_ops wcd_pinconf_ops = { + .is_generic = true, + .pin_config_group_get = wcd_config_get, + .pin_config_group_set = wcd_config_set, +}; + +static int wcd_gpio_direction_input(struct gpio_chip *chip, unsigned pin) +{ + struct wcd_gpio_priv *priv_data = to_gpio_state(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1); + + return wcd_config_set(priv_data->ctrl, pin, &config, 1); +} + +static int wcd_gpio_direction_output(struct gpio_chip *chip, + unsigned pin, int val) +{ + struct wcd_gpio_priv *priv_data = to_gpio_state(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, val); + + return wcd_config_set(priv_data->ctrl, pin, &config, 1); +} + +static int wcd_gpio_get(struct gpio_chip *chip, unsigned pin) +{ + struct wcd_gpio_priv *priv_data = to_gpio_state(chip); + struct wcd_gpio_pad *pad; + int value; + + pad = priv_data->ctrl->desc->pins[pin].drv_data; + + if (!pad->is_valid) + return -EINVAL; + + value = wcd_gpio_read(priv_data, pad, WCD_REG_VAL_CTL); + return value; +} + +static void wcd_gpio_set(struct gpio_chip *chip, unsigned pin, int value) +{ + struct wcd_gpio_priv *priv_data = to_gpio_state(chip); + unsigned long config; + + config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value); + + wcd_config_set(priv_data->ctrl, pin, &config, 1); +} + +static const struct gpio_chip wcd_gpio_chip = { + .direction_input = wcd_gpio_direction_input, + .direction_output = wcd_gpio_direction_output, + .get = wcd_gpio_get, + .set = wcd_gpio_set, +}; + +static int wcd_pinctrl_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pinctrl_pin_desc *pindesc; + struct pinctrl_desc *pctrldesc; + struct wcd_gpio_pad *pad, *pads; + struct wcd_gpio_priv *priv_data; + int ret, i, j; + u32 npins; + char **name; + + ret = of_property_read_u32(dev->of_node, "qcom,num-gpios", &npins); + if (ret) { + dev_err(dev, "%s: Looking up %s property in node %s failed\n", + __func__, "qcom,num-gpios", dev->of_node->full_name); + ret = -EINVAL; + goto err_priv_alloc; + } + if (!npins) { + dev_err(dev, "%s: no.of pins are 0\n", __func__); + ret = -EINVAL; + goto err_priv_alloc; + } + + priv_data = devm_kzalloc(dev, sizeof(*priv_data), GFP_KERNEL); + if (!priv_data) { + ret = -ENOMEM; + goto err_priv_alloc; + } + + priv_data->dev = dev; + priv_data->map = dev_get_regmap(dev->parent, NULL); + if (!priv_data->map) { + dev_err(dev, "%s: failed to get regmap\n", __func__); + ret = -EINVAL; + goto err_regmap; + } + + pindesc = devm_kcalloc(dev, npins, sizeof(*pindesc), GFP_KERNEL); + if (!pindesc) { + ret = -ENOMEM; + goto err_pinsec_alloc; + } + + pads = devm_kcalloc(dev, npins, sizeof(*pads), GFP_KERNEL); + if (!pads) { + ret = -ENOMEM; + goto err_pads_alloc; + } + + pctrldesc = devm_kzalloc(dev, sizeof(*pctrldesc), GFP_KERNEL); + if (!pctrldesc) { + ret = -ENOMEM; + goto err_pinctrl_alloc; + } + + pctrldesc->pctlops = &wcd_pinctrl_ops; + pctrldesc->confops = &wcd_pinconf_ops; + pctrldesc->owner = THIS_MODULE; + pctrldesc->name = dev_name(dev); + pctrldesc->pins = pindesc; + pctrldesc->npins = npins; + + name = devm_kcalloc(dev, npins, sizeof(char *), GFP_KERNEL); + if (!name) { + ret = -ENOMEM; + goto err_name_alloc; + } + for (i = 0; i < npins; i++, pindesc++) { + name[i] = devm_kzalloc(dev, sizeof(char) * WCD_GPIO_STRING_LEN, + GFP_KERNEL); + if (!name[i]) { + ret = -ENOMEM; + goto err_pin; + } + pad = &pads[i]; + pindesc->drv_data = pad; + pindesc->number = i; + snprintf(name[i], (WCD_GPIO_STRING_LEN - 1), "gpio%d", (i+1)); + pindesc->name = name[i]; + pad->offset = i; + pad->is_valid = true; + } + + priv_data->chip = wcd_gpio_chip; + priv_data->chip.dev = dev; + priv_data->chip.base = -1; + priv_data->chip.ngpio = npins; + priv_data->chip.label = dev_name(dev); + priv_data->chip.of_gpio_n_cells = 2; + priv_data->chip.can_sleep = false; + + priv_data->ctrl = pinctrl_register(pctrldesc, dev, priv_data); + if (IS_ERR(priv_data->ctrl)) { + dev_err(dev, "%s: failed to register to pinctrl\n", __func__); + ret = PTR_ERR(priv_data->ctrl); + goto err_pin; + } + + ret = gpiochip_add(&priv_data->chip); + if (ret) { + dev_err(dev, "%s: can't add gpio chip\n", __func__); + goto err_chip; + } + + ret = gpiochip_add_pin_range(&priv_data->chip, dev_name(dev), 0, 0, + npins); + if (ret) { + dev_err(dev, "%s: failed to add pin range\n", __func__); + goto err_range; + } + platform_set_drvdata(pdev, priv_data); + + return 0; + +err_range: + gpiochip_remove(&priv_data->chip); +err_chip: + pinctrl_unregister(priv_data->ctrl); +err_pin: + for (j = 0; j < i; j++) + devm_kfree(dev, name[j]); + devm_kfree(dev, name); +err_name_alloc: + devm_kfree(dev, pctrldesc); +err_pinctrl_alloc: + devm_kfree(dev, pads); +err_pads_alloc: + devm_kfree(dev, pindesc); +err_pinsec_alloc: +err_regmap: + devm_kfree(dev, priv_data); +err_priv_alloc: + return ret; +} + +static int wcd_pinctrl_remove(struct platform_device *pdev) +{ + struct wcd_gpio_priv *priv_data = platform_get_drvdata(pdev); + + gpiochip_remove(&priv_data->chip); + pinctrl_unregister(priv_data->ctrl); + + return 0; +} + +static const struct of_device_id wcd_pinctrl_of_match[] = { + { .compatible = "qcom,wcd-pinctrl" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, wcd_pinctrl_of_match); + +static struct platform_driver wcd_pinctrl_driver = { + .driver = { + .name = "qcom-wcd-pinctrl", + .of_match_table = wcd_pinctrl_of_match, + }, + .probe = wcd_pinctrl_probe, + .remove = wcd_pinctrl_remove, +}; + +module_platform_driver(wcd_pinctrl_driver); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc WCD GPIO pin control driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3