1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2021 MediaTek Inc. |
4 | */ |
5 | |
6 | #include <linux/bitfield.h> |
7 | #include <linux/clk.h> |
8 | #include <linux/component.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/soc/mediatek/mtk-cmdq.h> |
13 | |
14 | #include "mtk_disp_drv.h" |
15 | #include "mtk_drm_crtc.h" |
16 | #include "mtk_drm_ddp_comp.h" |
17 | #include "mtk_drm_drv.h" |
18 | |
19 | #define DISP_GAMMA_EN 0x0000 |
20 | #define GAMMA_EN BIT(0) |
21 | #define DISP_GAMMA_CFG 0x0020 |
22 | #define GAMMA_RELAY_MODE BIT(0) |
23 | #define GAMMA_LUT_EN BIT(1) |
24 | #define GAMMA_DITHERING BIT(2) |
25 | #define GAMMA_LUT_TYPE BIT(2) |
26 | #define DISP_GAMMA_SIZE 0x0030 |
27 | #define DISP_GAMMA_SIZE_HSIZE GENMASK(28, 16) |
28 | #define DISP_GAMMA_SIZE_VSIZE GENMASK(12, 0) |
29 | #define DISP_GAMMA_BANK 0x0100 |
30 | #define DISP_GAMMA_BANK_BANK GENMASK(1, 0) |
31 | #define DISP_GAMMA_BANK_DATA_MODE BIT(2) |
32 | #define DISP_GAMMA_LUT 0x0700 |
33 | #define DISP_GAMMA_LUT1 0x0b00 |
34 | |
35 | /* For 10 bit LUT layout, R/G/B are in the same register */ |
36 | #define DISP_GAMMA_LUT_10BIT_R GENMASK(29, 20) |
37 | #define DISP_GAMMA_LUT_10BIT_G GENMASK(19, 10) |
38 | #define DISP_GAMMA_LUT_10BIT_B GENMASK(9, 0) |
39 | |
40 | /* For 12 bit LUT layout, R/G are in LUT, B is in LUT1 */ |
41 | #define DISP_GAMMA_LUT_12BIT_R GENMASK(11, 0) |
42 | #define DISP_GAMMA_LUT_12BIT_G GENMASK(23, 12) |
43 | #define DISP_GAMMA_LUT_12BIT_B GENMASK(11, 0) |
44 | |
45 | struct mtk_disp_gamma_data { |
46 | bool has_dither; |
47 | bool lut_diff; |
48 | u16 lut_bank_size; |
49 | u16 lut_size; |
50 | u8 lut_bits; |
51 | }; |
52 | |
53 | /** |
54 | * struct mtk_disp_gamma - Display Gamma driver structure |
55 | * @clk: clock for DISP_GAMMA block |
56 | * @regs: MMIO registers base |
57 | * @cmdq_reg: CMDQ Client register |
58 | * @data: platform data for DISP_GAMMA |
59 | */ |
60 | struct mtk_disp_gamma { |
61 | struct clk *clk; |
62 | void __iomem *regs; |
63 | struct cmdq_client_reg cmdq_reg; |
64 | const struct mtk_disp_gamma_data *data; |
65 | }; |
66 | |
67 | int mtk_gamma_clk_enable(struct device *dev) |
68 | { |
69 | struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); |
70 | |
71 | return clk_prepare_enable(clk: gamma->clk); |
72 | } |
73 | |
74 | void mtk_gamma_clk_disable(struct device *dev) |
75 | { |
76 | struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); |
77 | |
78 | clk_disable_unprepare(clk: gamma->clk); |
79 | } |
80 | |
81 | unsigned int mtk_gamma_get_lut_size(struct device *dev) |
82 | { |
83 | struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); |
84 | |
85 | if (gamma && gamma->data) |
86 | return gamma->data->lut_size; |
87 | return 0; |
88 | } |
89 | |
90 | static bool mtk_gamma_lut_is_descending(struct drm_color_lut *lut, u32 lut_size) |
91 | { |
92 | u64 first, last; |
93 | int last_entry = lut_size - 1; |
94 | |
95 | first = lut[0].red + lut[0].green + lut[0].blue; |
96 | last = lut[last_entry].red + lut[last_entry].green + lut[last_entry].blue; |
97 | |
98 | return !!(first > last); |
99 | } |
100 | |
101 | /* |
102 | * SoCs supporting 12-bits LUTs are using a new register layout that does |
103 | * always support (by HW) both 12-bits and 10-bits LUT but, on those, we |
104 | * ignore the support for 10-bits in this driver and always use 12-bits. |
105 | * |
106 | * Summarizing: |
107 | * - SoC HW support 9/10-bits LUT only |
108 | * - Old register layout |
109 | * - 10-bits LUT supported |
110 | * - 9-bits LUT not supported |
111 | * - SoC HW support both 10/12bits LUT |
112 | * - New register layout |
113 | * - 12-bits LUT supported |
114 | * - 10-its LUT not supported |
115 | */ |
116 | void mtk_gamma_set(struct device *dev, struct drm_crtc_state *state) |
117 | { |
118 | struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); |
119 | void __iomem *lut0_base = gamma->regs + DISP_GAMMA_LUT; |
120 | void __iomem *lut1_base = gamma->regs + DISP_GAMMA_LUT1; |
121 | u32 cfg_val, data_mode, lbank_val, word[2]; |
122 | u8 lut_bits = gamma->data->lut_bits; |
123 | int cur_bank, num_lut_banks; |
124 | struct drm_color_lut *lut; |
125 | unsigned int i; |
126 | |
127 | /* If there's no gamma lut there's nothing to do here. */ |
128 | if (!state->gamma_lut) |
129 | return; |
130 | |
131 | num_lut_banks = gamma->data->lut_size / gamma->data->lut_bank_size; |
132 | lut = (struct drm_color_lut *)state->gamma_lut->data; |
133 | |
134 | /* Switch to 12 bits data mode if supported */ |
135 | data_mode = FIELD_PREP(DISP_GAMMA_BANK_DATA_MODE, !!(lut_bits == 12)); |
136 | |
137 | for (cur_bank = 0; cur_bank < num_lut_banks; cur_bank++) { |
138 | |
139 | /* Switch gamma bank and set data mode before writing LUT */ |
140 | if (num_lut_banks > 1) { |
141 | lbank_val = FIELD_PREP(DISP_GAMMA_BANK_BANK, cur_bank); |
142 | lbank_val |= data_mode; |
143 | writel(val: lbank_val, addr: gamma->regs + DISP_GAMMA_BANK); |
144 | } |
145 | |
146 | for (i = 0; i < gamma->data->lut_bank_size; i++) { |
147 | int n = cur_bank * gamma->data->lut_bank_size + i; |
148 | struct drm_color_lut diff, hwlut; |
149 | |
150 | hwlut.red = drm_color_lut_extract(user_input: lut[n].red, bit_precision: lut_bits); |
151 | hwlut.green = drm_color_lut_extract(user_input: lut[n].green, bit_precision: lut_bits); |
152 | hwlut.blue = drm_color_lut_extract(user_input: lut[n].blue, bit_precision: lut_bits); |
153 | |
154 | if (!gamma->data->lut_diff || (i % 2 == 0)) { |
155 | if (lut_bits == 12) { |
156 | word[0] = FIELD_PREP(DISP_GAMMA_LUT_12BIT_R, hwlut.red); |
157 | word[0] |= FIELD_PREP(DISP_GAMMA_LUT_12BIT_G, hwlut.green); |
158 | word[1] = FIELD_PREP(DISP_GAMMA_LUT_12BIT_B, hwlut.blue); |
159 | } else { |
160 | word[0] = FIELD_PREP(DISP_GAMMA_LUT_10BIT_R, hwlut.red); |
161 | word[0] |= FIELD_PREP(DISP_GAMMA_LUT_10BIT_G, hwlut.green); |
162 | word[0] |= FIELD_PREP(DISP_GAMMA_LUT_10BIT_B, hwlut.blue); |
163 | } |
164 | } else { |
165 | diff.red = lut[n].red - lut[n - 1].red; |
166 | diff.red = drm_color_lut_extract(user_input: diff.red, bit_precision: lut_bits); |
167 | |
168 | diff.green = lut[n].green - lut[n - 1].green; |
169 | diff.green = drm_color_lut_extract(user_input: diff.green, bit_precision: lut_bits); |
170 | |
171 | diff.blue = lut[n].blue - lut[n - 1].blue; |
172 | diff.blue = drm_color_lut_extract(user_input: diff.blue, bit_precision: lut_bits); |
173 | |
174 | if (lut_bits == 12) { |
175 | word[0] = FIELD_PREP(DISP_GAMMA_LUT_12BIT_R, diff.red); |
176 | word[0] |= FIELD_PREP(DISP_GAMMA_LUT_12BIT_G, diff.green); |
177 | word[1] = FIELD_PREP(DISP_GAMMA_LUT_12BIT_B, diff.blue); |
178 | } else { |
179 | word[0] = FIELD_PREP(DISP_GAMMA_LUT_10BIT_R, diff.red); |
180 | word[0] |= FIELD_PREP(DISP_GAMMA_LUT_10BIT_G, diff.green); |
181 | word[0] |= FIELD_PREP(DISP_GAMMA_LUT_10BIT_B, diff.blue); |
182 | } |
183 | } |
184 | writel(val: word[0], addr: lut0_base + i * 4); |
185 | if (lut_bits == 12) |
186 | writel(val: word[1], addr: lut1_base + i * 4); |
187 | } |
188 | } |
189 | |
190 | cfg_val = readl(addr: gamma->regs + DISP_GAMMA_CFG); |
191 | |
192 | if (!gamma->data->has_dither) { |
193 | /* Descending or Rising LUT */ |
194 | if (mtk_gamma_lut_is_descending(lut, lut_size: gamma->data->lut_size - 1)) |
195 | cfg_val |= FIELD_PREP(GAMMA_LUT_TYPE, 1); |
196 | else |
197 | cfg_val &= ~GAMMA_LUT_TYPE; |
198 | } |
199 | |
200 | /* Enable the gamma table */ |
201 | cfg_val |= FIELD_PREP(GAMMA_LUT_EN, 1); |
202 | |
203 | /* Disable RELAY mode to pass the processed image */ |
204 | cfg_val &= ~GAMMA_RELAY_MODE; |
205 | |
206 | writel(val: cfg_val, addr: gamma->regs + DISP_GAMMA_CFG); |
207 | } |
208 | |
209 | void mtk_gamma_config(struct device *dev, unsigned int w, |
210 | unsigned int h, unsigned int vrefresh, |
211 | unsigned int bpc, struct cmdq_pkt *cmdq_pkt) |
212 | { |
213 | struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); |
214 | u32 sz; |
215 | |
216 | sz = FIELD_PREP(DISP_GAMMA_SIZE_HSIZE, w); |
217 | sz |= FIELD_PREP(DISP_GAMMA_SIZE_VSIZE, h); |
218 | |
219 | mtk_ddp_write(cmdq_pkt, value: sz, cmdq_reg: &gamma->cmdq_reg, regs: gamma->regs, DISP_GAMMA_SIZE); |
220 | if (gamma->data && gamma->data->has_dither) |
221 | mtk_dither_set_common(regs: gamma->regs, cmdq_reg: &gamma->cmdq_reg, bpc, |
222 | DISP_GAMMA_CFG, GAMMA_DITHERING, cmdq_pkt); |
223 | } |
224 | |
225 | void mtk_gamma_start(struct device *dev) |
226 | { |
227 | struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); |
228 | |
229 | writel(GAMMA_EN, addr: gamma->regs + DISP_GAMMA_EN); |
230 | } |
231 | |
232 | void mtk_gamma_stop(struct device *dev) |
233 | { |
234 | struct mtk_disp_gamma *gamma = dev_get_drvdata(dev); |
235 | |
236 | writel_relaxed(0x0, gamma->regs + DISP_GAMMA_EN); |
237 | } |
238 | |
239 | static int mtk_disp_gamma_bind(struct device *dev, struct device *master, |
240 | void *data) |
241 | { |
242 | return 0; |
243 | } |
244 | |
245 | static void mtk_disp_gamma_unbind(struct device *dev, struct device *master, |
246 | void *data) |
247 | { |
248 | } |
249 | |
250 | static const struct component_ops mtk_disp_gamma_component_ops = { |
251 | .bind = mtk_disp_gamma_bind, |
252 | .unbind = mtk_disp_gamma_unbind, |
253 | }; |
254 | |
255 | static int mtk_disp_gamma_probe(struct platform_device *pdev) |
256 | { |
257 | struct device *dev = &pdev->dev; |
258 | struct mtk_disp_gamma *priv; |
259 | struct resource *res; |
260 | int ret; |
261 | |
262 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
263 | if (!priv) |
264 | return -ENOMEM; |
265 | |
266 | priv->clk = devm_clk_get(dev, NULL); |
267 | if (IS_ERR(ptr: priv->clk)) { |
268 | dev_err(dev, "failed to get gamma clk\n" ); |
269 | return PTR_ERR(ptr: priv->clk); |
270 | } |
271 | |
272 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
273 | priv->regs = devm_ioremap_resource(dev, res); |
274 | if (IS_ERR(ptr: priv->regs)) { |
275 | dev_err(dev, "failed to ioremap gamma\n" ); |
276 | return PTR_ERR(ptr: priv->regs); |
277 | } |
278 | |
279 | #if IS_REACHABLE(CONFIG_MTK_CMDQ) |
280 | ret = cmdq_dev_get_client_reg(dev, client_reg: &priv->cmdq_reg, idx: 0); |
281 | if (ret) |
282 | dev_dbg(dev, "get mediatek,gce-client-reg fail!\n" ); |
283 | #endif |
284 | |
285 | priv->data = of_device_get_match_data(dev); |
286 | platform_set_drvdata(pdev, data: priv); |
287 | |
288 | ret = component_add(dev, &mtk_disp_gamma_component_ops); |
289 | if (ret) |
290 | dev_err(dev, "Failed to add component: %d\n" , ret); |
291 | |
292 | return ret; |
293 | } |
294 | |
295 | static void mtk_disp_gamma_remove(struct platform_device *pdev) |
296 | { |
297 | component_del(&pdev->dev, &mtk_disp_gamma_component_ops); |
298 | } |
299 | |
300 | static const struct mtk_disp_gamma_data mt8173_gamma_driver_data = { |
301 | .has_dither = true, |
302 | .lut_bank_size = 512, |
303 | .lut_bits = 10, |
304 | .lut_size = 512, |
305 | }; |
306 | |
307 | static const struct mtk_disp_gamma_data mt8183_gamma_driver_data = { |
308 | .lut_bank_size = 512, |
309 | .lut_bits = 10, |
310 | .lut_diff = true, |
311 | .lut_size = 512, |
312 | }; |
313 | |
314 | static const struct mtk_disp_gamma_data mt8195_gamma_driver_data = { |
315 | .lut_bank_size = 256, |
316 | .lut_bits = 12, |
317 | .lut_diff = true, |
318 | .lut_size = 1024, |
319 | }; |
320 | |
321 | static const struct of_device_id mtk_disp_gamma_driver_dt_match[] = { |
322 | { .compatible = "mediatek,mt8173-disp-gamma" , |
323 | .data = &mt8173_gamma_driver_data}, |
324 | { .compatible = "mediatek,mt8183-disp-gamma" , |
325 | .data = &mt8183_gamma_driver_data}, |
326 | { .compatible = "mediatek,mt8195-disp-gamma" , |
327 | .data = &mt8195_gamma_driver_data}, |
328 | {}, |
329 | }; |
330 | MODULE_DEVICE_TABLE(of, mtk_disp_gamma_driver_dt_match); |
331 | |
332 | struct platform_driver mtk_disp_gamma_driver = { |
333 | .probe = mtk_disp_gamma_probe, |
334 | .remove_new = mtk_disp_gamma_remove, |
335 | .driver = { |
336 | .name = "mediatek-disp-gamma" , |
337 | .owner = THIS_MODULE, |
338 | .of_match_table = mtk_disp_gamma_driver_dt_match, |
339 | }, |
340 | }; |
341 | |