]>
Commit | Line | Data |
---|---|---|
a2ca53b5 JG |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // | |
3 | // HiSilicon SPI NOR V3XX Flash Controller Driver for hi16xx chipsets | |
4 | // | |
5 | // Copyright (c) 2019 HiSilicon Technologies Co., Ltd. | |
6 | // Author: John Garry <john.garry@huawei.com> | |
7 | ||
8 | #include <linux/acpi.h> | |
9 | #include <linux/bitops.h> | |
10 | #include <linux/iopoll.h> | |
11 | #include <linux/module.h> | |
12 | #include <linux/platform_device.h> | |
13 | #include <linux/slab.h> | |
14 | #include <linux/spi/spi.h> | |
15 | #include <linux/spi/spi-mem.h> | |
16 | ||
17 | #define HISI_SFC_V3XX_VERSION (0x1f8) | |
18 | ||
19 | #define HISI_SFC_V3XX_CMD_CFG (0x300) | |
20 | #define HISI_SFC_V3XX_CMD_CFG_DATA_CNT_OFF 9 | |
21 | #define HISI_SFC_V3XX_CMD_CFG_RW_MSK BIT(8) | |
22 | #define HISI_SFC_V3XX_CMD_CFG_DATA_EN_MSK BIT(7) | |
23 | #define HISI_SFC_V3XX_CMD_CFG_DUMMY_CNT_OFF 4 | |
24 | #define HISI_SFC_V3XX_CMD_CFG_ADDR_EN_MSK BIT(3) | |
25 | #define HISI_SFC_V3XX_CMD_CFG_CS_SEL_OFF 1 | |
26 | #define HISI_SFC_V3XX_CMD_CFG_START_MSK BIT(0) | |
27 | #define HISI_SFC_V3XX_CMD_INS (0x308) | |
28 | #define HISI_SFC_V3XX_CMD_ADDR (0x30c) | |
29 | #define HISI_SFC_V3XX_CMD_DATABUF0 (0x400) | |
30 | ||
31 | struct hisi_sfc_v3xx_host { | |
32 | struct device *dev; | |
33 | void __iomem *regbase; | |
34 | int max_cmd_dword; | |
35 | }; | |
36 | ||
37 | #define HISI_SFC_V3XX_WAIT_TIMEOUT_US 1000000 | |
38 | #define HISI_SFC_V3XX_WAIT_POLL_INTERVAL_US 10 | |
39 | ||
40 | static int hisi_sfc_v3xx_wait_cmd_idle(struct hisi_sfc_v3xx_host *host) | |
41 | { | |
42 | u32 reg; | |
43 | ||
44 | return readl_poll_timeout(host->regbase + HISI_SFC_V3XX_CMD_CFG, reg, | |
45 | !(reg & HISI_SFC_V3XX_CMD_CFG_START_MSK), | |
46 | HISI_SFC_V3XX_WAIT_POLL_INTERVAL_US, | |
47 | HISI_SFC_V3XX_WAIT_TIMEOUT_US); | |
48 | } | |
49 | ||
50 | static int hisi_sfc_v3xx_adjust_op_size(struct spi_mem *mem, | |
51 | struct spi_mem_op *op) | |
52 | { | |
53 | struct spi_device *spi = mem->spi; | |
54 | struct hisi_sfc_v3xx_host *host; | |
55 | uintptr_t addr = (uintptr_t)op->data.buf.in; | |
56 | int max_byte_count; | |
57 | ||
58 | host = spi_controller_get_devdata(spi->master); | |
59 | ||
60 | max_byte_count = host->max_cmd_dword * 4; | |
61 | ||
62 | if (!IS_ALIGNED(addr, 4) && op->data.nbytes >= 4) | |
63 | op->data.nbytes = 4 - (addr % 4); | |
64 | else if (op->data.nbytes > max_byte_count) | |
65 | op->data.nbytes = max_byte_count; | |
66 | ||
67 | return 0; | |
68 | } | |
69 | ||
70 | /* | |
71 | * memcpy_{to,from}io doesn't gurantee 32b accesses - which we require for the | |
72 | * DATABUF registers -so use __io{read,write}32_copy when possible. For | |
73 | * trailing bytes, copy them byte-by-byte from the DATABUF register, as we | |
74 | * can't clobber outside the source/dest buffer. | |
75 | * | |
76 | * For efficient data read/write, we try to put any start 32b unaligned data | |
77 | * into a separate transaction in hisi_sfc_v3xx_adjust_op_size(). | |
78 | */ | |
79 | static void hisi_sfc_v3xx_read_databuf(struct hisi_sfc_v3xx_host *host, | |
80 | u8 *to, unsigned int len) | |
81 | { | |
82 | void __iomem *from; | |
83 | int i; | |
84 | ||
85 | from = host->regbase + HISI_SFC_V3XX_CMD_DATABUF0; | |
86 | ||
87 | if (IS_ALIGNED((uintptr_t)to, 4)) { | |
88 | int words = len / 4; | |
89 | ||
90 | __ioread32_copy(to, from, words); | |
91 | ||
92 | len -= words * 4; | |
93 | if (len) { | |
94 | u32 val; | |
95 | ||
96 | to += words * 4; | |
97 | from += words * 4; | |
98 | ||
99 | val = __raw_readl(from); | |
100 | ||
101 | for (i = 0; i < len; i++, val >>= 8, to++) | |
102 | *to = (u8)val; | |
103 | } | |
104 | } else { | |
105 | for (i = 0; i < DIV_ROUND_UP(len, 4); i++, from += 4) { | |
106 | u32 val = __raw_readl(from); | |
107 | int j; | |
108 | ||
109 | for (j = 0; j < 4 && (j + (i * 4) < len); | |
110 | to++, val >>= 8, j++) | |
111 | *to = (u8)val; | |
112 | } | |
113 | } | |
114 | } | |
115 | ||
116 | static void hisi_sfc_v3xx_write_databuf(struct hisi_sfc_v3xx_host *host, | |
117 | const u8 *from, unsigned int len) | |
118 | { | |
119 | void __iomem *to; | |
120 | int i; | |
121 | ||
122 | to = host->regbase + HISI_SFC_V3XX_CMD_DATABUF0; | |
123 | ||
124 | if (IS_ALIGNED((uintptr_t)from, 4)) { | |
125 | int words = len / 4; | |
126 | ||
127 | __iowrite32_copy(to, from, words); | |
128 | ||
129 | len -= words * 4; | |
130 | if (len) { | |
131 | u32 val = 0; | |
132 | ||
133 | to += words * 4; | |
134 | from += words * 4; | |
135 | ||
136 | for (i = 0; i < len; i++, from++) | |
137 | val |= *from << i * 8; | |
138 | __raw_writel(val, to); | |
139 | } | |
140 | ||
141 | } else { | |
142 | for (i = 0; i < DIV_ROUND_UP(len, 4); i++, to += 4) { | |
143 | u32 val = 0; | |
144 | int j; | |
145 | ||
146 | for (j = 0; j < 4 && (j + (i * 4) < len); | |
147 | from++, j++) | |
148 | val |= *from << j * 8; | |
149 | __raw_writel(val, to); | |
150 | } | |
151 | } | |
152 | } | |
153 | ||
154 | static int hisi_sfc_v3xx_generic_exec_op(struct hisi_sfc_v3xx_host *host, | |
155 | const struct spi_mem_op *op, | |
156 | u8 chip_select) | |
157 | { | |
158 | int ret, len = op->data.nbytes; | |
159 | u32 config = 0; | |
160 | ||
161 | if (op->addr.nbytes) | |
162 | config |= HISI_SFC_V3XX_CMD_CFG_ADDR_EN_MSK; | |
163 | ||
164 | if (op->data.dir != SPI_MEM_NO_DATA) { | |
165 | config |= (len - 1) << HISI_SFC_V3XX_CMD_CFG_DATA_CNT_OFF; | |
166 | config |= HISI_SFC_V3XX_CMD_CFG_DATA_EN_MSK; | |
167 | } | |
168 | ||
169 | if (op->data.dir == SPI_MEM_DATA_OUT) | |
170 | hisi_sfc_v3xx_write_databuf(host, op->data.buf.out, len); | |
171 | else if (op->data.dir == SPI_MEM_DATA_IN) | |
172 | config |= HISI_SFC_V3XX_CMD_CFG_RW_MSK; | |
173 | ||
174 | config |= op->dummy.nbytes << HISI_SFC_V3XX_CMD_CFG_DUMMY_CNT_OFF | | |
175 | chip_select << HISI_SFC_V3XX_CMD_CFG_CS_SEL_OFF | | |
176 | HISI_SFC_V3XX_CMD_CFG_START_MSK; | |
177 | ||
178 | writel(op->addr.val, host->regbase + HISI_SFC_V3XX_CMD_ADDR); | |
179 | writel(op->cmd.opcode, host->regbase + HISI_SFC_V3XX_CMD_INS); | |
180 | ||
181 | writel(config, host->regbase + HISI_SFC_V3XX_CMD_CFG); | |
182 | ||
183 | ret = hisi_sfc_v3xx_wait_cmd_idle(host); | |
184 | if (ret) | |
185 | return ret; | |
186 | ||
187 | if (op->data.dir == SPI_MEM_DATA_IN) | |
188 | hisi_sfc_v3xx_read_databuf(host, op->data.buf.in, len); | |
189 | ||
190 | return 0; | |
191 | } | |
192 | ||
193 | static int hisi_sfc_v3xx_exec_op(struct spi_mem *mem, | |
194 | const struct spi_mem_op *op) | |
195 | { | |
196 | struct hisi_sfc_v3xx_host *host; | |
197 | struct spi_device *spi = mem->spi; | |
198 | u8 chip_select = spi->chip_select; | |
199 | ||
200 | host = spi_controller_get_devdata(spi->master); | |
201 | ||
202 | return hisi_sfc_v3xx_generic_exec_op(host, op, chip_select); | |
203 | } | |
204 | ||
205 | static const struct spi_controller_mem_ops hisi_sfc_v3xx_mem_ops = { | |
206 | .adjust_op_size = hisi_sfc_v3xx_adjust_op_size, | |
207 | .exec_op = hisi_sfc_v3xx_exec_op, | |
208 | }; | |
209 | ||
210 | static int hisi_sfc_v3xx_probe(struct platform_device *pdev) | |
211 | { | |
212 | struct device *dev = &pdev->dev; | |
213 | struct hisi_sfc_v3xx_host *host; | |
214 | struct spi_controller *ctlr; | |
215 | u32 version; | |
216 | int ret; | |
217 | ||
218 | ctlr = spi_alloc_master(&pdev->dev, sizeof(*host)); | |
219 | if (!ctlr) | |
220 | return -ENOMEM; | |
221 | ||
222 | ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | | |
223 | SPI_TX_DUAL | SPI_TX_QUAD; | |
224 | ||
225 | host = spi_controller_get_devdata(ctlr); | |
226 | host->dev = dev; | |
227 | ||
228 | platform_set_drvdata(pdev, host); | |
229 | ||
230 | host->regbase = devm_platform_ioremap_resource(pdev, 0); | |
231 | if (IS_ERR(host->regbase)) { | |
232 | ret = PTR_ERR(host->regbase); | |
233 | goto err_put_master; | |
234 | } | |
235 | ||
236 | ctlr->bus_num = -1; | |
237 | ctlr->num_chipselect = 1; | |
238 | ctlr->mem_ops = &hisi_sfc_v3xx_mem_ops; | |
239 | ||
240 | version = readl(host->regbase + HISI_SFC_V3XX_VERSION); | |
241 | ||
242 | switch (version) { | |
243 | case 0x351: | |
244 | host->max_cmd_dword = 64; | |
245 | break; | |
246 | default: | |
247 | host->max_cmd_dword = 16; | |
248 | break; | |
249 | } | |
250 | ||
251 | ret = devm_spi_register_controller(dev, ctlr); | |
252 | if (ret) | |
253 | goto err_put_master; | |
254 | ||
255 | dev_info(&pdev->dev, "hw version 0x%x\n", version); | |
256 | ||
257 | return 0; | |
258 | ||
259 | err_put_master: | |
260 | spi_master_put(ctlr); | |
261 | return ret; | |
262 | } | |
263 | ||
264 | #if IS_ENABLED(CONFIG_ACPI) | |
265 | static const struct acpi_device_id hisi_sfc_v3xx_acpi_ids[] = { | |
266 | {"HISI0341", 0}, | |
267 | {} | |
268 | }; | |
269 | MODULE_DEVICE_TABLE(acpi, hisi_sfc_v3xx_acpi_ids); | |
270 | #endif | |
271 | ||
272 | static struct platform_driver hisi_sfc_v3xx_spi_driver = { | |
273 | .driver = { | |
274 | .name = "hisi-sfc-v3xx", | |
275 | .acpi_match_table = ACPI_PTR(hisi_sfc_v3xx_acpi_ids), | |
276 | }, | |
277 | .probe = hisi_sfc_v3xx_probe, | |
278 | }; | |
279 | ||
280 | module_platform_driver(hisi_sfc_v3xx_spi_driver); | |
281 | ||
282 | MODULE_LICENSE("GPL"); | |
283 | MODULE_AUTHOR("John Garry <john.garry@huawei.com>"); | |
284 | MODULE_DESCRIPTION("HiSilicon SPI NOR V3XX Flash Controller Driver for hi16xx chipsets"); |