// SPDX-License-Identifier: GPL-2.0-only /* * Pinmux & pinconf driver for the IP block found in the Nomadik SoC. This * depends on gpio-nomadik and some handling is intertwined; see nmk_gpio_chips * which is used by this driver to access the GPIO banks array. * * Copyright (C) 2008,2009 STMicroelectronics * Copyright (C) 2009 Alessandro Rubini <rubini@unipv.it> * Rewritten based on work by Prafulla WADASKAR <prafulla.wadaskar@st.com> * Copyright (C) 2011-2013 Linus Walleij <linus.walleij@linaro.org>
*/
/* Since we request GPIOs from ourself */ #include <linux/pinctrl/consumer.h> #include <linux/pinctrl/machine.h> #include <linux/pinctrl/pinconf.h> #include <linux/pinctrl/pinctrl.h> #include <linux/pinctrl/pinmux.h>
#include"../core.h" #include"../pinctrl-utils.h"
#include <linux/gpio/gpio-nomadik.h>
/* * pin configurations are represented by 32-bit integers: * * bit 0.. 8 - Pin Number (512 Pins Maximum) * bit 9..10 - Alternate Function Selection * bit 11..12 - Pull up/down state * bit 13 - Sleep mode behaviour * bit 14 - Direction * bit 15 - Value (if output) * bit 16..18 - SLPM pull up/down state * bit 19..20 - SLPM direction * bit 21..22 - SLPM Value (if output) * bit 23..25 - PDIS value (if input) * bit 26 - Gpio mode * bit 27 - Sleep mode * * to facilitate the definition, the following macros are provided * * PIN_CFG_DEFAULT - default config (0): * pull up/down = disabled * sleep mode = input/wakeup * direction = input * value = low * SLPM direction = same as normal * SLPM pull = same as normal * SLPM value = same as normal * * PIN_CFG - default config with alternate function
*/
#define PIN_SLPM_GPIO PIN_SLPM_WAKEUP_ENABLE /* In SLPM, pin is a gpio */ #define PIN_SLPM_ALTFUNC PIN_SLPM_WAKEUP_DISABLE /* In SLPM, pin is altfunc */
if (alt_num > PRCM_IDX_GPIOCR_ALTC_MAX) {
dev_err(npct->dev, "PRCM GPIOCR: alternate-C%i is invalid\n",
alt_num); return;
}
for (i = 0 ; i < npct->soc->npins_altcx ; i++) { if (npct->soc->altcx_pins[i].pin == offset) break;
} if (i == npct->soc->npins_altcx) {
dev_dbg(npct->dev, "PRCM GPIOCR: pin %i is not found\n",
offset); return;
}
/* * If alt_num is NULL, just clear current ALTCx selection * to make sure we come back to a pure ALTC selection
*/ if (!alt_num) { for (i = 0 ; i < PRCM_IDX_GPIOCR_ALTC_MAX ; i++) { if (pin_desc->altcx[i].used) {
reg = gpiocr_regs[pin_desc->altcx[i].reg_index];
bit = pin_desc->altcx[i].control_bit; if (readl(npct->prcm_base + reg) & BIT(bit)) {
nmk_write_masked(npct->prcm_base + reg, BIT(bit), 0);
dev_dbg(npct->dev, "PRCM GPIOCR: pin %i: alternate-C%i has been disabled\n",
offset, i + 1);
}
}
} return;
}
alt_index = alt_num - 1; if (!pin_desc->altcx[alt_index].used) {
dev_warn(npct->dev, "PRCM GPIOCR: pin %i: alternate-C%i does not exist\n",
offset, alt_num); return;
}
/* * Check if any other ALTCx functions are activated on this pin * and disable it first.
*/ for (i = 0 ; i < PRCM_IDX_GPIOCR_ALTC_MAX ; i++) { if (i == alt_index) continue; if (pin_desc->altcx[i].used) {
reg = gpiocr_regs[pin_desc->altcx[i].reg_index];
bit = pin_desc->altcx[i].control_bit; if (readl(npct->prcm_base + reg) & BIT(bit)) {
nmk_write_masked(npct->prcm_base + reg, BIT(bit), 0);
dev_dbg(npct->dev, "PRCM GPIOCR: pin %i: alternate-C%i has been disabled\n",
offset, i + 1);
}
}
}
reg = gpiocr_regs[pin_desc->altcx[alt_index].reg_index];
bit = pin_desc->altcx[alt_index].control_bit;
dev_dbg(npct->dev, "PRCM GPIOCR: pin %i: alternate-C%i has been selected\n",
offset, alt_index + 1);
nmk_write_masked(npct->prcm_base + reg, BIT(bit), BIT(bit));
}
/* * Safe sequence used to switch IOs between GPIO and Alternate-C mode: * - Save SLPM registers * - Set SLPM=0 for the IOs you want to switch and others to 1 * - Configure the GPIO registers for the IOs that are being switched * - Set IOFORCE=1 * - Modify the AFLSA/B registers for the IOs that are being switched * - Set IOFORCE=0 * - Restore SLPM registers * - Any spurious wake up event during switch sequence to be ignored and * cleared
*/ staticint nmk_gpio_glitch_slpm_init(unsignedint *slpm)
{ int i, j, ret;
for (i = 0; i < NMK_MAX_BANKS; i++) { struct nmk_gpio_chip *chip = nmk_gpio_chips[i]; unsignedint temp = slpm[i];
if (!chip) break;
ret = clk_enable(chip->clk); if (ret) { for (j = 0; j < i; j++) {
chip = nmk_gpio_chips[j];
clk_disable(chip->clk);
}
staticvoid nmk_gpio_glitch_slpm_restore(unsignedint *slpm)
{ int i;
for (i = 0; i < NMK_MAX_BANKS; i++) { struct nmk_gpio_chip *chip = nmk_gpio_chips[i];
if (!chip) break;
writel(slpm[i], chip->addr + NMK_GPIO_SLPC);
clk_disable(chip->clk);
}
}
/* Only called by gpio-nomadik but requires knowledge of struct nmk_pinctrl. */ int __maybe_unused nmk_prcm_gpiocr_get_mode(struct pinctrl_dev *pctldev, int gpio)
{ int i;
u16 reg;
u8 bit; struct nmk_pinctrl *npct = pinctrl_dev_get_drvdata(pctldev); conststruct prcm_gpiocr_altcx_pin_desc *pin_desc; const u16 *gpiocr_regs;
if (!npct->prcm_base) return NMK_GPIO_ALT_C;
for (i = 0; i < npct->soc->npins_altcx; i++) { if (npct->soc->altcx_pins[i].pin == gpio) break;
} if (i == npct->soc->npins_altcx) return NMK_GPIO_ALT_C;
pin_desc = npct->soc->altcx_pins + i;
gpiocr_regs = npct->soc->prcm_gpiocr_registers; for (i = 0; i < PRCM_IDX_GPIOCR_ALTC_MAX; i++) { if (pin_desc->altcx[i].used) {
reg = gpiocr_regs[pin_desc->altcx[i].reg_index];
bit = pin_desc->altcx[i].control_bit; if (readl(npct->prcm_base + reg) & BIT(bit)) return NMK_GPIO_ALT_C + i + 1;
}
} return NMK_GPIO_ALT_C;
}
/* This makes the mapping from pin number to a GPIO chip. We also return the pin * offset in the GPIO chip for convenience (and to avoid a second loop).
*/ staticstruct nmk_gpio_chip *find_nmk_gpio_from_pin(unsignedint pin, unsignedint *offset)
{ int i, j = 0; struct nmk_gpio_chip *nmk_gpio;
/* We assume that pins are allocated in bank order. */ for (i = 0; i < NMK_MAX_BANKS; i++) {
nmk_gpio = nmk_gpio_chips[i]; if (!nmk_gpio) continue; if (pin >= j && pin < j + nmk_gpio->chip.ngpio) { if (offset)
*offset = pin - j; return nmk_gpio;
}
j += nmk_gpio->chip.ngpio;
} return NULL;
}
staticint nmk_dt_pin_config(int index, int val, unsignedlong *config)
{ if (!nmk_cfg_params[index].choice) {
*config = nmk_cfg_params[index].config;
} else { /* test if out of range */ if (val < nmk_cfg_params[index].size) {
*config = nmk_cfg_params[index].config |
nmk_cfg_params[index].choice[val];
}
} return 0;
}
staticconstchar *nmk_find_pin_name(struct pinctrl_dev *pctldev, constchar *pin_name)
{ int i, pin_number; struct nmk_pinctrl *npct = pinctrl_dev_get_drvdata(pctldev);
if (sscanf((char *)pin_name, "GPIO%d", &pin_number) == 1) for (i = 0; i < npct->soc->npins; i++) if (npct->soc->pins[i].number == pin_number) return npct->soc->pins[i].name; return NULL;
}
staticbool nmk_pinctrl_dt_get_config(struct device_node *np, unsignedlong *configs)
{ bool has_config = 0; unsignedlong cfg = 0; int i, val, ret;
for (i = 0; i < ARRAY_SIZE(nmk_cfg_params); i++) {
ret = of_property_read_u32(np, nmk_cfg_params[i].property, &val); if (ret != -EINVAL) { if (nmk_dt_pin_config(i, val, &cfg) == 0) {
*configs |= cfg;
has_config = 1;
}
}
}
dev_dbg(npct->dev, "enable group %s, %zu pins\n", g->grp.name, g->grp.npins);
/* * If we're setting altfunc C by setting both AFSLA and AFSLB to 1, * we may pass through an undesired state. In this case we take * some extra care. * * Safe sequence used to switch IOs between GPIO and Alternate-C mode: * - Save SLPM registers (since we have a shadow register in the * nmk_chip we're using that as backup) * - Set SLPM=0 for the IOs you want to switch and others to 1 * - Configure the GPIO registers for the IOs that are being switched * - Set IOFORCE=1 * - Modify the AFLSA/B registers for the IOs that are being switched * - Set IOFORCE=0 * - Restore SLPM registers * - Any spurious wake up event during switch sequence to be ignored * and cleared * * We REALLY need to save ALL slpm registers, because the external * IOFORCE will switch *all* ports to their sleepmode setting to as * to avoid glitches. (Not just one port!)
*/
glitch = ((g->altsetting & NMK_GPIO_ALT_C) == NMK_GPIO_ALT_C);
if (glitch) {
spin_lock_irqsave(&nmk_gpio_slpm_lock, flags);
/* Initially don't put any pins to sleep when switching */
memset(slpm, 0xff, sizeof(slpm));
/* * Then mask the pins that need to be sleeping now when we're * switching to the ALT C function.
*/ for (i = 0; i < g->grp.npins; i++) { struct nmk_gpio_chip *nmk_chip; unsignedint bit;
nmk_chip = find_nmk_gpio_from_pin(g->grp.pins[i], &bit); if (!nmk_chip) {
dev_err(npct->dev, "invalid pin offset %d in group %s at index %d\n",
g->grp.pins[i], g->grp.name, i); goto out_pre_slpm_init;
}
slpm[nmk_chip->bank] &= ~BIT(bit);
}
ret = nmk_gpio_glitch_slpm_init(slpm); if (ret) goto out_pre_slpm_init;
}
for (i = 0; i < g->grp.npins; i++) { struct nmk_gpio_chip *nmk_chip; unsignedint bit;
nmk_chip = find_nmk_gpio_from_pin(g->grp.pins[i], &bit); if (!nmk_chip) {
dev_err(npct->dev, "invalid pin offset %d in group %s at index %d\n",
g->grp.pins[i], g->grp.name, i); goto out_glitch;
}
dev_dbg(npct->dev, "setting pin %d to altsetting %d\n",
g->grp.pins[i], g->altsetting);
ret = clk_enable(nmk_chip->clk); if (ret) goto out_glitch;
/* * If the pin is switching to altfunc, and there was an * interrupt installed on it which has been lazy disabled, * actually mask the interrupt to prevent spurious interrupts * that would occur while the pin is under control of the * peripheral. Only SKE does this.
*/
nmk_gpio_disable_lazy_irq(nmk_chip, bit);
/* * Call PRCM GPIOCR config function in case ALTC * has been selected: * - If selection is a ALTCx, some bits in PRCM GPIOCR registers * must be set. * - If selection is pure ALTC and previous selection was ALTCx, * then some bits in PRCM GPIOCR registers must be cleared.
*/ if ((g->altsetting & NMK_GPIO_ALT_C) == NMK_GPIO_ALT_C)
nmk_prcm_altcx_set_mode(npct, g->grp.pins[i],
g->altsetting >> NMK_GPIO_ALT_CX_SHIFT);
}
/* When all pins are successfully reconfigured we get here */
ret = 0;
out_glitch: if (glitch)
nmk_gpio_glitch_slpm_restore(slpm);
out_pre_slpm_init: if (glitch)
spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags);
if (!range) {
dev_err(npct->dev, "invalid range\n"); return -EINVAL;
} if (!range->gc) {
dev_err(npct->dev, "missing GPIO chip in range\n"); return -EINVAL;
}
chip = range->gc;
nmk_chip = gpiochip_get_data(chip);
dev_dbg(npct->dev, "enable pin %u as GPIO\n", pin);
find_nmk_gpio_from_pin(pin, &bit);
ret = clk_enable(nmk_chip->clk); if (ret) return ret; /* There is no glitch when converting any pin to GPIO */
__nmk_gpio_set_mode(nmk_chip, bit, NMK_GPIO_ALT_GPIO);
clk_disable(nmk_chip->clk);
for (i = 0; i < num_configs; i++) { /* * The pin config contains pin number and altfunction fields, * here we just ignore that part. It's being handled by the * framework and pinmux callback respectively.
*/
cfg = configs[i];
pull = PIN_PULL(cfg);
slpm = PIN_SLPM(cfg);
output = PIN_DIR(cfg);
val = PIN_VAL(cfg);
lowemi = PIN_LOWEMI(cfg);
gpiomode = PIN_GPIOMODE(cfg);
sleep = PIN_SLEEPMODE(cfg);
if (sleep) { int slpm_pull = PIN_SLPM_PULL(cfg); int slpm_output = PIN_SLPM_DIR(cfg); int slpm_val = PIN_SLPM_VAL(cfg);
/* All pins go into GPIO mode at sleep */
gpiomode = true;
/* * The SLPM_* values are normal values + 1 to allow zero * to mean "same as normal".
*/ if (slpm_pull)
pull = slpm_pull - 1; if (slpm_output)
output = slpm_output - 1; if (slpm_val)
val = slpm_val - 1;
ret = clk_enable(nmk_chip->clk); if (ret) return ret; if (gpiomode) /* No glitch when going to GPIO mode */
__nmk_gpio_set_mode(nmk_chip, bit, NMK_GPIO_ALT_GPIO); if (output) {
__nmk_gpio_make_output(nmk_chip, bit, val);
} else {
__nmk_gpio_make_input(nmk_chip, bit);
__nmk_gpio_set_pull(nmk_chip, bit, pull);
} /* TODO: isn't this only applicable on output pins? */
__nmk_gpio_set_lowemi(nmk_chip, bit, lowemi);
__nmk_gpio_set_slpm(nmk_chip, bit, slpm);
clk_disable(nmk_chip->clk);
} /* for each config */
npct = devm_kzalloc(&pdev->dev, sizeof(*npct), GFP_KERNEL); if (!npct) return -ENOMEM;
version = (uintptr_t)device_get_match_data(&pdev->dev);
/* Poke in other ASIC variants here */ if (version == PINCTRL_NMK_STN8815)
nmk_pinctrl_stn8815_init(&npct->soc); if (version == PINCTRL_NMK_DB8500)
nmk_pinctrl_db8500_init(&npct->soc);
/* * Since we depend on the GPIO chips to provide clock and register base * for the pin control operations, make sure that we have these * populated before we continue. Follow the phandles to instantiate * them. The GPIO portion of the actual hardware may be probed before * or after this point: it shouldn't matter as the APIs are orthogonal.
*/ for (i = 0; i < NMK_MAX_BANKS; i++) { struct fwnode_handle *gpio_fwnode; struct nmk_gpio_chip *nmk_chip;
gpio_fwnode = fwnode_find_reference(fwnode, "nomadik-gpio-chips", i); if (IS_ERR(gpio_fwnode)) continue;
dev_info(&pdev->dev, "populate NMK GPIO %d \"%pfwP\"\n", i, gpio_fwnode);
nmk_chip = nmk_gpio_populate_chip(gpio_fwnode, pdev); if (IS_ERR(nmk_chip))
dev_err(&pdev->dev, "could not populate nmk chip struct - continue anyway\n"); else /* We are NOT compatible with mobileye,eyeq5-gpio. */
BUG_ON(nmk_chip->is_mobileye_soc);
fwnode_handle_put(gpio_fwnode);
}
prcm_fwnode = fwnode_find_reference(fwnode, "prcm", 0); if (!IS_ERR(prcm_fwnode)) {
npct->prcm_base = fwnode_iomap(prcm_fwnode, 0);
fwnode_handle_put(prcm_fwnode);
} if (!npct->prcm_base) { if (version == PINCTRL_NMK_STN8815) {
dev_info(&pdev->dev, "No PRCM base, assuming no ALT-Cx control is available\n");
} else {
dev_err(&pdev->dev, "missing PRCM base address\n"); return -EINVAL;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.