summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYeleswarapu Nagaradhesh <nagaradh@codeaurora.org>2016-07-19 05:57:18 +0530
committerYeleswarapu Nagaradhesh <nagaradh@codeaurora.org>2016-07-19 10:58:13 +0530
commit8aa9aba2221380022cf3a6afe1f77cea84b6245f (patch)
treea374dbb924ff34c3461534a499683110d05c2552
parent00f153a5b10897acd1a0b302c29635adc750d4ec (diff)
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 <nagaradh@codeaurora.org>
-rw-r--r--Documentation/devicetree/bindings/pinctrl/qcom,wcd-pinctrl.txt138
-rw-r--r--drivers/mfd/Kconfig1
-rw-r--r--drivers/pinctrl/qcom/Kconfig7
-rw-r--r--drivers/pinctrl/qcom/Makefile1
-rw-r--r--drivers/pinctrl/qcom/pinctrl-wcd.c443
5 files changed, 590 insertions, 0 deletions
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: <string>
+ Definition: must be "qcom,wcd-pinctrl"
+
+- qcom,num-gpios:
+ Usage: required
+ Value type: <u32>
+ Definition: Number of GPIO's supported by the controller
+
+- gpio-controller:
+ Usage: required
+ Value type: <none>
+ Definition: Mark the device node as a GPIO controller
+
+- #gpio-cells:
+ Usage: required
+ Value type: <u32>
+ 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: <string-array>
+ 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: <none>
+ Definition: The specified pins should be configured as no pull.
+
+- bias-pull-down:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins should be configured as pull down.
+
+- bias-pull-up:
+ Usage: optional
+ Value type: <empty>
+ Definition: The specified pins should be configured as pull up.
+
+- qcom,pull-up-strength:
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the strength to use for pull up, if selected.
+
+- bias-high-impedance:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins will put in high-Z mode and disabled.
+
+- input-enable:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are put in input mode.
+
+- output-high:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are configured in output mode, driven
+ high.
+
+- output-low:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are configured in output mode, driven
+ low.
+
+- qcom,drive-strength:
+ Usage: optional
+ Value type: <u32>
+ 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 <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/mfd/wcd934x/registers.h>
+
+#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");