1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Freescale UPM NAND driver. |
4 | * |
5 | * Copyright © 2007-2008 MontaVista Software, Inc. |
6 | * |
7 | * Author: Anton Vorontsov <avorontsov@ru.mvista.com> |
8 | */ |
9 | |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/mtd/rawnand.h> |
14 | #include <linux/mtd/partitions.h> |
15 | #include <linux/mtd/mtd.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/io.h> |
19 | #include <linux/slab.h> |
20 | #include <asm/fsl_lbc.h> |
21 | |
22 | struct fsl_upm_nand { |
23 | struct nand_controller base; |
24 | struct device *dev; |
25 | struct nand_chip chip; |
26 | struct fsl_upm upm; |
27 | uint8_t upm_addr_offset; |
28 | uint8_t upm_cmd_offset; |
29 | void __iomem *io_base; |
30 | struct gpio_desc *rnb_gpio[NAND_MAX_CHIPS]; |
31 | uint32_t mchip_offsets[NAND_MAX_CHIPS]; |
32 | uint32_t mchip_count; |
33 | uint32_t mchip_number; |
34 | }; |
35 | |
36 | static inline struct fsl_upm_nand *to_fsl_upm_nand(struct mtd_info *mtdinfo) |
37 | { |
38 | return container_of(mtd_to_nand(mtdinfo), struct fsl_upm_nand, |
39 | chip); |
40 | } |
41 | |
42 | static int fun_chip_init(struct fsl_upm_nand *fun, |
43 | const struct device_node *upm_np, |
44 | const struct resource *io_res) |
45 | { |
46 | struct mtd_info *mtd = nand_to_mtd(chip: &fun->chip); |
47 | int ret; |
48 | struct device_node *flash_np; |
49 | |
50 | fun->chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; |
51 | fun->chip.ecc.algo = NAND_ECC_ALGO_HAMMING; |
52 | fun->chip.controller = &fun->base; |
53 | mtd->dev.parent = fun->dev; |
54 | |
55 | flash_np = of_get_next_child(node: upm_np, NULL); |
56 | if (!flash_np) |
57 | return -ENODEV; |
58 | |
59 | nand_set_flash_node(chip: &fun->chip, np: flash_np); |
60 | mtd->name = devm_kasprintf(dev: fun->dev, GFP_KERNEL, fmt: "0x%llx.%pOFn" , |
61 | (u64)io_res->start, |
62 | flash_np); |
63 | if (!mtd->name) { |
64 | ret = -ENOMEM; |
65 | goto err; |
66 | } |
67 | |
68 | ret = nand_scan(chip: &fun->chip, max_chips: fun->mchip_count); |
69 | if (ret) |
70 | goto err; |
71 | |
72 | ret = mtd_device_register(mtd, NULL, 0); |
73 | err: |
74 | of_node_put(node: flash_np); |
75 | return ret; |
76 | } |
77 | |
78 | static int func_exec_instr(struct nand_chip *chip, |
79 | const struct nand_op_instr *instr) |
80 | { |
81 | struct fsl_upm_nand *fun = to_fsl_upm_nand(mtdinfo: nand_to_mtd(chip)); |
82 | u32 mar, reg_offs = fun->mchip_offsets[fun->mchip_number]; |
83 | unsigned int i; |
84 | const u8 *out; |
85 | u8 *in; |
86 | |
87 | switch (instr->type) { |
88 | case NAND_OP_CMD_INSTR: |
89 | fsl_upm_start_pattern(&fun->upm, fun->upm_cmd_offset); |
90 | mar = (instr->ctx.cmd.opcode << (32 - fun->upm.width)) | |
91 | reg_offs; |
92 | fsl_upm_run_pattern(&fun->upm, fun->io_base + reg_offs, mar); |
93 | fsl_upm_end_pattern(&fun->upm); |
94 | return 0; |
95 | |
96 | case NAND_OP_ADDR_INSTR: |
97 | fsl_upm_start_pattern(&fun->upm, fun->upm_addr_offset); |
98 | for (i = 0; i < instr->ctx.addr.naddrs; i++) { |
99 | mar = (instr->ctx.addr.addrs[i] << (32 - fun->upm.width)) | |
100 | reg_offs; |
101 | fsl_upm_run_pattern(&fun->upm, fun->io_base + reg_offs, mar); |
102 | } |
103 | fsl_upm_end_pattern(&fun->upm); |
104 | return 0; |
105 | |
106 | case NAND_OP_DATA_IN_INSTR: |
107 | in = instr->ctx.data.buf.in; |
108 | for (i = 0; i < instr->ctx.data.len; i++) |
109 | in[i] = in_8(fun->io_base + reg_offs); |
110 | return 0; |
111 | |
112 | case NAND_OP_DATA_OUT_INSTR: |
113 | out = instr->ctx.data.buf.out; |
114 | for (i = 0; i < instr->ctx.data.len; i++) |
115 | out_8(fun->io_base + reg_offs, out[i]); |
116 | return 0; |
117 | |
118 | case NAND_OP_WAITRDY_INSTR: |
119 | if (!fun->rnb_gpio[fun->mchip_number]) |
120 | return nand_soft_waitrdy(chip, timeout_ms: instr->ctx.waitrdy.timeout_ms); |
121 | |
122 | return nand_gpio_waitrdy(chip, gpiod: fun->rnb_gpio[fun->mchip_number], |
123 | timeout_ms: instr->ctx.waitrdy.timeout_ms); |
124 | |
125 | default: |
126 | return -EINVAL; |
127 | } |
128 | |
129 | return 0; |
130 | } |
131 | |
132 | static int fun_exec_op(struct nand_chip *chip, const struct nand_operation *op, |
133 | bool check_only) |
134 | { |
135 | struct fsl_upm_nand *fun = to_fsl_upm_nand(mtdinfo: nand_to_mtd(chip)); |
136 | unsigned int i; |
137 | int ret; |
138 | |
139 | if (op->cs >= NAND_MAX_CHIPS) |
140 | return -EINVAL; |
141 | |
142 | if (check_only) |
143 | return 0; |
144 | |
145 | fun->mchip_number = op->cs; |
146 | |
147 | for (i = 0; i < op->ninstrs; i++) { |
148 | ret = func_exec_instr(chip, instr: &op->instrs[i]); |
149 | if (ret) |
150 | return ret; |
151 | |
152 | if (op->instrs[i].delay_ns) |
153 | ndelay(op->instrs[i].delay_ns); |
154 | } |
155 | |
156 | return 0; |
157 | } |
158 | |
159 | static const struct nand_controller_ops fun_ops = { |
160 | .exec_op = fun_exec_op, |
161 | }; |
162 | |
163 | static int fun_probe(struct platform_device *ofdev) |
164 | { |
165 | struct fsl_upm_nand *fun; |
166 | struct resource *io_res; |
167 | const __be32 *prop; |
168 | int ret; |
169 | int size; |
170 | int i; |
171 | |
172 | fun = devm_kzalloc(dev: &ofdev->dev, size: sizeof(*fun), GFP_KERNEL); |
173 | if (!fun) |
174 | return -ENOMEM; |
175 | |
176 | fun->io_base = devm_platform_get_and_ioremap_resource(pdev: ofdev, index: 0, res: &io_res); |
177 | if (IS_ERR(ptr: fun->io_base)) |
178 | return PTR_ERR(ptr: fun->io_base); |
179 | |
180 | ret = fsl_upm_find(io_res->start, &fun->upm); |
181 | if (ret) { |
182 | dev_err(&ofdev->dev, "can't find UPM\n" ); |
183 | return ret; |
184 | } |
185 | |
186 | prop = of_get_property(node: ofdev->dev.of_node, name: "fsl,upm-addr-offset" , |
187 | lenp: &size); |
188 | if (!prop || size != sizeof(uint32_t)) { |
189 | dev_err(&ofdev->dev, "can't get UPM address offset\n" ); |
190 | return -EINVAL; |
191 | } |
192 | fun->upm_addr_offset = *prop; |
193 | |
194 | prop = of_get_property(node: ofdev->dev.of_node, name: "fsl,upm-cmd-offset" , lenp: &size); |
195 | if (!prop || size != sizeof(uint32_t)) { |
196 | dev_err(&ofdev->dev, "can't get UPM command offset\n" ); |
197 | return -EINVAL; |
198 | } |
199 | fun->upm_cmd_offset = *prop; |
200 | |
201 | prop = of_get_property(node: ofdev->dev.of_node, |
202 | name: "fsl,upm-addr-line-cs-offsets" , lenp: &size); |
203 | if (prop && (size / sizeof(uint32_t)) > 0) { |
204 | fun->mchip_count = size / sizeof(uint32_t); |
205 | if (fun->mchip_count >= NAND_MAX_CHIPS) { |
206 | dev_err(&ofdev->dev, "too much multiple chips\n" ); |
207 | return -EINVAL; |
208 | } |
209 | for (i = 0; i < fun->mchip_count; i++) |
210 | fun->mchip_offsets[i] = be32_to_cpu(prop[i]); |
211 | } else { |
212 | fun->mchip_count = 1; |
213 | } |
214 | |
215 | for (i = 0; i < fun->mchip_count; i++) { |
216 | fun->rnb_gpio[i] = devm_gpiod_get_index_optional(dev: &ofdev->dev, |
217 | NULL, index: i, |
218 | flags: GPIOD_IN); |
219 | if (IS_ERR(ptr: fun->rnb_gpio[i])) { |
220 | dev_err(&ofdev->dev, "RNB gpio #%d is invalid\n" , i); |
221 | return PTR_ERR(ptr: fun->rnb_gpio[i]); |
222 | } |
223 | } |
224 | |
225 | nand_controller_init(nfc: &fun->base); |
226 | fun->base.ops = &fun_ops; |
227 | fun->dev = &ofdev->dev; |
228 | |
229 | ret = fun_chip_init(fun, upm_np: ofdev->dev.of_node, io_res); |
230 | if (ret) |
231 | return ret; |
232 | |
233 | dev_set_drvdata(dev: &ofdev->dev, data: fun); |
234 | |
235 | return 0; |
236 | } |
237 | |
238 | static void fun_remove(struct platform_device *ofdev) |
239 | { |
240 | struct fsl_upm_nand *fun = dev_get_drvdata(dev: &ofdev->dev); |
241 | struct nand_chip *chip = &fun->chip; |
242 | struct mtd_info *mtd = nand_to_mtd(chip); |
243 | int ret; |
244 | |
245 | ret = mtd_device_unregister(master: mtd); |
246 | WARN_ON(ret); |
247 | nand_cleanup(chip); |
248 | } |
249 | |
250 | static const struct of_device_id of_fun_match[] = { |
251 | { .compatible = "fsl,upm-nand" }, |
252 | {}, |
253 | }; |
254 | MODULE_DEVICE_TABLE(of, of_fun_match); |
255 | |
256 | static struct platform_driver of_fun_driver = { |
257 | .driver = { |
258 | .name = "fsl,upm-nand" , |
259 | .of_match_table = of_fun_match, |
260 | }, |
261 | .probe = fun_probe, |
262 | .remove_new = fun_remove, |
263 | }; |
264 | |
265 | module_platform_driver(of_fun_driver); |
266 | |
267 | MODULE_LICENSE("GPL" ); |
268 | MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>" ); |
269 | MODULE_DESCRIPTION("Driver for NAND chips working through Freescale " |
270 | "LocalBus User-Programmable Machine" ); |
271 | |