[PATCH 2/2] pinctrl: spacemit: support I/O power domain configuration
Yixun Lan
dlan at gentoo.org
Tue Dec 23 01:32:28 PST 2025
Hi Troy,
On 17:11 Tue 23 Dec , Troy Mitchell wrote:
> IO domain power control registers are used to configure the operating
> voltage of dual-voltage GPIO banks. By default, these registers are
> configured for 3.3V operation. As a result, even when a GPIO bank is
> externally supplied with 1.8V, the internal logic continues to
> operate in the 3.3V domain, which may lead to functional failures.
>
> This patch adds support for programming the IO domain power control
> registers, allowing dual-voltage GPIO banks to be explicitly configured
> for 1.8V operation when required.
>
> Care must be taken when configuring these registers. If a GPIO bank is
> externally supplied with 3.3V while the corresponding IO power domain
> is configured for 1.8V, external current injection (back-powering)
> may occur, potentially causing damage to the GPIO pin.
>
> Due to these hardware constraints and safety considerations, the IO
> domain power control registers are implemented as secure registers.
> Access to these registers requires unlocking via the AIB Secure Access
> Register (ASAR) in the APBC block before a single read or write
> operation can be performed.
>
> Signed-off-by: Troy Mitchell <troy.mitchell at linux.spacemit.com>
> ---
> arch/riscv/boot/dts/spacemit/k1.dtsi | 4 +-
> drivers/pinctrl/spacemit/pinctrl-k1.c | 131 +++++++++++++++++++++++++++++++++-
> 2 files changed, 131 insertions(+), 4 deletions(-)
>
> diff --git a/arch/riscv/boot/dts/spacemit/k1.dtsi b/arch/riscv/boot/dts/spacemit/k1.dtsi
> index 7818ca4979b6a7755722919a5958512aa11950ab..23ecb19624f227f3c39de35bf3078379f7a2490e 100644
> --- a/arch/riscv/boot/dts/spacemit/k1.dtsi
> +++ b/arch/riscv/boot/dts/spacemit/k1.dtsi
dtsi should go as separated patch, then route to SoC tree
> @@ -565,10 +565,12 @@ i2c8: i2c at d401d800 {
>
> pinctrl: pinctrl at d401e000 {
> compatible = "spacemit,k1-pinctrl";
> - reg = <0x0 0xd401e000 0x0 0x400>;
> + reg = <0x0 0xd401e000 0x0 0x400>,
> + <0x0 0xd401e800 0x0 0x34>;
> clocks = <&syscon_apbc CLK_AIB>,
> <&syscon_apbc CLK_AIB_BUS>;
> clock-names = "func", "bus";
> + spacemit,apbc = <&syscon_apbc 0x50>;
> };
>
> pwm8: pwm at d4020000 {
> diff --git a/drivers/pinctrl/spacemit/pinctrl-k1.c b/drivers/pinctrl/spacemit/pinctrl-k1.c
> index 8ca247fb8ba0321c02423f9739130e03277d1053..b3ffb32f88a79ebf6b64e62a7846df60b92799fe 100644
> --- a/drivers/pinctrl/spacemit/pinctrl-k1.c
> +++ b/drivers/pinctrl/spacemit/pinctrl-k1.c
> @@ -7,8 +7,10 @@
> #include <linux/io.h>
> #include <linux/of.h>
> #include <linux/platform_device.h>
> +#include <linux/regmap.h>
> #include <linux/seq_file.h>
> #include <linux/spinlock.h>
> +#include <linux/mfd/syscon.h>
> #include <linux/module.h>
> #include <linux/mutex.h>
>
> @@ -47,6 +49,25 @@
> #define PAD_PULLUP BIT(14)
> #define PAD_PULL_EN BIT(15)
>
> +#define IO_PWR_DOMAIN_GPIO2_Kx 0x0c
> +#define IO_PWR_DOMAIN_MMC_Kx 0x1c
> +
> +#define IO_PWR_DOMAIN_GPIO3_K1 0x10
> +#define IO_PWR_DOMAIN_QSPI_K1 0x20
> +
> +#define IO_PWR_DOMAIN_GPIO1_K3 0x04
> +#define IO_PWR_DOMAIN_GPIO5_K3 0x10
> +#define IO_PWR_DOMAIN_GPIO4_K3 0x20
> +#define IO_PWR_DOMAIN_QSPI_K3 0x2c
> +
> +#define IO_PWR_DOMAIN_V18EN BIT(2)
> +
> +#define APBC_ASFAR 0x00
> +#define APBC_ASSAR 0x04
> +
> +#define APBC_ASFAR_AKEY 0xbaba
> +#define APBC_ASSAR_AKEY 0xeb10
> +
> struct spacemit_pin_drv_strength {
> u8 val;
> u32 mA;
> @@ -78,6 +99,10 @@ struct spacemit_pinctrl {
> raw_spinlock_t lock;
>
> void __iomem *regs;
> + void __iomem *io_pd_reg;
> +
> + struct regmap *regmap_apbc;
> + u32 regmap_apbc_offset;
> };
>
> struct spacemit_pinctrl_data {
> @@ -85,6 +110,7 @@ struct spacemit_pinctrl_data {
> const struct spacemit_pin *data;
> u16 npins;
> unsigned int (*pin_to_offset)(unsigned int pin);
> + unsigned int (*pin_to_io_pd_offset)(unsigned int pin);
> const struct spacemit_pinctrl_dconf *dconf;
> };
>
> @@ -146,6 +172,56 @@ static unsigned int spacemit_k3_pin_to_offset(unsigned int pin)
> return offset << 2;
> }
>
> +static unsigned int spacemit_k1_pin_to_io_pd_offset(unsigned int pin)
> +{
> + unsigned int offset = 0;
> +
> + switch (pin) {
> + case 47 ... 52:
> + offset = IO_PWR_DOMAIN_GPIO3_K1;
> + break;
> + case 75 ... 80:
> + offset = IO_PWR_DOMAIN_GPIO2_Kx;
> + break;
> + case 98 ... 103:
> + offset = IO_PWR_DOMAIN_QSPI_K1;
> + break;
> + case 104 ... 109:
> + offset = IO_PWR_DOMAIN_MMC_Kx;
> + break;
> + }
> +
> + return offset;
> +}
> +
> +static unsigned int spacemit_k3_pin_to_io_pd_offset(unsigned int pin)
> +{
> + unsigned int offset = 0;
> +
> + switch (pin) {
> + case 0 ... 20:
> + offset = IO_PWR_DOMAIN_GPIO1_K3;
> + break;
> + case 21 ... 41:
> + offset = IO_PWR_DOMAIN_GPIO2_Kx;
> + break;
> + case 76 ... 98:
> + offset = IO_PWR_DOMAIN_GPIO4_K3;
> + break;
> + case 99 ... 127:
> + offset = IO_PWR_DOMAIN_GPIO5_K3;
> + break;
> + case 132 ... 137:
> + offset = IO_PWR_DOMAIN_MMC_Kx;
> + break;
> + case 138 ... 144:
> + offset = IO_PWR_DOMAIN_QSPI_K3;
> + break;
> + }
> +
> + return offset;
> +}
> +
> static inline void __iomem *spacemit_pin_to_reg(struct spacemit_pinctrl *pctrl,
> unsigned int pin)
> {
> @@ -365,6 +441,38 @@ static int spacemit_pctrl_check_power(struct pinctrl_dev *pctldev,
> return 0;
> }
>
> +static void spacemit_set_io_pwr_domain(struct spacemit_pinctrl *pctrl,
> + const struct spacemit_pin *spin,
> + const enum spacemit_pin_io_type type)
> +{
> + u32 offset = pctrl->data->pin_to_io_pd_offset(spin->pin);
> + u32 val = 0;
> +
> + /* Other bits are reserved so don't need to save them */
> + if (type == IO_TYPE_1V8)
> + val = IO_PWR_DOMAIN_V18EN;
> +
> + /*
> + * IO power domain registers are protected and cannot be accessed
> + * directly. Before performing any read or write to the IO power
> + * domain registers, an explicit unlock sequence must be issued
> + * via the AIB Secure Access Register (ASAR).
> + *
> + * The unlock sequence allows exactly one subsequent access to the
> + * IO power domain registers. After that access completes, the ASAR
> + * keys are automatically cleared, and the registers become locked
> + * again.
> + *
> + * This mechanism ensures that IO power domain configuration is
> + * performed intentionally, as incorrect voltage settings may
> + * result in functional failures or hardware damage.
> + */
> + regmap_write(pctrl->regmap_apbc, pctrl->regmap_apbc_offset + APBC_ASFAR, APBC_ASFAR_AKEY);
> + regmap_write(pctrl->regmap_apbc, pctrl->regmap_apbc_offset + APBC_ASSAR, APBC_ASSAR_AKEY);
> +
> + writel_relaxed(val, pctrl->io_pd_reg + offset);
> +}
> +
> static int spacemit_pctrl_dt_node_to_map(struct pinctrl_dev *pctldev,
> struct device_node *np,
> struct pinctrl_map **maps,
> @@ -572,7 +680,8 @@ static int spacemit_pinconf_get(struct pinctrl_dev *pctldev,
>
> #define ENABLE_DRV_STRENGTH BIT(1)
> #define ENABLE_SLEW_RATE BIT(2)
> -static int spacemit_pinconf_generate_config(const struct spacemit_pin *spin,
> +static int spacemit_pinconf_generate_config(struct spacemit_pinctrl *pctrl,
> + const struct spacemit_pin *spin,
> const struct spacemit_pinctrl_dconf *dconf,
> unsigned long *configs,
> unsigned int num_configs,
> @@ -646,6 +755,7 @@ static int spacemit_pinconf_generate_config(const struct spacemit_pin *spin,
> default:
> return -EINVAL;
> }
> + spacemit_set_io_pwr_domain(pctrl, spin, type);
> }
>
> val = spacemit_get_driver_strength(type, dconf, drv_strength);
> @@ -701,7 +811,7 @@ static int spacemit_pinconf_set(struct pinctrl_dev *pctldev,
> const struct spacemit_pin *spin = spacemit_get_pin(pctrl, pin);
> u32 value;
>
> - if (spacemit_pinconf_generate_config(spin, pctrl->data->dconf,
> + if (spacemit_pinconf_generate_config(pctrl, spin, pctrl->data->dconf,
> configs, num_configs, &value))
> return -EINVAL;
>
> @@ -724,7 +834,7 @@ static int spacemit_pinconf_group_set(struct pinctrl_dev *pctldev,
> return -EINVAL;
>
> spin = spacemit_get_pin(pctrl, group->grp.pins[0]);
> - if (spacemit_pinconf_generate_config(spin, pctrl->data->dconf,
> + if (spacemit_pinconf_generate_config(pctrl, spin, pctrl->data->dconf,
> configs, num_configs, &value))
> return -EINVAL;
>
> @@ -795,6 +905,7 @@ static const struct pinconf_ops spacemit_pinconf_ops = {
>
> static int spacemit_pinctrl_probe(struct platform_device *pdev)
> {
> + struct device_node *np = pdev->dev.of_node;
> struct device *dev = &pdev->dev;
> struct spacemit_pinctrl *pctrl;
> struct clk *func_clk, *bus_clk;
> @@ -816,6 +927,18 @@ static int spacemit_pinctrl_probe(struct platform_device *pdev)
> if (IS_ERR(pctrl->regs))
> return PTR_ERR(pctrl->regs);
>
> + pctrl->io_pd_reg = devm_platform_ioremap_resource(pdev, 1);
> + if (IS_ERR(pctrl->io_pd_reg))
> + return PTR_ERR(pctrl->io_pd_reg);
> +
> + pctrl->regmap_apbc =
> + syscon_regmap_lookup_by_phandle_args(np, "spacemit,apbc", 1,
> + &pctrl->regmap_apbc_offset);
Can you simply use syscon_regmap_lookup_by_phandle(), then define
#define APBC_ASFAR 0x50
#define APBC_ASSAR 0x54
> +
> + if (IS_ERR(pctrl->regmap_apbc))
> + return dev_err_probe(dev, PTR_ERR(pctrl->regmap_apbc),
> + "failed to get syscon\n");
> +
> func_clk = devm_clk_get_enabled(dev, "func");
> if (IS_ERR(func_clk))
> return dev_err_probe(dev, PTR_ERR(func_clk), "failed to get func clock\n");
> @@ -1118,6 +1241,7 @@ static const struct spacemit_pinctrl_data k1_pinctrl_data = {
> .data = k1_pin_data,
> .npins = ARRAY_SIZE(k1_pin_desc),
> .pin_to_offset = spacemit_k1_pin_to_offset,
> + .pin_to_io_pd_offset = spacemit_k1_pin_to_io_pd_offset,
> .dconf = &k1_drive_conf,
> };
>
> @@ -1455,6 +1579,7 @@ static const struct spacemit_pinctrl_data k3_pinctrl_data = {
> .data = k3_pin_data,
> .npins = ARRAY_SIZE(k3_pin_desc),
> .pin_to_offset = spacemit_k3_pin_to_offset,
> + .pin_to_io_pd_offset = spacemit_k3_pin_to_io_pd_offset,
> .dconf = &k3_drive_conf,
> };
>
>
> --
> 2.52.0
>
--
Yixun Lan (dlan)
More information about the linux-riscv
mailing list