1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2019 Nuvoton Technology corporation |
3 | |
4 | #include <linux/bitfield.h> |
5 | #include <linux/clk.h> |
6 | #include <linux/interrupt.h> |
7 | #include <linux/jiffies.h> |
8 | #include <linux/module.h> |
9 | #include <linux/of.h> |
10 | #include <linux/peci.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/regmap.h> |
13 | #include <linux/reset.h> |
14 | |
15 | /* NPCM GCR module */ |
16 | #define NPCM_INTCR3_OFFSET 0x9C |
17 | #define NPCM_INTCR3_PECIVSEL BIT(19) |
18 | |
19 | /* NPCM PECI Registers */ |
20 | #define NPCM_PECI_CTL_STS 0x00 |
21 | #define NPCM_PECI_RD_LENGTH 0x04 |
22 | #define NPCM_PECI_ADDR 0x08 |
23 | #define NPCM_PECI_CMD 0x0C |
24 | #define NPCM_PECI_CTL2 0x10 |
25 | #define NPCM_PECI_WR_LENGTH 0x1C |
26 | #define NPCM_PECI_PDDR 0x2C |
27 | #define NPCM_PECI_DAT_INOUT(n) (0x100 + ((n) * 4)) |
28 | |
29 | #define NPCM_PECI_MAX_REG 0x200 |
30 | |
31 | /* NPCM_PECI_CTL_STS - 0x00 : Control Register */ |
32 | #define NPCM_PECI_CTRL_DONE_INT_EN BIT(6) |
33 | #define NPCM_PECI_CTRL_ABRT_ERR BIT(4) |
34 | #define NPCM_PECI_CTRL_CRC_ERR BIT(3) |
35 | #define NPCM_PECI_CTRL_DONE BIT(1) |
36 | #define NPCM_PECI_CTRL_START_BUSY BIT(0) |
37 | |
38 | /* NPCM_PECI_RD_LENGTH - 0x04 : Command Register */ |
39 | #define NPCM_PECI_RD_LEN_MASK GENMASK(6, 0) |
40 | |
41 | /* NPCM_PECI_CMD - 0x10 : Command Register */ |
42 | #define NPCM_PECI_CTL2_MASK GENMASK(7, 6) |
43 | |
44 | /* NPCM_PECI_WR_LENGTH - 0x1C : Command Register */ |
45 | #define NPCM_PECI_WR_LEN_MASK GENMASK(6, 0) |
46 | |
47 | /* NPCM_PECI_PDDR - 0x2C : Command Register */ |
48 | #define NPCM_PECI_PDDR_MASK GENMASK(4, 0) |
49 | |
50 | #define NPCM_PECI_INT_MASK (NPCM_PECI_CTRL_ABRT_ERR | \ |
51 | NPCM_PECI_CTRL_CRC_ERR | \ |
52 | NPCM_PECI_CTRL_DONE) |
53 | |
54 | #define NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC (50 * USEC_PER_MSEC) |
55 | #define NPCM_PECI_IDLE_CHECK_INTERVAL_USEC (10 * USEC_PER_MSEC) |
56 | #define NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT 1000 |
57 | #define NPCM_PECI_CMD_TIMEOUT_MS_MAX 60000 |
58 | #define NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT 15 |
59 | #define NPCM_PECI_PULL_DOWN_DEFAULT 0 |
60 | |
61 | struct npcm_peci { |
62 | u32 cmd_timeout_ms; |
63 | struct completion xfer_complete; |
64 | struct regmap *regmap; |
65 | u32 status; |
66 | spinlock_t lock; /* to sync completion status handling */ |
67 | struct peci_controller *controller; |
68 | struct device *dev; |
69 | struct clk *clk; |
70 | int irq; |
71 | }; |
72 | |
73 | static int npcm_peci_xfer(struct peci_controller *controller, u8 addr, struct peci_request *req) |
74 | { |
75 | struct npcm_peci *priv = dev_get_drvdata(dev: controller->dev.parent); |
76 | unsigned long timeout = msecs_to_jiffies(m: priv->cmd_timeout_ms); |
77 | unsigned int msg_rd; |
78 | u32 cmd_sts; |
79 | int i, ret; |
80 | |
81 | /* Check command sts and bus idle state */ |
82 | ret = regmap_read_poll_timeout(priv->regmap, NPCM_PECI_CTL_STS, cmd_sts, |
83 | !(cmd_sts & NPCM_PECI_CTRL_START_BUSY), |
84 | NPCM_PECI_IDLE_CHECK_INTERVAL_USEC, |
85 | NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC); |
86 | if (ret) |
87 | return ret; /* -ETIMEDOUT */ |
88 | |
89 | spin_lock_irq(lock: &priv->lock); |
90 | reinit_completion(x: &priv->xfer_complete); |
91 | |
92 | regmap_write(map: priv->regmap, NPCM_PECI_ADDR, val: addr); |
93 | regmap_write(map: priv->regmap, NPCM_PECI_RD_LENGTH, NPCM_PECI_WR_LEN_MASK & req->rx.len); |
94 | regmap_write(map: priv->regmap, NPCM_PECI_WR_LENGTH, NPCM_PECI_WR_LEN_MASK & req->tx.len); |
95 | |
96 | if (req->tx.len) { |
97 | regmap_write(map: priv->regmap, NPCM_PECI_CMD, val: req->tx.buf[0]); |
98 | |
99 | for (i = 0; i < (req->tx.len - 1); i++) |
100 | regmap_write(map: priv->regmap, NPCM_PECI_DAT_INOUT(i), val: req->tx.buf[i + 1]); |
101 | } |
102 | |
103 | #if IS_ENABLED(CONFIG_DYNAMIC_DEBUG) |
104 | dev_dbg(priv->dev, "addr : %#02x, tx.len : %#02x, rx.len : %#02x\n" , |
105 | addr, req->tx.len, req->rx.len); |
106 | print_hex_dump_bytes("TX : " , DUMP_PREFIX_NONE, req->tx.buf, req->tx.len); |
107 | #endif |
108 | |
109 | priv->status = 0; |
110 | regmap_update_bits(map: priv->regmap, NPCM_PECI_CTL_STS, NPCM_PECI_CTRL_START_BUSY, |
111 | NPCM_PECI_CTRL_START_BUSY); |
112 | |
113 | spin_unlock_irq(lock: &priv->lock); |
114 | |
115 | ret = wait_for_completion_interruptible_timeout(x: &priv->xfer_complete, timeout); |
116 | if (ret < 0) |
117 | return ret; |
118 | |
119 | if (ret == 0) { |
120 | dev_dbg(priv->dev, "timeout waiting for a response\n" ); |
121 | return -ETIMEDOUT; |
122 | } |
123 | |
124 | spin_lock_irq(lock: &priv->lock); |
125 | |
126 | if (priv->status != NPCM_PECI_CTRL_DONE) { |
127 | spin_unlock_irq(lock: &priv->lock); |
128 | dev_dbg(priv->dev, "no valid response, status: %#02x\n" , priv->status); |
129 | return -EIO; |
130 | } |
131 | |
132 | regmap_write(map: priv->regmap, NPCM_PECI_CMD, val: 0); |
133 | |
134 | for (i = 0; i < req->rx.len; i++) { |
135 | regmap_read(map: priv->regmap, NPCM_PECI_DAT_INOUT(i), val: &msg_rd); |
136 | req->rx.buf[i] = (u8)msg_rd; |
137 | } |
138 | |
139 | spin_unlock_irq(lock: &priv->lock); |
140 | |
141 | #if IS_ENABLED(CONFIG_DYNAMIC_DEBUG) |
142 | print_hex_dump_bytes("RX : " , DUMP_PREFIX_NONE, req->rx.buf, req->rx.len); |
143 | #endif |
144 | return 0; |
145 | } |
146 | |
147 | static irqreturn_t npcm_peci_irq_handler(int irq, void *arg) |
148 | { |
149 | struct npcm_peci *priv = arg; |
150 | u32 status_ack = 0; |
151 | u32 status; |
152 | |
153 | spin_lock(lock: &priv->lock); |
154 | regmap_read(map: priv->regmap, NPCM_PECI_CTL_STS, val: &status); |
155 | priv->status |= (status & NPCM_PECI_INT_MASK); |
156 | |
157 | if (status & NPCM_PECI_CTRL_CRC_ERR) |
158 | status_ack |= NPCM_PECI_CTRL_CRC_ERR; |
159 | |
160 | if (status & NPCM_PECI_CTRL_ABRT_ERR) |
161 | status_ack |= NPCM_PECI_CTRL_ABRT_ERR; |
162 | |
163 | /* |
164 | * All commands should be ended up with a NPCM_PECI_CTRL_DONE |
165 | * bit set even in an error case. |
166 | */ |
167 | if (status & NPCM_PECI_CTRL_DONE) { |
168 | status_ack |= NPCM_PECI_CTRL_DONE; |
169 | complete(&priv->xfer_complete); |
170 | } |
171 | |
172 | regmap_write_bits(map: priv->regmap, NPCM_PECI_CTL_STS, NPCM_PECI_INT_MASK, val: status_ack); |
173 | |
174 | spin_unlock(lock: &priv->lock); |
175 | return IRQ_HANDLED; |
176 | } |
177 | |
178 | static int npcm_peci_init_ctrl(struct npcm_peci *priv) |
179 | { |
180 | u32 cmd_sts; |
181 | int ret; |
182 | |
183 | priv->clk = devm_clk_get_enabled(dev: priv->dev, NULL); |
184 | if (IS_ERR(ptr: priv->clk)) { |
185 | dev_err(priv->dev, "failed to get ref clock\n" ); |
186 | return PTR_ERR(ptr: priv->clk); |
187 | } |
188 | |
189 | ret = device_property_read_u32(dev: priv->dev, propname: "cmd-timeout-ms" , val: &priv->cmd_timeout_ms); |
190 | if (ret) { |
191 | priv->cmd_timeout_ms = NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT; |
192 | } else if (priv->cmd_timeout_ms > NPCM_PECI_CMD_TIMEOUT_MS_MAX || |
193 | priv->cmd_timeout_ms == 0) { |
194 | dev_warn(priv->dev, "invalid cmd-timeout-ms: %u, falling back to: %u\n" , |
195 | priv->cmd_timeout_ms, NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT); |
196 | |
197 | priv->cmd_timeout_ms = NPCM_PECI_CMD_TIMEOUT_MS_DEFAULT; |
198 | } |
199 | |
200 | regmap_update_bits(map: priv->regmap, NPCM_PECI_CTL2, NPCM_PECI_CTL2_MASK, |
201 | NPCM_PECI_PULL_DOWN_DEFAULT << 6); |
202 | |
203 | regmap_update_bits(map: priv->regmap, NPCM_PECI_PDDR, NPCM_PECI_PDDR_MASK, |
204 | NPCM_PECI_HOST_NEG_BIT_RATE_DEFAULT); |
205 | |
206 | ret = regmap_read_poll_timeout(priv->regmap, NPCM_PECI_CTL_STS, cmd_sts, |
207 | !(cmd_sts & NPCM_PECI_CTRL_START_BUSY), |
208 | NPCM_PECI_IDLE_CHECK_INTERVAL_USEC, |
209 | NPCM_PECI_IDLE_CHECK_TIMEOUT_USEC); |
210 | if (ret) |
211 | return ret; /* -ETIMEDOUT */ |
212 | |
213 | /* PECI interrupt enable */ |
214 | regmap_update_bits(map: priv->regmap, NPCM_PECI_CTL_STS, NPCM_PECI_CTRL_DONE_INT_EN, |
215 | NPCM_PECI_CTRL_DONE_INT_EN); |
216 | |
217 | return 0; |
218 | } |
219 | |
220 | static const struct regmap_config npcm_peci_regmap_config = { |
221 | .reg_bits = 8, |
222 | .val_bits = 8, |
223 | .max_register = NPCM_PECI_MAX_REG, |
224 | .fast_io = true, |
225 | }; |
226 | |
227 | static struct peci_controller_ops npcm_ops = { |
228 | .xfer = npcm_peci_xfer, |
229 | }; |
230 | |
231 | static int npcm_peci_probe(struct platform_device *pdev) |
232 | { |
233 | struct peci_controller *controller; |
234 | struct npcm_peci *priv; |
235 | void __iomem *base; |
236 | int ret; |
237 | |
238 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
239 | if (!priv) |
240 | return -ENOMEM; |
241 | |
242 | priv->dev = &pdev->dev; |
243 | dev_set_drvdata(dev: &pdev->dev, data: priv); |
244 | |
245 | base = devm_platform_ioremap_resource(pdev, index: 0); |
246 | if (IS_ERR(ptr: base)) |
247 | return PTR_ERR(ptr: base); |
248 | |
249 | priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, &npcm_peci_regmap_config); |
250 | if (IS_ERR(ptr: priv->regmap)) |
251 | return PTR_ERR(ptr: priv->regmap); |
252 | |
253 | priv->irq = platform_get_irq(pdev, 0); |
254 | if (priv->irq < 0) |
255 | return priv->irq; |
256 | |
257 | ret = devm_request_irq(dev: &pdev->dev, irq: priv->irq, handler: npcm_peci_irq_handler, |
258 | irqflags: 0, devname: "peci-npcm-irq" , dev_id: priv); |
259 | if (ret) |
260 | return ret; |
261 | |
262 | init_completion(x: &priv->xfer_complete); |
263 | spin_lock_init(&priv->lock); |
264 | |
265 | ret = npcm_peci_init_ctrl(priv); |
266 | if (ret) |
267 | return ret; |
268 | |
269 | controller = devm_peci_controller_add(parent: priv->dev, ops: &npcm_ops); |
270 | if (IS_ERR(ptr: controller)) |
271 | return dev_err_probe(dev: priv->dev, err: PTR_ERR(ptr: controller), |
272 | fmt: "failed to add npcm peci controller\n" ); |
273 | |
274 | priv->controller = controller; |
275 | |
276 | return 0; |
277 | } |
278 | |
279 | static const struct of_device_id npcm_peci_of_table[] = { |
280 | { .compatible = "nuvoton,npcm750-peci" , }, |
281 | { .compatible = "nuvoton,npcm845-peci" , }, |
282 | { } |
283 | }; |
284 | MODULE_DEVICE_TABLE(of, npcm_peci_of_table); |
285 | |
286 | static struct platform_driver npcm_peci_driver = { |
287 | .probe = npcm_peci_probe, |
288 | .driver = { |
289 | .name = KBUILD_MODNAME, |
290 | .of_match_table = npcm_peci_of_table, |
291 | }, |
292 | }; |
293 | module_platform_driver(npcm_peci_driver); |
294 | |
295 | MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>" ); |
296 | MODULE_DESCRIPTION("NPCM PECI driver" ); |
297 | MODULE_LICENSE("GPL" ); |
298 | MODULE_IMPORT_NS(PECI); |
299 | |