1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright 2021~2022 NXP |
4 | * |
5 | * The driver exports a standard gpiochip interface |
6 | * to control the PIN resources on SCU domain. |
7 | */ |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/module.h> |
11 | #include <linux/gpio/driver.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/firmware/imx/svc/rm.h> |
14 | #include <dt-bindings/firmware/imx/rsrc.h> |
15 | |
16 | struct scu_gpio_priv { |
17 | struct gpio_chip chip; |
18 | struct mutex lock; |
19 | struct device *dev; |
20 | struct imx_sc_ipc *handle; |
21 | }; |
22 | |
23 | static unsigned int scu_rsrc_arr[] = { |
24 | IMX_SC_R_BOARD_R0, |
25 | IMX_SC_R_BOARD_R1, |
26 | IMX_SC_R_BOARD_R2, |
27 | IMX_SC_R_BOARD_R3, |
28 | IMX_SC_R_BOARD_R4, |
29 | IMX_SC_R_BOARD_R5, |
30 | IMX_SC_R_BOARD_R6, |
31 | IMX_SC_R_BOARD_R7, |
32 | }; |
33 | |
34 | static int imx_scu_gpio_get(struct gpio_chip *chip, unsigned int offset) |
35 | { |
36 | struct scu_gpio_priv *priv = gpiochip_get_data(gc: chip); |
37 | int level; |
38 | int err; |
39 | |
40 | if (offset >= chip->ngpio) |
41 | return -EINVAL; |
42 | |
43 | mutex_lock(&priv->lock); |
44 | |
45 | /* to read PIN state via scu api */ |
46 | err = imx_sc_misc_get_control(ipc: priv->handle, |
47 | resource: scu_rsrc_arr[offset], ctrl: 0, val: &level); |
48 | mutex_unlock(lock: &priv->lock); |
49 | |
50 | if (err) { |
51 | dev_err(priv->dev, "SCU get failed: %d\n" , err); |
52 | return err; |
53 | } |
54 | |
55 | return level; |
56 | } |
57 | |
58 | static void imx_scu_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) |
59 | { |
60 | struct scu_gpio_priv *priv = gpiochip_get_data(gc: chip); |
61 | int err; |
62 | |
63 | if (offset >= chip->ngpio) |
64 | return; |
65 | |
66 | mutex_lock(&priv->lock); |
67 | |
68 | /* to set PIN output level via scu api */ |
69 | err = imx_sc_misc_set_control(ipc: priv->handle, |
70 | resource: scu_rsrc_arr[offset], ctrl: 0, val: value); |
71 | mutex_unlock(lock: &priv->lock); |
72 | |
73 | if (err) |
74 | dev_err(priv->dev, "SCU set (%d) failed: %d\n" , |
75 | scu_rsrc_arr[offset], err); |
76 | } |
77 | |
78 | static int imx_scu_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) |
79 | { |
80 | if (offset >= chip->ngpio) |
81 | return -EINVAL; |
82 | |
83 | return GPIO_LINE_DIRECTION_OUT; |
84 | } |
85 | |
86 | static int imx_scu_gpio_probe(struct platform_device *pdev) |
87 | { |
88 | struct device *dev = &pdev->dev; |
89 | struct scu_gpio_priv *priv; |
90 | struct gpio_chip *gc; |
91 | int ret; |
92 | |
93 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
94 | if (!priv) |
95 | return -ENOMEM; |
96 | |
97 | ret = imx_scu_get_handle(ipc: &priv->handle); |
98 | if (ret) |
99 | return ret; |
100 | |
101 | priv->dev = dev; |
102 | mutex_init(&priv->lock); |
103 | |
104 | gc = &priv->chip; |
105 | gc->base = -1; |
106 | gc->parent = dev; |
107 | gc->ngpio = ARRAY_SIZE(scu_rsrc_arr); |
108 | gc->label = dev_name(dev); |
109 | gc->get = imx_scu_gpio_get; |
110 | gc->set = imx_scu_gpio_set; |
111 | gc->get_direction = imx_scu_gpio_get_direction; |
112 | |
113 | platform_set_drvdata(pdev, data: priv); |
114 | |
115 | return devm_gpiochip_add_data(dev, gc, priv); |
116 | } |
117 | |
118 | static const struct of_device_id imx_scu_gpio_dt_ids[] = { |
119 | { .compatible = "fsl,imx8qxp-sc-gpio" }, |
120 | { /* sentinel */ } |
121 | }; |
122 | |
123 | static struct platform_driver imx_scu_gpio_driver = { |
124 | .driver = { |
125 | .name = "gpio-imx-scu" , |
126 | .of_match_table = imx_scu_gpio_dt_ids, |
127 | }, |
128 | .probe = imx_scu_gpio_probe, |
129 | }; |
130 | |
131 | static int __init _imx_scu_gpio_init(void) |
132 | { |
133 | return platform_driver_register(&imx_scu_gpio_driver); |
134 | } |
135 | |
136 | subsys_initcall_sync(_imx_scu_gpio_init); |
137 | |
138 | MODULE_AUTHOR("Shenwei Wang <shenwei.wang@nxp.com>" ); |
139 | MODULE_DESCRIPTION("NXP GPIO over IMX SCU API" ); |
140 | |