Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Jim Liu | 2884 | 100.00% | 2 | 100.00% |
Total | 2884 | 2 |
// SPDX-License-Identifier: GPL-2.0 /* * Nuvoton NPCM Serial GPIO Driver * * Copyright (C) 2021 Nuvoton Technologies */ #include <linux/bitfield.h> #include <linux/clk.h> #include <linux/gpio/driver.h> #include <linux/hashtable.h> #include <linux/init.h> #include <linux/io.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/spinlock.h> #include <linux/string.h> #include <linux/units.h> #define MAX_NR_HW_SGPIO 64 #define NPCM_IOXCFG1 0x2A #define NPCM_IOXCFG1_SFT_CLK GENMASK(3, 0) #define NPCM_IOXCFG1_SCLK_POL BIT(4) #define NPCM_IOXCFG1_LDSH_POL BIT(5) #define NPCM_IOXCTS 0x28 #define NPCM_IOXCTS_IOXIF_EN BIT(7) #define NPCM_IOXCTS_RD_MODE GENMASK(2, 1) #define NPCM_IOXCTS_RD_MODE_PERIODIC BIT(2) #define NPCM_IOXCFG2 0x2B #define NPCM_IOXCFG2_PORT GENMASK(3, 0) #define NPCM_IXOEVCFG_MASK GENMASK(1, 0) #define NPCM_IXOEVCFG_FALLING BIT(1) #define NPCM_IXOEVCFG_RISING BIT(0) #define NPCM_IXOEVCFG_BOTH (NPCM_IXOEVCFG_FALLING | NPCM_IXOEVCFG_RISING) #define NPCM_CLK_MHZ (8 * HZ_PER_MHZ) #define NPCM_750_OPT 6 #define NPCM_845_OPT 5 #define GPIO_BANK(x) ((x) / 8) #define GPIO_BIT(x) ((x) % 8) /* * Select the frequency of shift clock. * The shift clock is a division of the APB clock. */ struct npcm_clk_cfg { unsigned int *sft_clk; unsigned int *clk_sel; unsigned int cfg_opt; }; struct npcm_sgpio { struct gpio_chip chip; struct clk *pclk; struct irq_chip intc; raw_spinlock_t lock; void __iomem *base; int irq; u8 nin_sgpio; u8 nout_sgpio; u8 in_port; u8 out_port; u8 int_type[MAX_NR_HW_SGPIO]; }; struct npcm_sgpio_bank { u8 rdata_reg; u8 wdata_reg; u8 event_config; u8 event_status; }; enum npcm_sgpio_reg { READ_DATA, WRITE_DATA, EVENT_CFG, EVENT_STS, }; static const struct npcm_sgpio_bank npcm_sgpio_banks[] = { { .wdata_reg = 0x00, .rdata_reg = 0x08, .event_config = 0x10, .event_status = 0x20, }, { .wdata_reg = 0x01, .rdata_reg = 0x09, .event_config = 0x12, .event_status = 0x21, }, { .wdata_reg = 0x02, .rdata_reg = 0x0a, .event_config = 0x14, .event_status = 0x22, }, { .wdata_reg = 0x03, .rdata_reg = 0x0b, .event_config = 0x16, .event_status = 0x23, }, { .wdata_reg = 0x04, .rdata_reg = 0x0c, .event_config = 0x18, .event_status = 0x24, }, { .wdata_reg = 0x05, .rdata_reg = 0x0d, .event_config = 0x1a, .event_status = 0x25, }, { .wdata_reg = 0x06, .rdata_reg = 0x0e, .event_config = 0x1c, .event_status = 0x26, }, { .wdata_reg = 0x07, .rdata_reg = 0x0f, .event_config = 0x1e, .event_status = 0x27, }, }; static void __iomem *bank_reg(struct npcm_sgpio *gpio, const struct npcm_sgpio_bank *bank, const enum npcm_sgpio_reg reg) { switch (reg) { case READ_DATA: return gpio->base + bank->rdata_reg; case WRITE_DATA: return gpio->base + bank->wdata_reg; case EVENT_CFG: return gpio->base + bank->event_config; case EVENT_STS: return gpio->base + bank->event_status; default: /* actually if code runs to here, it's an error case */ dev_WARN(gpio->chip.parent, "Getting here is an error condition"); return NULL; } } static const struct npcm_sgpio_bank *offset_to_bank(unsigned int offset) { unsigned int bank = GPIO_BANK(offset); return &npcm_sgpio_banks[bank]; } static void npcm_sgpio_irqd_to_data(struct irq_data *d, struct npcm_sgpio **gpio, const struct npcm_sgpio_bank **bank, u8 *bit, unsigned int *offset) { struct npcm_sgpio *internal; *offset = irqd_to_hwirq(d); internal = irq_data_get_irq_chip_data(d); *gpio = internal; *offset -= internal->nout_sgpio; *bank = offset_to_bank(*offset); *bit = GPIO_BIT(*offset); } static int npcm_sgpio_init_port(struct npcm_sgpio *gpio) { u8 in_port, out_port, set_port, reg; in_port = GPIO_BANK(gpio->nin_sgpio); if (GPIO_BIT(gpio->nin_sgpio) > 0) in_port += 1; out_port = GPIO_BANK(gpio->nout_sgpio); if (GPIO_BIT(gpio->nout_sgpio) > 0) out_port += 1; gpio->in_port = in_port; gpio->out_port = out_port; set_port = (out_port & NPCM_IOXCFG2_PORT) << 4 | (in_port & NPCM_IOXCFG2_PORT); iowrite8(set_port, gpio->base + NPCM_IOXCFG2); reg = ioread8(gpio->base + NPCM_IOXCFG2); return reg == set_port ? 0 : -EINVAL; } static int npcm_sgpio_dir_in(struct gpio_chip *gc, unsigned int offset) { struct npcm_sgpio *gpio = gpiochip_get_data(gc); return offset < gpio->nout_sgpio ? -EINVAL : 0; } static int npcm_sgpio_dir_out(struct gpio_chip *gc, unsigned int offset, int val) { gc->set(gc, offset, val); return 0; } static int npcm_sgpio_get_direction(struct gpio_chip *gc, unsigned int offset) { struct npcm_sgpio *gpio = gpiochip_get_data(gc); if (offset < gpio->nout_sgpio) return GPIO_LINE_DIRECTION_OUT; return GPIO_LINE_DIRECTION_IN; } static void npcm_sgpio_set(struct gpio_chip *gc, unsigned int offset, int val) { struct npcm_sgpio *gpio = gpiochip_get_data(gc); const struct npcm_sgpio_bank *bank = offset_to_bank(offset); void __iomem *addr; u8 reg = 0; addr = bank_reg(gpio, bank, WRITE_DATA); reg = ioread8(addr); if (val) reg |= BIT(GPIO_BIT(offset)); else reg &= ~BIT(GPIO_BIT(offset)); iowrite8(reg, addr); } static int npcm_sgpio_get(struct gpio_chip *gc, unsigned int offset) { struct npcm_sgpio *gpio = gpiochip_get_data(gc); const struct npcm_sgpio_bank *bank; void __iomem *addr; u8 reg; if (offset < gpio->nout_sgpio) { bank = offset_to_bank(offset); addr = bank_reg(gpio, bank, WRITE_DATA); } else { offset -= gpio->nout_sgpio; bank = offset_to_bank(offset); addr = bank_reg(gpio, bank, READ_DATA); } reg = ioread8(addr); return !!(reg & BIT(GPIO_BIT(offset))); } static void npcm_sgpio_setup_enable(struct npcm_sgpio *gpio, bool enable) { u8 reg; reg = ioread8(gpio->base + NPCM_IOXCTS); reg = (reg & ~NPCM_IOXCTS_RD_MODE) | NPCM_IOXCTS_RD_MODE_PERIODIC; if (enable) reg |= NPCM_IOXCTS_IOXIF_EN; else reg &= ~NPCM_IOXCTS_IOXIF_EN; iowrite8(reg, gpio->base + NPCM_IOXCTS); } static int npcm_sgpio_setup_clk(struct npcm_sgpio *gpio, const struct npcm_clk_cfg *clk_cfg) { unsigned long apb_freq; u32 val; u8 tmp; int i; apb_freq = clk_get_rate(gpio->pclk); tmp = ioread8(gpio->base + NPCM_IOXCFG1) & ~NPCM_IOXCFG1_SFT_CLK; for (i = clk_cfg->cfg_opt-1; i > 0; i--) { val = apb_freq / clk_cfg->sft_clk[i]; if (NPCM_CLK_MHZ > val) { iowrite8(clk_cfg->clk_sel[i] | tmp, gpio->base + NPCM_IOXCFG1); return 0; } } return -EINVAL; } static void npcm_sgpio_irq_init_valid_mask(struct gpio_chip *gc, unsigned long *valid_mask, unsigned int ngpios) { struct npcm_sgpio *gpio = gpiochip_get_data(gc); /* input GPIOs in the high range */ bitmap_set(valid_mask, gpio->nout_sgpio, gpio->nin_sgpio); bitmap_clear(valid_mask, 0, gpio->nout_sgpio); } static void npcm_sgpio_irq_set_mask(struct irq_data *d, bool set) { const struct npcm_sgpio_bank *bank; struct npcm_sgpio *gpio; unsigned long flags; void __iomem *addr; unsigned int offset; u16 reg, type; u8 bit; npcm_sgpio_irqd_to_data(d, &gpio, &bank, &bit, &offset); addr = bank_reg(gpio, bank, EVENT_CFG); reg = ioread16(addr); if (set) { reg &= ~(NPCM_IXOEVCFG_MASK << (bit * 2)); } else { type = gpio->int_type[offset]; reg |= (type << (bit * 2)); } raw_spin_lock_irqsave(&gpio->lock, flags); npcm_sgpio_setup_enable(gpio, false); iowrite16(reg, addr); npcm_sgpio_setup_enable(gpio, true); addr = bank_reg(gpio, bank, EVENT_STS); reg = ioread8(addr); reg |= BIT(bit); iowrite8(reg, addr); raw_spin_unlock_irqrestore(&gpio->lock, flags); } static void npcm_sgpio_irq_ack(struct irq_data *d) { const struct npcm_sgpio_bank *bank; struct npcm_sgpio *gpio; unsigned long flags; void __iomem *status_addr; unsigned int offset; u8 bit; npcm_sgpio_irqd_to_data(d, &gpio, &bank, &bit, &offset); status_addr = bank_reg(gpio, bank, EVENT_STS); raw_spin_lock_irqsave(&gpio->lock, flags); iowrite8(BIT(bit), status_addr); raw_spin_unlock_irqrestore(&gpio->lock, flags); } static void npcm_sgpio_irq_mask(struct irq_data *d) { npcm_sgpio_irq_set_mask(d, true); } static void npcm_sgpio_irq_unmask(struct irq_data *d) { npcm_sgpio_irq_set_mask(d, false); } static int npcm_sgpio_set_type(struct irq_data *d, unsigned int type) { const struct npcm_sgpio_bank *bank; irq_flow_handler_t handler; struct npcm_sgpio *gpio; unsigned long flags; void __iomem *addr; unsigned int offset; u16 reg, val; u8 bit; npcm_sgpio_irqd_to_data(d, &gpio, &bank, &bit, &offset); switch (type & IRQ_TYPE_SENSE_MASK) { case IRQ_TYPE_EDGE_BOTH: val = NPCM_IXOEVCFG_BOTH; break; case IRQ_TYPE_EDGE_RISING: case IRQ_TYPE_LEVEL_HIGH: val = NPCM_IXOEVCFG_RISING; break; case IRQ_TYPE_EDGE_FALLING: case IRQ_TYPE_LEVEL_LOW: val = NPCM_IXOEVCFG_FALLING; break; default: return -EINVAL; } if (type & IRQ_TYPE_LEVEL_MASK) handler = handle_level_irq; else handler = handle_edge_irq; gpio->int_type[offset] = val; raw_spin_lock_irqsave(&gpio->lock, flags); npcm_sgpio_setup_enable(gpio, false); addr = bank_reg(gpio, bank, EVENT_CFG); reg = ioread16(addr); reg |= (val << (bit * 2)); iowrite16(reg, addr); npcm_sgpio_setup_enable(gpio, true); raw_spin_unlock_irqrestore(&gpio->lock, flags); irq_set_handler_locked(d, handler); return 0; } static void npcm_sgpio_irq_handler(struct irq_desc *desc) { struct gpio_chip *gc = irq_desc_get_handler_data(desc); struct irq_chip *ic = irq_desc_get_chip(desc); struct npcm_sgpio *gpio = gpiochip_get_data(gc); unsigned int i, j; unsigned long reg; chained_irq_enter(ic, desc); for (i = 0; i < ARRAY_SIZE(npcm_sgpio_banks); i++) { const struct npcm_sgpio_bank *bank = &npcm_sgpio_banks[i]; reg = ioread8(bank_reg(gpio, bank, EVENT_STS)); for_each_set_bit(j, ®, 8) generic_handle_domain_irq(gc->irq.domain, i * 8 + gpio->nout_sgpio + j); } chained_irq_exit(ic, desc); } static const struct irq_chip sgpio_irq_chip = { .name = "sgpio-irq", .irq_ack = npcm_sgpio_irq_ack, .irq_mask = npcm_sgpio_irq_mask, .irq_unmask = npcm_sgpio_irq_unmask, .irq_set_type = npcm_sgpio_set_type, .flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND, GPIOCHIP_IRQ_RESOURCE_HELPERS, }; static int npcm_sgpio_setup_irqs(struct npcm_sgpio *gpio, struct platform_device *pdev) { int rc, i; struct gpio_irq_chip *irq; rc = platform_get_irq(pdev, 0); if (rc < 0) return rc; gpio->irq = rc; npcm_sgpio_setup_enable(gpio, false); /* Disable IRQ and clear Interrupt status registers for all SGPIO Pins. */ for (i = 0; i < ARRAY_SIZE(npcm_sgpio_banks); i++) { const struct npcm_sgpio_bank *bank = &npcm_sgpio_banks[i]; iowrite16(0, bank_reg(gpio, bank, EVENT_CFG)); iowrite8(0xff, bank_reg(gpio, bank, EVENT_STS)); } irq = &gpio->chip.irq; gpio_irq_chip_set_chip(irq, &sgpio_irq_chip); irq->init_valid_mask = npcm_sgpio_irq_init_valid_mask; irq->handler = handle_bad_irq; irq->default_type = IRQ_TYPE_NONE; irq->parent_handler = npcm_sgpio_irq_handler; irq->parent_handler_data = gpio; irq->parents = &gpio->irq; irq->num_parents = 1; return 0; } static int npcm_sgpio_probe(struct platform_device *pdev) { struct npcm_sgpio *gpio; const struct npcm_clk_cfg *clk_cfg; int rc; u32 nin_gpios, nout_gpios; gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); if (!gpio) return -ENOMEM; gpio->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(gpio->base)) return PTR_ERR(gpio->base); clk_cfg = device_get_match_data(&pdev->dev); if (!clk_cfg) return -EINVAL; rc = device_property_read_u32(&pdev->dev, "nuvoton,input-ngpios", &nin_gpios); if (rc < 0) return dev_err_probe(&pdev->dev, rc, "Could not read ngpios property\n"); rc = device_property_read_u32(&pdev->dev, "nuvoton,output-ngpios", &nout_gpios); if (rc < 0) return dev_err_probe(&pdev->dev, rc, "Could not read ngpios property\n"); gpio->nin_sgpio = nin_gpios; gpio->nout_sgpio = nout_gpios; if (gpio->nin_sgpio > MAX_NR_HW_SGPIO || gpio->nout_sgpio > MAX_NR_HW_SGPIO) return dev_err_probe(&pdev->dev, -EINVAL, "Number of GPIOs exceeds the maximum of %d: input: %d output: %d\n", MAX_NR_HW_SGPIO, nin_gpios, nout_gpios); gpio->pclk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(gpio->pclk)) return dev_err_probe(&pdev->dev, PTR_ERR(gpio->pclk), "Could not get pclk\n"); rc = npcm_sgpio_setup_clk(gpio, clk_cfg); if (rc < 0) return dev_err_probe(&pdev->dev, rc, "Failed to setup clock\n"); raw_spin_lock_init(&gpio->lock); gpio->chip.parent = &pdev->dev; gpio->chip.ngpio = gpio->nin_sgpio + gpio->nout_sgpio; gpio->chip.direction_input = npcm_sgpio_dir_in; gpio->chip.direction_output = npcm_sgpio_dir_out; gpio->chip.get_direction = npcm_sgpio_get_direction; gpio->chip.get = npcm_sgpio_get; gpio->chip.set = npcm_sgpio_set; gpio->chip.label = dev_name(&pdev->dev); gpio->chip.base = -1; rc = npcm_sgpio_init_port(gpio); if (rc < 0) return rc; rc = npcm_sgpio_setup_irqs(gpio, pdev); if (rc < 0) return rc; rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio); if (rc) return dev_err_probe(&pdev->dev, rc, "GPIO registering failed\n"); npcm_sgpio_setup_enable(gpio, true); return 0; } static unsigned int npcm750_SFT_CLK[NPCM_750_OPT] = { 1024, 32, 8, 4, 3, 2, }; static unsigned int npcm750_CLK_SEL[NPCM_750_OPT] = { 0x00, 0x05, 0x07, 0x0C, 0x0D, 0x0E, }; static unsigned int npcm845_SFT_CLK[NPCM_845_OPT] = { 1024, 32, 16, 8, 4, }; static unsigned int npcm845_CLK_SEL[NPCM_845_OPT] = { 0x00, 0x05, 0x06, 0x07, 0x0C, }; static struct npcm_clk_cfg npcm750_sgpio_pdata = { .sft_clk = npcm750_SFT_CLK, .clk_sel = npcm750_CLK_SEL, .cfg_opt = NPCM_750_OPT, }; static const struct npcm_clk_cfg npcm845_sgpio_pdata = { .sft_clk = npcm845_SFT_CLK, .clk_sel = npcm845_CLK_SEL, .cfg_opt = NPCM_845_OPT, }; static const struct of_device_id npcm_sgpio_of_table[] = { { .compatible = "nuvoton,npcm750-sgpio", .data = &npcm750_sgpio_pdata, }, { .compatible = "nuvoton,npcm845-sgpio", .data = &npcm845_sgpio_pdata, }, {} }; MODULE_DEVICE_TABLE(of, npcm_sgpio_of_table); static struct platform_driver npcm_sgpio_driver = { .driver = { .name = KBUILD_MODNAME, .of_match_table = npcm_sgpio_of_table, }, .probe = npcm_sgpio_probe, }; module_platform_driver(npcm_sgpio_driver); MODULE_AUTHOR("Jim Liu <jjliu0@nuvoton.com>"); MODULE_AUTHOR("Joseph Liu <kwliu@nuvoton.com>"); MODULE_DESCRIPTION("Nuvoton NPCM Serial GPIO Driver"); MODULE_LICENSE("GPL v2");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1