561 lines
13 KiB
C
561 lines
13 KiB
C
/*
|
|
* Rockchip pinctrl and gpio driver for Barebox
|
|
*
|
|
* Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com>
|
|
*
|
|
* Based on Linux pinctrl-rockchip:
|
|
* Copyright (C) 2013 MundoReader S.L.
|
|
* Copyright (C) 2012 Samsung Electronics Co., Ltd.
|
|
* Copyright (C) 2012 Linaro Ltd
|
|
* Copyright (C) 2011-2012 Jean-Christophe PLAGNIOL-VILLARD
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* 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 <common.h>
|
|
#include <gpio.h>
|
|
#include <init.h>
|
|
#include <malloc.h>
|
|
#include <of.h>
|
|
#include <of_address.h>
|
|
#include <pinctrl.h>
|
|
|
|
#include <linux/basic_mmio_gpio.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
|
|
enum rockchip_pinctrl_type {
|
|
RK2928,
|
|
RK3066B,
|
|
RK3188,
|
|
};
|
|
|
|
enum rockchip_pin_bank_type {
|
|
COMMON_BANK,
|
|
RK3188_BANK0,
|
|
};
|
|
|
|
struct rockchip_pin_bank {
|
|
void __iomem *reg_base;
|
|
void __iomem *reg_pull;
|
|
struct clk *clk;
|
|
u32 pin_base;
|
|
u8 nr_pins;
|
|
char *name;
|
|
u8 bank_num;
|
|
enum rockchip_pin_bank_type bank_type;
|
|
bool valid;
|
|
struct device_node *of_node;
|
|
struct rockchip_pinctrl *drvdata;
|
|
struct bgpio_chip bgpio_chip;
|
|
};
|
|
|
|
#define PIN_BANK(id, pins, label) \
|
|
{ \
|
|
.bank_num = id, \
|
|
.nr_pins = pins, \
|
|
.name = label, \
|
|
}
|
|
|
|
struct rockchip_pin_ctrl {
|
|
struct rockchip_pin_bank *pin_banks;
|
|
u32 nr_banks;
|
|
u32 nr_pins;
|
|
char *label;
|
|
enum rockchip_pinctrl_type type;
|
|
int mux_offset;
|
|
void (*pull_calc_reg)(struct rockchip_pin_bank *bank, int pin_num,
|
|
void __iomem **reg, u8 *bit);
|
|
};
|
|
|
|
struct rockchip_pinctrl {
|
|
void __iomem *reg_base;
|
|
void __iomem *reg_pull;
|
|
struct pinctrl_device pctl_dev;
|
|
struct rockchip_pin_ctrl *ctrl;
|
|
};
|
|
|
|
enum {
|
|
RK_BIAS_DISABLE = 0,
|
|
RK_BIAS_PULL_UP,
|
|
RK_BIAS_PULL_DOWN,
|
|
RK_BIAS_BUS_HOLD,
|
|
};
|
|
|
|
/* GPIO registers */
|
|
enum {
|
|
RK_GPIO_SWPORT_DR = 0x00,
|
|
RK_GPIO_SWPORT_DDR = 0x04,
|
|
RK_GPIO_EXT_PORT = 0x50,
|
|
};
|
|
|
|
static int rockchip_gpiolib_register(struct device_d *dev,
|
|
struct rockchip_pinctrl *info)
|
|
{
|
|
struct rockchip_pin_ctrl *ctrl = info->ctrl;
|
|
struct rockchip_pin_bank *bank = ctrl->pin_banks;
|
|
void __iomem *reg_base;
|
|
int ret;
|
|
int i;
|
|
|
|
for (i = 0; i < ctrl->nr_banks; ++i, ++bank) {
|
|
if (!bank->valid) {
|
|
dev_warn(dev, "bank %s is not valid\n", bank->name);
|
|
continue;
|
|
}
|
|
|
|
reg_base = bank->reg_base;
|
|
|
|
ret = bgpio_init(&bank->bgpio_chip, dev, 4,
|
|
reg_base + RK_GPIO_EXT_PORT,
|
|
reg_base + RK_GPIO_SWPORT_DR, NULL,
|
|
reg_base + RK_GPIO_SWPORT_DDR, NULL, 0);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
bank->bgpio_chip.gc.ngpio = bank->nr_pins;
|
|
ret = gpiochip_add(&bank->bgpio_chip.gc);
|
|
if (ret) {
|
|
dev_err(dev, "failed to register gpio_chip %s, error code: %d\n",
|
|
bank->name, ret);
|
|
goto fail;
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
fail:
|
|
for (--i, --bank; i >= 0; --i, --bank) {
|
|
if (!bank->valid)
|
|
continue;
|
|
|
|
gpiochip_remove(&bank->bgpio_chip.gc);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static struct rockchip_pinctrl *to_rockchip_pinctrl(struct pinctrl_device *pdev)
|
|
{
|
|
return container_of(pdev, struct rockchip_pinctrl, pctl_dev);
|
|
}
|
|
|
|
static struct rockchip_pin_bank *bank_num_to_bank(struct rockchip_pinctrl *info,
|
|
unsigned num)
|
|
{
|
|
struct rockchip_pin_bank *b = info->ctrl->pin_banks;
|
|
int i;
|
|
|
|
for (i = 0; i < info->ctrl->nr_banks; i++, b++) {
|
|
if (b->bank_num == num)
|
|
return b;
|
|
}
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
static int parse_bias_config(struct device_node *np)
|
|
{
|
|
u32 val;
|
|
|
|
if (of_property_read_u32(np, "bias-pull-up", &val) != -EINVAL)
|
|
return RK_BIAS_PULL_UP;
|
|
else if (of_property_read_u32(np, "bias-pull-down", &val) != -EINVAL)
|
|
return RK_BIAS_PULL_DOWN;
|
|
else if (of_property_read_u32(np, "bias-bus-hold", &val) != -EINVAL)
|
|
return RK_BIAS_BUS_HOLD;
|
|
else
|
|
return RK_BIAS_DISABLE;
|
|
}
|
|
|
|
|
|
#define RK2928_PULL_OFFSET 0x118
|
|
#define RK2928_PULL_PINS_PER_REG 16
|
|
#define RK2928_PULL_BANK_STRIDE 8
|
|
|
|
static void rk2928_calc_pull_reg_and_bit(struct rockchip_pin_bank *bank,
|
|
int pin_num, void __iomem **reg,
|
|
u8 *bit)
|
|
{
|
|
struct rockchip_pinctrl *info = bank->drvdata;
|
|
|
|
*reg = info->reg_base + RK2928_PULL_OFFSET;
|
|
*reg += bank->bank_num * RK2928_PULL_BANK_STRIDE;
|
|
*reg += (pin_num / RK2928_PULL_PINS_PER_REG) * 4;
|
|
|
|
*bit = pin_num % RK2928_PULL_PINS_PER_REG;
|
|
};
|
|
|
|
#define RK3188_PULL_BITS_PER_PIN 2
|
|
#define RK3188_PULL_PINS_PER_REG 8
|
|
#define RK3188_PULL_BANK_STRIDE 16
|
|
|
|
static void rk3188_calc_pull_reg_and_bit(struct rockchip_pin_bank *bank,
|
|
int pin_num, void __iomem **reg,
|
|
u8 *bit)
|
|
{
|
|
struct rockchip_pinctrl *info = bank->drvdata;
|
|
|
|
/* The first 12 pins of the first bank are located elsewhere */
|
|
if (bank->bank_type == RK3188_BANK0 && pin_num < 12) {
|
|
*reg = bank->reg_pull +
|
|
((pin_num / RK3188_PULL_PINS_PER_REG) * 4);
|
|
*bit = pin_num % RK3188_PULL_PINS_PER_REG;
|
|
*bit *= RK3188_PULL_BITS_PER_PIN;
|
|
} else {
|
|
*reg = info->reg_pull - 4;
|
|
*reg += bank->bank_num * RK3188_PULL_BANK_STRIDE;
|
|
*reg += ((pin_num / RK3188_PULL_PINS_PER_REG) * 4);
|
|
|
|
/*
|
|
* The bits in these registers have an inverse ordering
|
|
* with the lowest pin being in bits 15:14 and the highest
|
|
* pin in bits 1:0
|
|
*/
|
|
*bit = 7 - (pin_num % RK3188_PULL_PINS_PER_REG);
|
|
*bit *= RK3188_PULL_BITS_PER_PIN;
|
|
}
|
|
}
|
|
|
|
static int rockchip_pinctrl_set_func(struct rockchip_pin_bank *bank, int pin,
|
|
int mux)
|
|
{
|
|
struct rockchip_pinctrl *info = bank->drvdata;
|
|
void __iomem *reg = info->reg_base + info->ctrl->mux_offset;
|
|
u8 bit;
|
|
u32 data;
|
|
|
|
/* get basic quadruple of mux registers and the correct reg inside */
|
|
reg += bank->bank_num * 0x10;
|
|
reg += (pin / 8) * 4;
|
|
bit = (pin % 8) * 2;
|
|
|
|
data = 3 << (bit + 16);
|
|
data |= (mux & 3) << bit;
|
|
writel(data, reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_pinctrl_set_pull(struct rockchip_pin_bank *bank,
|
|
int pin_num, int pull)
|
|
{
|
|
struct rockchip_pinctrl *info = bank->drvdata;
|
|
struct rockchip_pin_ctrl *ctrl = info->ctrl;
|
|
void __iomem *reg;
|
|
u8 bit;
|
|
u32 data;
|
|
|
|
dev_dbg(info->pctl_dev.dev, "setting pull of GPIO%d-%d to %d\n",
|
|
bank->bank_num, pin_num, pull);
|
|
|
|
/* rk3066b doesn't support any pulls */
|
|
if (ctrl->type == RK3066B)
|
|
return pull ? -EINVAL : 0;
|
|
|
|
ctrl->pull_calc_reg(bank, pin_num, ®, &bit);
|
|
|
|
switch (ctrl->type) {
|
|
case RK2928:
|
|
data = BIT(bit + 16);
|
|
if (pull == RK_BIAS_DISABLE)
|
|
data |= BIT(bit);
|
|
writel(data, reg);
|
|
break;
|
|
case RK3188:
|
|
data = ((1 << RK3188_PULL_BITS_PER_PIN) - 1) << (bit + 16);
|
|
data |= pull << bit;
|
|
writel(data, reg);
|
|
break;
|
|
default:
|
|
dev_err(info->pctl_dev.dev, "unsupported pinctrl type\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_pinctrl_set_state(struct pinctrl_device *pdev,
|
|
struct device_node *np)
|
|
{
|
|
struct rockchip_pinctrl *info = to_rockchip_pinctrl(pdev);
|
|
const __be32 *list;
|
|
int i, size;
|
|
int bank_num, pin_num, func;
|
|
|
|
/*
|
|
* the binding format is rockchip,pins = <bank pin mux CONFIG>,
|
|
* do sanity check and calculate pins number
|
|
*/
|
|
list = of_get_property(np, "rockchip,pins", &size);
|
|
size /= sizeof(*list);
|
|
|
|
if (!size || size % 4) {
|
|
dev_err(pdev->dev, "wrong pins number or pins and configs should be by 4\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < size; i += 4) {
|
|
const __be32 *phandle;
|
|
struct device_node *np_config;
|
|
struct rockchip_pin_bank *bank;
|
|
|
|
bank_num = be32_to_cpu(*list++);
|
|
pin_num = be32_to_cpu(*list++);
|
|
func = be32_to_cpu(*list++);
|
|
phandle = list++;
|
|
|
|
if (!phandle)
|
|
return -EINVAL;
|
|
|
|
np_config = of_find_node_by_phandle(be32_to_cpup(phandle));
|
|
bank = bank_num_to_bank(info, bank_num);
|
|
rockchip_pinctrl_set_func(bank, pin_num, func);
|
|
rockchip_pinctrl_set_pull(bank, pin_num,
|
|
parse_bias_config(np_config));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct pinctrl_ops rockchip_pinctrl_ops = {
|
|
.set_state = rockchip_pinctrl_set_state,
|
|
};
|
|
|
|
static int rockchip_get_bank_data(struct rockchip_pin_bank *bank,
|
|
struct device_d *dev)
|
|
{
|
|
struct resource node_res, *res;
|
|
|
|
if (of_address_to_resource(bank->of_node, 0, &node_res)) {
|
|
dev_err(dev, "cannot find IO resource for bank\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
res = request_iomem_region(dev_name(dev), node_res.start, node_res.end);
|
|
if (!res) {
|
|
dev_err(dev, "cannot request iomem region %08x\n",
|
|
node_res.start);
|
|
return -ENOENT;
|
|
}
|
|
|
|
bank->reg_base = (void __iomem *)res->start;
|
|
|
|
/*
|
|
* special case, where parts of the pull setting-registers are
|
|
* part of the PMU register space
|
|
*/
|
|
if (of_device_is_compatible(bank->of_node,
|
|
"rockchip,rk3188-gpio-bank0")) {
|
|
bank->bank_type = RK3188_BANK0;
|
|
|
|
if (of_address_to_resource(bank->of_node, 1, &node_res)) {
|
|
dev_err(dev, "cannot find IO resource for bank\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
res = request_iomem_region(dev_name(dev), node_res.start,
|
|
node_res.end);
|
|
if (!res) {
|
|
dev_err(dev, "cannot request iomem region %08x\n",
|
|
node_res.start);
|
|
return -ENOENT;
|
|
}
|
|
|
|
bank->reg_pull = (void __iomem *)res->start;
|
|
} else {
|
|
bank->bank_type = COMMON_BANK;
|
|
}
|
|
|
|
bank->clk = of_clk_get(bank->of_node, 0);
|
|
if (IS_ERR(bank->clk))
|
|
return PTR_ERR(bank->clk);
|
|
|
|
return clk_enable(bank->clk);
|
|
}
|
|
|
|
static struct of_device_id rockchip_pinctrl_dt_match[];
|
|
|
|
static struct rockchip_pin_ctrl *rockchip_pinctrl_get_soc_data(
|
|
struct rockchip_pinctrl *d, struct device_d *dev)
|
|
{
|
|
const struct of_device_id *match;
|
|
struct device_node *node = dev->device_node;
|
|
struct device_node *np;
|
|
struct rockchip_pin_ctrl *ctrl;
|
|
struct rockchip_pin_bank *bank;
|
|
char *name;
|
|
int i;
|
|
|
|
match = of_match_node(rockchip_pinctrl_dt_match, node);
|
|
ctrl = (struct rockchip_pin_ctrl *)match->data;
|
|
|
|
for_each_child_of_node(node, np) {
|
|
if (!of_find_property(np, "gpio-controller", NULL))
|
|
continue;
|
|
|
|
bank = ctrl->pin_banks;
|
|
for (i = 0; i < ctrl->nr_banks; ++i, ++bank) {
|
|
name = bank->name;
|
|
if (!strncmp(name, np->name, strlen(name))) {
|
|
bank->of_node = np;
|
|
if (!rockchip_get_bank_data(bank, dev))
|
|
bank->valid = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bank = ctrl->pin_banks;
|
|
for (i = 0; i < ctrl->nr_banks; ++i, ++bank) {
|
|
bank->drvdata = d;
|
|
bank->pin_base = ctrl->nr_pins;
|
|
ctrl->nr_pins += bank->nr_pins;
|
|
}
|
|
|
|
return ctrl;
|
|
}
|
|
|
|
static int rockchip_pinctrl_probe(struct device_d *dev)
|
|
{
|
|
struct rockchip_pinctrl *info;
|
|
struct rockchip_pin_ctrl *ctrl;
|
|
int ret;
|
|
|
|
info = xzalloc(sizeof(struct rockchip_pinctrl));
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
ctrl = rockchip_pinctrl_get_soc_data(info, dev);
|
|
if (!ctrl) {
|
|
dev_err(dev, "driver data not available\n");
|
|
return -EINVAL;
|
|
}
|
|
info->ctrl = ctrl;
|
|
|
|
info->reg_base = dev_request_mem_region(dev, 0);
|
|
if (!info->reg_base) {
|
|
dev_err(dev, "Could not get reg_base region\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* The RK3188 has its pull registers in a separate place */
|
|
if (ctrl->type == RK3188) {
|
|
info->reg_pull = dev_request_mem_region(dev, 1);
|
|
if (!info->reg_pull) {
|
|
dev_err(dev, "Could not get reg_pull region\n");
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
info->pctl_dev.dev = dev;
|
|
info->pctl_dev.ops = &rockchip_pinctrl_ops;
|
|
|
|
ret = rockchip_gpiolib_register(dev, info);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = pinctrl_register(&info->pctl_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct rockchip_pin_bank rk2928_pin_banks[] = {
|
|
PIN_BANK(0, 32, "gpio0"),
|
|
PIN_BANK(1, 32, "gpio1"),
|
|
PIN_BANK(2, 32, "gpio2"),
|
|
PIN_BANK(3, 32, "gpio3"),
|
|
};
|
|
|
|
static struct rockchip_pin_ctrl rk2928_pin_ctrl = {
|
|
.pin_banks = rk2928_pin_banks,
|
|
.nr_banks = ARRAY_SIZE(rk2928_pin_banks),
|
|
.type = RK2928,
|
|
.mux_offset = 0xa8,
|
|
.pull_calc_reg = rk2928_calc_pull_reg_and_bit,
|
|
};
|
|
|
|
static struct rockchip_pin_bank rk3066a_pin_banks[] = {
|
|
PIN_BANK(0, 32, "gpio0"),
|
|
PIN_BANK(1, 32, "gpio1"),
|
|
PIN_BANK(2, 32, "gpio2"),
|
|
PIN_BANK(3, 32, "gpio3"),
|
|
PIN_BANK(4, 32, "gpio4"),
|
|
PIN_BANK(6, 16, "gpio6"),
|
|
};
|
|
|
|
static struct rockchip_pin_ctrl rk3066a_pin_ctrl = {
|
|
.pin_banks = rk3066a_pin_banks,
|
|
.nr_banks = ARRAY_SIZE(rk3066a_pin_banks),
|
|
.type = RK2928,
|
|
.mux_offset = 0xa8,
|
|
.pull_calc_reg = rk2928_calc_pull_reg_and_bit,
|
|
};
|
|
|
|
static struct rockchip_pin_bank rk3066b_pin_banks[] = {
|
|
PIN_BANK(0, 32, "gpio0"),
|
|
PIN_BANK(1, 32, "gpio1"),
|
|
PIN_BANK(2, 32, "gpio2"),
|
|
PIN_BANK(3, 32, "gpio3"),
|
|
};
|
|
|
|
static struct rockchip_pin_ctrl rk3066b_pin_ctrl = {
|
|
.pin_banks = rk3066b_pin_banks,
|
|
.nr_banks = ARRAY_SIZE(rk3066b_pin_banks),
|
|
.type = RK3066B,
|
|
.mux_offset = 0x60,
|
|
};
|
|
|
|
static struct rockchip_pin_bank rk3188_pin_banks[] = {
|
|
PIN_BANK(0, 32, "gpio0"),
|
|
PIN_BANK(1, 32, "gpio1"),
|
|
PIN_BANK(2, 32, "gpio2"),
|
|
PIN_BANK(3, 32, "gpio3"),
|
|
};
|
|
|
|
static struct rockchip_pin_ctrl rk3188_pin_ctrl = {
|
|
.pin_banks = rk3188_pin_banks,
|
|
.nr_banks = ARRAY_SIZE(rk3188_pin_banks),
|
|
.type = RK3188,
|
|
.mux_offset = 0x60,
|
|
.pull_calc_reg = rk3188_calc_pull_reg_and_bit,
|
|
};
|
|
|
|
static struct of_device_id rockchip_pinctrl_dt_match[] = {
|
|
{
|
|
.compatible = "rockchip,rk2928-pinctrl",
|
|
.data = (long)&rk2928_pin_ctrl,
|
|
},
|
|
{
|
|
.compatible = "rockchip,rk3066a-pinctrl",
|
|
.data = (long)&rk3066a_pin_ctrl,
|
|
},
|
|
{
|
|
.compatible = "rockchip,rk3066b-pinctrl",
|
|
.data = (long)&rk3066b_pin_ctrl,
|
|
},
|
|
{
|
|
.compatible = "rockchip,rk3188-pinctrl",
|
|
.data = (long)&rk3188_pin_ctrl,
|
|
}, {
|
|
/* sentinel */
|
|
}
|
|
};
|
|
|
|
static struct driver_d rockchip_pinctrl_driver = {
|
|
.name = "rockchip-pinctrl",
|
|
.probe = rockchip_pinctrl_probe,
|
|
.of_compatible = DRV_OF_COMPAT(rockchip_pinctrl_dt_match),
|
|
};
|
|
|
|
console_platform_driver(rockchip_pinctrl_driver);
|