u-boot/drivers/gpio/sh_pfc.c
Tom Rini d678a59d2d Revert "Merge patch series "arm: dts: am62-beagleplay: Fix Beagleplay Ethernet""
When bringing in the series 'arm: dts: am62-beagleplay: Fix Beagleplay
Ethernet"' I failed to notice that b4 noticed it was based on next and
so took that as the base commit and merged that part of next to master.

This reverts commit c8ffd1356d, reversing
changes made to 2ee6f3a5f7.

Reported-by: Jonas Karlman <jonas@kwiboo.se>
Signed-off-by: Tom Rini <trini@konsulko.com>
2024-05-19 08:16:36 -06:00

640 lines
13 KiB
C

/*
* Pinmuxed GPIO support for SuperH.
* Copy from linux kernel driver/sh/pfc.c
*
* Copyright (C) 2008 Magnus Damm
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*/
#include <common.h>
#include <log.h>
#include <malloc.h>
#include <asm/bitops.h>
#include <asm/io.h>
#include <sh_pfc.h>
#include <linux/bitops.h>
#include <linux/bug.h>
static struct pinmux_info *gpioc;
#define pfc_phys_to_virt(p, a) ((void *)a)
static int enum_in_range(pinmux_enum_t enum_id, struct pinmux_range *r)
{
if (enum_id < r->begin)
return 0;
if (enum_id > r->end)
return 0;
return 1;
}
static unsigned long gpio_read_raw_reg(void *mapped_reg,
unsigned long reg_width)
{
switch (reg_width) {
case 8:
return readb(mapped_reg);
case 16:
return readw(mapped_reg);
case 32:
return readl(mapped_reg);
}
BUG();
return 0;
}
static void gpio_write_raw_reg(void *mapped_reg,
unsigned long reg_width,
unsigned long data)
{
switch (reg_width) {
case 8:
writeb(data, mapped_reg);
return;
case 16:
writew(data, mapped_reg);
return;
case 32:
writel(data, mapped_reg);
return;
}
BUG();
}
static int gpio_read_bit(struct pinmux_data_reg *dr,
unsigned long offset,
unsigned long in_pos)
{
unsigned long pos;
pos = dr->reg_width - (in_pos + 1);
debug("read_bit: addr = %lx, pos = %ld, r_width = %ld\n",
dr->reg + offset, pos, dr->reg_width);
return (gpio_read_raw_reg(dr->mapped_reg + offset,
dr->reg_width) >> pos) & 1;
}
static void gpio_write_bit(struct pinmux_data_reg *dr,
unsigned long in_pos, unsigned long value)
{
unsigned long pos;
pos = dr->reg_width - (in_pos + 1);
debug("write_bit addr = %lx, value = %d, pos = %ld, "
"r_width = %ld\n",
dr->reg, !!value, pos, dr->reg_width);
if (value)
__set_bit(pos, &dr->reg_shadow);
else
__clear_bit(pos, &dr->reg_shadow);
gpio_write_raw_reg(dr->mapped_reg, dr->reg_width, dr->reg_shadow);
}
static void config_reg_helper(struct pinmux_info *gpioc,
struct pinmux_cfg_reg *crp,
unsigned long in_pos,
#if 0
void __iomem **mapped_regp,
#else
void **mapped_regp,
#endif
unsigned long *maskp,
unsigned long *posp)
{
int k;
*mapped_regp = pfc_phys_to_virt(gpioc, crp->reg);
if (crp->field_width) {
*maskp = (1 << crp->field_width) - 1;
*posp = crp->reg_width - ((in_pos + 1) * crp->field_width);
} else {
*maskp = (1 << crp->var_field_width[in_pos]) - 1;
*posp = crp->reg_width;
for (k = 0; k <= in_pos; k++)
*posp -= abs(crp->var_field_width[k]);
}
}
static int read_config_reg(struct pinmux_info *gpioc,
struct pinmux_cfg_reg *crp,
unsigned long field)
{
void *mapped_reg;
unsigned long mask, pos;
config_reg_helper(gpioc, crp, field, &mapped_reg, &mask, &pos);
debug("read_reg: addr = %lx, field = %ld, "
"r_width = %ld, f_width = %ld\n",
crp->reg, field, crp->reg_width, crp->field_width);
return (gpio_read_raw_reg(mapped_reg, crp->reg_width) >> pos) & mask;
}
static void write_config_reg(struct pinmux_info *gpioc,
struct pinmux_cfg_reg *crp,
unsigned long field, unsigned long value)
{
void *mapped_reg;
unsigned long mask, pos, data;
config_reg_helper(gpioc, crp, field, &mapped_reg, &mask, &pos);
debug("write_reg addr = %lx, value = %ld, field = %ld, "
"r_width = %ld, f_width = %ld\n",
crp->reg, value, field, crp->reg_width, crp->field_width);
mask = ~(mask << pos);
value = value << pos;
data = gpio_read_raw_reg(mapped_reg, crp->reg_width);
data &= mask;
data |= value;
if (gpioc->unlock_reg)
gpio_write_raw_reg(pfc_phys_to_virt(gpioc, gpioc->unlock_reg),
32, ~data);
gpio_write_raw_reg(mapped_reg, crp->reg_width, data);
}
static int setup_data_reg(struct pinmux_info *gpioc, unsigned gpio)
{
struct pinmux_gpio *gpiop = &gpioc->gpios[gpio];
struct pinmux_data_reg *data_reg;
int k, n;
if (!enum_in_range(gpiop->enum_id, &gpioc->data))
return -1;
k = 0;
while (1) {
data_reg = gpioc->data_regs + k;
if (!data_reg->reg_width)
break;
data_reg->mapped_reg = pfc_phys_to_virt(gpioc, data_reg->reg);
for (n = 0; n < data_reg->reg_width; n++) {
if (data_reg->enum_ids[n] == gpiop->enum_id) {
gpiop->flags &= ~PINMUX_FLAG_DREG;
gpiop->flags |= (k << PINMUX_FLAG_DREG_SHIFT);
gpiop->flags &= ~PINMUX_FLAG_DBIT;
gpiop->flags |= (n << PINMUX_FLAG_DBIT_SHIFT);
return 0;
}
}
k++;
}
BUG();
return -1;
}
static void setup_data_regs(struct pinmux_info *gpioc)
{
struct pinmux_data_reg *drp;
int k;
for (k = gpioc->first_gpio; k <= gpioc->last_gpio; k++)
setup_data_reg(gpioc, k);
k = 0;
while (1) {
drp = gpioc->data_regs + k;
if (!drp->reg_width)
break;
drp->reg_shadow = gpio_read_raw_reg(drp->mapped_reg,
drp->reg_width);
k++;
}
}
static int get_data_reg(struct pinmux_info *gpioc, unsigned gpio,
struct pinmux_data_reg **drp, int *bitp)
{
struct pinmux_gpio *gpiop = &gpioc->gpios[gpio];
int k, n;
if (!enum_in_range(gpiop->enum_id, &gpioc->data))
return -1;
k = (gpiop->flags & PINMUX_FLAG_DREG) >> PINMUX_FLAG_DREG_SHIFT;
n = (gpiop->flags & PINMUX_FLAG_DBIT) >> PINMUX_FLAG_DBIT_SHIFT;
*drp = gpioc->data_regs + k;
*bitp = n;
return 0;
}
static int get_config_reg(struct pinmux_info *gpioc, pinmux_enum_t enum_id,
struct pinmux_cfg_reg **crp,
int *fieldp, int *valuep,
unsigned long **cntp)
{
struct pinmux_cfg_reg *config_reg;
unsigned long r_width, f_width, curr_width, ncomb;
int k, m, n, pos, bit_pos;
k = 0;
while (1) {
config_reg = gpioc->cfg_regs + k;
r_width = config_reg->reg_width;
f_width = config_reg->field_width;
if (!r_width)
break;
pos = 0;
m = 0;
for (bit_pos = 0; bit_pos < r_width; bit_pos += curr_width) {
if (f_width)
curr_width = f_width;
else
curr_width = config_reg->var_field_width[m];
ncomb = 1 << curr_width;
for (n = 0; n < ncomb; n++) {
if (config_reg->enum_ids[pos + n] == enum_id) {
*crp = config_reg;
*fieldp = m;
*valuep = n;
*cntp = &config_reg->cnt[m];
return 0;
}
}
pos += ncomb;
m++;
}
k++;
}
return -1;
}
static int get_gpio_enum_id(struct pinmux_info *gpioc, unsigned gpio,
int pos, pinmux_enum_t *enum_idp)
{
pinmux_enum_t enum_id = gpioc->gpios[gpio].enum_id;
pinmux_enum_t *data = gpioc->gpio_data;
int k;
if (!enum_in_range(enum_id, &gpioc->data)) {
if (!enum_in_range(enum_id, &gpioc->mark)) {
debug("non data/mark enum_id for gpio %d\n", gpio);
return -1;
}
}
if (pos) {
*enum_idp = data[pos + 1];
return pos + 1;
}
for (k = 0; k < gpioc->gpio_data_size; k++) {
if (data[k] == enum_id) {
*enum_idp = data[k + 1];
return k + 1;
}
}
debug("cannot locate data/mark enum_id for gpio %d\n", gpio);
return -1;
}
enum { GPIO_CFG_DRYRUN, GPIO_CFG_REQ, GPIO_CFG_FREE };
static int pinmux_config_gpio(struct pinmux_info *gpioc, unsigned gpio,
int pinmux_type, int cfg_mode)
{
struct pinmux_cfg_reg *cr = NULL;
pinmux_enum_t enum_id;
struct pinmux_range *range;
int in_range, pos, field, value;
unsigned long *cntp;
switch (pinmux_type) {
case PINMUX_TYPE_FUNCTION:
range = NULL;
break;
case PINMUX_TYPE_OUTPUT:
range = &gpioc->output;
break;
case PINMUX_TYPE_INPUT:
range = &gpioc->input;
break;
case PINMUX_TYPE_INPUT_PULLUP:
range = &gpioc->input_pu;
break;
case PINMUX_TYPE_INPUT_PULLDOWN:
range = &gpioc->input_pd;
break;
default:
goto out_err;
}
pos = 0;
enum_id = 0;
field = 0;
value = 0;
while (1) {
pos = get_gpio_enum_id(gpioc, gpio, pos, &enum_id);
if (pos <= 0)
goto out_err;
if (!enum_id)
break;
/* first check if this is a function enum */
in_range = enum_in_range(enum_id, &gpioc->function);
if (!in_range) {
/* not a function enum */
if (range) {
/*
* other range exists, so this pin is
* a regular GPIO pin that now is being
* bound to a specific direction.
*
* for this case we only allow function enums
* and the enums that match the other range.
*/
in_range = enum_in_range(enum_id, range);
/*
* special case pass through for fixed
* input-only or output-only pins without
* function enum register association.
*/
if (in_range && enum_id == range->force)
continue;
} else {
/*
* no other range exists, so this pin
* must then be of the function type.
*
* allow function type pins to select
* any combination of function/in/out
* in their MARK lists.
*/
in_range = 1;
}
}
if (!in_range)
continue;
if (get_config_reg(gpioc, enum_id, &cr,
&field, &value, &cntp) != 0)
goto out_err;
switch (cfg_mode) {
case GPIO_CFG_DRYRUN:
if (!*cntp ||
(read_config_reg(gpioc, cr, field) != value))
continue;
break;
case GPIO_CFG_REQ:
write_config_reg(gpioc, cr, field, value);
*cntp = *cntp + 1;
break;
case GPIO_CFG_FREE:
*cntp = *cntp - 1;
break;
}
}
return 0;
out_err:
return -1;
}
#if 0
static DEFINE_SPINLOCK(gpio_lock);
static struct pinmux_info *chip_to_pinmux(struct gpio_chip *chip)
{
return container_of(chip, struct pinmux_info, chip);
}
#endif
static int sh_gpio_request(unsigned offset)
{
struct pinmux_data_reg *dummy;
int i, ret, pinmux_type;
ret = -1;
if (!gpioc)
goto err_out;
if ((gpioc->gpios[offset].flags & PINMUX_FLAG_TYPE) != PINMUX_TYPE_NONE)
goto err_out;
/* setup pin function here if no data is associated with pin */
if (get_data_reg(gpioc, offset, &dummy, &i) != 0)
pinmux_type = PINMUX_TYPE_FUNCTION;
else
pinmux_type = PINMUX_TYPE_GPIO;
if (pinmux_type == PINMUX_TYPE_FUNCTION) {
if (pinmux_config_gpio(gpioc, offset,
pinmux_type,
GPIO_CFG_DRYRUN) != 0)
goto err_out;
if (pinmux_config_gpio(gpioc, offset,
pinmux_type,
GPIO_CFG_REQ) != 0)
BUG();
}
gpioc->gpios[offset].flags &= ~PINMUX_FLAG_TYPE;
gpioc->gpios[offset].flags |= pinmux_type;
ret = 0;
err_out:
return ret;
}
static void sh_gpio_free(unsigned offset)
{
int pinmux_type;
if (!gpioc)
return;
pinmux_type = gpioc->gpios[offset].flags & PINMUX_FLAG_TYPE;
pinmux_config_gpio(gpioc, offset, pinmux_type, GPIO_CFG_FREE);
gpioc->gpios[offset].flags &= ~PINMUX_FLAG_TYPE;
gpioc->gpios[offset].flags |= PINMUX_TYPE_NONE;
}
static int pinmux_direction(struct pinmux_info *gpioc,
unsigned gpio, int new_pinmux_type)
{
int pinmux_type;
int ret = -1;
if (!gpioc)
goto err_out;
pinmux_type = gpioc->gpios[gpio].flags & PINMUX_FLAG_TYPE;
switch (pinmux_type) {
case PINMUX_TYPE_GPIO:
break;
case PINMUX_TYPE_OUTPUT:
case PINMUX_TYPE_INPUT:
case PINMUX_TYPE_INPUT_PULLUP:
case PINMUX_TYPE_INPUT_PULLDOWN:
pinmux_config_gpio(gpioc, gpio, pinmux_type, GPIO_CFG_FREE);
break;
default:
goto err_out;
}
if (pinmux_config_gpio(gpioc, gpio,
new_pinmux_type,
GPIO_CFG_DRYRUN) != 0)
goto err_out;
if (pinmux_config_gpio(gpioc, gpio,
new_pinmux_type,
GPIO_CFG_REQ) != 0)
BUG();
gpioc->gpios[gpio].flags &= ~PINMUX_FLAG_TYPE;
gpioc->gpios[gpio].flags |= new_pinmux_type;
ret = 0;
err_out:
return ret;
}
static int sh_gpio_direction_input(unsigned offset)
{
return pinmux_direction(gpioc, offset, PINMUX_TYPE_INPUT);
}
static void sh_gpio_set_value(struct pinmux_info *gpioc,
unsigned gpio, int value)
{
struct pinmux_data_reg *dr = NULL;
int bit = 0;
if (!gpioc || get_data_reg(gpioc, gpio, &dr, &bit) != 0)
BUG();
else
gpio_write_bit(dr, bit, value);
}
static int sh_gpio_direction_output(unsigned offset, int value)
{
sh_gpio_set_value(gpioc, offset, value);
return pinmux_direction(gpioc, offset, PINMUX_TYPE_OUTPUT);
}
static int sh_gpio_get_value(struct pinmux_info *gpioc, unsigned gpio)
{
struct pinmux_data_reg *dr = NULL;
int bit = 0, offset = 0;
if (!gpioc || get_data_reg(gpioc, gpio, &dr, &bit) != 0)
return -1;
if (IS_ENABLED(CONFIG_RCAR_64) &&
((gpioc->gpios[gpio].flags & PINMUX_FLAG_TYPE) == PINMUX_TYPE_INPUT))
offset += 4;
return gpio_read_bit(dr, offset, bit);
}
static int sh_gpio_get(unsigned offset)
{
return sh_gpio_get_value(gpioc, offset);
}
static void sh_gpio_set(unsigned offset, int value)
{
sh_gpio_set_value(gpioc, offset, value);
}
int register_pinmux(struct pinmux_info *pip)
{
if (pip != NULL) {
gpioc = pip;
debug("%s deregistering\n", pip->name);
setup_data_regs(gpioc);
}
return 0;
}
int unregister_pinmux(struct pinmux_info *pip)
{
debug("%s deregistering\n", pip->name);
if (gpioc != pip)
return -1;
gpioc = NULL;
return 0;
}
int gpio_request(unsigned gpio, const char *label)
{
sh_gpio_request(gpio);
return 0;
}
int gpio_free(unsigned gpio)
{
sh_gpio_free(gpio);
return 0;
}
int gpio_direction_input(unsigned gpio)
{
return sh_gpio_direction_input(gpio);
}
int gpio_direction_output(unsigned gpio, int value)
{
return sh_gpio_direction_output(gpio, value);
}
void gpio_set_value(unsigned gpio, int value)
{
sh_gpio_set(gpio, value);
}
int gpio_get_value(unsigned gpio)
{
return sh_gpio_get(gpio);
}