1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
// SPDX-License-Identifier: GPL-2.0+
/*
* R-Car Display Unit Color Management Module
*
* Copyright (C) 2019 Jacopo Mondi <jacopo+renesas@jmondi.org>
*/
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <drm/drm_color_mgmt.h>
#include "rcar_cmm.h"
#define CM2_LUT_CTRL 0x0000
#define CM2_LUT_CTRL_LUT_EN BIT(0)
#define CM2_LUT_TBL_BASE 0x0600
#define CM2_LUT_TBL(__i) (CM2_LUT_TBL_BASE + (__i) * 4)
struct rcar_cmm {
void __iomem *base;
/*
* @lut: 1D-LUT state
* @lut.enabled: 1D-LUT enabled flag
*/
struct {
bool enabled;
} lut;
};
static inline int rcar_cmm_read(struct rcar_cmm *rcmm, u32 reg)
{
return ioread32(rcmm->base + reg);
}
static inline void rcar_cmm_write(struct rcar_cmm *rcmm, u32 reg, u32 data)
{
iowrite32(data, rcmm->base + reg);
}
/*
* rcar_cmm_lut_write() - Scale the DRM LUT table entries to hardware precision
* and write to the CMM registers
* @rcmm: Pointer to the CMM device
* @drm_lut: Pointer to the DRM LUT table
*/
static void rcar_cmm_lut_write(struct rcar_cmm *rcmm,
const struct drm_color_lut *drm_lut)
{
unsigned int i;
for (i = 0; i < CM2_LUT_SIZE; ++i) {
u32 entry = drm_color_lut_extract(drm_lut[i].red, 8) << 16
| drm_color_lut_extract(drm_lut[i].green, 8) << 8
| drm_color_lut_extract(drm_lut[i].blue, 8);
rcar_cmm_write(rcmm, CM2_LUT_TBL(i), entry);
}
}
/*
* rcar_cmm_setup() - Configure the CMM unit
* @pdev: The platform device associated with the CMM instance
* @config: The CMM unit configuration
*
* Configure the CMM unit with the given configuration. Currently enabling,
* disabling and programming of the 1-D LUT unit is supported.
*
* As rcar_cmm_setup() accesses the CMM registers the unit should be powered
* and its functional clock enabled. To guarantee this, before any call to
* this function is made, the CMM unit has to be enabled by calling
* rcar_cmm_enable() first.
*
* TODO: Add support for LUT double buffer operations to avoid updating the
* LUT table entries while a frame is being displayed.
*/
int rcar_cmm_setup(struct platform_device *pdev,
const struct rcar_cmm_config *config)
{
struct rcar_cmm *rcmm = platform_get_drvdata(pdev);
/* Disable LUT if no table is provided. */
if (!config->lut.table) {
if (rcmm->lut.enabled) {
rcar_cmm_write(rcmm, CM2_LUT_CTRL, 0);
rcmm->lut.enabled = false;
}
return 0;
}
/* Enable LUT and program the new gamma table values. */
if (!rcmm->lut.enabled) {
rcar_cmm_write(rcmm, CM2_LUT_CTRL, CM2_LUT_CTRL_LUT_EN);
rcmm->lut.enabled = true;
}
rcar_cmm_lut_write(rcmm, config->lut.table);
return 0;
}
EXPORT_SYMBOL_GPL(rcar_cmm_setup);
/*
* rcar_cmm_enable() - Enable the CMM unit
* @pdev: The platform device associated with the CMM instance
*
* When the output of the corresponding DU channel is routed to the CMM unit,
* the unit shall be enabled before the DU channel is started, and remain
* enabled until the channel is stopped. The CMM unit shall be disabled with
* rcar_cmm_disable().
*
* Calls to rcar_cmm_enable() and rcar_cmm_disable() are not reference-counted.
* It is an error to attempt to enable an already enabled CMM unit, or to
* attempt to disable a disabled unit.
*/
int rcar_cmm_enable(struct platform_device *pdev)
{
int ret;
ret = pm_runtime_resume_and_get(&pdev->dev);
if (ret < 0)
return ret;
return 0;
}
EXPORT_SYMBOL_GPL(rcar_cmm_enable);
/*
* rcar_cmm_disable() - Disable the CMM unit
* @pdev: The platform device associated with the CMM instance
*
* See rcar_cmm_enable() for usage information.
*
* Disabling the CMM unit disable all the internal processing blocks. The CMM
* state shall thus be restored with rcar_cmm_setup() when re-enabling the CMM
* unit after the next rcar_cmm_enable() call.
*/
void rcar_cmm_disable(struct platform_device *pdev)
{
struct rcar_cmm *rcmm = platform_get_drvdata(pdev);
rcar_cmm_write(rcmm, CM2_LUT_CTRL, 0);
rcmm->lut.enabled = false;
pm_runtime_put(&pdev->dev);
}
EXPORT_SYMBOL_GPL(rcar_cmm_disable);
/*
* rcar_cmm_init() - Initialize the CMM unit
* @pdev: The platform device associated with the CMM instance
*
* Return: 0 on success, -EPROBE_DEFER if the CMM is not available yet,
* -ENODEV if the DRM_RCAR_CMM config option is disabled
*/
int rcar_cmm_init(struct platform_device *pdev)
{
struct rcar_cmm *rcmm = platform_get_drvdata(pdev);
if (!rcmm)
return -EPROBE_DEFER;
return 0;
}
EXPORT_SYMBOL_GPL(rcar_cmm_init);
static int rcar_cmm_probe(struct platform_device *pdev)
{
struct rcar_cmm *rcmm;
rcmm = devm_kzalloc(&pdev->dev, sizeof(*rcmm), GFP_KERNEL);
if (!rcmm)
return -ENOMEM;
platform_set_drvdata(pdev, rcmm);
rcmm->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(rcmm->base))
return PTR_ERR(rcmm->base);
pm_runtime_enable(&pdev->dev);
return 0;
}
static void rcar_cmm_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static const struct of_device_id rcar_cmm_of_table[] = {
{ .compatible = "renesas,rcar-gen3-cmm", },
{ .compatible = "renesas,rcar-gen2-cmm", },
{ },
};
MODULE_DEVICE_TABLE(of, rcar_cmm_of_table);
static struct platform_driver rcar_cmm_platform_driver = {
.probe = rcar_cmm_probe,
.remove = rcar_cmm_remove,
.driver = {
.name = "rcar-cmm",
.of_match_table = rcar_cmm_of_table,
},
};
module_platform_driver(rcar_cmm_platform_driver);
MODULE_AUTHOR("Jacopo Mondi <jacopo+renesas@jmondi.org>");
MODULE_DESCRIPTION("Renesas R-Car CMM Driver");
MODULE_LICENSE("GPL v2");
|