| /* |
| * Copyright (C) 2016 Freescale Semiconductor, Inc. |
| * Copyright 2017 NXP |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * 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/err.h> |
| #include <linux/init.h> |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/pinctrl/machine.h> |
| #include <linux/pinctrl/pinconf.h> |
| #include <linux/pinctrl/pinctrl.h> |
| #include <linux/pinctrl/pinmux.h> |
| #include <linux/slab.h> |
| |
| #include "../core.h" |
| #include "pinctrl-imx.h" |
| |
| #define IMX_PAD_SION 0x40000000 /* set SION */ |
| |
| #define IOMUXC_IBE (1 << 16) |
| #define IOMUXC_OBE (1 << 17) |
| |
| int imx_pmx_set_one_pin_mem(struct imx_pinctrl *ipctl, struct imx_pin *pin) |
| { |
| const struct imx_pinctrl_soc_info *info = ipctl->info; |
| unsigned int pin_id = pin->pin; |
| struct imx_pin_reg *pin_reg; |
| struct imx_pin_memmap *pin_memmap; |
| u32 mux_shift = info->mux_mask ? ffs(info->mux_mask) - 1 : 0; |
| pin_reg = &info->pin_regs[pin_id]; |
| pin_memmap = &pin->pin_conf.pin_memmap; |
| |
| if (pin_reg->mux_reg == -1) { |
| dev_err(ipctl->dev, "Pin(%s) does not support mux function\n", |
| info->pins[pin_id].name); |
| return 0; |
| } |
| |
| if (info->flags & SHARE_MUX_CONF_REG) { |
| u32 reg; |
| reg = readl(ipctl->base + pin_reg->mux_reg); |
| reg &= ~info->mux_mask; |
| reg |= (pin_memmap->mux_mode << mux_shift); |
| writel(reg, ipctl->base + pin_reg->mux_reg); |
| } else { |
| writel(pin_memmap->mux_mode, ipctl->base + pin_reg->mux_reg); |
| } |
| dev_dbg(ipctl->dev, "write: offset 0x%x val 0x%x\n", |
| pin_reg->mux_reg, pin_memmap->mux_mode); |
| |
| /* |
| * If the select input value begins with 0xff, it's a quirky |
| * select input and the value should be interpreted as below. |
| * 31 23 15 7 0 |
| * | 0xff | shift | width | select | |
| * It's used to work around the problem that the select |
| * input for some pin is not implemented in the select |
| * input register but in some general purpose register. |
| * We encode the select input value, width and shift of |
| * the bit field into input_val cell of pin function ID |
| * in device tree, and then decode them here for setting |
| * up the select input bits in general purpose register. |
| */ |
| if (pin_memmap->input_val >> 24 == 0xff) { |
| u32 val = pin_memmap->input_val; |
| u8 select = val & 0xff; |
| u8 width = (val >> 8) & 0xff; |
| u8 shift = (val >> 16) & 0xff; |
| u32 mask = ((1 << width) - 1) << shift; |
| /* |
| * The input_reg[i] here is actually some IOMUXC general |
| * purpose register, not regular select input register. |
| */ |
| val = readl(ipctl->base + pin_memmap->input_reg); |
| val &= ~mask; |
| val |= select << shift; |
| writel(val, ipctl->base + pin_memmap->input_reg); |
| } else if (pin_memmap->input_reg) { |
| /* |
| * Regular select input register can never be at offset |
| * 0, and we only print register value for regular case. |
| */ |
| if (ipctl->input_sel_base) |
| writel(pin_memmap->input_val, ipctl->input_sel_base + |
| pin_memmap->input_reg); |
| else |
| writel(pin_memmap->input_val, ipctl->base + |
| pin_memmap->input_reg); |
| dev_dbg(ipctl->dev, |
| "==>select_input: offset 0x%x val 0x%x\n", |
| pin_memmap->input_reg, pin_memmap->input_val); |
| } |
| |
| return 0; |
| } |
| |
| int imx_pmx_backend_gpio_request_enable_mem(struct pinctrl_dev *pctldev, |
| struct pinctrl_gpio_range *range, unsigned offset) |
| { |
| struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); |
| const struct imx_pinctrl_soc_info *info = ipctl->info; |
| const struct imx_pin_reg *pin_reg; |
| struct imx_pin_group *grp; |
| struct imx_pin *imx_pin; |
| unsigned int pin, group; |
| u32 reg, mux_shift; |
| |
| /* Currently implementation only for shared mux/conf register */ |
| if (!(info->flags & SHARE_MUX_CONF_REG)) |
| return 0; |
| |
| pin_reg = &info->pin_regs[offset]; |
| if (pin_reg->mux_reg == -1) |
| return -EINVAL; |
| |
| /* Find the pinctrl config with GPIO mux mode for the requested pin */ |
| for (group = 0; group < info->ngroups; group++) { |
| grp = &info->groups[group]; |
| for (pin = 0; pin < grp->npins; pin++) { |
| imx_pin = &grp->pins[pin]; |
| if (imx_pin->pin == offset) |
| goto mux_pin; |
| } |
| } |
| |
| return -EINVAL; |
| |
| mux_pin: |
| reg = readl(ipctl->base + pin_reg->mux_reg); |
| reg &= ~info->mux_mask; |
| mux_shift = info->mux_mask ? ffs(info->mux_mask) - 1 : 0; |
| reg |= (imx_pin->pin_conf.pin_memmap.mux_mode << mux_shift); |
| imx_pin->pin_conf.pin_memmap.config &= ~info->mux_mask; |
| reg |= imx_pin->pin_conf.pin_memmap.config; |
| writel(reg, ipctl->base + pin_reg->mux_reg); |
| |
| return 0; |
| } |
| |
| void imx_pmx_backend_gpio_disable_free_mem(struct pinctrl_dev *pctldev, |
| struct pinctrl_gpio_range *range, |
| unsigned offset) |
| { |
| struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); |
| const struct imx_pinctrl_soc_info *info = ipctl->info; |
| const struct imx_pin_reg *pin_reg; |
| u32 reg; |
| /* |
| * Only Vybrid has the input/output buffer enable flags (IBE/OBE) |
| * They are part of the shared mux/conf register. |
| */ |
| if (!(info->flags & SHARE_MUX_CONF_REG)) |
| return; |
| |
| pin_reg = &info->pin_regs[offset]; |
| if (pin_reg->mux_reg == -1) |
| return; |
| |
| /* Clear IBE/OBE/PUE to disable the pin (Hi-Z) */ |
| reg = readl(ipctl->base + pin_reg->mux_reg); |
| reg &= ~0x7; |
| writel(reg, ipctl->base + pin_reg->mux_reg); |
| } |
| |
| int imx_pmx_backend_gpio_set_direction_mem(struct pinctrl_dev *pctldev, |
| struct pinctrl_gpio_range *range, unsigned offset, bool input) |
| { |
| struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); |
| const struct imx_pinctrl_soc_info *info = ipctl->info; |
| const struct imx_pin_reg *pin_reg; |
| u32 reg; |
| |
| /* |
| * Only Vybrid has the input/output buffer enable flags (IBE/OBE) |
| * They are part of the shared mux/conf register. |
| */ |
| if (!(info->flags & SHARE_MUX_CONF_REG)) |
| return 0; |
| |
| pin_reg = &info->pin_regs[offset]; |
| if (pin_reg->mux_reg == -1) |
| return -EINVAL; |
| |
| /* IBE always enabled allows us to read the value "on the wire" */ |
| reg = readl(ipctl->base + pin_reg->mux_reg); |
| if (input) { |
| if (info->flags & CONFIG_IBE_OBE) { |
| reg &= ~IOMUXC_OBE; |
| reg |= IOMUXC_IBE; |
| } else { |
| reg &= ~0x2; |
| } |
| } else { |
| if (info->flags & CONFIG_IBE_OBE) { |
| reg &= ~IOMUXC_IBE; |
| reg |= IOMUXC_OBE; |
| } else { |
| reg |= 0x2; |
| } |
| } |
| writel(reg, ipctl->base + pin_reg->mux_reg); |
| |
| return 0; |
| } |
| |
| int imx_pinconf_backend_get_mem(struct pinctrl_dev *pctldev, |
| unsigned pin_id, unsigned long *config) |
| { |
| struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); |
| const struct imx_pinctrl_soc_info *info = ipctl->info; |
| const struct imx_pin_reg *pin_reg = &info->pin_regs[pin_id]; |
| |
| if (pin_reg->conf_reg == -1) { |
| dev_err(info->dev, "Pin(%s) does not support config function\n", |
| info->pins[pin_id].name); |
| return -EINVAL; |
| } |
| |
| *config = readl(ipctl->base + pin_reg->conf_reg); |
| |
| if (info->flags & SHARE_MUX_CONF_REG) |
| *config &= ~info->mux_mask; |
| |
| return 0; |
| } |
| |
| int imx_pinconf_backend_set_mem(struct pinctrl_dev *pctldev, |
| unsigned pin_id, unsigned long *configs, |
| unsigned num_configs) |
| { |
| struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev); |
| const struct imx_pinctrl_soc_info *info = ipctl->info; |
| const struct imx_pin_reg *pin_reg = &info->pin_regs[pin_id]; |
| int i; |
| |
| if (pin_reg->conf_reg == -1) { |
| dev_err(info->dev, "Pin(%s) does not support config function\n", |
| info->pins[pin_id].name); |
| return -EINVAL; |
| } |
| |
| dev_dbg(ipctl->dev, "pinconf set pin %s\n", |
| info->pins[pin_id].name); |
| |
| for (i = 0; i < num_configs; i++) { |
| if (info->flags & SHARE_MUX_CONF_REG) { |
| u32 reg; |
| reg = readl(ipctl->base + pin_reg->conf_reg); |
| reg &= info->mux_mask; |
| reg |= configs[i]; |
| writel(reg, ipctl->base + pin_reg->conf_reg); |
| } else { |
| writel(configs[i], ipctl->base + pin_reg->conf_reg); |
| } |
| dev_dbg(ipctl->dev, "write: offset 0x%x val 0x%lx\n", |
| pin_reg->conf_reg, configs[i]); |
| } /* for each config */ |
| |
| return 0; |
| } |
| |
| int imx_pinctrl_parse_pin_mem(struct imx_pinctrl_soc_info *info, |
| unsigned int *grp_pin_id, struct imx_pin *pin, |
| const __be32 **list_p) |
| { |
| struct imx_pin_memmap *pin_memmap = &pin->pin_conf.pin_memmap; |
| u32 mux_reg = be32_to_cpu(*((*list_p)++)); |
| u32 conf_reg; |
| u32 config; |
| unsigned int pin_id; |
| struct imx_pin_reg *pin_reg; |
| |
| if (info->flags & SHARE_MUX_CONF_REG) { |
| conf_reg = mux_reg; |
| } else { |
| conf_reg = be32_to_cpu(*((*list_p)++)); |
| if (!conf_reg) |
| conf_reg = -1; |
| } |
| |
| pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4; |
| pin_reg = &info->pin_regs[pin_id]; |
| pin->pin = pin_id; |
| *grp_pin_id = pin_id; |
| pin_reg->mux_reg = mux_reg; |
| pin_reg->conf_reg = conf_reg; |
| pin_memmap->input_reg = be32_to_cpu(*((*list_p)++)); |
| pin_memmap->mux_mode = be32_to_cpu(*((*list_p)++)); |
| pin_memmap->input_val = be32_to_cpu((*(*list_p)++)); |
| |
| /* SION bit is in mux register */ |
| config = be32_to_cpu(*((*list_p)++)); |
| if (config & IMX_PAD_SION) |
| pin_memmap->mux_mode |= IOMUXC_CONFIG_SION; |
| pin_memmap->config = config & ~IMX_PAD_SION; |
| |
| dev_dbg(info->dev, "%s: 0x%x 0x%08lx", info->pins[pin_id].name, |
| pin_memmap->mux_mode, pin_memmap->config); |
| |
| return 0; |
| } |