Commit | Line | Data |
---|---|---|
5dc17fa6 AL |
1 | /* |
2 | * mchp23k256.c | |
3 | * | |
4 | * Driver for Microchip 23k256 SPI RAM chips | |
5 | * | |
6 | * Copyright © 2016 Andrew Lunn <andrew@lunn.ch> | |
7 | * | |
8 | * This code is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License version 2 as | |
10 | * published by the Free Software Foundation. | |
11 | * | |
12 | */ | |
13 | #include <linux/device.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/mtd/mtd.h> | |
16 | #include <linux/mtd/partitions.h> | |
17 | #include <linux/mutex.h> | |
18 | #include <linux/sched.h> | |
19 | #include <linux/sizes.h> | |
20 | #include <linux/spi/flash.h> | |
21 | #include <linux/spi/spi.h> | |
4db4d35e | 22 | #include <linux/of_device.h> |
5dc17fa6 | 23 | |
4379075a CP |
24 | #define MAX_CMD_SIZE 4 |
25 | ||
26 | struct mchp23_caps { | |
27 | u8 addr_width; | |
28 | unsigned int size; | |
29 | }; | |
30 | ||
5dc17fa6 AL |
31 | struct mchp23k256_flash { |
32 | struct spi_device *spi; | |
33 | struct mutex lock; | |
34 | struct mtd_info mtd; | |
4379075a | 35 | const struct mchp23_caps *caps; |
5dc17fa6 AL |
36 | }; |
37 | ||
38 | #define MCHP23K256_CMD_WRITE_STATUS 0x01 | |
39 | #define MCHP23K256_CMD_WRITE 0x02 | |
40 | #define MCHP23K256_CMD_READ 0x03 | |
41 | #define MCHP23K256_MODE_SEQ BIT(6) | |
42 | ||
43 | #define to_mchp23k256_flash(x) container_of(x, struct mchp23k256_flash, mtd) | |
44 | ||
4379075a CP |
45 | static void mchp23k256_addr2cmd(struct mchp23k256_flash *flash, |
46 | unsigned int addr, u8 *cmd) | |
47 | { | |
48 | int i; | |
49 | ||
50 | /* | |
51 | * Address is sent in big endian (MSB first) and we skip | |
52 | * the first entry of the cmd array which contains the cmd | |
53 | * opcode. | |
54 | */ | |
55 | for (i = flash->caps->addr_width; i > 0; i--, addr >>= 8) | |
56 | cmd[i] = addr; | |
57 | } | |
58 | ||
59 | static int mchp23k256_cmdsz(struct mchp23k256_flash *flash) | |
60 | { | |
61 | return 1 + flash->caps->addr_width; | |
62 | } | |
63 | ||
5dc17fa6 AL |
64 | static int mchp23k256_write(struct mtd_info *mtd, loff_t to, size_t len, |
65 | size_t *retlen, const unsigned char *buf) | |
66 | { | |
67 | struct mchp23k256_flash *flash = to_mchp23k256_flash(mtd); | |
68 | struct spi_transfer transfer[2] = {}; | |
69 | struct spi_message message; | |
4379075a | 70 | unsigned char command[MAX_CMD_SIZE]; |
db601f3a | 71 | int ret; |
5dc17fa6 AL |
72 | |
73 | spi_message_init(&message); | |
74 | ||
75 | command[0] = MCHP23K256_CMD_WRITE; | |
4379075a | 76 | mchp23k256_addr2cmd(flash, to, command); |
5dc17fa6 AL |
77 | |
78 | transfer[0].tx_buf = command; | |
4379075a | 79 | transfer[0].len = mchp23k256_cmdsz(flash); |
5dc17fa6 AL |
80 | spi_message_add_tail(&transfer[0], &message); |
81 | ||
82 | transfer[1].tx_buf = buf; | |
83 | transfer[1].len = len; | |
84 | spi_message_add_tail(&transfer[1], &message); | |
85 | ||
86 | mutex_lock(&flash->lock); | |
87 | ||
db601f3a AB |
88 | ret = spi_sync(flash->spi, &message); |
89 | ||
90 | mutex_unlock(&flash->lock); | |
91 | ||
92 | if (ret) | |
93 | return ret; | |
5dc17fa6 AL |
94 | |
95 | if (retlen && message.actual_length > sizeof(command)) | |
96 | *retlen += message.actual_length - sizeof(command); | |
97 | ||
5dc17fa6 AL |
98 | return 0; |
99 | } | |
100 | ||
101 | static int mchp23k256_read(struct mtd_info *mtd, loff_t from, size_t len, | |
102 | size_t *retlen, unsigned char *buf) | |
103 | { | |
104 | struct mchp23k256_flash *flash = to_mchp23k256_flash(mtd); | |
105 | struct spi_transfer transfer[2] = {}; | |
106 | struct spi_message message; | |
4379075a | 107 | unsigned char command[MAX_CMD_SIZE]; |
db601f3a | 108 | int ret; |
5dc17fa6 AL |
109 | |
110 | spi_message_init(&message); | |
111 | ||
112 | memset(&transfer, 0, sizeof(transfer)); | |
113 | command[0] = MCHP23K256_CMD_READ; | |
4379075a | 114 | mchp23k256_addr2cmd(flash, from, command); |
5dc17fa6 AL |
115 | |
116 | transfer[0].tx_buf = command; | |
4379075a | 117 | transfer[0].len = mchp23k256_cmdsz(flash); |
5dc17fa6 AL |
118 | spi_message_add_tail(&transfer[0], &message); |
119 | ||
120 | transfer[1].rx_buf = buf; | |
121 | transfer[1].len = len; | |
122 | spi_message_add_tail(&transfer[1], &message); | |
123 | ||
124 | mutex_lock(&flash->lock); | |
125 | ||
db601f3a AB |
126 | ret = spi_sync(flash->spi, &message); |
127 | ||
128 | mutex_unlock(&flash->lock); | |
129 | ||
130 | if (ret) | |
131 | return ret; | |
5dc17fa6 AL |
132 | |
133 | if (retlen && message.actual_length > sizeof(command)) | |
134 | *retlen += message.actual_length - sizeof(command); | |
135 | ||
5dc17fa6 AL |
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(&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(&transfer, &message); | |
157 | ||
158 | return spi_sync(spi, &message); | |
159 | } | |
160 | ||
4379075a CP |
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 | ||
5dc17fa6 AL |
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(&spi->dev, 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, flash); | |
184 | ||
185 | err = mchp23k256_set_mode(spi); | |
186 | if (err) | |
187 | return err; | |
188 | ||
189 | data = dev_get_platdata(&spi->dev); | |
190 | ||
4379075a CP |
191 | flash->caps = of_device_get_match_data(&spi->dev); |
192 | if (!flash->caps) | |
193 | flash->caps = &mchp23k256_caps; | |
194 | ||
2f071ff1 | 195 | mtd_set_of_node(&flash->mtd, spi->dev.of_node); |
5dc17fa6 AL |
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; | |
4379075a | 200 | flash->mtd.size = flash->caps->size; |
5dc17fa6 AL |
201 | flash->mtd._read = mchp23k256_read; |
202 | flash->mtd._write = mchp23k256_write; | |
203 | ||
44225c9c CP |
204 | err = mtd_device_register(&flash->mtd, data ? data->parts : NULL, |
205 | data ? data->nr_parts : 0); | |
5dc17fa6 AL |
206 | if (err) |
207 | return err; | |
208 | ||
209 | return 0; | |
210 | } | |
211 | ||
212 | static int mchp23k256_remove(struct spi_device *spi) | |
213 | { | |
214 | struct mchp23k256_flash *flash = spi_get_drvdata(spi); | |
215 | ||
216 | return mtd_device_unregister(&flash->mtd); | |
217 | } | |
218 | ||
4db4d35e | 219 | static const struct of_device_id mchp23k256_of_table[] = { |
4379075a CP |
220 | { |
221 | .compatible = "microchip,mchp23k256", | |
222 | .data = &mchp23k256_caps, | |
223 | }, | |
224 | { | |
225 | .compatible = "microchip,mchp23lcv1024", | |
226 | .data = &mchp23lcv1024_caps, | |
227 | }, | |
4db4d35e CP |
228 | {} |
229 | }; | |
230 | MODULE_DEVICE_TABLE(of, mchp23k256_of_table); | |
231 | ||
5dc17fa6 AL |
232 | static struct spi_driver mchp23k256_driver = { |
233 | .driver = { | |
234 | .name = "mchp23k256", | |
4db4d35e | 235 | .of_match_table = of_match_ptr(mchp23k256_of_table), |
5dc17fa6 AL |
236 | }, |
237 | .probe = mchp23k256_probe, | |
238 | .remove = mchp23k256_remove, | |
239 | }; | |
240 | ||
241 | module_spi_driver(mchp23k256_driver); | |
242 | ||
243 | MODULE_DESCRIPTION("MTD SPI driver for MCHP23K256 RAM chips"); | |
244 | MODULE_AUTHOR("Andrew Lunn <andre@lunn.ch>"); | |
245 | MODULE_LICENSE("GPL v2"); | |
246 | MODULE_ALIAS("spi:mchp23k256"); |