1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // Loongson SPI Support |
3 | // Copyright (C) 2023 Loongson Technology Corporation Limited |
4 | |
5 | #include <linux/clk.h> |
6 | #include <linux/delay.h> |
7 | #include <linux/err.h> |
8 | #include <linux/init.h> |
9 | #include <linux/interrupt.h> |
10 | #include <linux/io.h> |
11 | #include <linux/iopoll.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> |
14 | #include <linux/spi/spi.h> |
15 | |
16 | #include "spi-loongson.h" |
17 | |
18 | static inline void loongson_spi_write_reg(struct loongson_spi *spi, unsigned char reg, |
19 | unsigned char data) |
20 | { |
21 | writeb(val: data, addr: spi->base + reg); |
22 | } |
23 | |
24 | static inline char loongson_spi_read_reg(struct loongson_spi *spi, unsigned char reg) |
25 | { |
26 | return readb(addr: spi->base + reg); |
27 | } |
28 | |
29 | static void loongson_spi_set_cs(struct spi_device *spi, bool en) |
30 | { |
31 | int cs; |
32 | unsigned char mask = (BIT(4) | BIT(0)) << spi_get_chipselect(spi, idx: 0); |
33 | unsigned char val = en ? mask : (BIT(0) << spi_get_chipselect(spi, idx: 0)); |
34 | struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctlr: spi->controller); |
35 | |
36 | cs = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_SFCS_REG) & ~mask; |
37 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_SFCS_REG, data: val | cs); |
38 | } |
39 | |
40 | static void loongson_spi_set_clk(struct loongson_spi *loongson_spi, unsigned int hz) |
41 | { |
42 | unsigned char val; |
43 | unsigned int div, div_tmp; |
44 | static const char rdiv[12] = {0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10, 11}; |
45 | |
46 | div = clamp_val(DIV_ROUND_UP_ULL(loongson_spi->clk_rate, hz), 2, 4096); |
47 | div_tmp = rdiv[fls(x: div - 1)]; |
48 | loongson_spi->spcr = (div_tmp & GENMASK(1, 0)) >> 0; |
49 | loongson_spi->sper = (div_tmp & GENMASK(3, 2)) >> 2; |
50 | val = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_SPCR_REG); |
51 | val &= ~GENMASK(1, 0); |
52 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_SPCR_REG, data: val | |
53 | loongson_spi->spcr); |
54 | val = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_SPER_REG); |
55 | val &= ~GENMASK(1, 0); |
56 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_SPER_REG, data: val | |
57 | loongson_spi->sper); |
58 | loongson_spi->hz = hz; |
59 | } |
60 | |
61 | static void loongson_spi_set_mode(struct loongson_spi *loongson_spi, |
62 | struct spi_device *spi) |
63 | { |
64 | unsigned char val; |
65 | |
66 | val = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_SPCR_REG); |
67 | val &= ~(LOONGSON_SPI_SPCR_CPOL | LOONGSON_SPI_SPCR_CPHA); |
68 | if (spi->mode & SPI_CPOL) |
69 | val |= LOONGSON_SPI_SPCR_CPOL; |
70 | if (spi->mode & SPI_CPHA) |
71 | val |= LOONGSON_SPI_SPCR_CPHA; |
72 | |
73 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_SPCR_REG, data: val); |
74 | loongson_spi->mode |= spi->mode; |
75 | } |
76 | |
77 | static int loongson_spi_update_state(struct loongson_spi *loongson_spi, |
78 | struct spi_device *spi, struct spi_transfer *t) |
79 | { |
80 | if (t && loongson_spi->hz != t->speed_hz) |
81 | loongson_spi_set_clk(loongson_spi, hz: t->speed_hz); |
82 | |
83 | if ((spi->mode ^ loongson_spi->mode) & SPI_MODE_X_MASK) |
84 | loongson_spi_set_mode(loongson_spi, spi); |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | static int loongson_spi_setup(struct spi_device *spi) |
90 | { |
91 | struct loongson_spi *loongson_spi; |
92 | |
93 | loongson_spi = spi_controller_get_devdata(ctlr: spi->controller); |
94 | if (spi->bits_per_word % 8) |
95 | return -EINVAL; |
96 | |
97 | if (spi_get_chipselect(spi, idx: 0) >= spi->controller->num_chipselect) |
98 | return -EINVAL; |
99 | |
100 | loongson_spi->hz = 0; |
101 | loongson_spi_set_cs(spi, en: true); |
102 | |
103 | return 0; |
104 | } |
105 | |
106 | static int loongson_spi_write_read_8bit(struct spi_device *spi, const u8 **tx_buf, |
107 | u8 **rx_buf, unsigned int num) |
108 | { |
109 | int ret; |
110 | struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctlr: spi->controller); |
111 | |
112 | if (tx_buf && *tx_buf) |
113 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_FIFO_REG, data: *((*tx_buf)++)); |
114 | else |
115 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_FIFO_REG, data: 0); |
116 | |
117 | ret = readb_poll_timeout(loongson_spi->base + LOONGSON_SPI_SPSR_REG, |
118 | loongson_spi->spsr, (loongson_spi->spsr & |
119 | LOONGSON_SPI_SPSR_RFEMPTY) != LOONGSON_SPI_SPSR_RFEMPTY, |
120 | 1, USEC_PER_MSEC); |
121 | |
122 | if (rx_buf && *rx_buf) |
123 | *(*rx_buf)++ = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_FIFO_REG); |
124 | else |
125 | loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_FIFO_REG); |
126 | |
127 | return ret; |
128 | } |
129 | |
130 | static int loongson_spi_write_read(struct spi_device *spi, struct spi_transfer *xfer) |
131 | { |
132 | int ret; |
133 | unsigned int count; |
134 | const u8 *tx = xfer->tx_buf; |
135 | u8 *rx = xfer->rx_buf; |
136 | |
137 | count = xfer->len; |
138 | do { |
139 | ret = loongson_spi_write_read_8bit(spi, tx_buf: &tx, rx_buf: &rx, num: count); |
140 | if (ret) |
141 | break; |
142 | } while (--count); |
143 | |
144 | return ret; |
145 | } |
146 | |
147 | static int loongson_spi_prepare_message(struct spi_controller *ctlr, struct spi_message *m) |
148 | { |
149 | struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctlr); |
150 | |
151 | loongson_spi->para = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_PARA_REG); |
152 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_PARA_REG, data: loongson_spi->para & |
153 | ~LOONGSON_SPI_PARA_MEM_EN); |
154 | |
155 | return 0; |
156 | } |
157 | |
158 | static int loongson_spi_transfer_one(struct spi_controller *ctrl, struct spi_device *spi, |
159 | struct spi_transfer *xfer) |
160 | { |
161 | struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctlr: spi->controller); |
162 | |
163 | loongson_spi_update_state(loongson_spi, spi, t: xfer); |
164 | if (xfer->len) |
165 | return loongson_spi_write_read(spi, xfer); |
166 | |
167 | return 0; |
168 | } |
169 | |
170 | static int loongson_spi_unprepare_message(struct spi_controller *ctrl, struct spi_message *m) |
171 | { |
172 | struct loongson_spi *loongson_spi = spi_controller_get_devdata(ctlr: ctrl); |
173 | |
174 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_PARA_REG, data: loongson_spi->para); |
175 | |
176 | return 0; |
177 | } |
178 | |
179 | static void loongson_spi_reginit(struct loongson_spi *loongson_spi_dev) |
180 | { |
181 | unsigned char val; |
182 | |
183 | val = loongson_spi_read_reg(spi: loongson_spi_dev, LOONGSON_SPI_SPCR_REG); |
184 | val &= ~LOONGSON_SPI_SPCR_SPE; |
185 | loongson_spi_write_reg(spi: loongson_spi_dev, LOONGSON_SPI_SPCR_REG, data: val); |
186 | |
187 | loongson_spi_write_reg(spi: loongson_spi_dev, LOONGSON_SPI_SPSR_REG, |
188 | data: (LOONGSON_SPI_SPSR_SPIF | LOONGSON_SPI_SPSR_WCOL)); |
189 | |
190 | val = loongson_spi_read_reg(spi: loongson_spi_dev, LOONGSON_SPI_SPCR_REG); |
191 | val |= LOONGSON_SPI_SPCR_SPE; |
192 | loongson_spi_write_reg(spi: loongson_spi_dev, LOONGSON_SPI_SPCR_REG, data: val); |
193 | } |
194 | |
195 | int loongson_spi_init_controller(struct device *dev, void __iomem *regs) |
196 | { |
197 | struct spi_controller *controller; |
198 | struct loongson_spi *spi; |
199 | struct clk *clk; |
200 | |
201 | controller = devm_spi_alloc_host(dev, size: sizeof(struct loongson_spi)); |
202 | if (controller == NULL) |
203 | return -ENOMEM; |
204 | |
205 | controller->mode_bits = SPI_MODE_X_MASK | SPI_CS_HIGH; |
206 | controller->setup = loongson_spi_setup; |
207 | controller->prepare_message = loongson_spi_prepare_message; |
208 | controller->transfer_one = loongson_spi_transfer_one; |
209 | controller->unprepare_message = loongson_spi_unprepare_message; |
210 | controller->set_cs = loongson_spi_set_cs; |
211 | controller->num_chipselect = 4; |
212 | device_set_node(dev: &controller->dev, dev_fwnode(dev)); |
213 | dev_set_drvdata(dev, data: controller); |
214 | |
215 | spi = spi_controller_get_devdata(ctlr: controller); |
216 | spi->base = regs; |
217 | spi->controller = controller; |
218 | |
219 | clk = devm_clk_get_optional(dev, NULL); |
220 | if (IS_ERR(ptr: clk)) |
221 | return dev_err_probe(dev, err: PTR_ERR(ptr: clk), fmt: "unable to get clock\n" ); |
222 | |
223 | spi->clk_rate = clk_get_rate(clk); |
224 | loongson_spi_reginit(loongson_spi_dev: spi); |
225 | |
226 | spi->mode = 0; |
227 | |
228 | return devm_spi_register_controller(dev, ctlr: controller); |
229 | } |
230 | EXPORT_SYMBOL_NS_GPL(loongson_spi_init_controller, SPI_LOONGSON_CORE); |
231 | |
232 | static int __maybe_unused loongson_spi_suspend(struct device *dev) |
233 | { |
234 | struct loongson_spi *loongson_spi; |
235 | struct spi_controller *controller; |
236 | |
237 | controller = dev_get_drvdata(dev); |
238 | spi_controller_suspend(ctlr: controller); |
239 | |
240 | loongson_spi = spi_controller_get_devdata(ctlr: controller); |
241 | |
242 | loongson_spi->spcr = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_SPCR_REG); |
243 | loongson_spi->sper = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_SPER_REG); |
244 | loongson_spi->spsr = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_SPSR_REG); |
245 | loongson_spi->para = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_PARA_REG); |
246 | loongson_spi->sfcs = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_SFCS_REG); |
247 | loongson_spi->timi = loongson_spi_read_reg(spi: loongson_spi, LOONGSON_SPI_TIMI_REG); |
248 | |
249 | return 0; |
250 | } |
251 | |
252 | static int __maybe_unused loongson_spi_resume(struct device *dev) |
253 | { |
254 | struct loongson_spi *loongson_spi; |
255 | struct spi_controller *controller; |
256 | |
257 | controller = dev_get_drvdata(dev); |
258 | loongson_spi = spi_controller_get_devdata(ctlr: controller); |
259 | |
260 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_SPCR_REG, data: loongson_spi->spcr); |
261 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_SPER_REG, data: loongson_spi->sper); |
262 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_SPSR_REG, data: loongson_spi->spsr); |
263 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_PARA_REG, data: loongson_spi->para); |
264 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_SFCS_REG, data: loongson_spi->sfcs); |
265 | loongson_spi_write_reg(spi: loongson_spi, LOONGSON_SPI_TIMI_REG, data: loongson_spi->timi); |
266 | |
267 | spi_controller_resume(ctlr: controller); |
268 | |
269 | return 0; |
270 | } |
271 | |
272 | const struct dev_pm_ops loongson_spi_dev_pm_ops = { |
273 | .suspend = loongson_spi_suspend, |
274 | .resume = loongson_spi_resume, |
275 | }; |
276 | EXPORT_SYMBOL_NS_GPL(loongson_spi_dev_pm_ops, SPI_LOONGSON_CORE); |
277 | |
278 | MODULE_DESCRIPTION("Loongson SPI core driver" ); |
279 | MODULE_LICENSE("GPL" ); |
280 | |