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