diff --git a/drivers/pinctrl/mediatek/mtk-eint.c b/drivers/pinctrl/mediatek/mtk-eint.c index 3e6b121cf593a..45f7856d9dab8 100644 --- a/drivers/pinctrl/mediatek/mtk-eint.c +++ b/drivers/pinctrl/mediatek/mtk-eint.c @@ -49,6 +49,9 @@ static const struct mtk_eint_regs mtk_generic_eint_regs = { .dbnc_ctrl = 0x500, .dbnc_set = 0x600, .dbnc_clr = 0x700, + .event = 0x800, + .event_set = 0x840, + .event_clr = 0x880, }; const unsigned int debounce_time_mt2701[] = { @@ -163,6 +166,47 @@ static void mtk_eint_unmask(struct irq_data *d) mtk_eint_flip_edge(eint, d->hwirq); } +/* + * Clear the event_clr bit for a single EINT so its wake-event is not + * suppressed. Mirrors mtk_eint_unmask() addressing: register is banked + * per instance and word-indexed by (idx / 32). + */ +static void mtk_eint_event_unmask_one(struct mtk_eint *eint, + unsigned int eint_num) +{ + unsigned int idx, inst; + unsigned int mask; + void __iomem *reg; + + if (eint_num >= eint->hw->ap_num) + return; + + idx = eint->pins[eint_num].index; + inst = eint->pins[eint_num].instance; + + if (inst >= eint->nbase) + return; + + mask = BIT(idx & 0x1f); + reg = mtk_eint_get_offset(eint, eint_num, eint->regs->event_clr); + writel(mask, reg); +} + +/* + * Walk the SoC's wake-event pin list (e.g. eint_event_mt8901[]) and + * clear each pin's event mask so wake sources survive init/resume. + */ +static void mtk_eint_apply_event_unmask(struct mtk_eint *eint) +{ + unsigned int i; + + if (!eint->event_pins || !eint->num_event_pins) + return; + + for (i = 0; i < eint->num_event_pins; i++) + mtk_eint_event_unmask_one(eint, eint->event_pins[i]); +} + static unsigned int mtk_eint_get_mask(struct mtk_eint *eint, unsigned int eint_num) { @@ -438,6 +482,7 @@ EXPORT_SYMBOL_GPL(mtk_eint_do_suspend); int mtk_eint_do_resume(struct mtk_eint *eint) { mtk_eint_chip_write_mask(eint, eint->base, eint->cur_mask); + mtk_eint_apply_event_unmask(eint); return 0; } @@ -605,6 +650,11 @@ int mtk_eint_do_init(struct mtk_eint *eint, struct mtk_eint_pin *eint_pin) irq_set_chained_handler_and_data(eint->irq, mtk_eint_irq_handler, eint); + mtk_eint_apply_event_unmask(eint); + + dev_info(eint->dev, "%s completed, %u EINTs registered\n", + __func__, eint->hw->ap_num); + return 0; err_eint: diff --git a/drivers/pinctrl/mediatek/mtk-eint.h b/drivers/pinctrl/mediatek/mtk-eint.h index 1b185f660affa..cbcca28308f88 100644 --- a/drivers/pinctrl/mediatek/mtk-eint.h +++ b/drivers/pinctrl/mediatek/mtk-eint.h @@ -31,6 +31,9 @@ struct mtk_eint_regs { unsigned int dbnc_ctrl; unsigned int dbnc_set; unsigned int dbnc_clr; + unsigned int event; + unsigned int event_set; + unsigned int event_clr; }; struct mtk_eint_hw { @@ -84,6 +87,12 @@ struct mtk_eint { struct mtk_eint_pin *pins; u16 num_db_time; + /* Wake-event pin list: bits to clear in event_clr after init/resume + * so wake sources are not suppressed. + */ + const unsigned int *event_pins; + unsigned int num_event_pins; + /* Used to fit into various pinctrl device */ void *pctl; const struct mtk_eint_xt *gpio_xlate; diff --git a/drivers/pinctrl/mediatek/pinctrl-mt8901.c b/drivers/pinctrl/mediatek/pinctrl-mt8901.c index 623cd0cd58f05..cf83aaa2844f0 100644 --- a/drivers/pinctrl/mediatek/pinctrl-mt8901.c +++ b/drivers/pinctrl/mediatek/pinctrl-mt8901.c @@ -1412,6 +1412,8 @@ static const struct mtk_eint_hw mt8901_eint_hw = { .db_time = debounce_time_mt8901, }; +static const unsigned int eint_event_mt8901[] = {20}; + static const struct mtk_pin_soc mt8901_data = { .reg_cal = mt8901_reg_cals, .pins = mtk_pins_mt8901, @@ -1432,6 +1434,8 @@ static const struct mtk_pin_soc mt8901_data = { .drive_get = mtk_pinconf_drive_get_rev1, .adv_drive_set = mtk_pinconf_adv_drive_set_raw, .adv_drive_get = mtk_pinconf_adv_drive_get_raw, + .eint_event = eint_event_mt8901, + .total_wake_eints = ARRAY_SIZE(eint_event_mt8901), }; static const struct acpi_device_id mt8901_pinctrl_acpi_match[] = { diff --git a/drivers/pinctrl/mediatek/pinctrl-mtk-common-v2.c b/drivers/pinctrl/mediatek/pinctrl-mtk-common-v2.c index fc71f9b267c52..d0e1e7adec3aa 100644 --- a/drivers/pinctrl/mediatek/pinctrl-mtk-common-v2.c +++ b/drivers/pinctrl/mediatek/pinctrl-mtk-common-v2.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "mtk-eint.h" #include "pinctrl-mtk-common-v2.h" @@ -434,6 +435,8 @@ int mtk_build_eint(struct mtk_pinctrl *hw, struct platform_device *pdev) hw->eint->hw = hw->soc->eint_hw; hw->eint->pctl = hw; hw->eint->gpio_xlate = &mtk_eint_xt; + hw->eint->event_pins = hw->soc->eint_event; + hw->eint->num_event_pins = hw->soc->total_wake_eints; ret = mtk_eint_do_init(hw->eint, hw->soc->eint_pin); if (ret) @@ -624,10 +627,15 @@ static int mtk_pinconf_bias_set_pu_pd(struct mtk_pinctrl *hw, if (arg == MTK_DISABLE) { pu = 0; pd = 0; - } else if ((arg == MTK_ENABLE) && pullup) { + } else if (arg == MTK_ENABLE && pullup == MTK_BUS_HOLD) { + if (pd_only) + return -EINVAL; /* Bus-hold needs both PU and PD. */ + pu = 1; + pd = 1; + } else if ((arg == MTK_ENABLE) && pullup == 1) { pu = 1; pd = 0; - } else if ((arg == MTK_ENABLE) && !pullup) { + } else if ((arg == MTK_ENABLE) && pullup == 0) { pu = 0; pd = 1; } else { @@ -649,6 +657,12 @@ static int mtk_pinconf_bias_set_pullsel_pullen(struct mtk_pinctrl *hw, { int err, enable; + /* Bus-hold is not supported for the PULLSEL/PULLEN path; reject explicitly */ + if (pullup == MTK_BUS_HOLD) { + err = -EINVAL; + goto out; + } + if (arg == MTK_DISABLE) enable = 0; else if (arg == MTK_ENABLE) @@ -674,6 +688,12 @@ static int mtk_pinconf_bias_set_pupd_r1_r0(struct mtk_pinctrl *hw, { int err, r0, r1; + /* Bus-hold is not supported for R1R0 path; reject explicitly */ + if (pullup == MTK_BUS_HOLD) { + err = -EINVAL; + goto out; + } + if ((arg == MTK_DISABLE) || (arg == MTK_PUPD_SET_R1R0_00)) { pullup = 0; r0 = 0; @@ -794,10 +814,18 @@ int mtk_pinconf_bias_set_combo(struct mtk_pinctrl *hw, else try_all_type = MTK_PULL_TYPE_MASK; + /* + * Explicitly handle bus-hold for RSEL type: skip the RSEL path when + * MTK_BUS_HOLD is requested. + */ if (try_all_type & MTK_PULL_RSEL_TYPE) { - err = mtk_pinconf_bias_set_pu_pd_rsel(hw, desc, pullup, arg); - if (!err) - return 0; + if (pullup == MTK_BUS_HOLD) { + /* Bus-hold not supported for RSEL */ + } else { + err = mtk_pinconf_bias_set_pu_pd_rsel(hw, desc, pullup, arg); + if (!err) + return 0; + } } if (try_all_type & MTK_PULL_PD_TYPE) { @@ -829,6 +857,128 @@ int mtk_pinconf_bias_set_combo(struct mtk_pinctrl *hw, } EXPORT_SYMBOL_GPL(mtk_pinconf_bias_set_combo); +/* + * Registry of active mtk_pinctrl instances so that client drivers + * (see mtk_pinctrl_program_bias_by_gpio) can resolve an absolute SoC + * pin number to the owning controller without having a device handle. + * Number of live controllers per system is small (typically 1-2), so a + * flat list guarded by a mutex is sufficient. + */ +static LIST_HEAD(mtk_pinctrl_instances); +static DEFINE_MUTEX(mtk_pinctrl_instances_lock); + +void mtk_pinctrl_register_instance(struct mtk_pinctrl *hw) +{ + mutex_lock(&mtk_pinctrl_instances_lock); + list_add_tail(&hw->instance_node, &mtk_pinctrl_instances); + mutex_unlock(&mtk_pinctrl_instances_lock); +} +EXPORT_SYMBOL_GPL(mtk_pinctrl_register_instance); + +void mtk_pinctrl_unregister_instance(struct mtk_pinctrl *hw) +{ + mutex_lock(&mtk_pinctrl_instances_lock); + list_del(&hw->instance_node); + mutex_unlock(&mtk_pinctrl_instances_lock); +} +EXPORT_SYMBOL_GPL(mtk_pinctrl_unregister_instance); + +/* + * Translate the public API's constants to the pinctrl-internal ones. + * Kept explicit rather than assumed-equal so the two can evolve + * independently (client drivers do not include pinctrl-mtk-common-v2.h). + */ +static int mtk_pinctrl_public_pullup_to_internal(u32 pub, u32 *out) +{ + switch (pub) { + case MTK_PIN_PULLDOWN: + *out = MTK_PULLDOWN; + return 0; + case MTK_PIN_PULLUP: + *out = MTK_PULLUP; + return 0; + case MTK_PIN_BUS_HOLD: + *out = MTK_BUS_HOLD; + return 0; + default: + return -EINVAL; + } +} + +static int mtk_pinctrl_public_arg_to_internal(u32 pub, u32 *out) +{ + switch (pub) { + case MTK_PIN_ENABLE: + *out = MTK_ENABLE; + return 0; + case MTK_PIN_DISABLE: + *out = MTK_DISABLE; + return 0; + default: + return -EINVAL; + } +} + +int mtk_pinctrl_program_bias_by_gpio(unsigned int gpio, u32 pullup, u32 arg) +{ + struct mtk_pinctrl *hw, *owner = NULL; + const struct mtk_pin_desc *desc; + u32 int_pullup, int_arg; + int err; + + err = mtk_pinctrl_public_pullup_to_internal(pullup, &int_pullup); + if (err) + return err; + err = mtk_pinctrl_public_arg_to_internal(arg, &int_arg); + if (err) + return err; + + mutex_lock(&mtk_pinctrl_instances_lock); + + /* + * MediaTek pin numbers are zero-based per controller, so a bare + * pin number cannot distinguish two controllers that both have + * at least @gpio + 1 pins. Every supported platform registers a + * single instance. Rather than silently program the first match, + * refuse when the request is ambiguous. + */ + list_for_each_entry(hw, &mtk_pinctrl_instances, instance_node) { + if (gpio >= hw->soc->npins) + continue; + + if (hw->soc->pins[gpio].number != gpio) + continue; + + if (owner) { + dev_warn_once(hw->dev, + "pin %u also owned by %s, refusing ambiguous bias request\n", + gpio, dev_name(owner->dev)); + err = -EINVAL; + goto out; + } + owner = hw; + } + + if (!owner) { + err = -ENODEV; + goto out; + } + + if (!owner->soc->bias_set_combo) { + err = -EOPNOTSUPP; + goto out; + } + + desc = &owner->soc->pins[gpio]; + err = owner->soc->bias_set_combo(owner, desc, int_pullup, int_arg); + +out: + mutex_unlock(&mtk_pinctrl_instances_lock); + + return err; +} +EXPORT_SYMBOL_GPL(mtk_pinctrl_program_bias_by_gpio); + static int mtk_rsel_get_si_unit(struct mtk_pinctrl *hw, const struct mtk_pin_desc *desc, u32 pullup, u32 rsel_val, u32 *si_unit) @@ -887,6 +1037,13 @@ static int mtk_pinconf_bias_get_pu_pd_rsel(struct mtk_pinctrl *hw, mtk_rsel_get_si_unit(hw, desc, *pullup, rsel, enable); else *enable = rsel + MTK_PULL_SET_RSEL_000; + } else if (pu == 1 && pd == 1) { + /* Keeper (bus-hold) reports pull-up resistance via up-RSEL lookup */ + *pullup = MTK_BUS_HOLD; + if (hw->rsel_si_unit) + mtk_rsel_get_si_unit(hw, desc, *pullup, rsel, enable); + else + *enable = rsel + MTK_PULL_SET_RSEL_000; } else { err = -EINVAL; goto out; @@ -919,8 +1076,12 @@ static int mtk_pinconf_bias_get_pu_pd(struct mtk_pinctrl *hw, } else if (pu == 0 && pd == 1) { *pullup = 0; *enable = MTK_ENABLE; - } else + } else if (pu == 1 && pd == 1) { + *pullup = MTK_BUS_HOLD; + *enable = MTK_ENABLE; + } else { err = -EINVAL; + } out: return err; diff --git a/drivers/pinctrl/mediatek/pinctrl-mtk-common-v2.h b/drivers/pinctrl/mediatek/pinctrl-mtk-common-v2.h index df8dce14744f9..58ce9f771127a 100644 --- a/drivers/pinctrl/mediatek/pinctrl-mtk-common-v2.h +++ b/drivers/pinctrl/mediatek/pinctrl-mtk-common-v2.h @@ -15,6 +15,8 @@ #define MTK_OUTPUT 1 #define MTK_DISABLE 0 #define MTK_ENABLE 1 +#define MTK_BUS_HOLD 2 +/* 0 = pull-down, 1 = pull-up, 2 = bus-hold (keeper: PU and PD both on) */ #define MTK_PULLDOWN 0 #define MTK_PULLUP 1 #define MTK_PULL_PU_PD_TYPE BIT(0) @@ -283,7 +285,9 @@ struct mtk_pin_soc { const struct mtk_pin_desc *desc, u32 arg); int (*adv_drive_get)(struct mtk_pinctrl *hw, const struct mtk_pin_desc *desc, u32 *val); - + /* For wake event */ + const unsigned int *eint_event; + const unsigned int total_wake_eints; /* Specific driver data */ void *driver_data; }; @@ -303,6 +307,8 @@ struct mtk_pinctrl { /* identify rsel setting by si unit or rsel define in dts node */ bool rsel_si_unit; struct pinctrl_gpio_range range; + /* Registry node, see mtk_pinctrl_register_instance in common-v2.c */ + struct list_head instance_node; }; void mtk_rmw(struct mtk_pinctrl *pctl, u8 i, u32 reg, u32 mask, u32 set); @@ -341,6 +347,15 @@ int mtk_pinconf_bias_get_combo(struct mtk_pinctrl *hw, const struct mtk_pin_desc *desc, u32 *pullup, u32 *enable); +/* + * Internal registration hooks for the cross-driver bias helper. + * The public API (mtk_pinctrl_program_bias_by_gpio()) lives in + * . Only mtk_paris_pinctrl_probe() + * should call these. + */ +void mtk_pinctrl_register_instance(struct mtk_pinctrl *hw); +void mtk_pinctrl_unregister_instance(struct mtk_pinctrl *hw); + int mtk_pinconf_drive_set(struct mtk_pinctrl *hw, const struct mtk_pin_desc *desc, u32 arg); int mtk_pinconf_drive_get(struct mtk_pinctrl *hw, diff --git a/drivers/pinctrl/mediatek/pinctrl-paris.c b/drivers/pinctrl/mediatek/pinctrl-paris.c index f74221acba8cb..7ba1a74cd69be 100644 --- a/drivers/pinctrl/mediatek/pinctrl-paris.c +++ b/drivers/pinctrl/mediatek/pinctrl-paris.c @@ -143,6 +143,7 @@ static int mtk_pinconf_get(struct pinctrl_dev *pctldev, case PIN_CONFIG_BIAS_DISABLE: case PIN_CONFIG_BIAS_PULL_UP: case PIN_CONFIG_BIAS_PULL_DOWN: + case PIN_CONFIG_BIAS_BUS_HOLD: if (!hw->soc->bias_get_combo) break; err = hw->soc->bias_get_combo(hw, desc, &pullup, &ret); @@ -154,10 +155,13 @@ static int mtk_pinconf_get(struct pinctrl_dev *pctldev, if (ret != MTK_DISABLE) err = -EINVAL; } else if (param == PIN_CONFIG_BIAS_PULL_UP) { - if (!pullup || ret == MTK_DISABLE) + if (pullup != 1 || ret == MTK_DISABLE) err = -EINVAL; } else if (param == PIN_CONFIG_BIAS_PULL_DOWN) { - if (pullup || ret == MTK_DISABLE) + if (pullup != 0 || ret == MTK_DISABLE) + err = -EINVAL; + } else if (param == PIN_CONFIG_BIAS_BUS_HOLD) { + if (pullup != MTK_BUS_HOLD || ret == MTK_DISABLE) err = -EINVAL; } break; @@ -284,6 +288,17 @@ static int mtk_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin, break; err = hw->soc->bias_set_combo(hw, desc, 0, arg); break; + case PIN_CONFIG_BIAS_BUS_HOLD: + if (!hw->soc->bias_set_combo) + break; + /* + * Generic bias-bus-hold carries no argument (parsed as 0, + * which would read as MTK_DISABLE). Presence of the property + * means enable, so always request MTK_ENABLE here. + */ + err = hw->soc->bias_set_combo(hw, desc, MTK_BUS_HOLD, + MTK_ENABLE); + break; case PIN_CONFIG_INPUT_ENABLE: /* regard all non-zero value as enable */ err = mtk_hw_set_value(hw, desc, PINCTRL_PIN_REG_IES, !!arg); @@ -973,6 +988,15 @@ static int mtk_build_gpiochip(struct mtk_pinctrl *hw) return 0; } +#ifdef CONFIG_GPIO_ACPI +static void mtk_pinctrl_acpi_free_interrupts(void *data) +{ + struct gpio_chip *chip = data; + + acpi_gpiochip_free_interrupts(chip); +} +#endif + static int mtk_pctrl_build_state(struct platform_device *pdev) { struct mtk_pinctrl *hw = platform_get_drvdata(pdev); @@ -1003,6 +1027,11 @@ static int mtk_pctrl_build_state(struct platform_device *pdev) return 0; } +static void mtk_paris_unregister_instance(void *data) +{ + mtk_pinctrl_unregister_instance(data); +} + int mtk_paris_pinctrl_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1094,9 +1123,38 @@ int mtk_paris_pinctrl_probe(struct platform_device *pdev) return dev_err_probe(dev, err, "Failed to add gpio_chip\n"); pinctrl_add_gpio_range(hw->pctrl, &hw->range); +#ifdef CONFIG_GPIO_ACPI + if (hw->eint) { + acpi_gpiochip_request_interrupts(&hw->chip); + + err = devm_add_action_or_reset(dev, + mtk_pinctrl_acpi_free_interrupts, + &hw->chip); + if (err) { + pinctrl_remove_gpio_range(hw->pctrl, &hw->range); + gpiochip_remove(&hw->chip); + return dev_err_probe(dev, err, + "Failed to add ACPI GPIO cleanup\n"); + } + } +#endif platform_set_drvdata(pdev, hw); + /* + * Publish this instance so client drivers (e.g. mtk-soundwire, which + * parses PinFunction() vendor encodings in its own _CRS) can look up + * the owning controller by GPIO number. Tie the un-publish to the + * device lifetime so hw is removed from the global list before devm + * frees it, avoiding a use-after-free in mtk_pinctrl_program_bias_by_gpio(). + */ + mtk_pinctrl_register_instance(hw); + + err = devm_add_action_or_reset(&pdev->dev, + mtk_paris_unregister_instance, hw); + if (err) + return err; + return 0; } EXPORT_SYMBOL_GPL(mtk_paris_pinctrl_probe); diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index fabe2baf7b509..1dafaf2df0d61 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -625,6 +625,11 @@ int devm_gpiochip_add_data_with_key(struct device *dev, struct gpio_chip *gc, void *data, struct lock_class_key *lock_key, struct lock_class_key *request_key); +#ifdef CONFIG_GPIO_ACPI +void acpi_gpiochip_request_interrupts(struct gpio_chip *chip); +void acpi_gpiochip_free_interrupts(struct gpio_chip *chip); +#endif + struct gpio_device *gpio_device_find(const void *data, int (*match)(struct gpio_chip *gc, const void *data)); diff --git a/include/linux/soc/mediatek/mtk-pinctrl.h b/include/linux/soc/mediatek/mtk-pinctrl.h new file mode 100644 index 0000000000000..c5d1e33f62896 --- /dev/null +++ b/include/linux/soc/mediatek/mtk-pinctrl.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026 MediaTek Inc. + * + * Cross-driver interface into MediaTek pinctrl for programming pad + * bias by absolute SoC GPIO number. + * + * Intended for client drivers (for example mtk-soundwire) that consume + * ACPI PinFunction() vendor encodings from their own _CRS and need to + * apply the resulting pad bias without going through the pinctrl-state + * machinery. + */ +#ifndef __LINUX_SOC_MEDIATEK_MTK_PINCTRL_H +#define __LINUX_SOC_MEDIATEK_MTK_PINCTRL_H + +#include + +/* + * Vendor-specific pin configuration values carried in the ACPI + * PinFunction() descriptor: + * + * 0x90 -> PD=1, PU=0 (plain pull-down) + * 0xF0 -> PD=1, PU=1 (bus-keeper) + * + * The Audio.asl entries look like: + * PinFunction(Exclusive, 0x90, 1, "\_SB.GIO0", 0, ..., ...) { 66 } + * // SOUNDWIRE0_CLK, PD=1, PU=0 + * PinFunction(Exclusive, 0xF0, 1, "\_SB.GIO0", 0, ..., ...) { 67 } + * // SOUNDWIRE0_DAT0, PD=1, PU=1 + */ +#define MTK_SDW_PIN_CFG_PD 0x90 +#define MTK_SDW_PIN_CFG_BUS_HOLD 0xF0 + +/* Pull-mode values accepted by mtk_pinctrl_program_bias_by_gpio(). */ +#define MTK_PIN_PULLDOWN 0 +#define MTK_PIN_PULLUP 1 +#define MTK_PIN_BUS_HOLD 2 + +/* Enable/disable values accepted by mtk_pinctrl_program_bias_by_gpio(). */ +#define MTK_PIN_DISABLE 0 +#define MTK_PIN_ENABLE 1 + +/** + * mtk_pinctrl_program_bias_by_gpio - program pad bias by absolute GPIO + * @gpio: absolute SoC pin number (matches the ACPI PinFunction + * pin_table entry). + * @pullup: one of MTK_PIN_PULLDOWN, MTK_PIN_PULLUP, MTK_PIN_BUS_HOLD. + * @arg: MTK_PIN_ENABLE to apply, MTK_PIN_DISABLE to release. + * + * Returns 0 on success, -EINVAL if @pullup or @arg is not one of the + * values listed above or if more than one registered mtk_pinctrl owns + * @gpio, -ENODEV if no registered mtk_pinctrl owns @gpio, + * -EOPNOTSUPP if the owning controller has no bias_set_combo, or a + * negative error code from the underlying bias operation. + */ +int mtk_pinctrl_program_bias_by_gpio(unsigned int gpio, u32 pullup, u32 arg); + +#endif /* __LINUX_SOC_MEDIATEK_MTK_PINCTRL_H */