1/*
2 * drivers/mtd/nand/orion_nand.c
3 *
4 * NAND support for Marvell Orion SoC platforms
5 *
6 * Tzachi Perelstein <tzachi@marvell.com>
7 *
8 * This file is licensed under  the terms of the GNU General Public
9 * License version 2. This program is licensed "as is" without any
10 * warranty of any kind, whether express or implied.
11 */
12
13#include <linux/slab.h>
14#include <linux/module.h>
15#include <linux/platform_device.h>
16#include <linux/of.h>
17#include <linux/mtd/mtd.h>
18#include <linux/mtd/nand.h>
19#include <linux/mtd/partitions.h>
20#include <linux/clk.h>
21#include <linux/err.h>
22#include <linux/io.h>
23#include <asm/sizes.h>
24#include <linux/platform_data/mtd-orion_nand.h>
25
26static void orion_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
27{
28	struct nand_chip *nc = mtd->priv;
29	struct orion_nand_data *board = nc->priv;
30	u32 offs;
31
32	if (cmd == NAND_CMD_NONE)
33		return;
34
35	if (ctrl & NAND_CLE)
36		offs = (1 << board->cle);
37	else if (ctrl & NAND_ALE)
38		offs = (1 << board->ale);
39	else
40		return;
41
42	if (nc->options & NAND_BUSWIDTH_16)
43		offs <<= 1;
44
45	writeb(cmd, nc->IO_ADDR_W + offs);
46}
47
48static void orion_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
49{
50	struct nand_chip *chip = mtd->priv;
51	void __iomem *io_base = chip->IO_ADDR_R;
52	uint64_t *buf64;
53	int i = 0;
54
55	while (len && (unsigned long)buf & 7) {
56		*buf++ = readb(io_base);
57		len--;
58	}
59	buf64 = (uint64_t *)buf;
60	while (i < len/8) {
61		/*
62		 * Since GCC has no proper constraint (PR 43518)
63		 * force x variable to r2/r3 registers as ldrd instruction
64		 * requires first register to be even.
65		 */
66		register uint64_t x asm ("r2");
67
68		asm volatile ("ldrd\t%0, [%1]" : "=&r" (x) : "r" (io_base));
69		buf64[i++] = x;
70	}
71	i *= 8;
72	while (i < len)
73		buf[i++] = readb(io_base);
74}
75
76static int __init orion_nand_probe(struct platform_device *pdev)
77{
78	struct mtd_info *mtd;
79	struct mtd_part_parser_data ppdata = {};
80	struct nand_chip *nc;
81	struct orion_nand_data *board;
82	struct resource *res;
83	struct clk *clk;
84	void __iomem *io_base;
85	int ret = 0;
86	u32 val = 0;
87
88	nc = devm_kzalloc(&pdev->dev,
89			sizeof(struct nand_chip) + sizeof(struct mtd_info),
90			GFP_KERNEL);
91	if (!nc)
92		return -ENOMEM;
93	mtd = (struct mtd_info *)(nc + 1);
94
95	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
96	io_base = devm_ioremap_resource(&pdev->dev, res);
97
98	if (IS_ERR(io_base))
99		return PTR_ERR(io_base);
100
101	if (pdev->dev.of_node) {
102		board = devm_kzalloc(&pdev->dev, sizeof(struct orion_nand_data),
103					GFP_KERNEL);
104		if (!board)
105			return -ENOMEM;
106		if (!of_property_read_u32(pdev->dev.of_node, "cle", &val))
107			board->cle = (u8)val;
108		else
109			board->cle = 0;
110		if (!of_property_read_u32(pdev->dev.of_node, "ale", &val))
111			board->ale = (u8)val;
112		else
113			board->ale = 1;
114		if (!of_property_read_u32(pdev->dev.of_node,
115						"bank-width", &val))
116			board->width = (u8)val * 8;
117		else
118			board->width = 8;
119		if (!of_property_read_u32(pdev->dev.of_node,
120						"chip-delay", &val))
121			board->chip_delay = (u8)val;
122	} else {
123		board = dev_get_platdata(&pdev->dev);
124	}
125
126	mtd->priv = nc;
127	mtd->dev.parent = &pdev->dev;
128
129	nc->priv = board;
130	nc->IO_ADDR_R = nc->IO_ADDR_W = io_base;
131	nc->cmd_ctrl = orion_nand_cmd_ctrl;
132	nc->read_buf = orion_nand_read_buf;
133	nc->ecc.mode = NAND_ECC_SOFT;
134
135	if (board->chip_delay)
136		nc->chip_delay = board->chip_delay;
137
138	WARN(board->width > 16,
139		"%d bit bus width out of range",
140		board->width);
141
142	if (board->width == 16)
143		nc->options |= NAND_BUSWIDTH_16;
144
145	if (board->dev_ready)
146		nc->dev_ready = board->dev_ready;
147
148	platform_set_drvdata(pdev, mtd);
149
150	/* Not all platforms can gate the clock, so it is not
151	   an error if the clock does not exists. */
152	clk = clk_get(&pdev->dev, NULL);
153	if (!IS_ERR(clk)) {
154		clk_prepare_enable(clk);
155		clk_put(clk);
156	}
157
158	if (nand_scan(mtd, 1)) {
159		ret = -ENXIO;
160		goto no_dev;
161	}
162
163	mtd->name = "orion_nand";
164	ppdata.of_node = pdev->dev.of_node;
165	ret = mtd_device_parse_register(mtd, NULL, &ppdata,
166			board->parts, board->nr_parts);
167	if (ret) {
168		nand_release(mtd);
169		goto no_dev;
170	}
171
172	return 0;
173
174no_dev:
175	if (!IS_ERR(clk)) {
176		clk_disable_unprepare(clk);
177		clk_put(clk);
178	}
179
180	return ret;
181}
182
183static int orion_nand_remove(struct platform_device *pdev)
184{
185	struct mtd_info *mtd = platform_get_drvdata(pdev);
186	struct clk *clk;
187
188	nand_release(mtd);
189
190	clk = clk_get(&pdev->dev, NULL);
191	if (!IS_ERR(clk)) {
192		clk_disable_unprepare(clk);
193		clk_put(clk);
194	}
195
196	return 0;
197}
198
199#ifdef CONFIG_OF
200static const struct of_device_id orion_nand_of_match_table[] = {
201	{ .compatible = "marvell,orion-nand", },
202	{},
203};
204MODULE_DEVICE_TABLE(of, orion_nand_of_match_table);
205#endif
206
207static struct platform_driver orion_nand_driver = {
208	.remove		= orion_nand_remove,
209	.driver		= {
210		.name	= "orion_nand",
211		.of_match_table = of_match_ptr(orion_nand_of_match_table),
212	},
213};
214
215module_platform_driver_probe(orion_nand_driver, orion_nand_probe);
216
217MODULE_LICENSE("GPL");
218MODULE_AUTHOR("Tzachi Perelstein");
219MODULE_DESCRIPTION("NAND glue for Orion platforms");
220MODULE_ALIAS("platform:orion_nand");
221