]>
Commit | Line | Data |
---|---|---|
62a8a739 BG |
1 | /* |
2 | * TI da8xx DDR2/mDDR controller driver | |
3 | * | |
4 | * Copyright (C) 2016 BayLibre SAS | |
5 | * | |
6 | * Author: | |
7 | * Bartosz Golaszewski <bgolaszewski@baylibre.com> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License version 2 as | |
11 | * published by the Free Software Foundation. | |
12 | */ | |
13 | ||
14 | #include <linux/module.h> | |
15 | #include <linux/of.h> | |
16 | #include <linux/of_device.h> | |
62a8a739 BG |
17 | #include <linux/platform_device.h> |
18 | #include <linux/io.h> | |
19 | ||
20 | /* | |
21 | * REVISIT: Linux doesn't have a good framework for the kind of performance | |
22 | * knobs this driver controls. We can't use device tree properties as it deals | |
23 | * with hardware configuration rather than description. We also don't want to | |
24 | * commit to maintaining some random sysfs attributes. | |
25 | * | |
26 | * For now we just hardcode the register values for the boards that need | |
27 | * some changes (as is the case for the LCD controller on da850-lcdk - the | |
28 | * first board we support here). When linux gets an appropriate framework, | |
29 | * we'll easily convert the driver to it. | |
30 | */ | |
31 | ||
32 | struct da8xx_ddrctl_config_knob { | |
33 | const char *name; | |
34 | u32 reg; | |
35 | u32 mask; | |
36 | u32 shift; | |
37 | }; | |
38 | ||
39 | static const struct da8xx_ddrctl_config_knob da8xx_ddrctl_knobs[] = { | |
40 | { | |
41 | .name = "da850-pbbpr", | |
42 | .reg = 0x20, | |
43 | .mask = 0xffffff00, | |
44 | .shift = 0, | |
45 | }, | |
46 | }; | |
47 | ||
48 | struct da8xx_ddrctl_setting { | |
49 | const char *name; | |
50 | u32 val; | |
51 | }; | |
52 | ||
53 | struct da8xx_ddrctl_board_settings { | |
54 | const char *board; | |
55 | const struct da8xx_ddrctl_setting *settings; | |
56 | }; | |
57 | ||
58 | static const struct da8xx_ddrctl_setting da850_lcdk_ddrctl_settings[] = { | |
59 | { | |
60 | .name = "da850-pbbpr", | |
61 | .val = 0x20, | |
62 | }, | |
63 | { } | |
64 | }; | |
65 | ||
66 | static const struct da8xx_ddrctl_board_settings da8xx_ddrctl_board_confs[] = { | |
67 | { | |
68 | .board = "ti,da850-lcdk", | |
69 | .settings = da850_lcdk_ddrctl_settings, | |
70 | }, | |
71 | }; | |
72 | ||
73 | static const struct da8xx_ddrctl_config_knob * | |
74 | da8xx_ddrctl_match_knob(const struct da8xx_ddrctl_setting *setting) | |
75 | { | |
76 | const struct da8xx_ddrctl_config_knob *knob; | |
77 | int i; | |
78 | ||
79 | for (i = 0; i < ARRAY_SIZE(da8xx_ddrctl_knobs); i++) { | |
80 | knob = &da8xx_ddrctl_knobs[i]; | |
81 | ||
82 | if (strcmp(knob->name, setting->name) == 0) | |
83 | return knob; | |
84 | } | |
85 | ||
86 | return NULL; | |
87 | } | |
88 | ||
89 | static const struct da8xx_ddrctl_setting *da8xx_ddrctl_get_board_settings(void) | |
90 | { | |
91 | const struct da8xx_ddrctl_board_settings *board_settings; | |
92 | int i; | |
93 | ||
94 | for (i = 0; i < ARRAY_SIZE(da8xx_ddrctl_board_confs); i++) { | |
95 | board_settings = &da8xx_ddrctl_board_confs[i]; | |
96 | ||
97 | if (of_machine_is_compatible(board_settings->board)) | |
98 | return board_settings->settings; | |
99 | } | |
100 | ||
101 | return NULL; | |
102 | } | |
103 | ||
104 | static int da8xx_ddrctl_probe(struct platform_device *pdev) | |
105 | { | |
106 | const struct da8xx_ddrctl_config_knob *knob; | |
107 | const struct da8xx_ddrctl_setting *setting; | |
108 | struct device_node *node; | |
109 | struct resource *res; | |
110 | void __iomem *ddrctl; | |
111 | struct device *dev; | |
112 | u32 reg; | |
113 | ||
114 | dev = &pdev->dev; | |
115 | node = dev->of_node; | |
116 | ||
117 | setting = da8xx_ddrctl_get_board_settings(); | |
118 | if (!setting) { | |
d0c7546f | 119 | dev_err(dev, "no settings defined for this board\n"); |
62a8a739 BG |
120 | return -EINVAL; |
121 | } | |
122 | ||
123 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
124 | ddrctl = devm_ioremap_resource(dev, res); | |
125 | if (IS_ERR(ddrctl)) { | |
126 | dev_err(dev, "unable to map memory controller registers\n"); | |
127 | return PTR_ERR(ddrctl); | |
128 | } | |
129 | ||
130 | for (; setting->name; setting++) { | |
131 | knob = da8xx_ddrctl_match_knob(setting); | |
132 | if (!knob) { | |
133 | dev_warn(dev, | |
134 | "no such config option: %s\n", setting->name); | |
135 | continue; | |
136 | } | |
137 | ||
138 | if (knob->reg + sizeof(u32) > resource_size(res)) { | |
139 | dev_warn(dev, | |
140 | "register offset of '%s' exceeds mapped memory size\n", | |
141 | knob->name); | |
142 | continue; | |
143 | } | |
144 | ||
145 | reg = readl(ddrctl + knob->reg); | |
146 | reg &= knob->mask; | |
147 | reg |= setting->val << knob->shift; | |
148 | ||
149 | dev_dbg(dev, "writing 0x%08x to %s\n", reg, setting->name); | |
150 | ||
151 | writel(reg, ddrctl + knob->reg); | |
152 | } | |
153 | ||
154 | return 0; | |
155 | } | |
156 | ||
157 | static const struct of_device_id da8xx_ddrctl_of_match[] = { | |
158 | { .compatible = "ti,da850-ddr-controller", }, | |
159 | { }, | |
160 | }; | |
161 | ||
162 | static struct platform_driver da8xx_ddrctl_driver = { | |
163 | .probe = da8xx_ddrctl_probe, | |
164 | .driver = { | |
165 | .name = "da850-ddr-controller", | |
166 | .of_match_table = da8xx_ddrctl_of_match, | |
167 | }, | |
168 | }; | |
169 | module_platform_driver(da8xx_ddrctl_driver); | |
170 | ||
171 | MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>"); | |
172 | MODULE_DESCRIPTION("TI da8xx DDR2/mDDR controller driver"); | |
173 | MODULE_LICENSE("GPL v2"); |