Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8aa9aba2 authored by Yeleswarapu Nagaradhesh's avatar Yeleswarapu Nagaradhesh
Browse files

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: default avatarYeleswarapu Nagaradhesh <nagaradh@codeaurora.org>
parent 00f153a5
Loading
Loading
Loading
Loading
+138 −0
Original line number Diff line number Diff line
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;
			};
		};
	};
+1 −0
Original line number Diff line number Diff line
@@ -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
+7 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+443 −0
Original line number Diff line number Diff line
/*
 * 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");