| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright 2025 NXP |
| */ |
| |
| #include <asm/io.h> |
| #include <asm/mach-imx/sys_proto.h> |
| #include <dm.h> |
| #include <dm/device_compat.h> |
| #include <dm/devres.h> |
| #include <dm/pinctrl.h> |
| #include <scmi_agent.h> |
| #include <scmi_agent-uclass.h> |
| #include <scmi_protocols.h> |
| |
| #include "pinctrl-imx.h" |
| |
| #define DAISY_OFFSET_IMX95 0x408 |
| |
| /* SCMI pin control types */ |
| #define PINCTRL_TYPE_MUX 192 |
| #define PINCTRL_TYPE_CONFIG 193 |
| #define PINCTRL_TYPE_DAISY_ID 194 |
| #define PINCTRL_TYPE_DAISY_CFG 195 |
| #define PINCTRL_NUM_CFGS_SHIFT 2 |
| |
| struct imx_scmi_pinctrl_priv { |
| u16 daisy_offset; |
| }; |
| |
| static int imx_pinconf_scmi_set(struct udevice *dev, u32 mux_ofs, u32 mux, u32 config_val, |
| u32 input_ofs, u32 input_val) |
| { |
| struct imx_scmi_pinctrl_priv *priv = dev_get_priv(dev); |
| int ret, num_cfgs = 0; |
| struct scmi_msg msg; |
| |
| /* Call SCMI API to set the pin mux and configuration. */ |
| struct scmi_pinctrl_config_set_out out; |
| struct scmi_pinctrl_config_set_in in = { |
| .identifier = mux_ofs / 4, |
| .function_id = 0xFFFFFFFF, |
| .attributes = 0, |
| }; |
| |
| if (mux_ofs) { |
| in.configs[num_cfgs].type = PINCTRL_TYPE_MUX; |
| in.configs[num_cfgs].val = mux; |
| num_cfgs++; |
| } |
| |
| if (config_val) { |
| in.configs[num_cfgs].type = PINCTRL_TYPE_CONFIG; |
| in.configs[num_cfgs].val = config_val; |
| num_cfgs++; |
| } |
| |
| if (input_ofs) { |
| in.configs[num_cfgs].type = PINCTRL_TYPE_DAISY_ID; |
| in.configs[num_cfgs].val = (input_ofs - priv->daisy_offset) / 4; |
| num_cfgs++; |
| in.configs[num_cfgs].type = PINCTRL_TYPE_DAISY_CFG; |
| in.configs[num_cfgs].val = input_val; |
| num_cfgs++; |
| } |
| |
| /* Update the number of configs sent in this call. */ |
| in.attributes = num_cfgs << PINCTRL_NUM_CFGS_SHIFT; |
| |
| msg = SCMI_MSG_IN(SCMI_PROTOCOL_ID_PINCTRL, |
| SCMI_MSG_PINCTRL_CONFIG_SET, in, out); |
| |
| ret = devm_scmi_process_msg(dev, &msg); |
| if (ret || out.status) { |
| dev_err(dev, "Failed to set PAD = %d, daisy = %d, scmi_err = %d, ret = %d\n", |
| mux_ofs / 4, input_ofs / 4, out.status, ret); |
| } |
| |
| return ret; |
| } |
| |
| static int imx_pinctrl_set_state_scmi(struct udevice *dev, struct udevice *config) |
| { |
| int mux_ofs, mux, config_val, input_reg, input_val; |
| u32 *pin_data; |
| int i, j = 0; |
| int npins; |
| int ret; |
| |
| ret = imx_pinctrl_set_state_common(dev, config, FSL_PIN_SIZE, |
| &pin_data, &npins); |
| if (ret) |
| return ret; |
| |
| /* |
| * Refer to linux documentation for details: |
| * Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt |
| */ |
| for (i = 0; i < npins; i++) { |
| mux_ofs = pin_data[j++]; |
| /* Skip config_reg */ |
| j++; |
| input_reg = pin_data[j++]; |
| |
| mux = pin_data[j++]; |
| input_val = pin_data[j++]; |
| config_val = pin_data[j++]; |
| |
| if (config_val & IMX_PAD_SION) |
| mux |= IOMUXC_CONFIG_SION; |
| |
| config_val &= ~IMX_PAD_SION; |
| |
| ret = imx_pinconf_scmi_set(dev, mux_ofs, mux, config_val, input_reg, input_val); |
| if (ret && ret != -EPERM) { |
| dev_err(dev, "Set pin %d, mux %d, val %d, error\n", |
| mux_ofs, mux, config_val); |
| } |
| } |
| |
| devm_kfree(dev, pin_data); |
| |
| return ret; |
| } |
| |
| static const struct pinctrl_ops imx_scmi_pinctrl_ops = { |
| .set_state = imx_pinctrl_set_state_scmi, |
| }; |
| |
| static int imx_scmi_pinctrl_probe(struct udevice *dev) |
| { |
| struct imx_scmi_pinctrl_priv *priv = dev_get_priv(dev); |
| |
| if (IS_ENABLED(CONFIG_IMX95)) |
| priv->daisy_offset = DAISY_OFFSET_IMX95; |
| else |
| return -EINVAL; |
| |
| return devm_scmi_of_get_channel(dev); |
| } |
| |
| static int imx_scmi_pinctrl_bind(struct udevice *dev) |
| { |
| if (IS_ENABLED(CONFIG_IMX95)) |
| return 0; |
| |
| return -ENODEV; |
| } |
| |
| U_BOOT_DRIVER(scmi_pinctrl_imx) = { |
| .name = "scmi_pinctrl_imx", |
| .id = UCLASS_PINCTRL, |
| .bind = imx_scmi_pinctrl_bind, |
| .probe = imx_scmi_pinctrl_probe, |
| .priv_auto = sizeof(struct imx_scmi_pinctrl_priv), |
| .ops = &imx_scmi_pinctrl_ops, |
| .flags = DM_FLAG_PRE_RELOC, |
| }; |
| |
| static struct scmi_proto_match match[] = { |
| { .proto_id = SCMI_PROTOCOL_ID_PINCTRL }, |
| { /* Sentinel */ } |
| }; |
| |
| U_BOOT_SCMI_PROTO_DRIVER(scmi_pinctrl_imx, match); |