6db4831e98
Android 14
432 lines
11 KiB
C
432 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2020 MediaTek Inc.
|
|
*
|
|
* Author: Light Hsieh <light.hsieh@mediatek.com>
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <dt-bindings/pinctrl/mt65xx.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <../../gpio/gpiolib.h>
|
|
#include <asm-generic/gpio.h>
|
|
#include <linux/delay.h>
|
|
#include "pinctrl-paris.h"
|
|
|
|
#define MTK_PINCTRL_DEV_NAME "pinctrl_paris"
|
|
#define PULL_DELAY 50 /* in ms */
|
|
#define FUN_3STATE "gpio_get_value_tristate"
|
|
|
|
static const char *pinctrl_paris_modname = MTK_PINCTRL_DEV_NAME;
|
|
static struct mtk_pinctrl *g_hw;
|
|
|
|
static void mtk_gpio_find_mtk_pinctrl_dev(void)
|
|
{
|
|
struct gpio_desc *gdesc;
|
|
unsigned int pin = ARCH_NR_GPIOS - 1;
|
|
|
|
do {
|
|
gdesc = gpio_to_desc(pin);
|
|
if (!gdesc)
|
|
break;
|
|
if (!strncmp(pinctrl_paris_modname,
|
|
gdesc->gdev->chip->label,
|
|
strlen(pinctrl_paris_modname))) {
|
|
g_hw = gpiochip_get_data(gdesc->gdev->chip);
|
|
return;
|
|
}
|
|
|
|
pin = (pin + 1) - gdesc->gdev->chip->base;
|
|
} while (pin > 0);
|
|
}
|
|
|
|
int gpio_get_tristate_input(unsigned int pin)
|
|
{
|
|
struct mtk_pinctrl *hw = NULL;
|
|
const struct mtk_pin_desc *desc;
|
|
int val, val_up, val_down, ret, pullup, pullen, pull_type;
|
|
|
|
if (!g_hw)
|
|
mtk_gpio_find_mtk_pinctrl_dev();
|
|
if (!g_hw)
|
|
return -ENOTSUPP;
|
|
hw = g_hw;
|
|
|
|
if (!hw->soc) {
|
|
pr_notice("invalid gpio chip\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!hw->soc->bias_set_combo) {
|
|
pr_notice("not supported gpio chip\n");
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
if (pin < hw->chip.base) {
|
|
pr_notice(FUN_3STATE ": please use virtual pin number\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pin -= hw->chip.base;
|
|
if (pin >= hw->soc->npins) {
|
|
pr_notice(FUN_3STATE ": invalid pin number: %u\n",
|
|
pin);
|
|
return -EINVAL;
|
|
}
|
|
|
|
desc = (const struct mtk_pin_desc *)&hw->soc->pins[pin];
|
|
ret = mtk_hw_get_value(hw, desc, PINCTRL_PIN_REG_MODE, &val);
|
|
if (ret)
|
|
return ret;
|
|
if (val != 0) {
|
|
pr_notice(FUN_3STATE ":GPIO%d in mode %d, not GPIO mode\n",
|
|
pin, val);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = hw->soc->bias_get_combo(hw, desc, &pullup, &pullen);
|
|
if (ret)
|
|
return ret;
|
|
if (pullen == 0 || pullen == MTK_PUPD_SET_R1R0_00) {
|
|
pr_notice(FUN_3STATE ":GPIO%d not pullen, skip floating test\n",
|
|
pin);
|
|
return gpio_get_value(pin+hw->chip.base);
|
|
}
|
|
if (pullen > MTK_PUPD_SET_R1R0_00)
|
|
pull_type = 1;
|
|
else
|
|
pull_type = 0;
|
|
|
|
/* set pullsel as pull-up and get input value */
|
|
pr_notice(FUN_3STATE ":pull up GPIO%d\n", pin);
|
|
ret = hw->soc->bias_set_combo(hw, desc, 1,
|
|
(pull_type ? MTK_PUPD_SET_R1R0_11 : MTK_ENABLE));
|
|
if (ret)
|
|
goto out;
|
|
mdelay(PULL_DELAY);
|
|
val_up = gpio_get_value(pin+hw->chip.base);
|
|
pr_notice(FUN_3STATE ":GPIO%d input %d\n", pin, val_up);
|
|
|
|
/* set pullsel as pull-down and get input value */
|
|
pr_notice(FUN_3STATE ":pull down GPIO%d\n", pin);
|
|
ret = hw->soc->bias_set_combo(hw, desc, 0,
|
|
(pull_type ? MTK_PUPD_SET_R1R0_11 : MTK_ENABLE));
|
|
if (ret)
|
|
goto out;
|
|
mdelay(PULL_DELAY);
|
|
val_down = gpio_get_value(pin+hw->chip.base);
|
|
pr_notice(FUN_3STATE ":GPIO%d input %d\n", pin, val_down);
|
|
|
|
if (val_up && val_down)
|
|
ret = 1;
|
|
else if (!val_up && !val_down)
|
|
ret = 0;
|
|
else if (val_up && !val_down)
|
|
ret = 2;
|
|
else {
|
|
pr_notice(FUN_3STATE ":GPIO%d pull HW is abnormal\n", pin);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
out:
|
|
/* restore pullsel */
|
|
hw->soc->bias_set_combo(hw, desc, pullup, pullen);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mtk_hw_set_value_wrap(struct mtk_pinctrl *hw, unsigned int gpio,
|
|
int value, int field)
|
|
{
|
|
const struct mtk_pin_desc *desc;
|
|
|
|
if (gpio > hw->soc->npins)
|
|
return -EINVAL;
|
|
|
|
desc = (const struct mtk_pin_desc *)&hw->soc->pins[gpio];
|
|
|
|
return mtk_hw_set_value(hw, desc, field, value);
|
|
}
|
|
|
|
#define mtk_pctrl_set_pinmux(hw, gpio, val) \
|
|
mtk_hw_set_value_wrap(hw, gpio, val, PINCTRL_PIN_REG_MODE)
|
|
|
|
/* MTK HW use 0 as input, 1 for output
|
|
* This interface is for set direct register value,
|
|
* so don't reverse
|
|
*/
|
|
#define mtk_pctrl_set_direction(hw, gpio, val) \
|
|
mtk_hw_set_value_wrap(hw, gpio, val, PINCTRL_PIN_REG_DIR)
|
|
|
|
#define mtk_pctrl_set_out(hw, gpio, val) \
|
|
mtk_hw_set_value_wrap(hw, gpio, val, PINCTRL_PIN_REG_DO)
|
|
|
|
#define mtk_pctrl_set_smt(hw, gpio, val) \
|
|
mtk_hw_set_value_wrap(hw, gpio, val, PINCTRL_PIN_REG_SMT)
|
|
|
|
#define mtk_pctrl_set_ies(hw, gpio, val) \
|
|
mtk_hw_set_value_wrap(hw, gpio, val, PINCTRL_PIN_REG_IES)
|
|
|
|
static ssize_t mtk_gpio_show_pin(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int len = 0;
|
|
unsigned int bufLen = PAGE_SIZE;
|
|
unsigned int i = 0;
|
|
struct mtk_pinctrl *hw = dev_get_drvdata(dev);
|
|
struct gpio_chip *chip;
|
|
|
|
if (!hw || !buf) {
|
|
pr_debug("[pinctrl] Err: NULL pointer!\n");
|
|
return len;
|
|
}
|
|
|
|
chip = &hw->chip;
|
|
|
|
len += snprintf(buf+len, bufLen-len,
|
|
"pins base: %d\n", chip->base);
|
|
len += snprintf(buf+len, bufLen-len,
|
|
"PIN: (MODE)(DIR)(DOUT)(DIN)(DRIVE)(SMT)(IES)(PULL_EN)(PULL_SEL)(R1 R0)\n");
|
|
|
|
for (i = 0; i < chip->ngpio; i++) {
|
|
if (len > (bufLen - 96)) {
|
|
pr_debug("[pinctrl]err:%d exceed to max size %d\n",
|
|
len, (bufLen - 96));
|
|
break;
|
|
}
|
|
len += mtk_pctrl_show_one_pin(hw, i, buf + len, bufLen - len);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
void gpio_dump_regs_range(int start, int end)
|
|
{
|
|
struct gpio_chip *chip = NULL;
|
|
struct mtk_pinctrl *hw;
|
|
char buf[96];
|
|
int i;
|
|
|
|
if (!g_hw)
|
|
mtk_gpio_find_mtk_pinctrl_dev();
|
|
if (!g_hw)
|
|
return;
|
|
|
|
hw = g_hw;
|
|
chip = &hw->chip;
|
|
|
|
if (start < 0) {
|
|
start = 0;
|
|
end = chip->ngpio - 1;
|
|
}
|
|
if (end < 0)
|
|
end = chip->ngpio - 1;
|
|
if (end > chip->ngpio - 1)
|
|
end = chip->ngpio - 1;
|
|
|
|
pr_notice("PIN: (MODE)(DIR)(DOUT)(DIN)(DRIVE)(SMT)(IES)(PULL_EN)(PULL_SEL)(R1 R0)\n");
|
|
|
|
for (i = start; i < end; i++) {
|
|
(void)mtk_pctrl_show_one_pin(hw, i, buf, 96);
|
|
pr_notice("%s", buf);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(gpio_dump_regs_range);
|
|
|
|
void gpio_dump_regs(void)
|
|
{
|
|
gpio_dump_regs_range(-1, -1);
|
|
}
|
|
|
|
static ssize_t mtk_gpio_store_pin(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
int i, gpio, val, val2, pullup = 0, pullen = 0;
|
|
int vals[12];
|
|
char attrs[12];
|
|
struct mtk_pinctrl *hw = dev_get_drvdata(dev);
|
|
const struct mtk_pin_desc *desc;
|
|
struct gpio_chip *chip;
|
|
int r1r0_en[4] = {MTK_PUPD_SET_R1R0_00, MTK_PUPD_SET_R1R0_01,
|
|
MTK_PUPD_SET_R1R0_10, MTK_PUPD_SET_R1R0_11};
|
|
|
|
if (!hw || !hw->soc) {
|
|
pr_notice("[pinctrl]cannot find %s device\n",
|
|
pinctrl_paris_modname);
|
|
return count;
|
|
}
|
|
|
|
chip = &hw->chip;
|
|
|
|
if (!strncmp(buf, "mode", 4)
|
|
&& (sscanf(buf+4, "%d %d", &gpio, &val) == 2)) {
|
|
mtk_pctrl_set_pinmux(hw, gpio, val);
|
|
} else if (!strncmp(buf, "dir", 3)
|
|
&& (sscanf(buf+3, "%d %d", &gpio, &val) == 2)) {
|
|
mtk_pctrl_set_direction(hw, gpio, val);
|
|
} else if (!strncmp(buf, "out", 3)
|
|
&& (sscanf(buf+3, "%d %d", &gpio, &val) == 2)) {
|
|
mtk_pctrl_set_direction(hw, gpio, 1);
|
|
/* mtk_gpio_set(chip, gpio, val); */
|
|
mtk_pctrl_set_out(hw, gpio, val);
|
|
} else if (!strncmp(buf, "pullen", 6)
|
|
&& (sscanf(buf+6, "%d %d", &gpio, &val) == 2)) {
|
|
if (gpio < 0 || gpio > hw->soc->npins) {
|
|
pr_notice("invalid pin number\n");
|
|
goto out;
|
|
}
|
|
desc = (const struct mtk_pin_desc *)&hw->soc->pins[gpio];
|
|
if (!hw->soc->bias_get_combo || !hw->soc->bias_set_combo)
|
|
goto no_bias_combo_out;
|
|
hw->soc->bias_get_combo(hw, desc, &pullup, &pullen);
|
|
if (pullen < MTK_PUPD_SET_R1R0_00) {
|
|
pullen = !!val;
|
|
} else {
|
|
if (val < 0)
|
|
val = 0;
|
|
else if (val > 3)
|
|
val = 3;
|
|
pullen = r1r0_en[val];
|
|
}
|
|
hw->soc->bias_set_combo(hw, desc, pullup, pullen);
|
|
} else if ((!strncmp(buf, "pullsel", 7))
|
|
&& (sscanf(buf+7, "%d %d", &gpio, &val) == 2)) {
|
|
if (gpio < 0 || gpio > hw->soc->npins) {
|
|
pr_notice("invalid pin number\n");
|
|
goto out;
|
|
}
|
|
desc = (const struct mtk_pin_desc *)&hw->soc->pins[gpio];
|
|
if (!hw->soc->bias_get_combo || !hw->soc->bias_set_combo)
|
|
goto no_bias_combo_out;
|
|
hw->soc->bias_get_combo(hw, desc, &pullup, &pullen);
|
|
hw->soc->bias_set_combo(hw, desc, !!val, pullen);
|
|
} else if ((!strncmp(buf, "ies", 3))
|
|
&& (sscanf(buf+3, "%d %d", &gpio, &val) == 2)) {
|
|
mtk_pctrl_set_ies(hw, gpio, val);
|
|
} else if ((!strncmp(buf, "smt", 3))
|
|
&& (sscanf(buf+3, "%d %d", &gpio, &val) == 2)) {
|
|
mtk_pctrl_set_smt(hw, gpio, val);
|
|
} else if ((!strncmp(buf, "driving", 7))
|
|
&& (sscanf(buf+7, "%d %d", &gpio, &val) == 2)) {
|
|
if (gpio < 0 || gpio > hw->soc->npins) {
|
|
pr_notice("invalid pin number\n");
|
|
goto out;
|
|
}
|
|
desc = (const struct mtk_pin_desc *)&hw->soc->pins[gpio];
|
|
hw->soc->drive_set(hw, desc, val);
|
|
} else if ((!strncmp(buf, "r1r0", 4))
|
|
&& (sscanf(buf+4, "%d %d %d", &gpio, &val, &val2) == 3)) {
|
|
if (gpio < 0 || gpio > hw->soc->npins) {
|
|
pr_notice("invalid pin number\n");
|
|
goto out;
|
|
}
|
|
desc = (const struct mtk_pin_desc *)&hw->soc->pins[gpio];
|
|
if (!hw->soc->bias_get_combo || !hw->soc->bias_set_combo)
|
|
goto no_bias_combo_out;
|
|
hw->soc->bias_get_combo(hw, desc, &pullup, &pullen);
|
|
pullen = r1r0_en[(((!!val) << 1) + !!val2)];
|
|
hw->soc->bias_set_combo(hw, desc, pullup, pullen);
|
|
} else if (!strncmp(buf, "set", 3)) {
|
|
val = sscanf(buf+3, "%d %c%c%c%c%c%c%c%c%c%c %c%c", &gpio,
|
|
&attrs[0], &attrs[1], &attrs[2], &attrs[3],
|
|
&attrs[4], &attrs[5], &attrs[6], &attrs[7],
|
|
&attrs[8], &attrs[9], &attrs[10], &attrs[11]);
|
|
if (val <= 0 || val > 13) {
|
|
pr_notice("invalid input count %d\n", val);
|
|
goto out;
|
|
}
|
|
if (!hw->soc->bias_get_combo || !hw->soc->bias_set_combo)
|
|
goto no_bias_combo_out;
|
|
for (i = 0; i < ARRAY_SIZE(attrs); i++) {
|
|
if ((attrs[i] >= '0') && (attrs[i] <= '9'))
|
|
vals[i] = attrs[i] - '0';
|
|
else
|
|
vals[i] = 0;
|
|
}
|
|
if (gpio < 0) {
|
|
pr_notice("invalid pin number\n");
|
|
goto out;
|
|
}
|
|
desc = (const struct mtk_pin_desc *)&hw->soc->pins[gpio];
|
|
/* MODE */
|
|
mtk_pctrl_set_pinmux(hw, gpio, vals[0]);
|
|
/* DIR */
|
|
mtk_pctrl_set_direction(hw, gpio, !!vals[1]);
|
|
/* DOUT */
|
|
if (vals[1])
|
|
/*mtk_gpio_set(chip, gpio, !!vals[2]); */
|
|
mtk_pctrl_set_out(hw, gpio, !!vals[2]);
|
|
/* DRIVING */
|
|
desc = (const struct mtk_pin_desc *)&hw->soc->pins[gpio];
|
|
hw->soc->drive_set(hw, desc, vals[4]*10 + vals[5]);
|
|
/* SMT */
|
|
mtk_pctrl_set_smt(hw, gpio, vals[6]);
|
|
/* IES */
|
|
mtk_pctrl_set_ies(hw, gpio, vals[7]);
|
|
/* PULL */
|
|
hw->soc->bias_get_combo(hw, desc, &pullup, &pullen);
|
|
if (pullen < MTK_PUPD_SET_R1R0_00) {
|
|
hw->soc->bias_set_combo(hw, desc, !!vals[9],
|
|
!!vals[8]);
|
|
} else {
|
|
pullen = r1r0_en[(((!!vals[10]) << 1) + !!vals[11])];
|
|
hw->soc->bias_set_combo(hw, desc, !!vals[9],
|
|
pullen);
|
|
}
|
|
}
|
|
|
|
out:
|
|
return count;
|
|
|
|
no_bias_combo_out:
|
|
pr_notice("[pinctrl]bias_set_combo/bias_get_combo not supported\n");
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(mt_gpio, 0444, mtk_gpio_show_pin, mtk_gpio_store_pin);
|
|
|
|
static int mtk_gpio_create_attr(void)
|
|
{
|
|
struct mtk_pinctrl *hw = NULL;
|
|
struct gpio_desc *gdesc;
|
|
unsigned int pin = ARCH_NR_GPIOS - 1;
|
|
int err = 0;
|
|
|
|
do {
|
|
gdesc = gpio_to_desc(pin);
|
|
if (!gdesc)
|
|
break;
|
|
if (!strncmp(pinctrl_paris_modname,
|
|
gdesc->gdev->chip->label,
|
|
strlen(pinctrl_paris_modname))) {
|
|
hw = gpiochip_get_data(gdesc->gdev->chip);
|
|
if (!hw || !hw->soc || !hw->dev) {
|
|
pr_notice("invalid gpio chip\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = device_create_file(hw->dev, &dev_attr_mt_gpio);
|
|
if (err) {
|
|
pr_notice("[pinctrl]error create mtk_gpio\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
pin = (pin + 1) - gdesc->gdev->chip->base;
|
|
} while (pin > 0);
|
|
|
|
return err;
|
|
}
|
|
static int __init pinctrl_mtk_debug_v2_init(void)
|
|
{
|
|
return mtk_gpio_create_attr();
|
|
}
|
|
|
|
late_initcall(pinctrl_mtk_debug_v2_init);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("MediaTek Pinctrl DEBUG Driver");
|