Merge remote-tracking branch 'torvalds/master' into perf/urgent
[linux-2.6-microblaze.git] / drivers / bus / imx-weim.c
1 /*
2  * EIM driver for Freescale's i.MX chips
3  *
4  * Copyright (C) 2013 Freescale Semiconductor, Inc.
5  *
6  * This file is licensed under the terms of the GNU General Public
7  * License version 2. This program is licensed "as is" without any
8  * warranty of any kind, whether express or implied.
9  */
10 #include <linux/module.h>
11 #include <linux/clk.h>
12 #include <linux/io.h>
13 #include <linux/of_device.h>
14 #include <linux/mfd/syscon.h>
15 #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
16 #include <linux/regmap.h>
17
18 struct imx_weim_devtype {
19         unsigned int    cs_count;
20         unsigned int    cs_regs_count;
21         unsigned int    cs_stride;
22         unsigned int    wcr_offset;
23         unsigned int    wcr_bcm;
24 };
25
26 static const struct imx_weim_devtype imx1_weim_devtype = {
27         .cs_count       = 6,
28         .cs_regs_count  = 2,
29         .cs_stride      = 0x08,
30 };
31
32 static const struct imx_weim_devtype imx27_weim_devtype = {
33         .cs_count       = 6,
34         .cs_regs_count  = 3,
35         .cs_stride      = 0x10,
36 };
37
38 static const struct imx_weim_devtype imx50_weim_devtype = {
39         .cs_count       = 4,
40         .cs_regs_count  = 6,
41         .cs_stride      = 0x18,
42         .wcr_offset     = 0x90,
43         .wcr_bcm        = BIT(0),
44 };
45
46 static const struct imx_weim_devtype imx51_weim_devtype = {
47         .cs_count       = 6,
48         .cs_regs_count  = 6,
49         .cs_stride      = 0x18,
50 };
51
52 #define MAX_CS_REGS_COUNT       6
53 #define MAX_CS_COUNT            6
54 #define OF_REG_SIZE             3
55
56 struct cs_timing {
57         bool is_applied;
58         u32 regs[MAX_CS_REGS_COUNT];
59 };
60
61 struct cs_timing_state {
62         struct cs_timing cs[MAX_CS_COUNT];
63 };
64
65 static const struct of_device_id weim_id_table[] = {
66         /* i.MX1/21 */
67         { .compatible = "fsl,imx1-weim", .data = &imx1_weim_devtype, },
68         /* i.MX25/27/31/35 */
69         { .compatible = "fsl,imx27-weim", .data = &imx27_weim_devtype, },
70         /* i.MX50/53/6Q */
71         { .compatible = "fsl,imx50-weim", .data = &imx50_weim_devtype, },
72         { .compatible = "fsl,imx6q-weim", .data = &imx50_weim_devtype, },
73         /* i.MX51 */
74         { .compatible = "fsl,imx51-weim", .data = &imx51_weim_devtype, },
75         { }
76 };
77 MODULE_DEVICE_TABLE(of, weim_id_table);
78
79 static int imx_weim_gpr_setup(struct platform_device *pdev)
80 {
81         struct device_node *np = pdev->dev.of_node;
82         struct property *prop;
83         const __be32 *p;
84         struct regmap *gpr;
85         u32 gprvals[4] = {
86                 05,     /* CS0(128M) CS1(0M)  CS2(0M)  CS3(0M)  */
87                 033,    /* CS0(64M)  CS1(64M) CS2(0M)  CS3(0M)  */
88                 0113,   /* CS0(64M)  CS1(32M) CS2(32M) CS3(0M)  */
89                 01111,  /* CS0(32M)  CS1(32M) CS2(32M) CS3(32M) */
90         };
91         u32 gprval = 0;
92         u32 val;
93         int cs = 0;
94         int i = 0;
95
96         gpr = syscon_regmap_lookup_by_phandle(np, "fsl,weim-cs-gpr");
97         if (IS_ERR(gpr)) {
98                 dev_dbg(&pdev->dev, "failed to find weim-cs-gpr\n");
99                 return 0;
100         }
101
102         of_property_for_each_u32(np, "ranges", prop, p, val) {
103                 if (i % 4 == 0) {
104                         cs = val;
105                 } else if (i % 4 == 3 && val) {
106                         val = (val / SZ_32M) | 1;
107                         gprval |= val << cs * 3;
108                 }
109                 i++;
110         }
111
112         if (i == 0 || i % 4)
113                 goto err;
114
115         for (i = 0; i < ARRAY_SIZE(gprvals); i++) {
116                 if (gprval == gprvals[i]) {
117                         /* Found it. Set up IOMUXC_GPR1[11:0] with it. */
118                         regmap_update_bits(gpr, IOMUXC_GPR1, 0xfff, gprval);
119                         return 0;
120                 }
121         }
122
123 err:
124         dev_err(&pdev->dev, "Invalid 'ranges' configuration\n");
125         return -EINVAL;
126 }
127
128 /* Parse and set the timing for this device. */
129 static int weim_timing_setup(struct device *dev,
130                              struct device_node *np, void __iomem *base,
131                              const struct imx_weim_devtype *devtype,
132                              struct cs_timing_state *ts)
133 {
134         u32 cs_idx, value[MAX_CS_REGS_COUNT];
135         int i, ret;
136         int reg_idx, num_regs;
137         struct cs_timing *cst;
138
139         if (WARN_ON(devtype->cs_regs_count > MAX_CS_REGS_COUNT))
140                 return -EINVAL;
141         if (WARN_ON(devtype->cs_count > MAX_CS_COUNT))
142                 return -EINVAL;
143
144         ret = of_property_read_u32_array(np, "fsl,weim-cs-timing",
145                                          value, devtype->cs_regs_count);
146         if (ret)
147                 return ret;
148
149         /*
150          * the child node's "reg" property may contain multiple address ranges,
151          * extract the chip select for each.
152          */
153         num_regs = of_property_count_elems_of_size(np, "reg", OF_REG_SIZE);
154         if (num_regs < 0)
155                 return num_regs;
156         if (!num_regs)
157                 return -EINVAL;
158         for (reg_idx = 0; reg_idx < num_regs; reg_idx++) {
159                 /* get the CS index from this child node's "reg" property. */
160                 ret = of_property_read_u32_index(np, "reg",
161                                         reg_idx * OF_REG_SIZE, &cs_idx);
162                 if (ret)
163                         break;
164
165                 if (cs_idx >= devtype->cs_count)
166                         return -EINVAL;
167
168                 /* prevent re-configuring a CS that's already been configured */
169                 cst = &ts->cs[cs_idx];
170                 if (cst->is_applied && memcmp(value, cst->regs,
171                                         devtype->cs_regs_count * sizeof(u32))) {
172                         dev_err(dev, "fsl,weim-cs-timing conflict on %pOF", np);
173                         return -EINVAL;
174                 }
175
176                 /* set the timing for WEIM */
177                 for (i = 0; i < devtype->cs_regs_count; i++)
178                         writel(value[i],
179                                 base + cs_idx * devtype->cs_stride + i * 4);
180                 if (!cst->is_applied) {
181                         cst->is_applied = true;
182                         memcpy(cst->regs, value,
183                                 devtype->cs_regs_count * sizeof(u32));
184                 }
185         }
186
187         return 0;
188 }
189
190 static int weim_parse_dt(struct platform_device *pdev, void __iomem *base)
191 {
192         const struct of_device_id *of_id = of_match_device(weim_id_table,
193                                                            &pdev->dev);
194         const struct imx_weim_devtype *devtype = of_id->data;
195         struct device_node *child;
196         int ret, have_child = 0;
197         struct cs_timing_state ts = {};
198         u32 reg;
199
200         if (devtype == &imx50_weim_devtype) {
201                 ret = imx_weim_gpr_setup(pdev);
202                 if (ret)
203                         return ret;
204         }
205
206         if (of_property_read_bool(pdev->dev.of_node, "fsl,burst-clk-enable")) {
207                 if (devtype->wcr_bcm) {
208                         reg = readl(base + devtype->wcr_offset);
209                         writel(reg | devtype->wcr_bcm,
210                                 base + devtype->wcr_offset);
211                 } else {
212                         dev_err(&pdev->dev, "burst clk mode not supported.\n");
213                         return -EINVAL;
214                 }
215         }
216
217         for_each_available_child_of_node(pdev->dev.of_node, child) {
218                 ret = weim_timing_setup(&pdev->dev, child, base, devtype, &ts);
219                 if (ret)
220                         dev_warn(&pdev->dev, "%pOF set timing failed.\n",
221                                 child);
222                 else
223                         have_child = 1;
224         }
225
226         if (have_child)
227                 ret = of_platform_default_populate(pdev->dev.of_node,
228                                                    NULL, &pdev->dev);
229         if (ret)
230                 dev_err(&pdev->dev, "%pOF fail to create devices.\n",
231                         pdev->dev.of_node);
232         return ret;
233 }
234
235 static int weim_probe(struct platform_device *pdev)
236 {
237         struct resource *res;
238         struct clk *clk;
239         void __iomem *base;
240         int ret;
241
242         /* get the resource */
243         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
244         base = devm_ioremap_resource(&pdev->dev, res);
245         if (IS_ERR(base))
246                 return PTR_ERR(base);
247
248         /* get the clock */
249         clk = devm_clk_get(&pdev->dev, NULL);
250         if (IS_ERR(clk))
251                 return PTR_ERR(clk);
252
253         ret = clk_prepare_enable(clk);
254         if (ret)
255                 return ret;
256
257         /* parse the device node */
258         ret = weim_parse_dt(pdev, base);
259         if (ret)
260                 clk_disable_unprepare(clk);
261         else
262                 dev_info(&pdev->dev, "Driver registered.\n");
263
264         return ret;
265 }
266
267 static struct platform_driver weim_driver = {
268         .driver = {
269                 .name           = "imx-weim",
270                 .of_match_table = weim_id_table,
271         },
272         .probe = weim_probe,
273 };
274 module_platform_driver(weim_driver);
275
276 MODULE_AUTHOR("Freescale Semiconductor Inc.");
277 MODULE_DESCRIPTION("i.MX EIM Controller Driver");
278 MODULE_LICENSE("GPL");