You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
464 lines
11 KiB
464 lines
11 KiB
/*
|
|
* Copyright (c) 2018-2019, 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/io.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/slab.h>
|
|
#include <linux/types.h>
|
|
|
|
#include "../core.h"
|
|
#include "../pinctrl-utils.h"
|
|
|
|
/**
|
|
* struct slpi_pin - SLPI pin definition
|
|
* @name: Name of the pin.
|
|
* @ctl_reg: Offset of the register holding control bits for this group.
|
|
* @io_reg: Offset of the register holding input/output bits for this group.
|
|
* @pull_bit: Offset in @ctl_reg for the bias configuration.
|
|
* @mux_bit: Offset in @ctl_reg for the pinmux function selection.
|
|
* @drv_bit: Offset in @ctl_reg for the drive strength configuration.
|
|
* @oe_bit: Offset in @ctl_reg for controlling output enable.
|
|
* @in_bit: Offset in @io_reg for the input bit value.
|
|
* @out_bit: Offset in @io_reg for the output bit value.
|
|
*/
|
|
struct slpi_pin {
|
|
void __iomem *base;
|
|
unsigned int offset;
|
|
|
|
unsigned int ctl_reg;
|
|
unsigned int io_reg;
|
|
|
|
unsigned int pull_bit:5;
|
|
unsigned int mux_bit:5;
|
|
unsigned int drv_bit:5;
|
|
unsigned int oe_bit:5;
|
|
unsigned int in_bit:5;
|
|
unsigned int out_bit:5;
|
|
};
|
|
|
|
|
|
/* The index of each function in slpi_pin_functions[] array */
|
|
enum slpi_pin_func_index {
|
|
SLPI_PIN_FUNC_INDEX_GPIO = 0x00,
|
|
SLPI_PIN_FUNC_INDEX_FUNC1 = 0x01,
|
|
SLPI_PIN_FUNC_INDEX_FUNC2 = 0x02,
|
|
SLPI_PIN_FUNC_INDEX_FUNC3 = 0x03,
|
|
SLPI_PIN_FUNC_INDEX_FUNC4 = 0x04,
|
|
SLPI_PIN_FUNC_INDEX_FUNC5 = 0x05,
|
|
};
|
|
|
|
#define SLPI_PIN_FUNC_GPIO "gpio"
|
|
#define SLPI_PIN_FUNC_FUNC1 "func1"
|
|
#define SLPI_PIN_FUNC_FUNC2 "func2"
|
|
#define SLPI_PIN_FUNC_FUNC3 "func3"
|
|
#define SLPI_PIN_FUNC_FUNC4 "func4"
|
|
#define SLPI_PIN_FUNC_FUNC5 "func5"
|
|
|
|
static const char *const slpi_gpio_groups[] = {
|
|
"gpio0", "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",
|
|
};
|
|
|
|
static const char *const slpi_pin_functions[] = {
|
|
[SLPI_PIN_FUNC_INDEX_GPIO] = SLPI_PIN_FUNC_GPIO,
|
|
[SLPI_PIN_FUNC_INDEX_FUNC1] = SLPI_PIN_FUNC_FUNC1,
|
|
[SLPI_PIN_FUNC_INDEX_FUNC2] = SLPI_PIN_FUNC_FUNC2,
|
|
[SLPI_PIN_FUNC_INDEX_FUNC3] = SLPI_PIN_FUNC_FUNC3,
|
|
[SLPI_PIN_FUNC_INDEX_FUNC4] = SLPI_PIN_FUNC_FUNC4,
|
|
[SLPI_PIN_FUNC_INDEX_FUNC5] = SLPI_PIN_FUNC_FUNC5,
|
|
};
|
|
|
|
static unsigned int slpi_read(struct slpi_pin *pin, u32 reg)
|
|
{
|
|
return readl_relaxed(pin->base + pin->offset + reg);
|
|
}
|
|
|
|
static void slpi_write(u32 val, struct slpi_pin *pin, u32 reg)
|
|
{
|
|
return writel_relaxed(val, pin->base + pin->offset + reg);
|
|
}
|
|
|
|
static int slpi_get_groups_count(struct pinctrl_dev *pctldev)
|
|
{
|
|
/* Every PIN is a group */
|
|
return pctldev->desc->npins;
|
|
}
|
|
|
|
static const char *slpi_get_group_name(struct pinctrl_dev *pctldev,
|
|
unsigned int pin)
|
|
{
|
|
return pctldev->desc->pins[pin].name;
|
|
}
|
|
|
|
static int slpi_get_group_pins(struct pinctrl_dev *pctldev,
|
|
unsigned int pin,
|
|
const unsigned int **pins,
|
|
unsigned int *num_pins)
|
|
{
|
|
*pins = &pctldev->desc->pins[pin].number;
|
|
*num_pins = 1;
|
|
return 0;
|
|
}
|
|
|
|
static const struct pinctrl_ops slpi_pinctrl_ops = {
|
|
.get_groups_count = slpi_get_groups_count,
|
|
.get_group_name = slpi_get_group_name,
|
|
.get_group_pins = slpi_get_group_pins,
|
|
.dt_node_to_map = pinconf_generic_dt_node_to_map_group,
|
|
.dt_free_map = pinctrl_utils_free_map,
|
|
};
|
|
|
|
static int slpi_get_functions_count(struct pinctrl_dev *pctldev)
|
|
{
|
|
return ARRAY_SIZE(slpi_pin_functions);
|
|
}
|
|
|
|
static const char *slpi_get_function_name(struct pinctrl_dev *pctldev,
|
|
unsigned int function)
|
|
{
|
|
return slpi_pin_functions[function];
|
|
}
|
|
|
|
static int slpi_get_function_groups(struct pinctrl_dev *pctldev,
|
|
unsigned int function,
|
|
const char * const **groups,
|
|
unsigned int * const num_groups)
|
|
{
|
|
*groups = slpi_gpio_groups;
|
|
*num_groups = pctldev->desc->npins;
|
|
return 0;
|
|
}
|
|
|
|
static int slpi_pinmux_set_mux(struct pinctrl_dev *pctldev,
|
|
unsigned int function,
|
|
unsigned int pin_index)
|
|
{
|
|
struct slpi_pin *pin;
|
|
u32 val;
|
|
|
|
pin = pctldev->desc->pins[pin_index].drv_data;
|
|
|
|
if (WARN_ON(function >= ARRAY_SIZE(slpi_pin_functions)))
|
|
return -EINVAL;
|
|
|
|
val = slpi_read(pin, pin->ctl_reg);
|
|
val &= ~(0x7 << pin->mux_bit);
|
|
val |= function << pin->mux_bit;
|
|
slpi_write(val, pin, pin->ctl_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pinmux_ops slpi_pinmux_ops = {
|
|
.get_functions_count = slpi_get_functions_count,
|
|
.get_function_name = slpi_get_function_name,
|
|
.get_function_groups = slpi_get_function_groups,
|
|
.set_mux = slpi_pinmux_set_mux,
|
|
};
|
|
|
|
static int slpi_config_reg(const struct slpi_pin *pin,
|
|
unsigned int param,
|
|
unsigned int *mask,
|
|
unsigned int *bit)
|
|
{
|
|
switch (param) {
|
|
case PIN_CONFIG_BIAS_DISABLE:
|
|
case PIN_CONFIG_BIAS_PULL_DOWN:
|
|
case PIN_CONFIG_BIAS_BUS_HOLD:
|
|
case PIN_CONFIG_BIAS_PULL_UP:
|
|
*bit = pin->pull_bit;
|
|
*mask = 3;
|
|
break;
|
|
case PIN_CONFIG_DRIVE_STRENGTH:
|
|
*bit = pin->drv_bit;
|
|
*mask = 7;
|
|
break;
|
|
case PIN_CONFIG_OUTPUT:
|
|
case PIN_CONFIG_INPUT_ENABLE:
|
|
*bit = pin->oe_bit;
|
|
*mask = 1;
|
|
break;
|
|
default:
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define MSM_NO_PULL 0
|
|
#define MSM_PULL_DOWN 1
|
|
#define MSM_KEEPER 2
|
|
#define MSM_PULL_UP 3
|
|
|
|
static unsigned int slpi_regval_to_drive(u32 val)
|
|
{
|
|
return (val + 1) * 2;
|
|
}
|
|
|
|
static int slpi_config_group_get(struct pinctrl_dev *pctldev,
|
|
unsigned int pin_index,
|
|
unsigned long *config)
|
|
{
|
|
unsigned int param = pinconf_to_config_param(*config);
|
|
struct slpi_pin *pin;
|
|
unsigned int mask;
|
|
unsigned int arg;
|
|
unsigned int bit;
|
|
int ret;
|
|
u32 val;
|
|
|
|
pin = pctldev->desc->pins[pin_index].drv_data;
|
|
ret = slpi_config_reg(pin, param, &mask, &bit);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val = slpi_read(pin, pin->ctl_reg);
|
|
arg = (val >> bit) & mask;
|
|
|
|
/* Convert register value to pinconf value */
|
|
switch (param) {
|
|
case PIN_CONFIG_BIAS_DISABLE:
|
|
arg = arg == MSM_NO_PULL;
|
|
break;
|
|
case PIN_CONFIG_BIAS_PULL_DOWN:
|
|
arg = arg == MSM_PULL_DOWN;
|
|
break;
|
|
case PIN_CONFIG_BIAS_BUS_HOLD:
|
|
arg = arg == MSM_KEEPER;
|
|
break;
|
|
case PIN_CONFIG_BIAS_PULL_UP:
|
|
arg = arg == MSM_PULL_UP;
|
|
break;
|
|
case PIN_CONFIG_DRIVE_STRENGTH:
|
|
arg = slpi_regval_to_drive(arg);
|
|
break;
|
|
case PIN_CONFIG_OUTPUT:
|
|
/* Pin is not output */
|
|
if (!arg)
|
|
return -EINVAL;
|
|
|
|
val = slpi_read(pin, pin->io_reg);
|
|
arg = !!(val & BIT(pin->in_bit));
|
|
break;
|
|
case PIN_CONFIG_INPUT_ENABLE:
|
|
/* Pin is output */
|
|
if (arg)
|
|
return -EINVAL;
|
|
arg = 1;
|
|
break;
|
|
default:
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
*config = pinconf_to_config_packed(param, arg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int slpi_config_group_set(struct pinctrl_dev *pctldev,
|
|
unsigned int pin_index,
|
|
unsigned long *configs,
|
|
unsigned int num_configs)
|
|
{
|
|
struct slpi_pin *pin;
|
|
unsigned int param;
|
|
unsigned int mask;
|
|
unsigned int arg;
|
|
unsigned int bit;
|
|
int ret;
|
|
u32 val;
|
|
int i;
|
|
|
|
pin = pctldev->desc->pins[pin_index].drv_data;
|
|
|
|
for (i = 0; i < num_configs; i++) {
|
|
param = pinconf_to_config_param(configs[i]);
|
|
arg = pinconf_to_config_argument(configs[i]);
|
|
|
|
ret = slpi_config_reg(pin, param, &mask, &bit);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Convert pinconf values to register values */
|
|
switch (param) {
|
|
case PIN_CONFIG_BIAS_DISABLE:
|
|
arg = MSM_NO_PULL;
|
|
break;
|
|
case PIN_CONFIG_BIAS_PULL_DOWN:
|
|
arg = MSM_PULL_DOWN;
|
|
break;
|
|
case PIN_CONFIG_BIAS_BUS_HOLD:
|
|
arg = MSM_KEEPER;
|
|
break;
|
|
case PIN_CONFIG_BIAS_PULL_UP:
|
|
arg = MSM_PULL_UP;
|
|
break;
|
|
case PIN_CONFIG_DRIVE_STRENGTH:
|
|
/* Check for invalid values */
|
|
if (arg > 16 || arg < 2 || (arg % 2) != 0)
|
|
arg = -1;
|
|
else
|
|
arg = (arg / 2) - 1;
|
|
break;
|
|
case PIN_CONFIG_OUTPUT:
|
|
/* set output value */
|
|
val = slpi_read(pin, pin->io_reg);
|
|
if (arg)
|
|
val |= BIT(pin->out_bit);
|
|
else
|
|
val &= ~BIT(pin->out_bit);
|
|
slpi_write(val, pin, pin->io_reg);
|
|
|
|
/* enable output */
|
|
arg = 1;
|
|
break;
|
|
case PIN_CONFIG_INPUT_ENABLE:
|
|
/* disable output */
|
|
arg = 0;
|
|
break;
|
|
default:
|
|
dev_err(pctldev->dev, "Unsupported config parameter: %x\n",
|
|
param);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Range-check user-supplied value */
|
|
if (arg & ~mask) {
|
|
dev_err(pctldev->dev, "config %x: %x is invalid\n",
|
|
param, arg);
|
|
return -EINVAL;
|
|
}
|
|
|
|
val = slpi_read(pin, pin->ctl_reg);
|
|
val &= ~(mask << bit);
|
|
val |= arg << bit;
|
|
slpi_write(val, pin, pin->ctl_reg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pinconf_ops slpi_pinconf_ops = {
|
|
.is_generic = true,
|
|
.pin_config_group_get = slpi_config_group_get,
|
|
.pin_config_group_set = slpi_config_group_set,
|
|
};
|
|
|
|
static int slpi_pinctrl_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct pinctrl_dev *pctldev;
|
|
struct pinctrl_pin_desc *pindesc;
|
|
struct pinctrl_desc *pctrldesc;
|
|
struct slpi_pin *pin, *pins;
|
|
struct resource *res;
|
|
int ret, npins, i;
|
|
void __iomem *base;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(base))
|
|
return PTR_ERR(base);
|
|
|
|
ret = of_property_read_u32(dev->of_node, "qcom,num-pins", &npins);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
pindesc = devm_kcalloc(dev, npins, sizeof(*pindesc), GFP_KERNEL);
|
|
if (!pindesc)
|
|
return -ENOMEM;
|
|
|
|
WARN_ON(npins > ARRAY_SIZE(slpi_gpio_groups));
|
|
|
|
pins = devm_kcalloc(dev, npins, sizeof(*pins), GFP_KERNEL);
|
|
if (!pins)
|
|
return -ENOMEM;
|
|
|
|
pctrldesc = devm_kzalloc(dev, sizeof(*pctrldesc), GFP_KERNEL);
|
|
if (!pctrldesc)
|
|
return -ENOMEM;
|
|
|
|
pctrldesc->pctlops = &slpi_pinctrl_ops;
|
|
pctrldesc->pmxops = &slpi_pinmux_ops;
|
|
pctrldesc->confops = &slpi_pinconf_ops;
|
|
pctrldesc->owner = THIS_MODULE;
|
|
pctrldesc->name = dev_name(&pdev->dev);
|
|
pctrldesc->pins = pindesc;
|
|
pctrldesc->npins = npins;
|
|
|
|
for (i = 0; i < npins; i++, pindesc++) {
|
|
pin = &pins[i];
|
|
pindesc->drv_data = pin;
|
|
pindesc->number = i;
|
|
pindesc->name = slpi_gpio_groups[i];
|
|
|
|
pin->base = base;
|
|
pin->offset = i * 0x1000;
|
|
pin->ctl_reg = 0x0;
|
|
pin->io_reg = 0x4;
|
|
|
|
pin->pull_bit = 0;
|
|
pin->out_bit = 1;
|
|
pin->mux_bit = 2;
|
|
pin->oe_bit = 9;
|
|
pin->drv_bit = 6;
|
|
pin->in_bit = 0;
|
|
}
|
|
|
|
pctldev = devm_pinctrl_register(&pdev->dev, pctrldesc, NULL);
|
|
if (IS_ERR(pctldev)) {
|
|
dev_err(dev, "Failed to register pinctrl device\n");
|
|
return PTR_ERR(pctldev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id slpi_pinctrl_of_match[] = {
|
|
{ .compatible = "qcom,slpi-pinctrl" }, /* Generic */
|
|
{ },
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, slpi_pinctrl_of_match);
|
|
|
|
static struct platform_driver slpi_pinctrl_driver = {
|
|
.driver = {
|
|
.name = "qcom-slpi-pinctrl",
|
|
.of_match_table = slpi_pinctrl_of_match,
|
|
},
|
|
.probe = slpi_pinctrl_probe,
|
|
};
|
|
|
|
static int __init slpi_pinctrl_init(void)
|
|
{
|
|
return platform_driver_register(&slpi_pinctrl_driver);
|
|
}
|
|
arch_initcall(slpi_pinctrl_init);
|
|
|
|
static void __exit slpi_pinctrl_exit(void)
|
|
{
|
|
platform_driver_unregister(&slpi_pinctrl_driver);
|
|
}
|
|
module_exit(slpi_pinctrl_exit);
|
|
|
|
MODULE_DESCRIPTION("QTI SLPI GPIO pin control driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
|