1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2006-2007 PA Semi, Inc |
4 | * |
5 | * Author: Egor Martovetsky <egor@pasemi.com> |
6 | * Maintained by: Olof Johansson <olof@lixom.net> |
7 | * |
8 | * Driver for the PWRficient onchip NAND flash interface |
9 | */ |
10 | |
11 | #undef DEBUG |
12 | |
13 | #include <linux/slab.h> |
14 | #include <linux/module.h> |
15 | #include <linux/mtd/mtd.h> |
16 | #include <linux/mtd/rawnand.h> |
17 | #include <linux/of_address.h> |
18 | #include <linux/of_irq.h> |
19 | #include <linux/of_platform.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/pci.h> |
22 | |
23 | #include <asm/io.h> |
24 | |
25 | #define LBICTRL_LPCCTL_NR 0x00004000 |
26 | #define CLE_PIN_CTL 15 |
27 | #define ALE_PIN_CTL 14 |
28 | |
29 | struct pasemi_ddata { |
30 | struct nand_chip chip; |
31 | unsigned int lpcctl; |
32 | struct nand_controller controller; |
33 | }; |
34 | |
35 | static const char driver_name[] = "pasemi-nand" ; |
36 | |
37 | static void pasemi_read_buf(struct nand_chip *chip, u_char *buf, int len) |
38 | { |
39 | while (len > 0x800) { |
40 | memcpy_fromio(buf, chip->legacy.IO_ADDR_R, 0x800); |
41 | buf += 0x800; |
42 | len -= 0x800; |
43 | } |
44 | memcpy_fromio(buf, chip->legacy.IO_ADDR_R, len); |
45 | } |
46 | |
47 | static void pasemi_write_buf(struct nand_chip *chip, const u_char *buf, |
48 | int len) |
49 | { |
50 | while (len > 0x800) { |
51 | memcpy_toio(chip->legacy.IO_ADDR_R, buf, 0x800); |
52 | buf += 0x800; |
53 | len -= 0x800; |
54 | } |
55 | memcpy_toio(chip->legacy.IO_ADDR_R, buf, len); |
56 | } |
57 | |
58 | static void pasemi_hwcontrol(struct nand_chip *chip, int cmd, |
59 | unsigned int ctrl) |
60 | { |
61 | struct pasemi_ddata *ddata = container_of(chip, struct pasemi_ddata, chip); |
62 | |
63 | if (cmd == NAND_CMD_NONE) |
64 | return; |
65 | |
66 | if (ctrl & NAND_CLE) |
67 | out_8(chip->legacy.IO_ADDR_W + (1 << CLE_PIN_CTL), cmd); |
68 | else |
69 | out_8(chip->legacy.IO_ADDR_W + (1 << ALE_PIN_CTL), cmd); |
70 | |
71 | /* Push out posted writes */ |
72 | eieio(); |
73 | inl(port: ddata->lpcctl); |
74 | } |
75 | |
76 | static int pasemi_device_ready(struct nand_chip *chip) |
77 | { |
78 | struct pasemi_ddata *ddata = container_of(chip, struct pasemi_ddata, chip); |
79 | |
80 | return !!(inl(port: ddata->lpcctl) & LBICTRL_LPCCTL_NR); |
81 | } |
82 | |
83 | static int pasemi_attach_chip(struct nand_chip *chip) |
84 | { |
85 | if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && |
86 | chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) |
87 | chip->ecc.algo = NAND_ECC_ALGO_HAMMING; |
88 | |
89 | return 0; |
90 | } |
91 | |
92 | static const struct nand_controller_ops pasemi_ops = { |
93 | .attach_chip = pasemi_attach_chip, |
94 | }; |
95 | |
96 | static int pasemi_nand_probe(struct platform_device *ofdev) |
97 | { |
98 | struct device *dev = &ofdev->dev; |
99 | struct pci_dev *pdev; |
100 | struct device_node *np = dev->of_node; |
101 | struct resource res; |
102 | struct nand_chip *chip; |
103 | struct nand_controller *controller; |
104 | int err = 0; |
105 | struct pasemi_ddata *ddata; |
106 | struct mtd_info *pasemi_nand_mtd; |
107 | |
108 | err = of_address_to_resource(dev: np, index: 0, r: &res); |
109 | |
110 | if (err) |
111 | return -EINVAL; |
112 | |
113 | dev_dbg(dev, "pasemi_nand at %pR\n" , &res); |
114 | |
115 | /* Allocate memory for MTD device structure and private data */ |
116 | ddata = kzalloc(size: sizeof(*ddata), GFP_KERNEL); |
117 | if (!ddata) { |
118 | err = -ENOMEM; |
119 | goto out; |
120 | } |
121 | platform_set_drvdata(pdev: ofdev, data: ddata); |
122 | chip = &ddata->chip; |
123 | controller = &ddata->controller; |
124 | |
125 | controller->ops = &pasemi_ops; |
126 | nand_controller_init(nfc: controller); |
127 | chip->controller = controller; |
128 | |
129 | pasemi_nand_mtd = nand_to_mtd(chip); |
130 | |
131 | /* Link the private data with the MTD structure */ |
132 | pasemi_nand_mtd->dev.parent = dev; |
133 | |
134 | chip->legacy.IO_ADDR_R = of_iomap(node: np, index: 0); |
135 | chip->legacy.IO_ADDR_W = chip->legacy.IO_ADDR_R; |
136 | |
137 | if (!chip->legacy.IO_ADDR_R) { |
138 | err = -EIO; |
139 | goto out_mtd; |
140 | } |
141 | |
142 | pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, device: 0xa008, NULL); |
143 | if (!pdev) { |
144 | err = -ENODEV; |
145 | goto out_ior; |
146 | } |
147 | |
148 | ddata->lpcctl = pci_resource_start(pdev, 0); |
149 | pci_dev_put(dev: pdev); |
150 | |
151 | if (!request_region(ddata->lpcctl, 4, driver_name)) { |
152 | err = -EBUSY; |
153 | goto out_ior; |
154 | } |
155 | |
156 | chip->legacy.cmd_ctrl = pasemi_hwcontrol; |
157 | chip->legacy.dev_ready = pasemi_device_ready; |
158 | chip->legacy.read_buf = pasemi_read_buf; |
159 | chip->legacy.write_buf = pasemi_write_buf; |
160 | chip->legacy.chip_delay = 0; |
161 | |
162 | /* Enable the following for a flash based bad block table */ |
163 | chip->bbt_options = NAND_BBT_USE_FLASH; |
164 | |
165 | /* |
166 | * This driver assumes that the default ECC engine should be TYPE_SOFT. |
167 | * Set ->engine_type before registering the NAND devices in order to |
168 | * provide a driver specific default value. |
169 | */ |
170 | chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; |
171 | |
172 | /* Scan to find existence of the device */ |
173 | err = nand_scan(chip, max_chips: 1); |
174 | if (err) |
175 | goto out_lpc; |
176 | |
177 | if (mtd_device_register(pasemi_nand_mtd, NULL, 0)) { |
178 | dev_err(dev, "Unable to register MTD device\n" ); |
179 | err = -ENODEV; |
180 | goto out_cleanup_nand; |
181 | } |
182 | |
183 | dev_info(dev, "PA Semi NAND flash at %pR, control at I/O %x\n" , &res, |
184 | ddata->lpcctl); |
185 | |
186 | return 0; |
187 | |
188 | out_cleanup_nand: |
189 | nand_cleanup(chip); |
190 | out_lpc: |
191 | release_region(ddata->lpcctl, 4); |
192 | out_ior: |
193 | iounmap(addr: chip->legacy.IO_ADDR_R); |
194 | out_mtd: |
195 | kfree(objp: ddata); |
196 | out: |
197 | return err; |
198 | } |
199 | |
200 | static void pasemi_nand_remove(struct platform_device *ofdev) |
201 | { |
202 | struct pasemi_ddata *ddata = platform_get_drvdata(pdev: ofdev); |
203 | struct mtd_info *pasemi_nand_mtd; |
204 | int ret; |
205 | struct nand_chip *chip; |
206 | |
207 | chip = &ddata->chip; |
208 | pasemi_nand_mtd = nand_to_mtd(chip); |
209 | |
210 | /* Release resources, unregister device */ |
211 | ret = mtd_device_unregister(master: pasemi_nand_mtd); |
212 | WARN_ON(ret); |
213 | nand_cleanup(chip); |
214 | |
215 | release_region(ddata->lpcctl, 4); |
216 | |
217 | iounmap(addr: chip->legacy.IO_ADDR_R); |
218 | |
219 | /* Free the MTD device structure */ |
220 | kfree(objp: ddata); |
221 | } |
222 | |
223 | static const struct of_device_id pasemi_nand_match[] = |
224 | { |
225 | { |
226 | .compatible = "pasemi,localbus-nand" , |
227 | }, |
228 | {}, |
229 | }; |
230 | |
231 | MODULE_DEVICE_TABLE(of, pasemi_nand_match); |
232 | |
233 | static struct platform_driver pasemi_nand_driver = |
234 | { |
235 | .driver = { |
236 | .name = driver_name, |
237 | .of_match_table = pasemi_nand_match, |
238 | }, |
239 | .probe = pasemi_nand_probe, |
240 | .remove_new = pasemi_nand_remove, |
241 | }; |
242 | |
243 | module_platform_driver(pasemi_nand_driver); |
244 | |
245 | MODULE_LICENSE("GPL" ); |
246 | MODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>" ); |
247 | MODULE_DESCRIPTION("NAND flash interface driver for PA Semi PWRficient" ); |
248 | |