1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * mchp23k256.c |
4 | * |
5 | * Driver for Microchip 23k256 SPI RAM chips |
6 | * |
7 | * Copyright © 2016 Andrew Lunn <andrew@lunn.ch> |
8 | */ |
9 | #include <linux/device.h> |
10 | #include <linux/module.h> |
11 | #include <linux/mtd/mtd.h> |
12 | #include <linux/mtd/partitions.h> |
13 | #include <linux/mutex.h> |
14 | #include <linux/sched.h> |
15 | #include <linux/sizes.h> |
16 | #include <linux/spi/flash.h> |
17 | #include <linux/spi/spi.h> |
18 | #include <linux/of.h> |
19 | |
20 | #define MAX_CMD_SIZE 4 |
21 | |
22 | struct mchp23_caps { |
23 | u8 addr_width; |
24 | unsigned int size; |
25 | }; |
26 | |
27 | struct mchp23k256_flash { |
28 | struct spi_device *spi; |
29 | struct mutex lock; |
30 | struct mtd_info mtd; |
31 | const struct mchp23_caps *caps; |
32 | }; |
33 | |
34 | #define MCHP23K256_CMD_WRITE_STATUS 0x01 |
35 | #define MCHP23K256_CMD_WRITE 0x02 |
36 | #define MCHP23K256_CMD_READ 0x03 |
37 | #define MCHP23K256_MODE_SEQ BIT(6) |
38 | |
39 | #define to_mchp23k256_flash(x) container_of(x, struct mchp23k256_flash, mtd) |
40 | |
41 | static void mchp23k256_addr2cmd(struct mchp23k256_flash *flash, |
42 | unsigned int addr, u8 *cmd) |
43 | { |
44 | int i; |
45 | |
46 | /* |
47 | * Address is sent in big endian (MSB first) and we skip |
48 | * the first entry of the cmd array which contains the cmd |
49 | * opcode. |
50 | */ |
51 | for (i = flash->caps->addr_width; i > 0; i--, addr >>= 8) |
52 | cmd[i] = addr; |
53 | } |
54 | |
55 | static int mchp23k256_cmdsz(struct mchp23k256_flash *flash) |
56 | { |
57 | return 1 + flash->caps->addr_width; |
58 | } |
59 | |
60 | static int mchp23k256_write(struct mtd_info *mtd, loff_t to, size_t len, |
61 | size_t *retlen, const unsigned char *buf) |
62 | { |
63 | struct mchp23k256_flash *flash = to_mchp23k256_flash(mtd); |
64 | struct spi_transfer transfer[2] = {}; |
65 | struct spi_message message; |
66 | unsigned char command[MAX_CMD_SIZE]; |
67 | int ret, cmd_len; |
68 | |
69 | spi_message_init(m: &message); |
70 | |
71 | cmd_len = mchp23k256_cmdsz(flash); |
72 | |
73 | command[0] = MCHP23K256_CMD_WRITE; |
74 | mchp23k256_addr2cmd(flash, addr: to, cmd: command); |
75 | |
76 | transfer[0].tx_buf = command; |
77 | transfer[0].len = cmd_len; |
78 | spi_message_add_tail(t: &transfer[0], m: &message); |
79 | |
80 | transfer[1].tx_buf = buf; |
81 | transfer[1].len = len; |
82 | spi_message_add_tail(t: &transfer[1], m: &message); |
83 | |
84 | mutex_lock(&flash->lock); |
85 | |
86 | ret = spi_sync(spi: flash->spi, message: &message); |
87 | |
88 | mutex_unlock(lock: &flash->lock); |
89 | |
90 | if (ret) |
91 | return ret; |
92 | |
93 | if (retlen && message.actual_length > cmd_len) |
94 | *retlen += message.actual_length - cmd_len; |
95 | |
96 | return 0; |
97 | } |
98 | |
99 | static int mchp23k256_read(struct mtd_info *mtd, loff_t from, size_t len, |
100 | size_t *retlen, unsigned char *buf) |
101 | { |
102 | struct mchp23k256_flash *flash = to_mchp23k256_flash(mtd); |
103 | struct spi_transfer transfer[2] = {}; |
104 | struct spi_message message; |
105 | unsigned char command[MAX_CMD_SIZE]; |
106 | int ret, cmd_len; |
107 | |
108 | spi_message_init(m: &message); |
109 | |
110 | cmd_len = mchp23k256_cmdsz(flash); |
111 | |
112 | memset(&transfer, 0, sizeof(transfer)); |
113 | command[0] = MCHP23K256_CMD_READ; |
114 | mchp23k256_addr2cmd(flash, addr: from, cmd: command); |
115 | |
116 | transfer[0].tx_buf = command; |
117 | transfer[0].len = cmd_len; |
118 | spi_message_add_tail(t: &transfer[0], m: &message); |
119 | |
120 | transfer[1].rx_buf = buf; |
121 | transfer[1].len = len; |
122 | spi_message_add_tail(t: &transfer[1], m: &message); |
123 | |
124 | mutex_lock(&flash->lock); |
125 | |
126 | ret = spi_sync(spi: flash->spi, message: &message); |
127 | |
128 | mutex_unlock(lock: &flash->lock); |
129 | |
130 | if (ret) |
131 | return ret; |
132 | |
133 | if (retlen && message.actual_length > cmd_len) |
134 | *retlen += message.actual_length - cmd_len; |
135 | |
136 | return 0; |
137 | } |
138 | |
139 | /* |
140 | * Set the device into sequential mode. This allows read/writes to the |
141 | * entire SRAM in a single operation |
142 | */ |
143 | static int mchp23k256_set_mode(struct spi_device *spi) |
144 | { |
145 | struct spi_transfer transfer = {}; |
146 | struct spi_message message; |
147 | unsigned char command[2]; |
148 | |
149 | spi_message_init(m: &message); |
150 | |
151 | command[0] = MCHP23K256_CMD_WRITE_STATUS; |
152 | command[1] = MCHP23K256_MODE_SEQ; |
153 | |
154 | transfer.tx_buf = command; |
155 | transfer.len = sizeof(command); |
156 | spi_message_add_tail(t: &transfer, m: &message); |
157 | |
158 | return spi_sync(spi, message: &message); |
159 | } |
160 | |
161 | static const struct mchp23_caps mchp23k256_caps = { |
162 | .size = SZ_32K, |
163 | .addr_width = 2, |
164 | }; |
165 | |
166 | static const struct mchp23_caps mchp23lcv1024_caps = { |
167 | .size = SZ_128K, |
168 | .addr_width = 3, |
169 | }; |
170 | |
171 | static int mchp23k256_probe(struct spi_device *spi) |
172 | { |
173 | struct mchp23k256_flash *flash; |
174 | struct flash_platform_data *data; |
175 | int err; |
176 | |
177 | flash = devm_kzalloc(dev: &spi->dev, size: sizeof(*flash), GFP_KERNEL); |
178 | if (!flash) |
179 | return -ENOMEM; |
180 | |
181 | flash->spi = spi; |
182 | mutex_init(&flash->lock); |
183 | spi_set_drvdata(spi, data: flash); |
184 | |
185 | err = mchp23k256_set_mode(spi); |
186 | if (err) |
187 | return err; |
188 | |
189 | data = dev_get_platdata(dev: &spi->dev); |
190 | |
191 | flash->caps = of_device_get_match_data(dev: &spi->dev); |
192 | if (!flash->caps) |
193 | flash->caps = &mchp23k256_caps; |
194 | |
195 | mtd_set_of_node(mtd: &flash->mtd, np: spi->dev.of_node); |
196 | flash->mtd.dev.parent = &spi->dev; |
197 | flash->mtd.type = MTD_RAM; |
198 | flash->mtd.flags = MTD_CAP_RAM; |
199 | flash->mtd.writesize = 1; |
200 | flash->mtd.size = flash->caps->size; |
201 | flash->mtd._read = mchp23k256_read; |
202 | flash->mtd._write = mchp23k256_write; |
203 | |
204 | err = mtd_device_register(&flash->mtd, data ? data->parts : NULL, |
205 | data ? data->nr_parts : 0); |
206 | if (err) |
207 | return err; |
208 | |
209 | return 0; |
210 | } |
211 | |
212 | static void mchp23k256_remove(struct spi_device *spi) |
213 | { |
214 | struct mchp23k256_flash *flash = spi_get_drvdata(spi); |
215 | |
216 | WARN_ON(mtd_device_unregister(&flash->mtd)); |
217 | } |
218 | |
219 | static const struct of_device_id mchp23k256_of_table[] = { |
220 | { |
221 | .compatible = "microchip,mchp23k256" , |
222 | .data = &mchp23k256_caps, |
223 | }, |
224 | { |
225 | .compatible = "microchip,mchp23lcv1024" , |
226 | .data = &mchp23lcv1024_caps, |
227 | }, |
228 | {} |
229 | }; |
230 | MODULE_DEVICE_TABLE(of, mchp23k256_of_table); |
231 | |
232 | static const struct spi_device_id mchp23k256_spi_ids[] = { |
233 | { |
234 | .name = "mchp23k256" , |
235 | .driver_data = (kernel_ulong_t)&mchp23k256_caps, |
236 | }, |
237 | { |
238 | .name = "mchp23lcv1024" , |
239 | .driver_data = (kernel_ulong_t)&mchp23lcv1024_caps, |
240 | }, |
241 | {} |
242 | }; |
243 | MODULE_DEVICE_TABLE(spi, mchp23k256_spi_ids); |
244 | |
245 | static struct spi_driver mchp23k256_driver = { |
246 | .driver = { |
247 | .name = "mchp23k256" , |
248 | .of_match_table = mchp23k256_of_table, |
249 | }, |
250 | .probe = mchp23k256_probe, |
251 | .remove = mchp23k256_remove, |
252 | .id_table = mchp23k256_spi_ids, |
253 | }; |
254 | |
255 | module_spi_driver(mchp23k256_driver); |
256 | |
257 | MODULE_DESCRIPTION("MTD SPI driver for MCHP23K256 RAM chips" ); |
258 | MODULE_AUTHOR("Andrew Lunn <andre@lunn.ch>" ); |
259 | MODULE_LICENSE("GPL v2" ); |
260 | MODULE_ALIAS("spi:mchp23k256" ); |
261 | |