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>
This commit is contained in:
Yeleswarapu Nagaradhesh 2016-07-19 05:57:18 +05:30
parent 00f153a5b1
commit 8aa9aba222
5 changed files with 590 additions and 0 deletions

View file

@ -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;
};
};
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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");