From 662d86a26fe38e3140a8e4533fc5156fe93ead15 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Fri, 23 Oct 2026 15:48:25 +0800 Subject: [PATCH 1/3] bus: phytium-pio: take the latch window base from the SoC The latch registers live in the SoC's LPC window, and that window does not necessarily start where the firmware places resource 0, which only describes the legacy port aperture. Reading the latch at the firmware base misses the register, so the source is never acknowledged and the level irq re-fires. Use the SoC address for the window and keep the latch offsets absolute. The firmware base stays the anchor for phytium_pio_is_lpc_mem(), which matches children against the window the firmware publishes. Signed-off-by: Wentao Guan --- drivers/bus/phytium_pio.c | 31 ++++++++++++++++--------------- drivers/bus/phytium_pio.h | 7 ++++--- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/drivers/bus/phytium_pio.c b/drivers/bus/phytium_pio.c index 713a51bcb7d9..e765f1397de1 100644 --- a/drivers/bus/phytium_pio.c +++ b/drivers/bus/phytium_pio.c @@ -127,8 +127,8 @@ EXPORT_SYMBOL(phytium_pio_get_irq); bool phytium_pio_is_lpc_mem(phys_addr_t addr) { - return ppio && addr >= ppio->lpc_base && - addr - ppio->lpc_base < PHYTIUM_LPC_IOMEM_SIZE; + return ppio && addr >= ppio->win_base && + addr - ppio->win_base < PHYTIUM_PIO_LENGTH_LONG; } EXPORT_SYMBOL(phytium_pio_is_lpc_mem); @@ -280,7 +280,6 @@ static int phytium_pio_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct logic_pio_hwaddr *range; struct phytium_pio *pio; - resource_size_t reg_size = PHYTIUM_LPC_IOMEM_SIZE; phys_addr_t reg_start; struct resource *res; int ret; @@ -291,7 +290,7 @@ static int phytium_pio_probe(struct platform_device *pdev) spin_lock_init(&pio->lock); pio->membase = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR_OR_NULL(pio->membase)) + if (IS_ERR(pio->membase)) return PTR_ERR(pio->membase); pio->irq = platform_get_irq(pdev, 0); @@ -300,21 +299,23 @@ static int phytium_pio_probe(struct platform_device *pdev) return pio->irq; } - /* - * The ACPI _CRS window only covers the short PIO aperture, while the - * interrupt latch registers live near the end of the long LPC window - * (base + 0x7ffffc0/0x7ffffc4). Map the long window starting at the - * firmware supplied LPC base (0x10000000 on D3000M, 0x20000000 on - * D2000/D3000): reading the latch through the short window can return - * unrelated device state such as UART_IIR. - */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -ENODEV; - reg_start = res->start; - pio->lpc_base = reg_start; + pio->win_base = res->start; - pio->regbase = devm_ioremap(dev, reg_start, reg_size); + /* The latch window is a SoC property, not what resource 0 describes. */ + if (cpu_is_ft_d2000()) { + reg_start = PHYTIUM_PIO_D2000_BASE; + } else if (is_pd2408()) { + reg_start = PHYTIUM_PIO_D3000M_BASE; + } else { + reg_start = res->start; + if (resource_size(res) < PHYTIUM_PIO_LENGTH_LONG) + dev_warn(dev, "short window on an unknown SoC, the latch address may be wrong\n"); + } + + pio->regbase = devm_ioremap(dev, reg_start, PHYTIUM_PIO_LENGTH_LONG); if (!pio->regbase) return -ENOMEM; diff --git a/drivers/bus/phytium_pio.h b/drivers/bus/phytium_pio.h index bb43c67e8912..116080766e68 100644 --- a/drivers/bus/phytium_pio.h +++ b/drivers/bus/phytium_pio.h @@ -18,8 +18,9 @@ #define FT2000_PIO_CLR_INT 0x7FFFFF0 #define LEGACY_ISA_SIZE 0x400 -/* Long LPC window holding the interrupt latch registers. */ -#define PHYTIUM_LPC_IOMEM_SIZE 0x08000000 +/* Base of the SoC window holding the interrupt latch registers. */ +#define PHYTIUM_PIO_D2000_BASE 0x20000000 +#define PHYTIUM_PIO_D3000M_BASE 0x10000000 /* LPC interrupt latch bits shared by the legacy children. */ #define PHYTIUM_PIO_I8042_KBD_INT 1 @@ -37,7 +38,7 @@ struct phytium_pio { u32 int_clear_reg; /* LPC latch bits owned by a registered child handler. */ u32 handled_bits; - phys_addr_t lpc_base; + phys_addr_t win_base; struct logic_pio_hwaddr *range; }; From d1aa954e3cb27b1ee24126f91731a86d57a6cb09 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Fri, 23 Oct 2026 16:13:48 +0800 Subject: [PATCH 2/3] bus: phytium-pio: give every latch bit its own irq The controller reports all legacy sources in one status register and raises a single interrupt. The children used to share that irq and acknowledge their own latch bit, which only works while everyone agrees on which bit is whose and breaks whenever a bit is pending without an owner: the handler acks a latch nobody will drain and the level irq re-fires forever. Own the irq here instead. The latch is acknowledged once and the status register is demultiplexed through an irq domain, so every bit becomes a Linux irq of its own; the children are back to plain request_irq()/free_irq() and never touch the register again. A source that stays asserted after the ack shuts the line down instead of looping. The bit masks and the EC helper left over from the polling ack have no users since the shared-latch rework, so drop them too. Signed-off-by: Wentao Guan --- drivers/bus/phytium_pio.c | 198 ++++++++++--------- drivers/bus/phytium_pio.h | 23 ++- drivers/input/serio/i8042-phytio.h | 24 +-- drivers/input/serio/i8042.c | 118 +---------- drivers/platform/arm64/phytium/ec_it8528.c | 10 - drivers/platform/arm64/phytium/ec_it8528.h | 3 - drivers/platform/arm64/phytium/phyt_laptop.c | 28 +-- drivers/tty/serial/8250/8250_core.c | 27 +-- drivers/tty/serial/8250/8250_pnp.c | 34 ++-- 9 files changed, 162 insertions(+), 303 deletions(-) diff --git a/drivers/bus/phytium_pio.c b/drivers/bus/phytium_pio.c index e765f1397de1..9f5850a826ef 100644 --- a/drivers/bus/phytium_pio.c +++ b/drivers/bus/phytium_pio.c @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -25,10 +28,8 @@ #define PHYT_PIO_DRV_VER "1.1.0" static struct phytium_pio *ppio; -int phytium_pio_int_state; -int phytium_pio_clr_int; - -static irqreturn_t phytium_pio_irq_handler(int irq, void *dev_id); +static int phytium_pio_int_state; +static int phytium_pio_clr_int; bool check_cpu_type(void) { @@ -43,88 +44,90 @@ bool check_cpu_type(void) } EXPORT_SYMBOL(check_cpu_type); -void phytium_pio_clear_interrupt(u32 val) +static void phytium_pio_irq_noop(struct irq_data *d) { - unsigned long flags; - - if (!ppio) - return; - spin_lock_irqsave(&ppio->lock, flags); - writel(val, ppio->regbase + ppio->int_clear_reg); - readl(ppio->regbase + ppio->int_status_reg); - spin_unlock_irqrestore(&ppio->lock, flags); } -EXPORT_SYMBOL(phytium_pio_clear_interrupt); - -void phytium_pio_handle_interrupt_bit(unsigned int bit) -{ - unsigned long flags; - - if (!ppio || bit >= 32) - return; - spin_lock_irqsave(&ppio->lock, flags); - ppio->handled_bits |= BIT(bit); - spin_unlock_irqrestore(&ppio->lock, flags); -} -EXPORT_SYMBOL(phytium_pio_handle_interrupt_bit); +static struct irq_chip phytium_pio_irq_chip = { + .name = PHYT_PIO_DRIVER_NAME, + .irq_mask = phytium_pio_irq_noop, + .irq_unmask = phytium_pio_irq_noop, + .irq_ack = phytium_pio_irq_noop, +}; -void phytium_pio_unhandle_interrupt_bit(unsigned int bit) +/* One Linux irq per latch bit, acknowledged once before the dispatch. */ +static void phytium_pio_irq_dispatch(struct irq_desc *desc) { - unsigned long flags; + struct phytium_pio *pio = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned long bits; + u32 pending, again; + unsigned int bit; - if (!ppio || bit >= 32) - return; + chained_irq_enter(chip, desc); + + pending = readl(pio->regbase + pio->int_status_reg); + if (!pending) + goto out; - spin_lock_irqsave(&ppio->lock, flags); - ppio->handled_bits &= ~BIT(bit); - spin_unlock_irqrestore(&ppio->lock, flags); + /* The latch is write-zero-to-clear. */ + writel(~pending, pio->regbase + pio->int_clear_reg); + again = readl(pio->regbase + pio->int_status_reg); + + bits = pending; + for_each_set_bit(bit, &bits, PHYTIUM_PIO_IRQ_NR) + generic_handle_domain_irq(pio->domain, bit); + + /* + * A source that is still asserted after its latch was cleared cannot + * be acknowledged; shut the line down instead of looping forever. + */ + if (again & pending && ++pio->stuck_acks > PHYTIUM_PIO_STUCK_LIMIT) { + pr_err("phytium-pio: latch bit 0x%x stuck, disabling irq %d\n", + again & pending, pio->irq); + disable_irq_nosync(pio->irq); + } else if (!(again & pending)) { + pio->stuck_acks = 0; + } +out: + chained_irq_exit(chip, desc); } -EXPORT_SYMBOL(phytium_pio_unhandle_interrupt_bit); -bool phytium_pio_clear_interrupt_bit(unsigned int bit) +static int phytium_pio_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) { - unsigned long flags; - u32 pending; - - if (!ppio || bit >= 32) - return false; - - spin_lock_irqsave(&ppio->lock, flags); - pending = readl(ppio->regbase + ppio->int_status_reg); - if (pending & BIT(bit)) { - writel((u32)~BIT(bit), ppio->regbase + ppio->int_clear_reg); - readl(ppio->regbase + ppio->int_status_reg); - } - spin_unlock_irqrestore(&ppio->lock, flags); + irq_set_chip_and_handler(virq, &phytium_pio_irq_chip, handle_simple_irq); + irq_set_noprobe(virq); - return pending & BIT(bit); + return 0; } -EXPORT_SYMBOL(phytium_pio_clear_interrupt_bit); -u32 phytium_pio_get_int_status(void) +static const struct irq_domain_ops phytium_pio_domain_ops = { + .map = phytium_pio_domain_map, +}; + +/** + * phytium_pio_irq_get - Linux irq of a latch bit + * @bit: latch bit, see PHYTIUM_PIO_*_INT + * + * The mapping is created on the first call. + * + * Return: the Linux irq, or a negative errno. + */ +int phytium_pio_irq_get(unsigned int bit) { - unsigned long flags; - u32 ret = 0; + unsigned int virq; - if (!ppio) - return ret; - spin_lock_irqsave(&ppio->lock, flags); - ret = readl(ppio->regbase + ppio->int_status_reg); - spin_unlock_irqrestore(&ppio->lock, flags); + if (!ppio || !ppio->domain || bit >= PHYTIUM_PIO_IRQ_NR) + return -ENODEV; - return ret; -} -EXPORT_SYMBOL(phytium_pio_get_int_status); + virq = irq_create_mapping(ppio->domain, bit); -int phytium_pio_get_irq(void) -{ - if (!ppio) - return -EINVAL; - return ppio->irq; + return virq ? virq : -ENOMEM; } -EXPORT_SYMBOL(phytium_pio_get_irq); +EXPORT_SYMBOL_GPL(phytium_pio_irq_get); +/* Children live in the firmware window, not in the latch window. */ bool phytium_pio_is_lpc_mem(phys_addr_t addr) { return ppio && addr >= ppio->win_base && @@ -132,25 +135,6 @@ bool phytium_pio_is_lpc_mem(phys_addr_t addr) } EXPORT_SYMBOL(phytium_pio_is_lpc_mem); -static irqreturn_t phytium_pio_irq_handler(int irq, void *dev_id) -{ - struct phytium_pio *pio = dev_id; - unsigned long flags; - u32 clear_bits, pending; - - spin_lock_irqsave(&pio->lock, flags); - pending = readl(pio->regbase + pio->int_status_reg); - clear_bits = pending & ~pio->handled_bits; - - if (clear_bits) { - writel(~clear_bits, pio->regbase + pio->int_clear_reg); - readl(pio->regbase + pio->int_status_reg); - } - spin_unlock_irqrestore(&pio->lock, flags); - - return IRQ_RETVAL(pending); -} - /* * phytium_pio_in - input the data from the target I/O port * @hostdata: pointer to the device information relevant to PIO controller @@ -335,24 +319,30 @@ static int phytium_pio_probe(struct platform_device *pdev) range->hostdata = pio; range->ops = &phytium_pio_ops; pio->range = range; - ppio = pio; - ret = devm_request_irq(dev, pio->irq, phytium_pio_irq_handler, - IRQF_SHARED, dev_name(dev), pio); - if (ret) { - dev_err(dev, "Failed to request irq (%d)!\n", ret); - goto out; + + pio->domain = irq_domain_create_linear(dev_fwnode(dev), + PHYTIUM_PIO_IRQ_NR, + &phytium_pio_domain_ops, pio); + if (!pio->domain) { + dev_err(dev, "Failed to create the latch irq domain\n"); + return -ENOMEM; } ret = logic_pio_register_range(range); if (ret) { dev_err(dev, "Failed to register logic pio range (%d)!\n", ret); - goto out; + irq_domain_remove(pio->domain); + pio->domain = NULL; + return ret; } + + irq_set_chained_handler_and_data(pio->irq, phytium_pio_irq_dispatch, pio); + + /* Children look the controller up through ppio. */ + ppio = pio; dev_set_drvdata(dev, pio); -out: - if (ret) - ppio = NULL; - return ret; + + return 0; } static int phytium_pio_remove(struct platform_device *pdev) @@ -364,6 +354,20 @@ static int phytium_pio_remove(struct platform_device *pdev) if (!range) return -EINVAL; + if (pio && pio->domain) { + int bit; + + irq_set_chained_handler_and_data(pio->irq, NULL, NULL); + for (bit = 0; bit < PHYTIUM_PIO_IRQ_NR; bit++) { + unsigned int virq = irq_find_mapping(pio->domain, bit); + + if (virq) + irq_dispose_mapping(virq); + } + irq_domain_remove(pio->domain); + pio->domain = NULL; + } + if (ppio == pio) ppio = NULL; logic_pio_unregister_range(range); diff --git a/drivers/bus/phytium_pio.h b/drivers/bus/phytium_pio.h index 116080766e68..16b818a621be 100644 --- a/drivers/bus/phytium_pio.h +++ b/drivers/bus/phytium_pio.h @@ -9,6 +9,7 @@ #define _PHYTIUM_PIO_H_ #include +#include #include #define PHYTIUM_PIO_LENGTH_LONG 0x8000000 @@ -16,40 +17,38 @@ #define FT3000_PIO_CLR_INT 0x7FFFFC0 #define FT2000_PIO_INT_STATE 0x7FFFFF4 #define FT2000_PIO_CLR_INT 0x7FFFFF0 -#define LEGACY_ISA_SIZE 0x400 - /* Base of the SoC window holding the interrupt latch registers. */ #define PHYTIUM_PIO_D2000_BASE 0x20000000 #define PHYTIUM_PIO_D3000M_BASE 0x10000000 -/* LPC interrupt latch bits shared by the legacy children. */ +#define PHYTIUM_PIO_IRQ_NR 32 +#define PHYTIUM_PIO_STUCK_LIMIT 1000 + +/* LPC interrupt latch bits, one Linux irq each. */ #define PHYTIUM_PIO_I8042_KBD_INT 1 #define PHYTIUM_PIO_UART_INT 4 #define PHYTIUM_PIO_EC_INT 11 #define PHYTIUM_PIO_I8042_AUX_INT 12 struct phytium_pio { + /* Firmware window: indirect PIO data accesses are decoded here. */ void __iomem *membase; - /* Long LPC window used for interrupt status/clear accesses. */ + /* Long LPC window: holds the interrupt latch registers. */ void __iomem *regbase; int irq; spinlock_t lock; u32 int_status_reg; u32 int_clear_reg; - /* LPC latch bits owned by a registered child handler. */ - u32 handled_bits; + struct irq_domain *domain; + u32 stuck_acks; + /* Physical start of the firmware window, see phytium_pio_is_lpc_mem(). */ phys_addr_t win_base; struct logic_pio_hwaddr *range; }; bool check_cpu_type(void); -int phytium_pio_get_irq(void); -void phytium_pio_handle_interrupt_bit(unsigned int bit); -void phytium_pio_unhandle_interrupt_bit(unsigned int bit); -bool phytium_pio_clear_interrupt_bit(unsigned int bit); +int phytium_pio_irq_get(unsigned int bit); bool phytium_pio_is_lpc_mem(phys_addr_t addr); -void phytium_pio_clear_interrupt(u32 val); -u32 phytium_pio_get_int_status(void); #endif diff --git a/drivers/input/serio/i8042-phytio.h b/drivers/input/serio/i8042-phytio.h index cb140eeb28a6..90f4dc9ede4f 100644 --- a/drivers/input/serio/i8042-phytio.h +++ b/drivers/input/serio/i8042-phytio.h @@ -6,9 +6,9 @@ #ifndef _I8042_PHYTIO_H #define _I8042_PHYTIO_H +#include #include #include -#include #include #include "../../bus/phytium_pio.h" @@ -35,10 +35,6 @@ static int i8042_aux_irq = 0x4c; #define I8042_STATUS_REG 0x64 #define I8042_DATA_REG 0x60 -#define EC_EVENT_BIT (1 << 11) -#define I8042_KEY_BIT (1 << 1) -#define I8042_TOUCH_BIT (1 << 12) - /* * i8042 IO operations using Logic PIO framework * The Logic PIO framework will automatically call Phytium PIO's @@ -69,24 +65,28 @@ static inline void i8042_write_command(int val) */ static inline int i8042_platform_init(void) { - int irq; + int kbd_irq, aux_irq; /* Check if we're running on Phytium CPU */ if (!check_cpu_type()) { - pr_info("i8042-phytio: Not running on Phytium CPU\n"); + pr_info("Not running on Phytium CPU\n"); return -ENODEV; } - irq = phytium_pio_get_irq(); - if (irq >= 0) { - i8042_kbd_irq = irq; - i8042_aux_irq = irq; + kbd_irq = phytium_pio_irq_get(PHYTIUM_PIO_I8042_KBD_INT); + aux_irq = phytium_pio_irq_get(PHYTIUM_PIO_I8042_AUX_INT); + if (kbd_irq < 0 || aux_irq < 0) { + pr_err("no irq for the KBC\n"); + return -ENODEV; } + i8042_kbd_irq = kbd_irq; + i8042_aux_irq = aux_irq; + /* Set reset behavior */ i8042_reset = I8042_RESET_ALWAYS; - pr_info("i8042-phytio: Initialized on Phytium platform\n"); + pr_info("irqs %d/%d\n", kbd_irq, aux_irq); return 0; } diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c index 32110a2598dd..29ff30fce384 100644 --- a/drivers/input/serio/i8042.c +++ b/drivers/input/serio/i8042.c @@ -519,14 +519,6 @@ static bool i8042_filter(unsigned char data, unsigned char str, return false; } -#ifdef CONFIG_PHYTIUM_PIO -static bool i8042_phytium_shared_parent(void) -{ - return check_cpu_type() && I8042_KBD_IRQ == I8042_AUX_IRQ && - phytium_pio_get_irq() == I8042_KBD_IRQ; -} -#endif - /* * i8042_interrupt() is the most important function in this driver - * it handles the interrupts from the i8042, and sends incoming bytes @@ -546,13 +538,6 @@ static irqreturn_t i8042_interrupt(int irq, void *dev_id) spin_lock_irqsave(&i8042_lock, flags); -#ifdef CONFIG_PHYTIUM_PIO - if (i8042_phytium_shared_parent()) { - phytium_pio_clear_interrupt_bit(PHYTIUM_PIO_I8042_KBD_INT); - phytium_pio_clear_interrupt_bit(PHYTIUM_PIO_I8042_AUX_INT); - } -#endif - str = i8042_read_status(); if (unlikely(~str & I8042_STR_OBF)) { spin_unlock_irqrestore(&i8042_lock, flags); @@ -1459,57 +1444,12 @@ static void i8042_unregister_ports(void) } } -#ifdef CONFIG_PHYTIUM_PIO -static unsigned int i8042_phytium_irq_users; - -static int i8042_phytium_request_irq(void) -{ - int error; - - if (i8042_phytium_irq_users++) - return 0; - - error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED, - "i8042", i8042_platform_device); - if (error) - i8042_phytium_irq_users = 0; - - return error; -} - -static void i8042_phytium_free_irq(void) -{ - if (i8042_phytium_irq_users && !--i8042_phytium_irq_users) - free_irq(I8042_KBD_IRQ, i8042_platform_device); -} -#endif - static void i8042_free_irqs(void) { -#ifdef CONFIG_PHYTIUM_PIO - bool shared_parent = i8042_phytium_shared_parent(); -#endif - - if (i8042_aux_irq_registered) { -#ifdef CONFIG_PHYTIUM_PIO - if (shared_parent) - phytium_pio_unhandle_interrupt_bit(PHYTIUM_PIO_I8042_AUX_INT); - if (shared_parent) - i8042_phytium_free_irq(); - else -#endif - free_irq(I8042_AUX_IRQ, i8042_platform_device); - } - if (i8042_kbd_irq_registered) { -#ifdef CONFIG_PHYTIUM_PIO - if (shared_parent) - phytium_pio_unhandle_interrupt_bit(PHYTIUM_PIO_I8042_KBD_INT); - if (shared_parent) - i8042_phytium_free_irq(); - else -#endif - free_irq(I8042_KBD_IRQ, i8042_platform_device); - } + if (i8042_aux_irq_registered) + free_irq(I8042_AUX_IRQ, i8042_platform_device); + if (i8042_kbd_irq_registered) + free_irq(I8042_KBD_IRQ, i8042_platform_device); i8042_aux_irq_registered = i8042_kbd_irq_registered = false; } @@ -1519,9 +1459,6 @@ static int i8042_setup_aux(void) int (*aux_enable)(void); int error; int i; -#ifdef CONFIG_PHYTIUM_PIO - bool shared_parent = i8042_phytium_shared_parent(); -#endif if (i8042_check_aux()) return -ENODEV; @@ -1540,19 +1477,10 @@ static int i8042_setup_aux(void) aux_enable = i8042_enable_mux_ports; } -#ifdef CONFIG_PHYTIUM_PIO - if (shared_parent) - error = i8042_phytium_request_irq(); - else -#endif - error = request_irq(I8042_AUX_IRQ, i8042_interrupt, IRQF_SHARED, - "i8042", i8042_platform_device); + error = request_irq(I8042_AUX_IRQ, i8042_interrupt, IRQF_SHARED, + "i8042", i8042_platform_device); if (error) goto err_free_ports; -#ifdef CONFIG_PHYTIUM_PIO - if (shared_parent) - phytium_pio_handle_interrupt_bit(PHYTIUM_PIO_I8042_AUX_INT); -#endif error = aux_enable(); if (error) @@ -1562,14 +1490,7 @@ static int i8042_setup_aux(void) return 0; err_free_irq: -#ifdef CONFIG_PHYTIUM_PIO - if (shared_parent) - phytium_pio_unhandle_interrupt_bit(PHYTIUM_PIO_I8042_AUX_INT); - if (shared_parent) - i8042_phytium_free_irq(); - else -#endif - free_irq(I8042_AUX_IRQ, i8042_platform_device); + free_irq(I8042_AUX_IRQ, i8042_platform_device); err_free_ports: i8042_free_aux_ports(); return error; @@ -1578,27 +1499,15 @@ static int i8042_setup_aux(void) static int i8042_setup_kbd(void) { int error; -#ifdef CONFIG_PHYTIUM_PIO - bool shared_parent = i8042_phytium_shared_parent(); -#endif error = i8042_create_kbd_port(); if (error) return error; -#ifdef CONFIG_PHYTIUM_PIO - if (shared_parent) - error = i8042_phytium_request_irq(); - else -#endif - error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED, - "i8042", i8042_platform_device); + error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED, + "i8042", i8042_platform_device); if (error) goto err_free_port; -#ifdef CONFIG_PHYTIUM_PIO - if (shared_parent) - phytium_pio_handle_interrupt_bit(PHYTIUM_PIO_I8042_KBD_INT); -#endif error = i8042_enable_kbd_port(); if (error) @@ -1608,14 +1517,7 @@ static int i8042_setup_kbd(void) return 0; err_free_irq: -#ifdef CONFIG_PHYTIUM_PIO - if (shared_parent) - phytium_pio_unhandle_interrupt_bit(PHYTIUM_PIO_I8042_KBD_INT); - if (shared_parent) - i8042_phytium_free_irq(); - else -#endif - free_irq(I8042_KBD_IRQ, i8042_platform_device); + free_irq(I8042_KBD_IRQ, i8042_platform_device); err_free_port: i8042_free_kbd_port(); return error; diff --git a/drivers/platform/arm64/phytium/ec_it8528.c b/drivers/platform/arm64/phytium/ec_it8528.c index 87dd3afe1b1f..08e135663a4b 100644 --- a/drivers/platform/arm64/phytium/ec_it8528.c +++ b/drivers/platform/arm64/phytium/ec_it8528.c @@ -211,13 +211,3 @@ int it8528_init(void) } EXPORT_SYMBOL(it8528_init); -/* - * Acknowledge the EC interrupt source only. The PIO clear register treats a - * zero bit as "clear this source", so the complemented mask leaves the - * keyboard and touchpad sources pending for i8042 instead of dropping them. - */ -void lpc_ec_interrupt_clear(void) -{ - phytium_pio_clear_interrupt(~EC_EVENT_BIT); -} -EXPORT_SYMBOL(lpc_ec_interrupt_clear); diff --git a/drivers/platform/arm64/phytium/ec_it8528.h b/drivers/platform/arm64/phytium/ec_it8528.h index 38a23c5beb6e..96dffa8f55cf 100644 --- a/drivers/platform/arm64/phytium/ec_it8528.h +++ b/drivers/platform/arm64/phytium/ec_it8528.h @@ -5,8 +5,6 @@ #include #include "../../../bus/phytium_pio.h" -#define EC_EVENT_BIT (1 << 11) - extern spinlock_t i8042_lock; /* @@ -155,7 +153,6 @@ extern int it8528_query_clean_event(void); /* The ec interrupt functions */ extern int it8528_init(void); -extern void lpc_ec_interrupt_clear(void); /* EC sensor/hwmon registration */ extern void ec_sensors_register(struct platform_device *pdev); diff --git a/drivers/platform/arm64/phytium/phyt_laptop.c b/drivers/platform/arm64/phytium/phyt_laptop.c index f3e34066d6b6..7015f576de06 100644 --- a/drivers/platform/arm64/phytium/phyt_laptop.c +++ b/drivers/platform/arm64/phytium/phyt_laptop.c @@ -342,23 +342,13 @@ static bool ec_drain_events(void) return it8528_get_ec_evt_flags(); } -/* EC event interrupt, shared with i8042 over the PIO controller. */ +/* EC event interrupt, one of the PIO controller's latch sources. */ static irqreturn_t ft_ec_int_routine(int irq, void *dev_id) { unsigned long flags; - /* - * Test the EC status port rather than the PIO latch: i8042 may have - * acknowledged the shared interrupt before us and cleared the latch - * while the EC still has an event queued. - */ - if (!it8528_get_ec_evt_flags()) { - if (phytium_pio_clear_interrupt_bit(PHYTIUM_PIO_EC_INT)) - return IRQ_HANDLED; + if (!it8528_get_ec_evt_flags()) return IRQ_NONE; - } - - phytium_pio_clear_interrupt_bit(PHYTIUM_PIO_EC_INT); spin_lock_irqsave(&i8042_lock, flags); ec_drain_events(); @@ -380,22 +370,20 @@ static int lpc_int_init(void) strscpy(ft_lpc_device->name, EC_LPC_DEV, sizeof(ft_lpc_device->name)); - /* get lpc irq */ - ret = phytium_pio_get_irq(); + /* the PIO controller owns the EC latch and gives us a per-bit irq */ + ret = phytium_pio_irq_get(PHYTIUM_PIO_EC_INT); if (ret < 0) { pr_err("phytium ec: Failed to get lpc irq: %d\n", ret); goto err_free; } ft_lpc_device->irq = ret; - /* Register EC on the shared LPC parent IRQ. */ - ret = request_irq(ft_lpc_device->irq, ft_ec_int_routine, - IRQF_SHARED, ft_lpc_device->name, ft_lpc_device); + ret = request_irq(ft_lpc_device->irq, ft_ec_int_routine, 0, + ft_lpc_device->name, ft_lpc_device); if (ret) { pr_err("phytium ec: Request irq fail: %d\n", ret); goto err_free; } - phytium_pio_handle_interrupt_bit(PHYTIUM_PIO_EC_INT); pr_debug("phytium: LPC init successful.\n"); @@ -1253,10 +1241,8 @@ static void ft_ec_irq_exit(void) if (ft_gpio_device) free_irq(ft_gpio_device->irq, ft_gpio_device); - if (ft_lpc_device) { - phytium_pio_unhandle_interrupt_bit(PHYTIUM_PIO_EC_INT); + if (ft_lpc_device) free_irq(ft_lpc_device->irq, ft_lpc_device); - } kfree(ft_gpio_device); ft_gpio_device = NULL; diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 28893444c26e..c536028e92dc 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -39,10 +39,6 @@ #include #endif -#if defined(CONFIG_PHYTIUM_PIO) -#include "../../../bus/phytium_pio.h" -#endif - #include #include "8250.h" @@ -118,11 +114,6 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id) pr_debug("%s(%d): start\n", __func__, irq); -#if defined(CONFIG_PHYTIUM_PIO) - if (check_cpu_type() && irq == phytium_pio_get_irq()) - phytium_pio_clear_interrupt_bit(PHYTIUM_PIO_UART_INT); -#endif - spin_lock(&i->lock); l = i->head; @@ -152,13 +143,6 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id) return IRQ_RETVAL(handled); } -#if defined(CONFIG_PHYTIUM_PIO) -static bool serial8250_is_phytium_pio_irq(unsigned int irq) -{ - return check_cpu_type() && irq == phytium_pio_get_irq(); -} -#endif - /* * To support ISA shared interrupts, we need to have one interrupt * handler that ensures that the IRQ line has been deasserted @@ -227,10 +211,6 @@ static int serial_link_irq_chain(struct uart_8250_port *up) up->port.irqflags, up->port.name, i); if (ret < 0) serial_do_unlink(i, up); -#if defined(CONFIG_PHYTIUM_PIO) - else if (serial8250_is_phytium_pio_irq(up->port.irq)) - phytium_pio_handle_interrupt_bit(PHYTIUM_PIO_UART_INT); -#endif } return ret; @@ -252,13 +232,8 @@ static void serial_unlink_irq_chain(struct uart_8250_port *up) BUG_ON(i == NULL); BUG_ON(i->head == NULL); - if (list_empty(i->head)) { -#if defined(CONFIG_PHYTIUM_PIO) - if (serial8250_is_phytium_pio_irq(up->port.irq)) - phytium_pio_unhandle_interrupt_bit(PHYTIUM_PIO_UART_INT); -#endif + if (list_empty(i->head)) free_irq(up->port.irq, i); - } serial_do_unlink(i, up); mutex_unlock(&hash_mutex); diff --git a/drivers/tty/serial/8250/8250_pnp.c b/drivers/tty/serial/8250/8250_pnp.c index c708486775dc..e35527ef624e 100644 --- a/drivers/tty/serial/8250/8250_pnp.c +++ b/drivers/tty/serial/8250/8250_pnp.c @@ -434,20 +434,22 @@ static int serial_pnp_guess_board(struct pnp_dev *dev) } #if defined(CONFIG_PHYTIUM_PIO) -static bool phytium_pnp_is_int_uart(struct pnp_dev *dev, - const struct pnp_device_id *dev_id) +/* UARTs behind the PIO controller get their irq from it, not from firmware. */ +static int phytium_pnp_uart_irq(struct pnp_dev *dev, + const struct pnp_device_id *dev_id) { - if (!check_cpu_type() || phytium_pio_get_irq() < 0) - return false; + if (!check_cpu_type()) + return -ENODEV; if (strcmp(dev_id->id, "PNP0501")) - return false; + return -ENODEV; - if (pnp_port_valid(dev, 0) || pnp_port_valid(dev, 2)) - return true; + if (!pnp_port_valid(dev, 0) && !pnp_port_valid(dev, 2) && + !(pnp_mem_valid(dev, 0) && + phytium_pio_is_lpc_mem(pnp_mem_start(dev, 0)))) + return -ENODEV; - return pnp_mem_valid(dev, 0) && - phytium_pio_is_lpc_mem(pnp_mem_start(dev, 0)); + return phytium_pio_irq_get(PHYTIUM_PIO_UART_INT); } #endif @@ -465,11 +467,15 @@ serial_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) memset(&uart, 0, sizeof(uart)); #if defined(CONFIG_PHYTIUM_PIO) - if (phytium_pnp_is_int_uart(dev, dev_id)) { - uart.port.irq = phytium_pio_get_irq(); - uart.port.flags |= UPF_SHARE_IRQ; - } else if (pnp_irq_valid(dev, 0)) { - uart.port.irq = pnp_irq(dev, 0); + { + int irq = phytium_pnp_uart_irq(dev, dev_id); + + if (irq >= 0) { + uart.port.irq = irq; + uart.port.flags |= UPF_SHARE_IRQ; + } else if (pnp_irq_valid(dev, 0)) { + uart.port.irq = pnp_irq(dev, 0); + } } #else if (pnp_irq_valid(dev, 0)) From 47a1e66bcab3dfdd13672521e9a0a9e1f4673ade Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Fri, 23 Oct 2026 16:13:48 +0800 Subject: [PATCH 3/3] Input: i8042 - skip a KBC that has no ports behind it A KBCI8042 node whose _CRS describes no I/O ports means nothing decodes 0x60/0x64, and the first access in i8042_probe() hangs the machine before anything can be reported. Keep probing as before by default and skip only on positive evidence: a board on the list, or a non-laptop whose ACPI node documents the controller without its ports. Signed-off-by: Wentao Guan --- drivers/input/serio/i8042-phytio.h | 81 ++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/drivers/input/serio/i8042-phytio.h b/drivers/input/serio/i8042-phytio.h index 90f4dc9ede4f..378d3cf10ced 100644 --- a/drivers/input/serio/i8042-phytio.h +++ b/drivers/input/serio/i8042-phytio.h @@ -6,6 +6,9 @@ #ifndef _I8042_PHYTIO_H #define _I8042_PHYTIO_H +#include +#include +#include #include #include #include @@ -60,11 +63,68 @@ static inline void i8042_write_command(int val) outb(val, I8042_COMMAND_REG); } +/* + * Boards that publish a KBCI8042 node but leave nothing behind the legacy + * ports: a read of 0x60/0x64 never completes, so i8042_probe() hangs. + */ +static const struct dmi_system_id phytium_i8042_skip[] = { + { + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "FD2000ZX200MB1"), + }, + }, + { } +}; + +static inline bool phytium_i8042_acpi_described(void) +{ + return IS_ENABLED(CONFIG_ACPI) && + acpi_dev_present("KBCI8042", NULL, -1); +} + +#ifdef CONFIG_ACPI +static int phytium_i8042_io_cb(struct acpi_resource *ares, void *data) +{ + bool *found = data; + + if (ares->type == ACPI_RESOURCE_TYPE_IO && + ares->data.io.minimum <= 0x60 && ares->data.io.maximum >= 0x64) + *found = true; + + return 1; +} + +/* Does _CRS describe the controller's own ports? */ +static inline bool phytium_i8042_acpi_has_ports(void) +{ + struct acpi_device *adev; + LIST_HEAD(res); + bool found = false; + + adev = acpi_dev_get_first_match_dev("KBCI8042", NULL, -1); + if (!adev) + return false; + + acpi_dev_get_resources(adev, &res, phytium_i8042_io_cb, &found); + acpi_dev_free_resource_list(&res); + acpi_dev_put(adev); + + return found; +} +#else +static inline bool phytium_i8042_acpi_has_ports(void) +{ + return true; +} +#endif + /* * Platform initialization */ static inline int i8042_platform_init(void) { + bool described = phytium_i8042_acpi_described(); + bool laptop; int kbd_irq, aux_irq; /* Check if we're running on Phytium CPU */ @@ -73,6 +133,27 @@ static inline int i8042_platform_init(void) return -ENODEV; } + if (dmi_check_system(phytium_i8042_skip)) { + pr_info("KBC does not answer on this board, skipping\n"); + return -ENODEV; + } + + /* + * Probe like before unless the firmware documents the controller + * without its ports. Laptops keep probing: an internal keyboard may + * be the only input. + */ + laptop = !IS_ENABLED(CONFIG_DMI) || chassis_types_is_laptop(); + if (described && !laptop && !phytium_i8042_acpi_has_ports()) { + pr_info("KBC has no ports described, skipping\n"); + return -ENODEV; + } + + /* + * The PIO controller hands out one Linux irq per latch bit and clears + * the latch itself, so the irq is not shared with the other legacy + * devices. + */ kbd_irq = phytium_pio_irq_get(PHYTIUM_PIO_I8042_KBD_INT); aux_irq = phytium_pio_irq_get(PHYTIUM_PIO_I8042_AUX_INT); if (kbd_irq < 0 || aux_irq < 0) {