Commit 4bbb9061 authored by Thomas Zimmermann's avatar Thomas Zimmermann

drm/ofdrm: Support color management

Support the CRTC's color-management property and implement each model's
palette support.

The OF hardware has different methods of setting the palette. The
respective code has been taken from fbdev's offb and refactored into
per-model device functions. The device functions integrate this
functionality into the overall modesetting.

As palette handling is a CRTC property that depends on the primary
plane's color format, the plane's atomic_check helper now updates the
format field in ofdrm's custom CRTC state. The CRTC's atomic_flush
helper updates the palette for the format as needed.

v4:
	* use cpu_to_be32() (Geert)
v3:
	* lookup CRTC state with drm_atomic_get_new_crtc_state()
	* access HW palette with writeb(), writel(), and readl() (Ben)
	* declare register values as u32
Signed-off-by: default avatarThomas Zimmermann <tzimmermann@suse.de>
Reviewed-by: default avatarJavier Martinez Canillas <javierm@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20221011150712.3928-5-tzimmermann@suse.de
parent f496834e
......@@ -13,6 +13,7 @@
#include <drm/drm_drv.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_format_helper.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_gem_shmem_helper.h>
......@@ -31,6 +32,33 @@
#define PCI_VENDOR_ID_ATI_R520 0x7100
#define PCI_VENDOR_ID_ATI_R600 0x9400
#define OFDRM_GAMMA_LUT_SIZE 256
/* Definitions used by the Avivo palette */
#define AVIVO_DC_LUT_RW_SELECT 0x6480
#define AVIVO_DC_LUT_RW_MODE 0x6484
#define AVIVO_DC_LUT_RW_INDEX 0x6488
#define AVIVO_DC_LUT_SEQ_COLOR 0x648c
#define AVIVO_DC_LUT_PWL_DATA 0x6490
#define AVIVO_DC_LUT_30_COLOR 0x6494
#define AVIVO_DC_LUT_READ_PIPE_SELECT 0x6498
#define AVIVO_DC_LUT_WRITE_EN_MASK 0x649c
#define AVIVO_DC_LUT_AUTOFILL 0x64a0
#define AVIVO_DC_LUTA_CONTROL 0x64c0
#define AVIVO_DC_LUTA_BLACK_OFFSET_BLUE 0x64c4
#define AVIVO_DC_LUTA_BLACK_OFFSET_GREEN 0x64c8
#define AVIVO_DC_LUTA_BLACK_OFFSET_RED 0x64cc
#define AVIVO_DC_LUTA_WHITE_OFFSET_BLUE 0x64d0
#define AVIVO_DC_LUTA_WHITE_OFFSET_GREEN 0x64d4
#define AVIVO_DC_LUTA_WHITE_OFFSET_RED 0x64d8
#define AVIVO_DC_LUTB_CONTROL 0x6cc0
#define AVIVO_DC_LUTB_BLACK_OFFSET_BLUE 0x6cc4
#define AVIVO_DC_LUTB_BLACK_OFFSET_GREEN 0x6cc8
#define AVIVO_DC_LUTB_BLACK_OFFSET_RED 0x6ccc
#define AVIVO_DC_LUTB_WHITE_OFFSET_BLUE 0x6cd0
#define AVIVO_DC_LUTB_WHITE_OFFSET_GREEN 0x6cd4
#define AVIVO_DC_LUTB_WHITE_OFFSET_RED 0x6cd8
enum ofdrm_model {
OFDRM_MODEL_UNKNOWN,
OFDRM_MODEL_MACH64, /* ATI Mach64 */
......@@ -211,7 +239,14 @@ static enum ofdrm_model display_get_model_of(struct drm_device *dev, struct devi
* Open Firmware display device
*/
struct ofdrm_device;
struct ofdrm_device_funcs {
void __iomem *(*cmap_ioremap)(struct ofdrm_device *odev,
struct device_node *of_node,
u64 fb_bas);
void (*cmap_write)(struct ofdrm_device *odev, unsigned char index,
unsigned char r, unsigned char g, unsigned char b);
};
struct ofdrm_device {
......@@ -226,6 +261,9 @@ struct ofdrm_device {
const struct drm_format_info *format;
unsigned int pitch;
/* colormap */
void __iomem *cmap_base;
/* modesetting */
uint32_t formats[8];
struct drm_plane primary_plane;
......@@ -338,12 +376,322 @@ static struct resource *ofdrm_find_fb_resource(struct ofdrm_device *odev,
return max_res;
}
/*
* Colormap / Palette
*/
static void __iomem *get_cmap_address_of(struct ofdrm_device *odev, struct device_node *of_node,
int bar_no, unsigned long offset, unsigned long size)
{
struct drm_device *dev = &odev->dev;
const __be32 *addr_p;
u64 max_size, address;
unsigned int flags;
void __iomem *mem;
addr_p = of_get_pci_address(of_node, bar_no, &max_size, &flags);
if (!addr_p)
addr_p = of_get_address(of_node, bar_no, &max_size, &flags);
if (!addr_p)
return ERR_PTR(-ENODEV);
if ((flags & (IORESOURCE_IO | IORESOURCE_MEM)) == 0)
return ERR_PTR(-ENODEV);
if ((offset + size) >= max_size)
return ERR_PTR(-ENODEV);
address = of_translate_address(of_node, addr_p);
if (address == OF_BAD_ADDR)
return ERR_PTR(-ENODEV);
mem = devm_ioremap(dev->dev, address + offset, size);
if (!mem)
return ERR_PTR(-ENOMEM);
return mem;
}
static void __iomem *ofdrm_mach64_cmap_ioremap(struct ofdrm_device *odev,
struct device_node *of_node,
u64 fb_base)
{
struct drm_device *dev = &odev->dev;
u64 address;
void __iomem *cmap_base;
address = fb_base & 0xff000000ul;
address += 0x7ff000;
cmap_base = devm_ioremap(dev->dev, address, 0x1000);
if (!cmap_base)
return ERR_PTR(-ENOMEM);
return cmap_base;
}
static void ofdrm_mach64_cmap_write(struct ofdrm_device *odev, unsigned char index,
unsigned char r, unsigned char g, unsigned char b)
{
void __iomem *addr = odev->cmap_base + 0xcc0;
void __iomem *data = odev->cmap_base + 0xcc0 + 1;
writeb(index, addr);
writeb(r, data);
writeb(g, data);
writeb(b, data);
}
static void __iomem *ofdrm_rage128_cmap_ioremap(struct ofdrm_device *odev,
struct device_node *of_node,
u64 fb_base)
{
return get_cmap_address_of(odev, of_node, 2, 0, 0x1fff);
}
static void ofdrm_rage128_cmap_write(struct ofdrm_device *odev, unsigned char index,
unsigned char r, unsigned char g, unsigned char b)
{
void __iomem *addr = odev->cmap_base + 0xb0;
void __iomem *data = odev->cmap_base + 0xb4;
u32 color = (r << 16) | (g << 8) | b;
writeb(index, addr);
writel(color, data);
}
static void __iomem *ofdrm_rage_m3a_cmap_ioremap(struct ofdrm_device *odev,
struct device_node *of_node,
u64 fb_base)
{
return get_cmap_address_of(odev, of_node, 2, 0, 0x1fff);
}
static void ofdrm_rage_m3a_cmap_write(struct ofdrm_device *odev, unsigned char index,
unsigned char r, unsigned char g, unsigned char b)
{
void __iomem *dac_ctl = odev->cmap_base + 0x58;
void __iomem *addr = odev->cmap_base + 0xb0;
void __iomem *data = odev->cmap_base + 0xb4;
u32 color = (r << 16) | (g << 8) | b;
u32 val;
/* Clear PALETTE_ACCESS_CNTL in DAC_CNTL */
val = readl(dac_ctl);
val &= ~0x20;
writel(val, dac_ctl);
/* Set color at palette index */
writeb(index, addr);
writel(color, data);
}
static void __iomem *ofdrm_rage_m3b_cmap_ioremap(struct ofdrm_device *odev,
struct device_node *of_node,
u64 fb_base)
{
return get_cmap_address_of(odev, of_node, 2, 0, 0x1fff);
}
static void ofdrm_rage_m3b_cmap_write(struct ofdrm_device *odev, unsigned char index,
unsigned char r, unsigned char g, unsigned char b)
{
void __iomem *dac_ctl = odev->cmap_base + 0x58;
void __iomem *addr = odev->cmap_base + 0xb0;
void __iomem *data = odev->cmap_base + 0xb4;
u32 color = (r << 16) | (g << 8) | b;
u32 val;
/* Set PALETTE_ACCESS_CNTL in DAC_CNTL */
val = readl(dac_ctl);
val |= 0x20;
writel(val, dac_ctl);
/* Set color at palette index */
writeb(index, addr);
writel(color, data);
}
static void __iomem *ofdrm_radeon_cmap_ioremap(struct ofdrm_device *odev,
struct device_node *of_node,
u64 fb_base)
{
return get_cmap_address_of(odev, of_node, 1, 0, 0x1fff);
}
static void __iomem *ofdrm_gxt2000_cmap_ioremap(struct ofdrm_device *odev,
struct device_node *of_node,
u64 fb_base)
{
return get_cmap_address_of(odev, of_node, 0, 0x6000, 0x1000);
}
static void ofdrm_gxt2000_cmap_write(struct ofdrm_device *odev, unsigned char index,
unsigned char r, unsigned char g, unsigned char b)
{
void __iomem *data = ((unsigned int __iomem *)odev->cmap_base) + index;
u32 color = (r << 16) | (g << 8) | b;
writel(color, data);
}
static void __iomem *ofdrm_avivo_cmap_ioremap(struct ofdrm_device *odev,
struct device_node *of_node,
u64 fb_base)
{
struct device_node *of_parent;
void __iomem *cmap_base;
of_parent = of_get_parent(of_node);
cmap_base = get_cmap_address_of(odev, of_parent, 0, 0, 0x10000);
of_node_put(of_parent);
return cmap_base;
}
static void ofdrm_avivo_cmap_write(struct ofdrm_device *odev, unsigned char index,
unsigned char r, unsigned char g, unsigned char b)
{
void __iomem *lutsel = odev->cmap_base + AVIVO_DC_LUT_RW_SELECT;
void __iomem *addr = odev->cmap_base + AVIVO_DC_LUT_RW_INDEX;
void __iomem *data = odev->cmap_base + AVIVO_DC_LUT_30_COLOR;
u32 color = (r << 22) | (g << 12) | (b << 2);
/* Write to both LUTs for now */
writel(1, lutsel);
writeb(index, addr);
writel(color, data);
writel(0, lutsel);
writeb(index, addr);
writel(color, data);
}
static void __iomem *ofdrm_qemu_cmap_ioremap(struct ofdrm_device *odev,
struct device_node *of_node,
u64 fb_base)
{
static const __be32 io_of_addr[3] = {
cpu_to_be32(0x01000000),
cpu_to_be32(0x00),
cpu_to_be32(0x00),
};
struct drm_device *dev = &odev->dev;
u64 address;
void __iomem *cmap_base;
address = of_translate_address(of_node, io_of_addr);
if (address == OF_BAD_ADDR)
return ERR_PTR(-ENODEV);
cmap_base = devm_ioremap(dev->dev, address + 0x3c8, 2);
if (!cmap_base)
return ERR_PTR(-ENOMEM);
return cmap_base;
}
static void ofdrm_qemu_cmap_write(struct ofdrm_device *odev, unsigned char index,
unsigned char r, unsigned char g, unsigned char b)
{
void __iomem *addr = odev->cmap_base;
void __iomem *data = odev->cmap_base + 1;
writeb(index, addr);
writeb(r, data);
writeb(g, data);
writeb(b, data);
}
static void ofdrm_device_set_gamma_linear(struct ofdrm_device *odev,
const struct drm_format_info *format)
{
struct drm_device *dev = &odev->dev;
int i;
switch (format->format) {
case DRM_FORMAT_RGB565:
/* Use better interpolation, to take 32 values from 0 to 255 */
for (i = 0; i < OFDRM_GAMMA_LUT_SIZE / 8; i++) {
unsigned char r = i * 8 + i / 4;
unsigned char g = i * 4 + i / 16;
unsigned char b = i * 8 + i / 4;
odev->funcs->cmap_write(odev, i, r, g, b);
}
/* Green has one more bit, so add padding with 0 for red and blue. */
for (i = OFDRM_GAMMA_LUT_SIZE / 8; i < OFDRM_GAMMA_LUT_SIZE / 4; i++) {
unsigned char r = 0;
unsigned char g = i * 4 + i / 16;
unsigned char b = 0;
odev->funcs->cmap_write(odev, i, r, g, b);
}
break;
case DRM_FORMAT_XRGB8888:
for (i = 0; i < OFDRM_GAMMA_LUT_SIZE; i++)
odev->funcs->cmap_write(odev, i, i, i, i);
break;
default:
drm_warn_once(dev, "Unsupported format %p4cc for gamma correction\n",
&format->format);
break;
}
}
static void ofdrm_device_set_gamma(struct ofdrm_device *odev,
const struct drm_format_info *format,
struct drm_color_lut *lut)
{
struct drm_device *dev = &odev->dev;
int i;
switch (format->format) {
case DRM_FORMAT_RGB565:
/* Use better interpolation, to take 32 values from lut[0] to lut[255] */
for (i = 0; i < OFDRM_GAMMA_LUT_SIZE / 8; i++) {
unsigned char r = lut[i * 8 + i / 4].red >> 8;
unsigned char g = lut[i * 4 + i / 16].green >> 8;
unsigned char b = lut[i * 8 + i / 4].blue >> 8;
odev->funcs->cmap_write(odev, i, r, g, b);
}
/* Green has one more bit, so add padding with 0 for red and blue. */
for (i = OFDRM_GAMMA_LUT_SIZE / 8; i < OFDRM_GAMMA_LUT_SIZE / 4; i++) {
unsigned char r = 0;
unsigned char g = lut[i * 4 + i / 16].green >> 8;
unsigned char b = 0;
odev->funcs->cmap_write(odev, i, r, g, b);
}
break;
case DRM_FORMAT_XRGB8888:
for (i = 0; i < OFDRM_GAMMA_LUT_SIZE; i++) {
unsigned char r = lut[i].red >> 8;
unsigned char g = lut[i].green >> 8;
unsigned char b = lut[i].blue >> 8;
odev->funcs->cmap_write(odev, i, r, g, b);
}
break;
default:
drm_warn_once(dev, "Unsupported format %p4cc for gamma correction\n",
&format->format);
break;
}
}
/*
* Modesetting
*/
struct ofdrm_crtc_state {
struct drm_crtc_state base;
/* Primary-plane format; required for color mgmt. */
const struct drm_format_info *format;
};
static struct ofdrm_crtc_state *to_ofdrm_crtc_state(struct drm_crtc_state *base)
......@@ -381,16 +729,30 @@ static int ofdrm_primary_plane_helper_atomic_check(struct drm_plane *plane,
struct drm_atomic_state *new_state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(new_state, plane);
struct drm_framebuffer *new_fb = new_plane_state->fb;
struct drm_crtc *new_crtc = new_plane_state->crtc;
struct drm_crtc_state *new_crtc_state = NULL;
struct ofdrm_crtc_state *new_ofdrm_crtc_state;
int ret;
if (new_crtc)
new_crtc_state = drm_atomic_get_new_crtc_state(new_state, new_plane_state->crtc);
return drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state,
DRM_PLANE_NO_SCALING,
DRM_PLANE_NO_SCALING,
false, false);
ret = drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state,
DRM_PLANE_NO_SCALING,
DRM_PLANE_NO_SCALING,
false, false);
if (ret)
return ret;
else if (!new_plane_state->visible)
return 0;
new_crtc_state = drm_atomic_get_new_crtc_state(new_state, new_plane_state->crtc);
new_ofdrm_crtc_state = to_ofdrm_crtc_state(new_crtc_state);
new_ofdrm_crtc_state->format = new_fb->format;
return 0;
}
static void ofdrm_primary_plane_helper_atomic_update(struct drm_plane *plane,
......@@ -492,12 +854,45 @@ static enum drm_mode_status ofdrm_crtc_helper_mode_valid(struct drm_crtc *crtc,
static int ofdrm_crtc_helper_atomic_check(struct drm_crtc *crtc,
struct drm_atomic_state *new_state)
{
static const size_t gamma_lut_length = OFDRM_GAMMA_LUT_SIZE * sizeof(struct drm_color_lut);
struct drm_device *dev = crtc->dev;
struct drm_crtc_state *new_crtc_state = drm_atomic_get_new_crtc_state(new_state, crtc);
int ret;
if (!new_crtc_state->enable)
return 0;
return drm_atomic_helper_check_crtc_primary_plane(new_crtc_state);
ret = drm_atomic_helper_check_crtc_primary_plane(new_crtc_state);
if (ret)
return ret;
if (new_crtc_state->color_mgmt_changed) {
struct drm_property_blob *gamma_lut = new_crtc_state->gamma_lut;
if (gamma_lut && (gamma_lut->length != gamma_lut_length)) {
drm_dbg(dev, "Incorrect gamma_lut length %zu\n", gamma_lut->length);
return -EINVAL;
}
}
return 0;
}
static void ofdrm_crtc_helper_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
{
struct ofdrm_device *odev = ofdrm_device_of_dev(crtc->dev);
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
struct ofdrm_crtc_state *ofdrm_crtc_state = to_ofdrm_crtc_state(crtc_state);
if (crtc_state->enable && crtc_state->color_mgmt_changed) {
const struct drm_format_info *format = ofdrm_crtc_state->format;
if (crtc_state->gamma_lut)
ofdrm_device_set_gamma(odev, format, crtc_state->gamma_lut->data);
else
ofdrm_device_set_gamma_linear(odev, format);
}
}
/*
......@@ -508,6 +903,7 @@ static int ofdrm_crtc_helper_atomic_check(struct drm_crtc *crtc,
static const struct drm_crtc_helper_funcs ofdrm_crtc_helper_funcs = {
.mode_valid = ofdrm_crtc_helper_mode_valid,
.atomic_check = ofdrm_crtc_helper_atomic_check,
.atomic_flush = ofdrm_crtc_helper_atomic_flush,
};
static void ofdrm_crtc_reset(struct drm_crtc *crtc)
......@@ -529,6 +925,7 @@ static struct drm_crtc_state *ofdrm_crtc_atomic_duplicate_state(struct drm_crtc
struct drm_device *dev = crtc->dev;
struct drm_crtc_state *crtc_state = crtc->state;
struct ofdrm_crtc_state *new_ofdrm_crtc_state;
struct ofdrm_crtc_state *ofdrm_crtc_state;
if (drm_WARN_ON(dev, !crtc_state))
return NULL;
......@@ -537,7 +934,10 @@ static struct drm_crtc_state *ofdrm_crtc_atomic_duplicate_state(struct drm_crtc
if (!new_ofdrm_crtc_state)
return NULL;
ofdrm_crtc_state = to_ofdrm_crtc_state(crtc_state);
__drm_atomic_helper_crtc_duplicate_state(crtc, &new_ofdrm_crtc_state->base);
new_ofdrm_crtc_state->format = ofdrm_crtc_state->format;
return &new_ofdrm_crtc_state->base;
}
......@@ -590,27 +990,43 @@ static const struct ofdrm_device_funcs ofdrm_unknown_device_funcs = {
};
static const struct ofdrm_device_funcs ofdrm_mach64_device_funcs = {
.cmap_ioremap = ofdrm_mach64_cmap_ioremap,
.cmap_write = ofdrm_mach64_cmap_write,
};
static const struct ofdrm_device_funcs ofdrm_rage128_device_funcs = {
.cmap_ioremap = ofdrm_rage128_cmap_ioremap,
.cmap_write = ofdrm_rage128_cmap_write,
};
static const struct ofdrm_device_funcs ofdrm_rage_m3a_device_funcs = {
.cmap_ioremap = ofdrm_rage_m3a_cmap_ioremap,
.cmap_write = ofdrm_rage_m3a_cmap_write,
};
static const struct ofdrm_device_funcs ofdrm_rage_m3b_device_funcs = {
.cmap_ioremap = ofdrm_rage_m3b_cmap_ioremap,
.cmap_write = ofdrm_rage_m3b_cmap_write,
};
static const struct ofdrm_device_funcs ofdrm_radeon_device_funcs = {
.cmap_ioremap = ofdrm_radeon_cmap_ioremap,
.cmap_write = ofdrm_rage128_cmap_write, /* same as Rage128 */
};
static const struct ofdrm_device_funcs ofdrm_gxt2000_device_funcs = {
.cmap_ioremap = ofdrm_gxt2000_cmap_ioremap,
.cmap_write = ofdrm_gxt2000_cmap_write,
};
static const struct ofdrm_device_funcs ofdrm_avivo_device_funcs = {
.cmap_ioremap = ofdrm_avivo_cmap_ioremap,
.cmap_write = ofdrm_avivo_cmap_write,
};
static const struct ofdrm_device_funcs ofdrm_qemu_device_funcs = {
.cmap_ioremap = ofdrm_qemu_cmap_ioremap,
.cmap_write = ofdrm_qemu_cmap_write,
};
static struct drm_display_mode ofdrm_mode(unsigned int width, unsigned int height)
......@@ -773,6 +1189,17 @@ static struct ofdrm_device *ofdrm_device_create(struct drm_driver *drv,
if (!screen_base)
return ERR_PTR(-ENOMEM);
if (odev->funcs->cmap_ioremap) {
void __iomem *cmap_base = odev->funcs->cmap_ioremap(odev, of_node, fb_base);
if (IS_ERR(cmap_base)) {
/* Don't fail; continue without colormap */
drm_warn(dev, "could not find colormap: error %ld\n", PTR_ERR(cmap_base));
} else {
odev->cmap_base = cmap_base;
}
}
/*
* Firmware framebuffer
*/
......@@ -837,6 +1264,11 @@ static struct ofdrm_device *ofdrm_device_create(struct drm_driver *drv,
return ERR_PTR(ret);
drm_crtc_helper_add(crtc, &ofdrm_crtc_helper_funcs);
if (odev->cmap_base) {
drm_mode_crtc_set_gamma_size(crtc, OFDRM_GAMMA_LUT_SIZE);
drm_crtc_enable_color_mgmt(crtc, 0, false, OFDRM_GAMMA_LUT_SIZE);
}
/* Encoder */
encoder = &odev->encoder;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment