| /* |
| * Freescale STMP378X/STMP378X Pin Multiplexing |
| * |
| * Author: Vladislav Buzov <vbuzov@embeddedalley.com> |
| * |
| * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. |
| * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. |
| */ |
| |
| /* |
| * The code contained herein is licensed under the GNU General Public |
| * License. You may obtain a copy of the GNU General Public License |
| * Version 2 or later at the following locations: |
| * |
| * http://www.opensource.org/licenses/gpl-license.html |
| * http://www.gnu.org/copyleft/gpl.html |
| */ |
| #define DEBUG |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/sysdev.h> |
| #include <linux/string.h> |
| #include <linux/bitops.h> |
| #include <linux/irq.h> |
| |
| #include <mach/hardware.h> |
| #include <mach/platform.h> |
| #include <mach/regs-pinctrl.h> |
| #include <mach/pins.h> |
| #include <mach/pinmux.h> |
| |
| #define NR_BANKS ARRAY_SIZE(pinmux_banks) |
| static struct stmp3xxx_pinmux_bank pinmux_banks[] = { |
| [0] = { |
| .hw_muxsel = { |
| REGS_PINCTRL_BASE + HW_PINCTRL_MUXSEL0, |
| REGS_PINCTRL_BASE + HW_PINCTRL_MUXSEL1, |
| }, |
| .hw_drive = { |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE0, |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE1, |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE2, |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE3, |
| }, |
| .hw_pull = REGS_PINCTRL_BASE + HW_PINCTRL_PULL0, |
| .functions = { 0x0, 0x1, 0x2, 0x3 }, |
| .strengths = { 0x0, 0x1, 0x2, 0x3, 0xff }, |
| |
| .hw_gpio_in = REGS_PINCTRL_BASE + HW_PINCTRL_DIN0, |
| .hw_gpio_out = REGS_PINCTRL_BASE + HW_PINCTRL_DOUT0, |
| .hw_gpio_doe = REGS_PINCTRL_BASE + HW_PINCTRL_DOE0, |
| .irq = IRQ_GPIO0, |
| |
| .pin2irq = REGS_PINCTRL_BASE + HW_PINCTRL_PIN2IRQ0, |
| .irqstat = REGS_PINCTRL_BASE + HW_PINCTRL_IRQSTAT0, |
| .irqlevel = REGS_PINCTRL_BASE + HW_PINCTRL_IRQLEVEL0, |
| .irqpolarity = REGS_PINCTRL_BASE + HW_PINCTRL_IRQPOL0, |
| .irqen = REGS_PINCTRL_BASE + HW_PINCTRL_IRQEN0, |
| }, |
| [1] = { |
| .hw_muxsel = { |
| REGS_PINCTRL_BASE + HW_PINCTRL_MUXSEL2, |
| REGS_PINCTRL_BASE + HW_PINCTRL_MUXSEL3, |
| }, |
| .hw_drive = { |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE4, |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE5, |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE6, |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE7, |
| }, |
| .hw_pull = REGS_PINCTRL_BASE + HW_PINCTRL_PULL1, |
| .functions = { 0x0, 0x1, 0x2, 0x3 }, |
| .strengths = { 0x0, 0x1, 0x2, 0x3, 0xff }, |
| |
| .hw_gpio_in = REGS_PINCTRL_BASE + HW_PINCTRL_DIN1, |
| .hw_gpio_out = REGS_PINCTRL_BASE + HW_PINCTRL_DOUT1, |
| .hw_gpio_doe = REGS_PINCTRL_BASE + HW_PINCTRL_DOE1, |
| .irq = IRQ_GPIO1, |
| |
| .pin2irq = REGS_PINCTRL_BASE + HW_PINCTRL_PIN2IRQ1, |
| .irqstat = REGS_PINCTRL_BASE + HW_PINCTRL_IRQSTAT1, |
| .irqlevel = REGS_PINCTRL_BASE + HW_PINCTRL_IRQLEVEL1, |
| .irqpolarity = REGS_PINCTRL_BASE + HW_PINCTRL_IRQPOL1, |
| .irqen = REGS_PINCTRL_BASE + HW_PINCTRL_IRQEN1, |
| }, |
| [2] = { |
| .hw_muxsel = { |
| REGS_PINCTRL_BASE + HW_PINCTRL_MUXSEL4, |
| REGS_PINCTRL_BASE + HW_PINCTRL_MUXSEL5, |
| }, |
| .hw_drive = { |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE8, |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE9, |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE10, |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE11, |
| }, |
| .hw_pull = REGS_PINCTRL_BASE + HW_PINCTRL_PULL2, |
| .functions = { 0x0, 0x1, 0x2, 0x3 }, |
| .strengths = { 0x0, 0x1, 0x2, 0x1, 0x2 }, |
| |
| .hw_gpio_in = REGS_PINCTRL_BASE + HW_PINCTRL_DIN2, |
| .hw_gpio_out = REGS_PINCTRL_BASE + HW_PINCTRL_DOUT2, |
| .hw_gpio_doe = REGS_PINCTRL_BASE + HW_PINCTRL_DOE2, |
| .irq = IRQ_GPIO2, |
| |
| .pin2irq = REGS_PINCTRL_BASE + HW_PINCTRL_PIN2IRQ2, |
| .irqstat = REGS_PINCTRL_BASE + HW_PINCTRL_IRQSTAT2, |
| .irqlevel = REGS_PINCTRL_BASE + HW_PINCTRL_IRQLEVEL2, |
| .irqpolarity = REGS_PINCTRL_BASE + HW_PINCTRL_IRQPOL2, |
| .irqen = REGS_PINCTRL_BASE + HW_PINCTRL_IRQEN2, |
| }, |
| [3] = { |
| .hw_muxsel = { |
| REGS_PINCTRL_BASE + HW_PINCTRL_MUXSEL6, |
| REGS_PINCTRL_BASE + HW_PINCTRL_MUXSEL7, |
| }, |
| .hw_drive = { |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE12, |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE13, |
| REGS_PINCTRL_BASE + HW_PINCTRL_DRIVE14, |
| NULL, |
| }, |
| .hw_pull = REGS_PINCTRL_BASE + HW_PINCTRL_PULL3, |
| .functions = {0x0, 0x1, 0x2, 0x3}, |
| .strengths = {0x0, 0x1, 0x2, 0x3, 0xff}, |
| }, |
| }; |
| |
| static inline struct stmp3xxx_pinmux_bank * |
| stmp3xxx_pinmux_bank(unsigned id, unsigned *bank, unsigned *pin) |
| { |
| unsigned b, p; |
| |
| b = STMP3XXX_PINID_TO_BANK(id); |
| p = STMP3XXX_PINID_TO_PINNUM(id); |
| BUG_ON(b >= NR_BANKS); |
| if (bank) |
| *bank = b; |
| if (pin) |
| *pin = p; |
| return &pinmux_banks[b]; |
| } |
| |
| /* Check if requested pin is owned by caller */ |
| static int stmp3xxx_check_pin(unsigned id, const char *label) |
| { |
| unsigned pin; |
| struct stmp3xxx_pinmux_bank *pm = stmp3xxx_pinmux_bank(id, NULL, &pin); |
| |
| if (!test_bit(pin, &pm->pin_map)) { |
| printk(KERN_WARNING |
| "%s: Accessing free pin %x, caller %s\n", |
| __func__, id, label); |
| |
| return -EINVAL; |
| } |
| |
| if (label && pm->pin_labels[pin] && |
| strcmp(label, pm->pin_labels[pin])) { |
| printk(KERN_WARNING |
| "%s: Wrong pin owner %x, caller %s owner %s\n", |
| __func__, id, label, pm->pin_labels[pin]); |
| |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| void stmp3xxx_pin_strength(unsigned id, enum pin_strength strength, |
| const char *label) |
| { |
| struct stmp3xxx_pinmux_bank *pbank; |
| void __iomem *hwdrive; |
| u32 shift, val; |
| u32 bank, pin; |
| |
| pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); |
| pr_debug("%s: label %s bank %d pin %d strength %d\n", __func__, label, |
| bank, pin, strength); |
| |
| hwdrive = pbank->hw_drive[pin / HW_DRIVE_PIN_NUM]; |
| shift = (pin % HW_DRIVE_PIN_NUM) * HW_DRIVE_PIN_LEN; |
| val = pbank->strengths[strength]; |
| if (val == 0xff) { |
| printk(KERN_WARNING |
| "%s: strength is not supported for bank %d, caller %s", |
| __func__, bank, label); |
| return; |
| } |
| |
| if (stmp3xxx_check_pin(id, label)) |
| return; |
| |
| pr_debug("%s: writing 0x%x to 0x%p register\n", __func__, |
| val << shift, hwdrive); |
| stmp3xxx_clearl(HW_DRIVE_PINDRV_MASK << shift, hwdrive); |
| stmp3xxx_setl(val << shift, hwdrive); |
| } |
| |
| void stmp3xxx_pin_voltage(unsigned id, enum pin_voltage voltage, |
| const char *label) |
| { |
| struct stmp3xxx_pinmux_bank *pbank; |
| void __iomem *hwdrive; |
| u32 shift; |
| u32 bank, pin; |
| |
| pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); |
| pr_debug("%s: label %s bank %d pin %d voltage %d\n", __func__, label, |
| bank, pin, voltage); |
| |
| hwdrive = pbank->hw_drive[pin / HW_DRIVE_PIN_NUM]; |
| shift = (pin % HW_DRIVE_PIN_NUM) * HW_DRIVE_PIN_LEN; |
| |
| if (stmp3xxx_check_pin(id, label)) |
| return; |
| |
| pr_debug("%s: changing 0x%x bit in 0x%p register\n", |
| __func__, HW_DRIVE_PINV_MASK << shift, hwdrive); |
| if (voltage == PIN_1_8V) |
| stmp3xxx_clearl(HW_DRIVE_PINV_MASK << shift, hwdrive); |
| else |
| stmp3xxx_setl(HW_DRIVE_PINV_MASK << shift, hwdrive); |
| } |
| |
| void stmp3xxx_pin_pullup(unsigned id, int enable, const char *label) |
| { |
| struct stmp3xxx_pinmux_bank *pbank; |
| void __iomem *hwpull; |
| u32 bank, pin; |
| |
| pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); |
| pr_debug("%s: label %s bank %d pin %d enable %d\n", __func__, label, |
| bank, pin, enable); |
| |
| hwpull = pbank->hw_pull; |
| |
| if (stmp3xxx_check_pin(id, label)) |
| return; |
| |
| pr_debug("%s: changing 0x%x bit in 0x%p register\n", |
| __func__, 1 << pin, hwpull); |
| if (enable) |
| stmp3xxx_setl(1 << pin, hwpull); |
| else |
| stmp3xxx_clearl(1 << pin, hwpull); |
| } |
| |
| int stmp3xxx_request_pin(unsigned id, enum pin_fun fun, const char *label) |
| { |
| struct stmp3xxx_pinmux_bank *pbank; |
| u32 bank, pin; |
| int ret = 0; |
| |
| pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); |
| pr_debug("%s: label %s bank %d pin %d fun %d\n", __func__, label, |
| bank, pin, fun); |
| |
| if (test_bit(pin, &pbank->pin_map)) { |
| printk(KERN_WARNING |
| "%s: CONFLICT DETECTED pin %d:%d caller %s owner %s\n", |
| __func__, bank, pin, label, pbank->pin_labels[pin]); |
| return -EBUSY; |
| } |
| |
| set_bit(pin, &pbank->pin_map); |
| pbank->pin_labels[pin] = label; |
| |
| stmp3xxx_set_pin_type(id, fun); |
| |
| return ret; |
| } |
| |
| void stmp3xxx_set_pin_type(unsigned id, enum pin_fun fun) |
| { |
| struct stmp3xxx_pinmux_bank *pbank; |
| void __iomem *hwmux; |
| u32 shift, val; |
| u32 bank, pin; |
| |
| pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); |
| |
| hwmux = pbank->hw_muxsel[pin / HW_MUXSEL_PIN_NUM]; |
| shift = (pin % HW_MUXSEL_PIN_NUM) * HW_MUXSEL_PIN_LEN; |
| |
| val = pbank->functions[fun]; |
| shift = (pin % HW_MUXSEL_PIN_NUM) * HW_MUXSEL_PIN_LEN; |
| pr_debug("%s: writing 0x%x to 0x%p register\n", |
| __func__, val << shift, hwmux); |
| stmp3xxx_clearl(HW_MUXSEL_PINFUN_MASK << shift, hwmux); |
| stmp3xxx_setl(val << shift, hwmux); |
| } |
| |
| void stmp3xxx_release_pin(unsigned id, const char *label) |
| { |
| struct stmp3xxx_pinmux_bank *pbank; |
| u32 bank, pin; |
| |
| pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); |
| pr_debug("%s: label %s bank %d pin %d\n", __func__, label, bank, pin); |
| |
| if (stmp3xxx_check_pin(id, label)) |
| return; |
| |
| clear_bit(pin, &pbank->pin_map); |
| pbank->pin_labels[pin] = NULL; |
| } |
| |
| int stmp3xxx_request_pin_group(struct pin_group *pin_group, const char *label) |
| { |
| struct pin_desc *pin; |
| int p; |
| int err = 0; |
| |
| /* Allocate and configure pins */ |
| for (p = 0; p < pin_group->nr_pins; p++) { |
| pr_debug("%s: #%d\n", __func__, p); |
| pin = &pin_group->pins[p]; |
| |
| err = stmp3xxx_request_pin(pin->id, pin->fun, label); |
| if (err) |
| goto out_err; |
| |
| stmp3xxx_pin_strength(pin->id, pin->strength, label); |
| stmp3xxx_pin_voltage(pin->id, pin->voltage, label); |
| stmp3xxx_pin_pullup(pin->id, pin->pullup, label); |
| } |
| |
| return 0; |
| |
| out_err: |
| /* Release allocated pins in case of error */ |
| while (--p >= 0) { |
| pr_debug("%s: releasing #%d\n", __func__, p); |
| stmp3xxx_release_pin(pin_group->pins[p].id, label); |
| } |
| return err; |
| } |
| EXPORT_SYMBOL(stmp3xxx_request_pin_group); |
| |
| void stmp3xxx_release_pin_group(struct pin_group *pin_group, const char *label) |
| { |
| struct pin_desc *pin; |
| int p; |
| |
| for (p = 0; p < pin_group->nr_pins; p++) { |
| pin = &pin_group->pins[p]; |
| stmp3xxx_release_pin(pin->id, label); |
| } |
| } |
| EXPORT_SYMBOL(stmp3xxx_release_pin_group); |
| |
| static int stmp3xxx_irq_data_to_gpio(struct irq_data *d, |
| struct stmp3xxx_pinmux_bank **bank, unsigned *gpio) |
| { |
| struct stmp3xxx_pinmux_bank *pm; |
| |
| for (pm = pinmux_banks; pm < pinmux_banks + NR_BANKS; pm++) |
| if (pm->virq <= d->irq && d->irq < pm->virq + 32) { |
| *bank = pm; |
| *gpio = d->irq - pm->virq; |
| return 0; |
| } |
| return -ENOENT; |
| } |
| |
| static int stmp3xxx_set_irqtype(struct irq_data *d, unsigned type) |
| { |
| struct stmp3xxx_pinmux_bank *pm; |
| unsigned gpio; |
| int l, p; |
| |
| stmp3xxx_irq_data_to_gpio(d, &pm, &gpio); |
| switch (type) { |
| case IRQ_TYPE_EDGE_RISING: |
| l = 0; p = 1; break; |
| case IRQ_TYPE_EDGE_FALLING: |
| l = 0; p = 0; break; |
| case IRQ_TYPE_LEVEL_HIGH: |
| l = 1; p = 1; break; |
| case IRQ_TYPE_LEVEL_LOW: |
| l = 1; p = 0; break; |
| default: |
| pr_debug("%s: Incorrect GPIO interrupt type 0x%x\n", |
| __func__, type); |
| return -ENXIO; |
| } |
| |
| if (l) |
| stmp3xxx_setl(1 << gpio, pm->irqlevel); |
| else |
| stmp3xxx_clearl(1 << gpio, pm->irqlevel); |
| if (p) |
| stmp3xxx_setl(1 << gpio, pm->irqpolarity); |
| else |
| stmp3xxx_clearl(1 << gpio, pm->irqpolarity); |
| return 0; |
| } |
| |
| static void stmp3xxx_pin_ack_irq(struct irq_data *d) |
| { |
| u32 stat; |
| struct stmp3xxx_pinmux_bank *pm; |
| unsigned gpio; |
| |
| stmp3xxx_irq_data_to_gpio(d, &pm, &gpio); |
| stat = __raw_readl(pm->irqstat) & (1 << gpio); |
| stmp3xxx_clearl(stat, pm->irqstat); |
| } |
| |
| static void stmp3xxx_pin_mask_irq(struct irq_data *d) |
| { |
| struct stmp3xxx_pinmux_bank *pm; |
| unsigned gpio; |
| |
| stmp3xxx_irq_data_to_gpio(d, &pm, &gpio); |
| stmp3xxx_clearl(1 << gpio, pm->irqen); |
| stmp3xxx_clearl(1 << gpio, pm->pin2irq); |
| } |
| |
| static void stmp3xxx_pin_unmask_irq(struct irq_data *d) |
| { |
| struct stmp3xxx_pinmux_bank *pm; |
| unsigned gpio; |
| |
| stmp3xxx_irq_data_to_gpio(d, &pm, &gpio); |
| stmp3xxx_setl(1 << gpio, pm->irqen); |
| stmp3xxx_setl(1 << gpio, pm->pin2irq); |
| } |
| |
| static inline |
| struct stmp3xxx_pinmux_bank *to_pinmux_bank(struct gpio_chip *chip) |
| { |
| return container_of(chip, struct stmp3xxx_pinmux_bank, chip); |
| } |
| |
| static int stmp3xxx_gpio_to_irq(struct gpio_chip *chip, unsigned offset) |
| { |
| struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); |
| return pm->virq + offset; |
| } |
| |
| static int stmp3xxx_gpio_get(struct gpio_chip *chip, unsigned offset) |
| { |
| struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); |
| unsigned v; |
| |
| v = __raw_readl(pm->hw_gpio_in) & (1 << offset); |
| return v ? 1 : 0; |
| } |
| |
| static void stmp3xxx_gpio_set(struct gpio_chip *chip, unsigned offset, int v) |
| { |
| struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); |
| |
| if (v) |
| stmp3xxx_setl(1 << offset, pm->hw_gpio_out); |
| else |
| stmp3xxx_clearl(1 << offset, pm->hw_gpio_out); |
| } |
| |
| static int stmp3xxx_gpio_output(struct gpio_chip *chip, unsigned offset, int v) |
| { |
| struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); |
| |
| stmp3xxx_setl(1 << offset, pm->hw_gpio_doe); |
| stmp3xxx_gpio_set(chip, offset, v); |
| return 0; |
| } |
| |
| static int stmp3xxx_gpio_input(struct gpio_chip *chip, unsigned offset) |
| { |
| struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); |
| |
| stmp3xxx_clearl(1 << offset, pm->hw_gpio_doe); |
| return 0; |
| } |
| |
| static int stmp3xxx_gpio_request(struct gpio_chip *chip, unsigned offset) |
| { |
| return stmp3xxx_request_pin(chip->base + offset, PIN_GPIO, "gpio"); |
| } |
| |
| static void stmp3xxx_gpio_free(struct gpio_chip *chip, unsigned offset) |
| { |
| stmp3xxx_release_pin(chip->base + offset, "gpio"); |
| } |
| |
| static void stmp3xxx_gpio_irq(u32 irq, struct irq_desc *desc) |
| { |
| struct stmp3xxx_pinmux_bank *pm = irq_get_handler_data(irq); |
| int gpio_irq = pm->virq; |
| u32 stat = __raw_readl(pm->irqstat); |
| |
| while (stat) { |
| if (stat & 1) |
| generic_handle_irq(gpio_irq); |
| gpio_irq++; |
| stat >>= 1; |
| } |
| } |
| |
| static struct irq_chip gpio_irq_chip = { |
| .irq_ack = stmp3xxx_pin_ack_irq, |
| .irq_mask = stmp3xxx_pin_mask_irq, |
| .irq_unmask = stmp3xxx_pin_unmask_irq, |
| .irq_set_type = stmp3xxx_set_irqtype, |
| }; |
| |
| int __init stmp3xxx_pinmux_init(int virtual_irq_start) |
| { |
| int b, r = 0; |
| struct stmp3xxx_pinmux_bank *pm; |
| int virq; |
| |
| for (b = 0; b < 3; b++) { |
| /* only banks 0,1,2 are allowed to GPIO */ |
| pm = pinmux_banks + b; |
| pm->chip.base = 32 * b; |
| pm->chip.ngpio = 32; |
| pm->chip.owner = THIS_MODULE; |
| pm->chip.can_sleep = 1; |
| pm->chip.exported = 1; |
| pm->chip.to_irq = stmp3xxx_gpio_to_irq; |
| pm->chip.direction_input = stmp3xxx_gpio_input; |
| pm->chip.direction_output = stmp3xxx_gpio_output; |
| pm->chip.get = stmp3xxx_gpio_get; |
| pm->chip.set = stmp3xxx_gpio_set; |
| pm->chip.request = stmp3xxx_gpio_request; |
| pm->chip.free = stmp3xxx_gpio_free; |
| pm->virq = virtual_irq_start + b * 32; |
| |
| for (virq = pm->virq; virq < pm->virq; virq++) { |
| gpio_irq_chip.irq_mask(irq_get_irq_data(virq)); |
| irq_set_chip_and_handler(virq, &gpio_irq_chip, |
| handle_level_irq); |
| set_irq_flags(virq, IRQF_VALID); |
| } |
| r = gpiochip_add(&pm->chip); |
| if (r < 0) |
| break; |
| irq_set_chained_handler(pm->irq, stmp3xxx_gpio_irq); |
| irq_set_handler_data(pm->irq, pm); |
| } |
| return r; |
| } |
| |
| MODULE_AUTHOR("Vladislav Buzov"); |
| MODULE_LICENSE("GPL"); |