On Wed, Nov 07, 2018 at 10:27:20AM +0200, Jyri Sarha wrote: > From: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > > This patch adds a new DRM driver for Texas Instruments DSS6 IP used on > Texas Instruments Keystone K2G SoC. The DSS6 IP is a major change to the > older DSS IP versions, which are supported by the omapdrm driver, and > while on higher level the DSS6 resembles the older DSS versions, the > registers and the internal pipelines differ a lot. > > DSS6 IP on K2G is a "ultra-light" version, and has only a single plane > and a single output. The driver will also support future DSS versions, > which support multiple planes and outputs, so the driver already has > support for those. > > Signed-off-by: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > Signed-off-by: Jyri Sarha <jsarha@xxxxxx> > +struct tidss_dispc_ops { General design comment, since you decided to write a new driver: Please don't stack midlayers like this. I know there's a bunch of other drivers who do this, but they all have the excuse that they predate atomic helpers. With atomic helpers either: - Use the atomic helper callbacks directly, and end up with a per generation callback structure for planes/crtc/whatever. - Or have your own callback/modeset sequence, using your own helpers (i.e. dispc_ops here). Pre-atomic this was a choice which was only possible at the full driver level, because the old crtc helpers were very monolithic. But with atomic helpers you can (and are supposed to!) pick&choose what fits, and ignore what doesn't. This is why we have stuff like ->atomic_commit_tail for your own commit sequence, and all the building blocks exposed so you can e.g. use atomic helpers for modeset, but have your own plane update code. If you look at all other big drivers that converted over to atomic, they all have their own atomic code in parts (where they then use their own callbacks), mixed with stuff provided by the shared helpers. But not nested one within the other, because that's just painful to maintain long-term. So here's my suggestion: For each function you have here in the framework: - either drop the dispc_ops callbacks, and just switch between platforms at the atomic_helper_funcs levels (with shared code extracted into helper functions of your own, as usuaul). - or if that doesn't make sense, just kick the atomic helpers out and write your own (it's really not hard, we have lots of pieces you can pick&choose) and directly call into your dispc_ops functions, bypassing atomic helpers. Cheers, Daniel > + u64 (*read_and_clear_irqstatus)(struct dispc_device *dispc); > + void (*write_irqenable)(struct dispc_device *dispc, u64 enable); > + > + u32 (*get_memory_bandwidth_limit)(struct dispc_device *dispc); > + > + int (*get_num_vps)(struct dispc_device *dispc); > + const char *(*vp_name)(struct dispc_device *dispc, > + u32 hw_videoport); > + const struct tidss_vp_feat *(*vp_feat)(struct dispc_device *dispc, > + u32 hw_videoport); > + void (*vp_prepare)(struct dispc_device *dispc, u32 hw_videoport, > + const struct drm_crtc_state *state); > + void (*vp_enable)(struct dispc_device *dispc, u32 hw_videoport, > + const struct drm_crtc_state *state); > + void (*vp_disable)(struct dispc_device *dispc, u32 hw_videoport); > + void (*vp_unprepare)(struct dispc_device *dispc, u32 hw_videoport); > + bool (*vp_go_busy)(struct dispc_device *dispc, > + u32 hw_videoport); > + void (*vp_go)(struct dispc_device *dispc, u32 hw_videoport); > + enum drm_mode_status (*vp_mode_valid)(struct dispc_device *dispc, > + u32 hw_videoport, > + const struct drm_display_mode *mode); > + int (*vp_check)(struct dispc_device *dispc, u32 hw_videoport, > + const struct drm_crtc_state *state); > + void (*vp_setup)(struct dispc_device *dispc, u32 hw_videoport, > + const struct drm_crtc_state *state); > + > + int (*vp_set_clk_rate)(struct dispc_device *dispc, > + u32 hw_videoport, unsigned long rate); > + int (*vp_enable_clk)(struct dispc_device *dispc, u32 hw_videoport); > + void (*vp_disable_clk)(struct dispc_device *dispc, u32 hw_videoport); > + > + int (*get_num_planes)(struct dispc_device *dispc); > + const char *(*plane_name)(struct dispc_device *dispc, > + u32 hw_plane); > + const struct tidss_plane_feat *(*plane_feat)(struct dispc_device *dispc, > + u32 hw_plane); > + int (*plane_enable)(struct dispc_device *dispc, u32 hw_plane, > + bool enable); > + int (*plane_check)(struct dispc_device *dispc, u32 hw_plane, > + const struct drm_plane_state *state, > + u32 hw_videoport); > + int (*plane_setup)(struct dispc_device *dispc, u32 hw_plane, > + const struct drm_plane_state *state, > + u32 hw_videoport); > + > + int (*runtime_get)(struct dispc_device *dispc); > + void (*runtime_put)(struct dispc_device *dispc); > + > + int (*runtime_suspend)(struct dispc_device *dispc); > + int (*runtime_resume)(struct dispc_device *dispc); > + > + void (*remove)(struct dispc_device *dispc); > + > + int (*modeset_init)(struct dispc_device *dispc); > +}; > + > +int dispc6_init(struct tidss_device *tidss); > + > +#endif > diff --git a/drivers/gpu/drm/tidss/tidss_dispc6.c b/drivers/gpu/drm/tidss/tidss_dispc6.c > new file mode 100644 > index 0000000..7513f3e > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_dispc6.c > @@ -0,0 +1,1507 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#include <linux/clk.h> > +#include <linux/component.h> > +#include <linux/delay.h> > +#include <linux/export.h> > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <linux/kernel.h> > +#include <linux/of.h> > +#include <linux/of_graph.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > + > +#include <drm/drm_fourcc.h> > +#include <drm/drm_of.h> > +#include <drm/drm_panel.h> > + > +#include "tidss_crtc.h" > +#include "tidss_drv.h" > +#include "tidss_encoder.h" > +#include "tidss_plane.h" > + > +#include "tidss_dispc6.h" > + > +static const struct { > + u32 fmt; > + u32 port_width; > +} dispc6_bus_formats[] = { > + { MEDIA_BUS_FMT_RGB444_1X12, 12 }, > + { MEDIA_BUS_FMT_RGB565_1X16, 16 }, > + { MEDIA_BUS_FMT_RGB666_1X18, 18 }, > + { MEDIA_BUS_FMT_RGB888_1X24, 24 }, > +}; > + > +/* > + * TRM gives bitfields as start:end, where start is the higher bit > + * number. For example 7:0 > + */ > + > +#define FLD_MASK(start, end) (((1 << ((start) - (end) + 1)) - 1) << (end)) > +#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end)) > +#define FLD_GET(val, start, end) (((val) & FLD_MASK(start, end)) >> (end)) > +#define FLD_MOD(orig, val, start, end) \ > + (((orig) & ~FLD_MASK(start, end)) | FLD_VAL(val, start, end)) > + > + > +#define REG_GET(dispc, idx, start, end) \ > + FLD_GET(dispc6_read(dispc, idx), start, end) > + > +#define REG_FLD_MOD(dispc, idx, val, start, end) \ > + dispc6_write(dispc, idx, FLD_MOD(dispc6_read(dispc, idx), val, start, end)) > + > +#define VID_REG_GET(dispc, plane, idx, start, end) \ > + FLD_GET(dispc6_vid_read(dispc, plane, idx), start, end) > + > +#define VID_REG_FLD_MOD(dispc, plane, idx, val, start, end) \ > + dispc6_vid_write(dispc, plane, idx, FLD_MOD(dispc6_vid_read(dispc, plane, idx), val, start, end)) > + > + > +#define VP_REG_GET(dispc, vp, idx, start, end) \ > + FLD_GET(dispc6_vp_read(dispc, vp, idx), start, end) > + > +#define VP_REG_FLD_MOD(dispc, vp, idx, val, start, end) \ > + dispc6_vp_write(dispc, vp, idx, FLD_MOD(dispc6_vp_read(dispc, vp, idx), val, start, end)) > + > +#define DISPC6_GAMMA_TABLE_SIZE 256 > + > +struct dispc_features { > + /* XXX should these come from the .dts? Min pclk is not feature of DSS IP */ > + unsigned long min_pclk; > + unsigned long max_pclk; > +}; > + > +/* Note: 9MHz is a special allowed case, and is handled separately in the code */ > +static const struct dispc_features k2g_dispc_feats = { > + .min_pclk = 43750000, > + .max_pclk = 150000000, > +}; > + > +static const struct of_device_id dispc6_of_match[] = { > + { .compatible = "ti,k2g-dss", .data = &k2g_dispc_feats, }, > + {}, > +}; > + > +struct dispc_device { > + struct device *dev; > + > + void __iomem *base_cfg; > + void __iomem *base_common; > + void __iomem *base_vid1; > + void __iomem *base_ovr1; > + void __iomem *base_vp1; > + > + const struct dispc_features *feat; > + > + struct clk *fclk; > + struct clk *vp_clk; > + > + bool is_enabled; > + > + u32 gamma_table[DISPC6_GAMMA_TABLE_SIZE]; > + > + struct tidss_device *tidss; > +}; > + > +static void dispc6_write(struct dispc_device *dispc, u16 reg, u32 val) > +{ > + iowrite32(val, dispc->base_common + reg); > +} > + > +static u32 dispc6_read(struct dispc_device *dispc, u16 reg) > +{ > + return ioread32(dispc->base_common + reg); > +} > + > +static void dispc6_vid_write(struct dispc_device *dispc, > + u32 hw_plane, u16 reg, u32 val) > +{ > + void __iomem *base = dispc->base_vid1; > + > + iowrite32(val, base + reg); > +} > + > +static u32 dispc6_vid_read(struct dispc_device *dispc, > + u32 hw_plane, u16 reg) > +{ > + void __iomem *base = dispc->base_vid1; > + > + return ioread32(base + reg); > +} > + > +static void dispc6_ovr_write(struct dispc_device *dispc, > + u32 hw_videoport, u16 reg, u32 val) > +{ > + void __iomem *base = dispc->base_ovr1; > + > + iowrite32(val, base + reg); > +} > + > +__maybe_unused > +static u32 dispc6_ovr_read(struct dispc_device *dispc, > + u32 hw_videoport, u16 reg) > +{ > + void __iomem *base = dispc->base_ovr1; > + > + return ioread32(base + reg); > +} > + > +static void dispc6_vp_write(struct dispc_device *dispc, > + u32 hw_videoport, u16 reg, u32 val) > +{ > + void __iomem *base = dispc->base_vp1; > + > + iowrite32(val, base + reg); > +} > + > +static u32 dispc6_vp_read(struct dispc_device *dispc, > + u32 hw_videoport, u16 reg) > +{ > + void __iomem *base = dispc->base_vp1; > + > + return ioread32(base + reg); > +} > + > +static int dispc6_runtime_get(struct dispc_device *dispc) > +{ > + int r; > + > + dev_dbg(dispc->dev, "dispc_runtime_get\n"); > + > + r = pm_runtime_get_sync(dispc->dev); > + WARN_ON(r < 0); > + return r < 0 ? r : 0; > +} > + > +static void dispc6_runtime_put(struct dispc_device *dispc) > +{ > + int r; > + > + dev_dbg(dispc->dev, "dispc_runtime_put\n"); > + > + r = pm_runtime_put_sync(dispc->dev); > + WARN_ON(r < 0); > +} > + > +static u64 dispc6_vp_irq_from_raw(u32 stat) > +{ > + u32 vp = 0; > + u64 vp_stat = 0; > + > + if (stat & BIT(0)) > + vp_stat |= DSS_IRQ_VP_FRAME_DONE(vp); > + if (stat & BIT(1)) > + vp_stat |= DSS_IRQ_VP_VSYNC_EVEN(vp); > + if (stat & BIT(2)) > + vp_stat |= DSS_IRQ_VP_VSYNC_ODD(vp); > + if (stat & BIT(4)) > + vp_stat |= DSS_IRQ_VP_SYNC_LOST(vp); > + > + return vp_stat; > +} > + > +static u32 dispc6_vp_irq_to_raw(u64 vpstat) > +{ > + u32 vp = 0; > + u32 stat = 0; > + > + if (vpstat & DSS_IRQ_VP_FRAME_DONE(vp)) > + stat |= BIT(0); > + if (vpstat & DSS_IRQ_VP_VSYNC_EVEN(vp)) > + stat |= BIT(1); > + if (vpstat & DSS_IRQ_VP_VSYNC_ODD(vp)) > + stat |= BIT(2); > + if (vpstat & DSS_IRQ_VP_SYNC_LOST(vp)) > + stat |= BIT(4); > + > + return stat; > +} > + > +static u64 dispc6_vid_irq_from_raw(u32 stat) > +{ > + u32 plane = 0; > + u64 vid_stat = 0; > + > + if (stat & BIT(0)) > + vid_stat |= DSS_IRQ_PLANE_FIFO_UNDERFLOW(plane); > + > + return vid_stat; > +} > + > +static u32 dispc6_vid_irq_to_raw(u64 vidstat) > +{ > + u32 plane = 0; > + u32 stat = 0; > + > + if (vidstat & DSS_IRQ_PLANE_FIFO_UNDERFLOW(plane)) > + stat |= BIT(0); > + > + return stat; > +} > + > + > +static u64 dispc6_vp_read_irqstatus(struct dispc_device *dispc, > + u32 hw_videoport) > +{ > + u32 stat = dispc6_vp_read(dispc, hw_videoport, DISPC_VP_IRQSTATUS); > + > + return dispc6_vp_irq_from_raw(stat); > +} > + > +static void dispc6_vp_write_irqstatus(struct dispc_device *dispc, > + u32 hw_videoport, > + u64 vpstat) > +{ > + u32 stat = dispc6_vp_irq_to_raw(vpstat); > + > + dispc6_vp_write(dispc, hw_videoport, DISPC_VP_IRQSTATUS, stat); > +} > + > +static u64 dispc6_vid_read_irqstatus(struct dispc_device *dispc, > + u32 hw_plane) > +{ > + u32 stat = dispc6_vid_read(dispc, hw_plane, DISPC_VID_IRQSTATUS); > + > + return dispc6_vid_irq_from_raw(stat); > +} > + > +static void dispc6_vid_write_irqstatus(struct dispc_device *dispc, > + u32 hw_plane, > + u64 vidstat) > +{ > + u32 stat = dispc6_vid_irq_to_raw(vidstat); > + > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_IRQSTATUS, stat); > +} > + > + > +static u64 dispc6_vp_read_irqenable(struct dispc_device *dispc, > + u32 hw_videoport) > +{ > + u32 stat = dispc6_vp_read(dispc, hw_videoport, DISPC_VP_IRQENABLE); > + > + return dispc6_vp_irq_from_raw(stat); > +} > + > +static void dispc6_vp_write_irqenable(struct dispc_device *dispc, > + u32 hw_videoport, > + u64 vpstat) > +{ > + u32 stat = dispc6_vp_irq_to_raw(vpstat); > + > + dispc6_vp_write(dispc, hw_videoport, DISPC_VP_IRQENABLE, stat); > +} > + > +static u64 dispc6_vid_read_irqenable(struct dispc_device *dispc, > + u32 hw_plane) > +{ > + u32 stat = dispc6_vid_read(dispc, hw_plane, DISPC_VID_IRQENABLE); > + > + return dispc6_vid_irq_from_raw(stat); > +} > + > +static void dispc6_vid_write_irqenable(struct dispc_device *dispc, > + u32 hw_plane, > + u64 vidstat) > +{ > + u32 stat = dispc6_vid_irq_to_raw(vidstat); > + > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_IRQENABLE, stat); > +} > + > + > +static void dispc6_clear_irqstatus(struct dispc_device *dispc, u64 mask) > +{ > + dispc6_vp_write_irqstatus(dispc, 0, mask); > + dispc6_vid_write_irqstatus(dispc, 0, mask); > +} > + > +static u64 dispc6_read_and_clear_irqstatus(struct dispc_device *dispc) > +{ > + u64 stat = 0; > + > + /* always clear the top level irqstatus */ > + dispc6_write(dispc, DISPC_IRQSTATUS, > + dispc6_read(dispc, DISPC_IRQSTATUS)); > + > + stat |= dispc6_vp_read_irqstatus(dispc, 0); > + stat |= dispc6_vid_read_irqstatus(dispc, 0); > + > + dispc6_clear_irqstatus(dispc, stat); > + > + return stat; > +} > + > +static u64 dispc6_read_irqenable(struct dispc_device *dispc) > +{ > + u64 stat = 0; > + > + stat |= dispc6_vp_read_irqenable(dispc, 0); > + stat |= dispc6_vid_read_irqenable(dispc, 0); > + > + return stat; > +} > + > +static void dispc6_write_irqenable(struct dispc_device *dispc, u64 mask) > +{ > + u64 old_mask = dispc6_read_irqenable(dispc); > + > + /* clear the irqstatus for newly enabled irqs */ > + dispc6_clear_irqstatus(dispc, (mask ^ old_mask) & mask); > + > + dispc6_vp_write_irqenable(dispc, 0, mask); > + dispc6_vid_write_irqenable(dispc, 0, mask); > + > + dispc6_write(dispc, DISPC_IRQENABLE_SET, (1 << 0) | (1 << 7)); > + > + /* flush posted write */ > + dispc6_read_irqenable(dispc); > +} > + > +static void dispc6_set_num_datalines(struct dispc_device *dispc, > + u32 hw_videoport, int num_lines) > +{ > + int v; > + > + switch (num_lines) { > + case 12: > + v = 0; break; > + case 16: > + v = 1; break; > + case 18: > + v = 2; break; > + case 24: > + v = 3; break; > + case 30: > + v = 4; break; > + case 36: > + v = 5; break; > + default: > + WARN_ON(1); > + v = 3; > + break; > + } > + > + VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, v, 10, 8); > +} > + > +static void dispc6_vp_enable(struct dispc_device *dispc, u32 hw_videoport, > + const struct drm_crtc_state *state) > +{ > + const struct drm_display_mode *mode = &state->adjusted_mode; > + const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state); > + bool align, onoff, rf, ieo, ipc, ihs, ivs; > + unsigned int i; > + u32 port_width; > + u32 hsw, hfp, hbp, vsw, vfp, vbp; > + > + for (i = 0; i < ARRAY_SIZE(dispc6_bus_formats); ++i) { > + if (dispc6_bus_formats[i].fmt != tstate->bus_format) > + continue; > + > + port_width = dispc6_bus_formats[i].port_width; > + break; > + } > + > + if (WARN_ON(i == ARRAY_SIZE(dispc6_bus_formats))) > + return; > + > + dispc6_set_num_datalines(dispc, hw_videoport, port_width); > + > + hfp = mode->hsync_start - mode->hdisplay; > + hsw = mode->hsync_end - mode->hsync_start; > + hbp = mode->htotal - mode->hsync_end; > + > + vfp = mode->vsync_start - mode->vdisplay; > + vsw = mode->vsync_end - mode->vsync_start; > + vbp = mode->vtotal - mode->vsync_end; > + > + dispc6_vp_write(dispc, hw_videoport, DISPC_VP_TIMING_H, > + FLD_VAL(hsw - 1, 7, 0) | > + FLD_VAL(hfp - 1, 19, 8) | > + FLD_VAL(hbp - 1, 31, 20)); > + > + dispc6_vp_write(dispc, hw_videoport, DISPC_VP_TIMING_V, > + FLD_VAL(vsw - 1, 7, 0) | > + FLD_VAL(vfp, 19, 8) | > + FLD_VAL(vbp, 31, 20)); > + > + if (mode->flags & DRM_MODE_FLAG_NVSYNC) > + ivs = true; > + else > + ivs = false; > + > + if (mode->flags & DRM_MODE_FLAG_NHSYNC) > + ihs = true; > + else > + ihs = false; > + > + if (tstate->bus_flags & DRM_BUS_FLAG_DE_LOW) > + ieo = true; > + else > + ieo = false; > + > + if (tstate->bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE) > + ipc = true; > + else > + ipc = false; > + > + /* always use the 'rf' setting */ > + onoff = true; > + > + if (tstate->bus_flags & DRM_BUS_FLAG_SYNC_NEGEDGE) > + rf = false; > + else > + rf = true; > + > + /* always use aligned syncs */ > + align = true; > + > + dispc6_vp_write(dispc, hw_videoport, DISPC_VP_POL_FREQ, > + FLD_VAL(align, 18, 18) | > + FLD_VAL(onoff, 17, 17) | > + FLD_VAL(rf, 16, 16) | > + FLD_VAL(ieo, 15, 15) | > + FLD_VAL(ipc, 14, 14) | > + FLD_VAL(ihs, 13, 13) | > + FLD_VAL(ivs, 12, 12)); > + > + dispc6_vp_write(dispc, hw_videoport, DISPC_VP_SIZE_SCREEN, > + FLD_VAL(mode->hdisplay - 1, 11, 0) | > + FLD_VAL(mode->vdisplay - 1, 27, 16)); > + > + VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1, 0, 0); > +} > + > +static void dispc6_vp_disable(struct dispc_device *dispc, u32 hw_videoport) > +{ > + VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 0, 0, 0); > +} > + > +static bool dispc6_vp_go_busy(struct dispc_device *dispc, > + u32 hw_videoport) > +{ > + return VP_REG_GET(dispc, hw_videoport, DISPC_VP_CONTROL, 5, 5); > +} > + > +static void dispc6_vp_go(struct dispc_device *dispc, > + u32 hw_videoport) > +{ > + VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1, 5, 5); > +} > + > +static u16 c8_to_c12(u8 c8) > +{ > + u16 c12; > + > + c12 = c8 << 4; > + > + /* Replication logic: Copy c8 4 MSB to 4 LSB for full scale c12 */ > + c12 = c8 >> 4; > + > + return c12; > +} > + > +static u64 argb8888_to_argb12121212(u32 argb8888) > +{ > + u8 a, r, g, b; > + u64 v; > + > + a = (argb8888 >> 24) & 0xff; > + r = (argb8888 >> 16) & 0xff; > + g = (argb8888 >> 8) & 0xff; > + b = (argb8888 >> 0) & 0xff; > + > + v = ((u64)c8_to_c12(a) << 36) | ((u64)c8_to_c12(r) << 24) | > + ((u64)c8_to_c12(g) << 12) | (u64)c8_to_c12(b); > + > + return v; > +} > + > +static void dispc6_vp_set_default_color(struct dispc_device *dispc, > + u32 hw_videoport, u32 default_color) > +{ > + u64 v; > + > + v = argb8888_to_argb12121212(default_color); > + > + dispc6_ovr_write(dispc, 0, DISPC_OVR_DEFAULT_COLOR, v & 0xffffffff); > + dispc6_ovr_write(dispc, 0, DISPC_OVR_DEFAULT_COLOR2, > + (v >> 32) & 0xffff); > +} > + > +static enum drm_mode_status dispc6_vp_mode_valid(struct dispc_device *dispc, > + u32 hw_videoport, > + const struct drm_display_mode *mode) > +{ > + u32 hsw, hfp, hbp, vsw, vfp, vbp; > + > + /* special case for 9MHz */ > + if (mode->clock * 1000 < dispc->feat->min_pclk && mode->clock != 9000) > + return MODE_CLOCK_LOW; > + > + if (mode->clock * 1000 > dispc->feat->max_pclk) > + return MODE_CLOCK_HIGH; > + > + if (mode->hdisplay > 4096) > + return MODE_BAD; > + > + if (mode->vdisplay > 4096) > + return MODE_BAD; > + > + /* TODO: add interlace support */ > + if (mode->flags & DRM_MODE_FLAG_INTERLACE) > + return MODE_NO_INTERLACE; > + > + hfp = mode->hsync_start - mode->hdisplay; > + hsw = mode->hsync_end - mode->hsync_start; > + hbp = mode->htotal - mode->hsync_end; > + > + vfp = mode->vsync_start - mode->vdisplay; > + vsw = mode->vsync_end - mode->vsync_start; > + vbp = mode->vtotal - mode->vsync_end; > + > + if (hsw < 1 || hsw > 256 || > + hfp < 1 || hfp > 4096 || > + hbp < 1 || hbp > 4096) > + return MODE_BAD_HVALUE; > + > + if (vsw < 1 || vsw > 256 || > + vfp < 0 || vfp > 4095 || > + vbp < 0 || vbp > 4095) > + return MODE_BAD_VVALUE; > + > + return MODE_OK; > +} > + > +static int dispc6_vp_check(struct dispc_device *dispc, u32 hw_videoport, > + const struct drm_crtc_state *state) > +{ > + const struct drm_display_mode *mode = &state->adjusted_mode; > + const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state); > + enum drm_mode_status ok; > + unsigned int i; > + > + ok = dispc6_vp_mode_valid(dispc, hw_videoport, mode); > + if (ok != MODE_OK) { > + dev_dbg(dispc->dev, "%s: bad mode: %ux%u pclk %u kHz\n", > + __func__, mode->hdisplay, mode->vdisplay, mode->clock); > + return -EINVAL; > + } > + > + for (i = 0; i < ARRAY_SIZE(dispc6_bus_formats); ++i) { > + if (dispc6_bus_formats[i].fmt == tstate->bus_format) > + break; > + } > + > + if (i == ARRAY_SIZE(dispc6_bus_formats)) { > + dev_dbg(dispc->dev, "%s: Unsupported bus format: %u\n", > + __func__, tstate->bus_format); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int dispc6_vp_enable_clk(struct dispc_device *dispc, u32 hw_videoport) > +{ > + int ret = clk_prepare_enable(dispc->vp_clk); > + > + if (ret) > + dev_err(dispc->dev, "%s: enabling clk failed: %d\n", __func__, > + ret); > + > + return ret; > +} > + > +static void dispc6_vp_disable_clk(struct dispc_device *dispc, > + u32 hw_videoport) > +{ > + clk_disable_unprepare(dispc->vp_clk); > +} > + > +static int dispc6_vp_set_clk_rate(struct dispc_device *dispc, > + u32 hw_videoport, unsigned long rate) > +{ > + int r; > + unsigned long new_rate; > + > + r = clk_set_rate(dispc->vp_clk, rate); > + if (r) { > + dev_err(dispc->dev, "Failed to set vp clk rate to %lu\n", > + rate); > + return r; > + } > + > + new_rate = clk_get_rate(dispc->vp_clk); > + > + if (rate != new_rate) > + dev_warn(dispc->dev, > + "Failed to get exact pix clock %lu != %lu\n", > + rate, new_rate); > + > + dev_dbg(dispc->dev, "New VP rate %lu Hz (requested %lu Hz)\n", > + clk_get_rate(dispc->vp_clk), rate); > + > + return 0; > +} > + > +/* CSC */ > + > +struct color_conv_coef { > + int ry, rcb, rcr; > + int gy, gcb, gcr; > + int by, bcb, bcr; > + int roffset, goffset, boffset; > + bool full_range; > +}; > + > +static void dispc6_vid_write_color_conv_coefs(struct dispc_device *dispc, > + u32 hw_plane, > + const struct color_conv_coef *ct) > +{ > +#define CVAL(x, y) (FLD_VAL(x, 26, 16) | FLD_VAL(y, 10, 0)) > + > + dispc6_vid_write(dispc, hw_plane, > + DISPC_VID_CONV_COEF(0), CVAL(ct->rcr, ct->ry)); > + dispc6_vid_write(dispc, hw_plane, > + DISPC_VID_CONV_COEF(1), CVAL(ct->gy, ct->rcb)); > + dispc6_vid_write(dispc, hw_plane, > + DISPC_VID_CONV_COEF(2), CVAL(ct->gcb, ct->gcr)); > + dispc6_vid_write(dispc, hw_plane, > + DISPC_VID_CONV_COEF(3), CVAL(ct->bcr, ct->by)); > + dispc6_vid_write(dispc, hw_plane, > + DISPC_VID_CONV_COEF(4), CVAL(0, ct->bcb)); > + > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_CONV_COEF(5), > + FLD_VAL(ct->roffset, 15, 3) | > + FLD_VAL(ct->goffset, 31, 19)); > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_CONV_COEF(6), > + FLD_VAL(ct->boffset, 15, 3)); > + > + VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, > + ct->full_range, 11, 11); > + > +#undef CVAL > +} > + > +static void dispc6_vid_csc_setup(struct dispc_device *dispc) > +{ > + /* YUV -> RGB, ITU-R BT.601, full range */ > + const struct color_conv_coef coefs_yuv2rgb_bt601_full = { > + 256, 0, 358, > + 256, -88, -182, > + 256, 452, 0, > + 0, -2048, -2048, > + true, > + }; > + > + dispc6_vid_write_color_conv_coefs(dispc, 0, &coefs_yuv2rgb_bt601_full); > +} > + > +static void dispc6_vid_csc_enable(struct dispc_device *dispc, > + u32 hw_plane, bool enable) > +{ > + VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, !!enable, 9, 9); > +} > + > +/* SCALER */ > + > +static u32 dispc6_calc_fir_inc(u32 in, u32 out) > +{ > + return (u32)div_u64(0x200000ull * in, out); > +} > + > +struct dispc6_vid_fir_coefs { > + s16 c2[16]; > + s16 c1[16]; > + u16 c0[9]; > +}; > + > +static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_null = { > + .c2 = { 0 }, > + .c1 = { 0 }, > + .c0 = { 512, 512, 512, 512, 512, 512, 512, 512, 256, }, > +}; > + > +/* M=8, Upscale x >= 1 */ > +static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m8 = { > + .c2 = { 0, -4, -8, -16, -24, -32, -40, -48, 0, 2, 4, 6, 8, 6, 4, 2, }, > + .c1 = { 0, 28, 56, 94, 132, 176, 220, 266, -56, -60, -64, -62, -60, -50, -40, -20, }, > + .c0 = { 512, 506, 500, 478, 456, 424, 392, 352, 312, }, > +}; > + > +/* 5-tap, M=22, Downscale Ratio 2.5 < x < 3 */ > +static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m22_5tap = { > + .c2 = { 16, 20, 24, 30, 36, 42, 48, 56, 0, 0, 0, 2, 4, 8, 12, 14, }, > + .c1 = { 132, 140, 148, 156, 164, 172, 180, 186, 64, 72, 80, 88, 96, 104, 112, 122, }, > + .c0 = { 216, 216, 216, 214, 212, 208, 204, 198, 192, }, > +}; > + > +/* 3-tap, M=22, Downscale Ratio 2.5 < x < 3 */ > +static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m22_3tap = { > + .c1 = { 100, 118, 136, 156, 176, 196, 216, 236, 0, 10, 20, 30, 40, 54, 68, 84, }, > + .c0 = { 312, 310, 308, 302, 296, 286, 276, 266, 256, }, > +}; > + > +enum dispc6_vid_fir_coef_set { > + DISPC6_VID_FIR_COEF_HORIZ, > + DISPC6_VID_FIR_COEF_HORIZ_UV, > + DISPC6_VID_FIR_COEF_VERT, > + DISPC6_VID_FIR_COEF_VERT_UV, > +}; > + > +static void dispc6_vid_write_fir_coefs(struct dispc_device *dispc, > + u32 hw_plane, > + enum dispc6_vid_fir_coef_set coef_set, > + const struct dispc6_vid_fir_coefs *coefs) > +{ > + static const u16 c0_regs[] = { > + [DISPC6_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H0, > + [DISPC6_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H0_C, > + [DISPC6_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V0, > + [DISPC6_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V0_C, > + }; > + > + static const u16 c12_regs[] = { > + [DISPC6_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H12, > + [DISPC6_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H12_C, > + [DISPC6_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V12, > + [DISPC6_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V12_C, > + }; > + > + const u16 c0_base = c0_regs[coef_set]; > + const u16 c12_base = c12_regs[coef_set]; > + int phase; > + > + for (phase = 0; phase <= 8; ++phase) { > + u16 reg = c0_base + phase * 4; > + u16 c0 = coefs->c0[phase]; > + > + dispc6_vid_write(dispc, hw_plane, reg, c0); > + } > + > + for (phase = 0; phase <= 15; ++phase) { > + u16 reg = c12_base + phase * 4; > + s16 c1, c2; > + u32 c12; > + > + c1 = coefs->c1[phase]; > + c2 = coefs->c2[phase]; > + c12 = FLD_VAL(c1, 19, 10) | FLD_VAL(c2, 29, 20); > + > + dispc6_vid_write(dispc, hw_plane, reg, c12); > + } > +} > + > +static void dispc6_vid_write_scale_coefs(struct dispc_device *dispc, > + u32 hw_plane) > +{ > + dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_HORIZ, > + &dispc6_fir_coefs_null); > + dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_HORIZ_UV, > + &dispc6_fir_coefs_null); > + dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_VERT, > + &dispc6_fir_coefs_null); > + dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_VERT_UV, > + &dispc6_fir_coefs_null); > +} > + > +static void dispc6_vid_set_scaling(struct dispc_device *dispc, > + u32 hw_plane, > + u32 orig_width, u32 orig_height, > + u32 out_width, u32 out_height, > + u32 fourcc) > +{ > + u32 in_w, in_h, in_w_uv, in_h_uv; > + u32 fir_hinc, fir_vinc, fir_hinc_uv, fir_vinc_uv; > + bool scale_x, scale_y; > + bool five_taps = false; /* XXX always 3-tap for now */ > + > + in_w = in_w_uv = orig_width; > + in_h = in_h_uv = orig_height; > + > + switch (fourcc) { > + case DRM_FORMAT_NV12: > + /* UV is subsampled by 2 horizontally and vertically */ > + in_h_uv >>= 1; > + in_w_uv >>= 1; > + break; > + > + case DRM_FORMAT_YUYV: > + case DRM_FORMAT_UYVY: > + /* UV is subsampled by 2 horizontally */ > + in_w_uv >>= 1; > + break; > + > + default: > + break; > + } > + > + scale_x = in_w != out_width || in_w_uv != out_width; > + scale_y = in_h != out_height || in_h_uv != out_height; > + > + /* HORIZONTAL RESIZE ENABLE */ > + VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, scale_x, 7, 7); > + > + /* VERTICAL RESIZE ENABLE */ > + VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, scale_y, 8, 8); > + > + /* Skip the rest if no scaling is used */ > + if (!scale_x && !scale_y) > + return; > + > + /* VERTICAL 5-TAPS */ > + VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, five_taps, 21, 21); > + > + /* FIR INC */ > + > + fir_hinc = dispc6_calc_fir_inc(in_w, out_width); > + fir_vinc = dispc6_calc_fir_inc(in_h, out_height); > + fir_hinc_uv = dispc6_calc_fir_inc(in_w_uv, out_width); > + fir_vinc_uv = dispc6_calc_fir_inc(in_h_uv, out_height); > + > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRH, fir_hinc); > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRV, fir_vinc); > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRH2, fir_hinc_uv); > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRV2, fir_vinc_uv); > + > + dispc6_vid_write_scale_coefs(dispc, hw_plane); > +} > + > +/* OTHER */ > + > +static const struct { > + u32 fourcc; > + u8 dss_code; > +} dispc6_color_formats[] = { > + { DRM_FORMAT_ARGB4444, 0x0, }, > + { DRM_FORMAT_ABGR4444, 0x1, }, > + { DRM_FORMAT_RGBA4444, 0x2, }, > + > + { DRM_FORMAT_RGB565, 0x3, }, > + { DRM_FORMAT_BGR565, 0x4, }, > + > + { DRM_FORMAT_ARGB1555, 0x5, }, > + { DRM_FORMAT_ABGR1555, 0x6, }, > + > + { DRM_FORMAT_ARGB8888, 0x7, }, > + { DRM_FORMAT_ABGR8888, 0x8, }, > + { DRM_FORMAT_RGBA8888, 0x9, }, > + { DRM_FORMAT_BGRA8888, 0xa, }, > + > + { DRM_FORMAT_XRGB8888, 0x27, }, > + { DRM_FORMAT_XBGR8888, 0x28, }, > + { DRM_FORMAT_RGBX8888, 0x29, }, > + { DRM_FORMAT_BGRX8888, 0x2a, }, > + > + { DRM_FORMAT_YUYV, 0x3e, }, > + { DRM_FORMAT_UYVY, 0x3f, }, > + > + { DRM_FORMAT_NV12, 0x3d, }, > +}; > + > +static bool dispc6_fourcc_is_yuv(u32 fourcc) > +{ > + switch (fourcc) { > + case DRM_FORMAT_YUYV: > + case DRM_FORMAT_UYVY: > + case DRM_FORMAT_NV12: > + return true; > + default: > + return false; > + } > +} > + > +static void dispc6_plane_set_pixel_format(struct dispc_device *dispc, > + u32 hw_plane, u32 fourcc) > +{ > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(dispc6_color_formats); ++i) { > + if (dispc6_color_formats[i].fourcc == fourcc) { > + VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, > + dispc6_color_formats[i].dss_code, > + 6, 1); > + return; > + } > + } > + > + WARN_ON(1); > +} > + > +static s32 pixinc(int pixels, u8 ps) > +{ > + if (pixels == 1) > + return 1; > + else if (pixels > 1) > + return 1 + (pixels - 1) * ps; > + else if (pixels < 0) > + return 1 - (-pixels + 1) * ps; > + > + WARN_ON(1); > + return 0; > +} > + > +static > +const struct tidss_plane_feat *dispc6_plane_feat(struct dispc_device *dispc, > + u32 hw_plane) > +{ > + static const struct tidss_plane_feat pfeat = { > + .color = { > + .encodings = BIT(DRM_COLOR_YCBCR_BT601), > + .ranges = BIT(DRM_COLOR_YCBCR_FULL_RANGE), > + .default_encoding = DRM_COLOR_YCBCR_BT601, > + .default_range = DRM_COLOR_YCBCR_FULL_RANGE, > + }, > + .blend = { > + .global_alpha = false, > + }, > + }; > + > + return &pfeat; > +} > + > +static int dispc6_plane_check(struct dispc_device *dispc, u32 hw_plane, > + const struct drm_plane_state *state, > + u32 hw_videoport) > +{ > + return 0; /* XXX: Dummy check function to be implemented later */ > +} > + > +static int dispc6_plane_setup(struct dispc_device *dispc, u32 hw_plane, > + const struct drm_plane_state *state, > + u32 hw_videoport) > +{ > + u32 fourcc = state->fb->format->format; > + dma_addr_t paddr = dispc7_plane_state_paddr(state); > + u16 cpp = state->fb->format->cpp[0]; > + u32 fb_width = state->fb->pitches[0] / cpp; > + u32 src_w = state->src_w >> 16; > + u32 src_h = state->src_h >> 16; > + > + dispc6_plane_set_pixel_format(dispc, hw_plane, fourcc); > + > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_0, paddr); > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_1, paddr); > + > + if (state->fb->format->num_planes == 2) { > + dma_addr_t p_uv_addr = dispc7_plane_state_p_uv_addr(state); > + > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_UV_0, p_uv_addr); > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_UV_1, p_uv_addr); > + } > + > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_PICTURE_SIZE, > + (src_w - 1) | ((src_h - 1) << 16)); > + > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_PIXEL_INC, > + pixinc(1, cpp)); > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_ROW_INC, > + pixinc(1 + fb_width - src_w, cpp)); > + > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_POSITION, > + state->crtc_x | (state->crtc_y << 16)); > + > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_SIZE, > + (state->crtc_w - 1) | ((state->crtc_h - 1) << 16)); > + > + dispc6_vid_set_scaling(dispc, hw_plane, src_w, src_h, > + state->crtc_w, state->crtc_h, > + fourcc); > + > + /* enable YUV->RGB color conversion */ > + if (dispc6_fourcc_is_yuv(fourcc)) > + dispc6_vid_csc_enable(dispc, hw_plane, true); > + else > + dispc6_vid_csc_enable(dispc, hw_plane, false); > + > + /* hw_videoport */ > + VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, 0, 16, 14); > + > + return 0; > +} > + > +static int dispc6_plane_enable(struct dispc_device *dispc, > + u32 hw_plane, bool enable) > +{ > + VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, !!enable, 0, 0); > + return 0; > +} > + > +static u32 dispc6_vid_get_fifo_size(struct dispc_device *dispc, > + u32 hw_plane) > +{ > + const u32 unit_size = 16; /* 128-bits */ > + > + return VID_REG_GET(dispc, hw_plane, DISPC_VID_BUF_SIZE_STATUS, 15, 0) * > + unit_size; > +} > + > +static void dispc6_vid_set_mflag_threshold(struct dispc_device *dispc, > + u32 hw_plane, > + u32 low, u32 high) > +{ > + dispc6_vid_write(dispc, hw_plane, DISPC_VID_MFLAG_THRESHOLD, > + FLD_VAL(high, 31, 16) | FLD_VAL(low, 15, 0)); > +} > + > +static void dispc6_mflag_setup(struct dispc_device *dispc) > +{ > + u32 hw_plane = 0; > + const u32 unit_size = 16; /* 128-bits */ > + u32 size = dispc6_vid_get_fifo_size(dispc, hw_plane); > + u32 low, high; > + > + /* MFLAG_CTRL */ > + REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 1, 1, 0); > + /* MFLAG_START */ > + REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 0, 2, 2); > + > + /* > + * Simulation team suggests below thesholds: > + * HT = fifosize * 5 / 8; > + * LT = fifosize * 4 / 8; > + */ > + > + low = size * 4 / 8 / unit_size; > + high = size * 5 / 8 / unit_size; > + > + dispc6_vid_set_mflag_threshold(dispc, hw_plane, low, high); > +} > + > +static void dispc6_initial_config(struct dispc_device *dispc) > +{ > + dispc6_vid_csc_setup(dispc); > + dispc6_mflag_setup(dispc); > + > + /* Enable the gamma Shadow bit-field */ > + VP_REG_FLD_MOD(dispc, 0, DISPC_VP_CONFIG, 1, 2, 2); > +} > + > +static int dispc6_init_features(struct dispc_device *dispc) > +{ > + const struct of_device_id *match; > + > + match = of_match_node(dispc6_of_match, dispc->dev->of_node); > + if (!match) { > + dev_err(dispc->dev, "Unsupported DISPC version\n"); > + return -ENODEV; > + } > + > + dispc->feat = match->data; > + > + return 0; > +} > + > +static int dispc6_get_num_planes(struct dispc_device *dispc) > +{ > + return 1; > +} > + > +static int dispc6_get_num_vps(struct dispc_device *dispc) > +{ > + return 1; > +} > + > +static const struct tidss_vp_feat *dispc6_vp_feat(struct dispc_device *dispc, > + u32 hw_videoport) > +{ > + static const struct tidss_vp_feat vp_feat = { > + .color = { > + .gamma_size = DISPC6_GAMMA_TABLE_SIZE, > + .has_ctm = false, /* Driver implementation missing */ > + }, > + }; > + > + return &vp_feat; > +} > + > +static void dispc6_vp_write_gamma_table(struct dispc_device *dispc, > + u32 hw_videoport) > +{ > + u32 *table = dispc->gamma_table; > + unsigned int hwlen = ARRAY_SIZE(dispc->gamma_table); > + unsigned int i; > + > + dev_dbg(dispc->dev, "%s: hw_videoport %d\n", __func__, hw_videoport); > + > + for (i = 0; i < hwlen; ++i) { > + u32 v = table[i]; > + > + v |= i << 24; > + > + dispc6_vp_write(dispc, hw_videoport, DISPC_VP_GAMMA_TABLE, v); > + } > +} > + > +static void dispc6_restore_gamma_tables(struct dispc_device *dispc) > +{ > + dev_dbg(dispc->dev, "%s()\n", __func__); > + > + dispc6_vp_write_gamma_table(dispc, 0); > +} > + > +static const struct drm_color_lut dispc6_vp_gamma_default_lut[] = { > + { .red = 0, .green = 0, .blue = 0, }, > + { .red = U16_MAX, .green = U16_MAX, .blue = U16_MAX, }, > +}; > + > +static void dispc6_vp_set_gamma(struct dispc_device *dispc, > + u32 hw_videoport, > + const struct drm_color_lut *lut, > + unsigned int length) > +{ > + u32 *table = dispc->gamma_table; > + unsigned int hwlen = ARRAY_SIZE(dispc->gamma_table); > + static const unsigned int hwbits = 8; > + unsigned int i; > + > + dev_dbg(dispc->dev, "%s: hw_videoport %d, lut len %u, hw len %u\n", > + __func__, hw_videoport, length, hwlen); > + > + if (lut == NULL || length < 2) { > + lut = dispc6_vp_gamma_default_lut; > + length = ARRAY_SIZE(dispc6_vp_gamma_default_lut); > + } > + > + for (i = 0; i < length - 1; ++i) { > + unsigned int first = i * (hwlen - 1) / (length - 1); > + unsigned int last = (i + 1) * (hwlen - 1) / (length - 1); > + unsigned int w = last - first; > + u16 r, g, b; > + unsigned int j; > + > + if (w == 0) > + continue; > + > + for (j = 0; j <= w; j++) { > + r = (lut[i].red * (w - j) + lut[i + 1].red * j) / w; > + g = (lut[i].green * (w - j) + lut[i + 1].green * j) / w; > + b = (lut[i].blue * (w - j) + lut[i + 1].blue * j) / w; > + > + r >>= 16 - hwbits; > + g >>= 16 - hwbits; > + b >>= 16 - hwbits; > + > + table[first + j] = (r << (hwbits * 2)) | > + (g << hwbits) | b; > + } > + } > + > + if (dispc->is_enabled) > + dispc6_vp_write_gamma_table(dispc, hw_videoport); > +} > + > +static void dispc6_vp_set_color_mgmt(struct dispc_device *dispc, > + u32 hw_videoport, > + const struct drm_crtc_state *state) > +{ > + struct drm_color_lut *lut = NULL; > + unsigned int length = 0; > + > + if (!state->color_mgmt_changed) > + return; > + > + if (state->gamma_lut) { > + lut = (struct drm_color_lut *) state->gamma_lut->data; > + length = state->gamma_lut->length / sizeof(*lut); > + } > + > + dispc6_vp_set_gamma(dispc, hw_videoport, lut, length); > +} > + > +static void dispc6_vp_setup(struct dispc_device *dispc, u32 hw_videoport, > + const struct drm_crtc_state *state) > +{ > + dispc6_vp_set_default_color(dispc, hw_videoport, 0); > + dispc6_vp_set_color_mgmt(dispc, hw_videoport, state); > +} > + > +static int dispc6_init_gamma_tables(struct dispc_device *dispc) > +{ > + dispc6_vp_set_gamma(dispc, 0, NULL, 0); > + > + return 0; > +} > + > +static u32 dispc6_get_memory_bandwidth_limit(struct dispc_device *dispc) > +{ > + u32 limit = 0; > + > + /* Optional maximum memory bandwidth */ > + of_property_read_u32(dispc->dev->of_node, "max-memory-bandwidth", > + &limit); > + > + return limit; > +} > + > +static const char *dispc6_plane_name(struct dispc_device *dispc, > + u32 hw_plane) > +{ > + return "vid1"; > +} > + > +static const char *dispc6_vp_name(struct dispc_device *dispc, > + u32 hw_videoport) > +{ > + return "vp1"; > +} > + > +static int dispc6_runtime_suspend(struct dispc_device *dispc) > +{ > + struct device *dev = dispc->dev; > + > + dev_dbg(dev, "suspend\n"); > + > + dispc->is_enabled = false; > + > + clk_disable_unprepare(dispc->fclk); > + > + return 0; > +} > + > +static int dispc6_runtime_resume(struct dispc_device *dispc) > +{ > + struct device *dev = dispc->dev; > + > + dev_dbg(dev, "resume\n"); > + > + clk_prepare_enable(dispc->fclk); > + > + if (REG_GET(dispc, DISPC_SYSSTATUS, 0, 0) == 0) > + dev_warn(dev, "DISPC FUNC RESET not done!\n"); > + if (REG_GET(dispc, DISPC_SYSSTATUS, 1, 1) == 0) > + dev_warn(dev, "DISPC VP RESET not done!\n"); > + > + dispc6_initial_config(dispc); > + > + dispc6_restore_gamma_tables(dispc); > + > + dispc->is_enabled = true; > + > + return 0; > +} > + > +static int dispc6_modeset_init(struct dispc_device *dispc) > +{ > + struct tidss_device *tidss = dispc->tidss; > + struct device *dev = tidss->dev; > + const u32 hw_videoport = 0; > + const u32 crtc_mask = 1; > + const u32 hw_plane_id = 0; > + struct drm_panel *panel; > + struct drm_bridge *bridge; > + u32 enc_type; > + int ret; > + struct tidss_plane *tplane; > + struct tidss_crtc *tcrtc; > + struct drm_encoder *enc; > + u32 fourccs[ARRAY_SIZE(dispc6_color_formats)]; > + unsigned int i; > + > + /* first find if there is a connected panel/bridge */ > + > + ret = drm_of_find_panel_or_bridge(dev->of_node, hw_videoport, 0, &panel, &bridge); > + if (ret) { > + dev_dbg(dev, "no panel or bridge found\n"); > + return ret; > + } > + > + if (panel) { > + dev_dbg(dev, "Setting up panel\n"); > + > + enc_type = DRM_MODE_ENCODER_DPI; > + > + bridge = devm_drm_panel_bridge_add(dev, panel, DRM_MODE_CONNECTOR_DPI); > + if (IS_ERR(bridge)) { > + dev_err(dev, "failed to set up panel bridge\n"); > + return PTR_ERR(bridge); > + } > + } else { > + enc_type = DRM_MODE_ENCODER_NONE; > + } > + > + /* then create a plane, a crtc and an encoder for the panel/bridge */ > + > + for (i = 0; i < ARRAY_SIZE(dispc6_color_formats); ++i) > + fourccs[i] = dispc6_color_formats[i].fourcc; > + > + tplane = tidss_plane_create(tidss, hw_plane_id, DRM_PLANE_TYPE_PRIMARY, > + crtc_mask, fourccs, ARRAY_SIZE(fourccs)); > + if (IS_ERR(tplane)) { > + dev_err(tidss->dev, "plane create failed\n"); > + return PTR_ERR(tplane); > + } > + > + tidss->planes[tidss->num_planes++] = &tplane->plane; > + > + tcrtc = tidss_crtc_create(tidss, hw_videoport, &tplane->plane); > + if (IS_ERR(tcrtc)) { > + dev_err(tidss->dev, "crtc create failed\n"); > + return PTR_ERR(tcrtc); > + } > + > + tidss->crtcs[tidss->num_crtcs++] = &tcrtc->crtc; > + > + enc = tidss_encoder_create(tidss, enc_type, 1 << tcrtc->crtc.index); > + if (IS_ERR(enc)) { > + dev_err(tidss->dev, "encoder create failed\n"); > + return PTR_ERR(enc); > + } > + > + ret = drm_bridge_attach(enc, bridge, NULL); > + if (ret) { > + dev_err(tidss->dev, "bridge attach failed: %d\n", ret); > + return ret; > + } > + > + return 0; > +} > + > +static void dispc6_remove(struct dispc_device *dispc); > + > +static const struct tidss_dispc_ops dispc6_ops = { > + .read_and_clear_irqstatus = dispc6_read_and_clear_irqstatus, > + .write_irqenable = dispc6_write_irqenable, > + > + .get_memory_bandwidth_limit = dispc6_get_memory_bandwidth_limit, > + > + .get_num_vps = dispc6_get_num_vps, > + .vp_name = dispc6_vp_name, > + .vp_feat = dispc6_vp_feat, > + .vp_enable = dispc6_vp_enable, > + .vp_disable = dispc6_vp_disable, > + .vp_go_busy = dispc6_vp_go_busy, > + .vp_go = dispc6_vp_go, > + .vp_mode_valid = dispc6_vp_mode_valid, > + .vp_check = dispc6_vp_check, > + .vp_setup = dispc6_vp_setup, > + > + .vp_set_clk_rate = dispc6_vp_set_clk_rate, > + .vp_enable_clk = dispc6_vp_enable_clk, > + .vp_disable_clk = dispc6_vp_disable_clk, > + > + .get_num_planes = dispc6_get_num_planes, > + .plane_name = dispc6_plane_name, > + .plane_feat = dispc6_plane_feat, > + .plane_enable = dispc6_plane_enable, > + .plane_check = dispc6_plane_check, > + .plane_setup = dispc6_plane_setup, > + > + .runtime_get = dispc6_runtime_get, > + .runtime_put = dispc6_runtime_put, > + > + .runtime_suspend = dispc6_runtime_suspend, > + .runtime_resume = dispc6_runtime_resume, > + > + .remove = dispc6_remove, > + > + .modeset_init = dispc6_modeset_init, > +}; > + > +static int dispc6_iomap_resource(struct platform_device *pdev, const char *name, > + void __iomem **base) > +{ > + struct resource *res; > + void __iomem *b; > + > + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); > + b = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(b)) { > + dev_err(&pdev->dev, "cannot ioremap resource '%s'\n", name); > + return PTR_ERR(b); > + } > + > + *base = b; > + > + return 0; > +} > + > +int dispc6_init(struct tidss_device *tidss) > +{ > + struct device *dev = tidss->dev; > + struct platform_device *pdev = to_platform_device(dev); > + struct dispc_device *dispc; > + int r; > + > + dev_dbg(dev, "%s\n", __func__); > + > + dispc = devm_kzalloc(dev, sizeof(*dispc), GFP_KERNEL); > + if (!dispc) > + return -ENOMEM; > + > + dispc->tidss = tidss; > + dispc->dev = dev; > + > + r = dispc6_init_features(dispc); > + if (r) > + goto err_free; > + > + r = dispc6_iomap_resource(pdev, "cfg", &dispc->base_cfg); > + if (r) > + goto err_free; > + > + r = dispc6_iomap_resource(pdev, "common", &dispc->base_common); > + if (r) > + goto err_free; > + > + r = dispc6_iomap_resource(pdev, "vid1", &dispc->base_vid1); > + if (r) > + goto err_free; > + > + r = dispc6_iomap_resource(pdev, "ovr1", &dispc->base_ovr1); > + if (r) > + goto err_free; > + > + r = dispc6_iomap_resource(pdev, "vp1", &dispc->base_vp1); > + if (r) > + goto err_free; > + > + dev_dbg(dev, "dispc6_bind: iores ok\n"); > + > + dispc->fclk = devm_clk_get(dev, "fck"); > + if (IS_ERR(dispc->fclk)) { > + dev_err(dev, "Failed to get fclk\n"); > + r = PTR_ERR(dispc->fclk); > + goto err_free; > + } > + > + dispc->vp_clk = devm_clk_get(dev, "vp1"); > + if (IS_ERR(dispc->vp_clk)) { > + dev_err(dev, "Failed to get vp1 clk\n"); > + r = PTR_ERR(dispc->vp_clk); > + goto err_free; > + } > + > + r = dispc6_init_gamma_tables(dispc); > + if (r) > + goto err_free; > + > + tidss->dispc_ops = &dispc6_ops; > + tidss->dispc = dispc; > + > + dev_dbg(dev, "%s done\n", __func__); > + > + return 0; > +err_free: > + dev_err(dev, "%s failed: %d\n", __func__, r); > + return r; > +} > + > +static void dispc6_remove(struct dispc_device *dispc) > +{ > + struct device *dev = dispc->dev; > + > + dev_dbg(dev, "dispc6_unbind\n"); > + > + dispc->tidss->dispc_ops = NULL; > + dispc->tidss->dispc = NULL; > + > + dev_dbg(dev, "dispc6_unbind done\n"); > +} > diff --git a/drivers/gpu/drm/tidss/tidss_dispc6.h b/drivers/gpu/drm/tidss/tidss_dispc6.h > new file mode 100644 > index 0000000..80197c8 > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_dispc6.h > @@ -0,0 +1,109 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#ifndef __TIDSS_DISPC6_H__ > +#define __TIDSS_DISPC6_H__ > + > +/* COMMON */ > + > +#define DISPC_REVISION 0x000 > +#define DISPC_SYSCONFIG 0x004 > +#define DISPC_SYSSTATUS 0x008 > + > +#define DISPC_IRQ_EOI 0x020 > +#define DISPC_IRQSTATUS_RAW 0x024 > +#define DISPC_IRQSTATUS 0x028 > +#define DISPC_IRQENABLE_SET 0x02c > +#define DISPC_IRQENABLE_CLR 0x030 > +#define DISPC_IRQWAKEEN 0x034 > + > +#define DISPC_GLOBAL_MFLAG_ATTRIBUTE 0x040 > +#define DISPC_GLOBAL_BUFFER 0x044 > +#define DISPC_BA0_FLIPIMMEDIATE_EN 0x048 > + > +#define DISPC_DBG_CONTROL 0x04c > +#define DISPC_DBG_STATUS 0x050 > + > +#define DISPC_CLKGATING_DISABLE 0x054 > + > +/* VID */ > + > +#define DISPC_VID_ACCUH_0 0x0 > +#define DISPC_VID_ACCUH_1 0x4 > +#define DISPC_VID_ACCUH2_0 0x8 > +#define DISPC_VID_ACCUH2_1 0xc > + > +#define DISPC_VID_ACCUV_0 0x10 > +#define DISPC_VID_ACCUV_1 0x14 > +#define DISPC_VID_ACCUV2_0 0x18 > +#define DISPC_VID_ACCUV2_1 0x1c > + > +#define DISPC_VID_ATTRIBUTES 0x20 > +#define DISPC_VID_ATTRIBUTES2 0x24 > + > +#define DISPC_VID_BA_0 0x28 > +#define DISPC_VID_BA_1 0x2c > +#define DISPC_VID_BA_UV_0 0x30 > +#define DISPC_VID_BA_UV_1 0x34 > +#define DISPC_VID_BUF_SIZE_STATUS 0x38 > +#define DISPC_VID_BUF_THRESHOLD 0x3c > + > +#define DISPC_VID_CONV_COEF(n) (0x40 + (n) * 4) > + > +#define DISPC_VID_FIRH 0x5c > +#define DISPC_VID_FIRH2 0x60 > +#define DISPC_VID_FIRV 0x64 > +#define DISPC_VID_FIRV2 0x68 > + > +#define DISPC_VID_FIR_COEFS_H0 0x6c > +#define DISPC_VID_FIR_COEF_H0(phase) (0x6c + (phase) * 4) > +#define DISPC_VID_FIR_COEFS_H0_C 0x90 > +#define DISPC_VID_FIR_COEF_H0_C(phase) (0x90 + (phase) * 4) > + > +#define DISPC_VID_FIR_COEFS_H12 0xb4 > +#define DISPC_VID_FIR_COEF_H12(phase) (0xb4 + (phase) * 4) > +#define DISPC_VID_FIR_COEFS_H12_C 0xf4 > +#define DISPC_VID_FIR_COEF_H12_C(phase) (0xf4 + (phase) * 4) > + > +#define DISPC_VID_FIR_COEFS_V0 0x134 > +#define DISPC_VID_FIR_COEF_V0(phase) (0x134 + (phase) * 4) > +#define DISPC_VID_FIR_COEFS_V0_C 0x158 > +#define DISPC_VID_FIR_COEF_V0_C(phase) (0x158 + (phase) * 4) > + > +#define DISPC_VID_FIR_COEFS_V12 0x17c > +#define DISPC_VID_FIR_COEF_V12(phase) (0x17c + (phase) * 4) > +#define DISPC_VID_FIR_COEFS_V12_C 0x1bc > +#define DISPC_VID_FIR_COEF_V12_C(phase) (0x1bc + (phase) * 4) > + > +#define DISPC_VID_IRQENABLE 0x200 > +#define DISPC_VID_IRQSTATUS 0x204 > + > +#define DISPC_VID_MFLAG_THRESHOLD 0x208 > +#define DISPC_VID_PICTURE_SIZE 0x20c > +#define DISPC_VID_PIXEL_INC 0x210 > +#define DISPC_VID_POSITION 0x214 > +#define DISPC_VID_PRELOAD 0x218 > +#define DISPC_VID_ROW_INC 0x21c > +#define DISPC_VID_SIZE 0x220 > + > +/* OVR */ > + > +#define DISPC_OVR_DEFAULT_COLOR 0x08 > +#define DISPC_OVR_DEFAULT_COLOR2 0x0c > + > +/* VP */ > + > +#define DISPC_VP_CONFIG 0x00 > +#define DISPC_VP_CONTROL 0x04 > +#define DISPC_VP_GAMMA_TABLE 0x20 > +#define DISPC_VP_IRQENABLE 0x3c > +#define DISPC_VP_IRQSTATUS 0x40 > +#define DISPC_VP_POL_FREQ 0x4c > +#define DISPC_VP_SIZE_SCREEN 0x50 > +#define DISPC_VP_TIMING_H 0x54 > +#define DISPC_VP_TIMING_V 0x58 > + > +#endif > diff --git a/drivers/gpu/drm/tidss/tidss_drv.c b/drivers/gpu/drm/tidss/tidss_drv.c > new file mode 100644 > index 0000000..8485f9a > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_drv.c > @@ -0,0 +1,305 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#include <linux/console.h> > +#include <linux/of_device.h> > +#include <linux/pm_runtime.h> > + > +#include <drm/drmP.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_fb_cma_helper.h> > +#include <drm/drm_fb_helper.h> > +#include <drm/drm_gem_cma_helper.h> > + > +#include "tidss_dispc.h" > +#include "tidss_drv.h" > +#include "tidss_irq.h" > +#include "tidss_kms.h" > + > +/* ----------------------------------------------------------------------------- > + * Device Information > + */ > + > +DEFINE_DRM_GEM_CMA_FOPS(tidss_fops); > + > +static struct drm_driver tidss_driver = { > + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME > + | DRIVER_ATOMIC | DRIVER_HAVE_IRQ, > + .gem_free_object_unlocked = drm_gem_cma_free_object, > + .gem_vm_ops = &drm_gem_cma_vm_ops, > + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, > + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, > + .gem_prime_import = drm_gem_prime_import, > + .gem_prime_export = drm_gem_prime_export, > + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, > + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, > + .gem_prime_vmap = drm_gem_cma_prime_vmap, > + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, > + .gem_prime_mmap = drm_gem_cma_prime_mmap, > + .dumb_create = drm_gem_cma_dumb_create, > + .fops = &tidss_fops, > + .name = "tidss", > + .desc = "TI Keystone DSS", > + .date = "20180215", > + .major = 1, > + .minor = 0, > + > + .irq_preinstall = tidss_irq_preinstall, > + .irq_postinstall = tidss_irq_postinstall, > + .irq_handler = tidss_irq_handler, > + .irq_uninstall = tidss_irq_uninstall, > +}; > + > +#ifdef CONFIG_PM > +/* ----------------------------------------------------------------------------- > + * Power management > + */ > + > +static int tidss_pm_runtime_suspend(struct device *dev) > +{ > + struct tidss_device *tidss = dev_get_drvdata(dev); > + > + dev_dbg(dev, "%s\n", __func__); > + > + return tidss->dispc_ops->runtime_suspend(tidss->dispc); > +} > + > +static int tidss_pm_runtime_resume(struct device *dev) > +{ > + struct tidss_device *tidss = dev_get_drvdata(dev); > + int r; > + > + dev_dbg(dev, "%s\n", __func__); > + > + r = tidss->dispc_ops->runtime_resume(tidss->dispc); > + if (r) > + return r; > + > + tidss_irq_resume(tidss->ddev); > + > + return 0; > +} > + > +#ifdef CONFIG_PM_SLEEP > +static int tidss_suspend(struct device *dev) > +{ > + struct tidss_device *tidss = dev_get_drvdata(dev); > + > + dev_dbg(dev, "%s\n", __func__); > + > + return drm_mode_config_helper_suspend(tidss->ddev); > +} > + > +static int tidss_resume(struct device *dev) > +{ > + struct tidss_device *tidss = dev_get_drvdata(dev); > + > + dev_dbg(dev, "%s\n", __func__); > + > + return drm_mode_config_helper_resume(tidss->ddev); > +} > +#endif /* CONFIG_PM_SLEEP */ > + > +static const struct dev_pm_ops tidss_pm_ops = { > + .runtime_suspend = tidss_pm_runtime_suspend, > + .runtime_resume = tidss_pm_runtime_resume, > + SET_SYSTEM_SLEEP_PM_OPS(tidss_suspend, tidss_resume) > +}; > + > +#endif /* CONFIG_PM */ > + > +/* ----------------------------------------------------------------------------- > + * Platform driver > + */ > + > +static int tidss_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct tidss_device *tidss; > + struct drm_device *ddev; > + int ret; > + int irq; > + > + dev_dbg(dev, "%s\n", __func__); > + > + tidss = devm_kzalloc(dev, sizeof(*tidss), GFP_KERNEL); > + if (tidss == NULL) > + return -ENOMEM; > + > + tidss->dev = dev; > + tidss->features = of_device_get_match_data(dev); > + > + platform_set_drvdata(pdev, tidss); > + > + ddev = drm_dev_alloc(&tidss_driver, dev); > + if (IS_ERR(ddev)) > + return PTR_ERR(ddev); > + > + tidss->ddev = ddev; > + ddev->dev_private = tidss; > + > + pm_runtime_enable(dev); > + > + ret = tidss->features->dispc_init(tidss); > + if (ret) { > + dev_err(dev, "failed to initialize dispc: %d\n", ret); > + goto err_disable_pm; > + } > + > +#ifndef CONFIG_PM_SLEEP > + /* no PM, so force enable DISPC */ > + tidss->dispc_ops->runtime_resume(tidss->dispc); > +#endif > + > + ret = tidss_modeset_init(tidss); > + if (ret < 0) { > + if (ret != -EPROBE_DEFER) > + dev_err(dev, "failed to init DRM/KMS (%d)\n", ret); > + goto err_runtime_suspend; > + } > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) { > + ret = irq; > + dev_err(dev, "platform_get_irq failed: %d\n", ret); > + goto err_modeset_cleanup; > + } > + > + ret = drm_irq_install(ddev, irq); > + if (ret) { > + dev_err(dev, "drm_irq_install failed: %d\n", ret); > + goto err_modeset_cleanup; > + } > + > +#ifdef CONFIG_DRM_FBDEV_EMULATION > + if (ddev->mode_config.num_connector) { > + struct drm_fbdev_cma *fbdev; > + > + fbdev = drm_fbdev_cma_init(ddev, 32, > + ddev->mode_config.num_connector); > + if (IS_ERR(fbdev)) { > + dev_err(tidss->dev, "fbdev init failed\n"); > + ret = PTR_ERR(fbdev); > + goto err_irq_uninstall; > + } > + > + tidss->fbdev = fbdev; > + } > +#endif > + > + drm_kms_helper_poll_init(ddev); > + > + ret = drm_dev_register(ddev, 0); > + if (ret) { > + dev_err(dev, "failed to register DRM device\n"); > + goto err_poll_fini; > + } > + > + dev_dbg(dev, "%s done\n", __func__); > + > + return 0; > + > +err_poll_fini: > + drm_kms_helper_poll_fini(ddev); > + > + if (tidss->fbdev) > + drm_fbdev_cma_fini(tidss->fbdev); > + > + drm_atomic_helper_shutdown(ddev); > + > +#ifdef CONFIG_DRM_FBDEV_EMULATION > +err_irq_uninstall: > +#endif > + drm_irq_uninstall(ddev); > + > +err_modeset_cleanup: > + drm_mode_config_cleanup(ddev); > + > +err_runtime_suspend: > +#ifndef CONFIG_PM_SLEEP > + /* no PM, so force disable DISPC */ > + tidss->dispc_ops->runtime_suspend(tidss->dispc); > +#endif > + > + tidss->dispc_ops->remove(tidss->dispc); > + > +err_disable_pm: > + pm_runtime_disable(dev); > + > + drm_dev_put(ddev); > + > + return ret; > +} > + > +static int tidss_remove(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct tidss_device *tidss = platform_get_drvdata(pdev); > + struct drm_device *ddev = tidss->ddev; > + > + dev_dbg(dev, "%s\n", __func__); > + > + drm_dev_unregister(ddev); > + > + drm_kms_helper_poll_fini(ddev); > + > + if (tidss->fbdev) > + drm_fbdev_cma_fini(tidss->fbdev); > + > + drm_atomic_helper_shutdown(ddev); > + > + drm_irq_uninstall(ddev); > + > + drm_mode_config_cleanup(ddev); > + > +#ifndef CONFIG_PM_SLEEP > + /* no PM, so force disable DISPC */ > + tidss->dispc_ops->runtime_suspend(tidss->dispc); > +#endif > + > + tidss->dispc_ops->remove(tidss->dispc); > + > + pm_runtime_disable(dev); > + > + drm_dev_put(ddev); > + > + dev_dbg(dev, "%s done\n", __func__); > + > + return 0; > +} > + > +static const struct tidss_features tidss_k2g_features = { > + .dispc_init = dispc6_init, > +}; > + > +static const struct of_device_id tidss_of_table[] = { > + { .compatible = "ti,k2g-dss", .data = &tidss_k2g_features }, > + { } > +}; > + > +MODULE_DEVICE_TABLE(of, tidss_of_table); > + > +static struct platform_driver tidss_platform_driver = { > + .probe = tidss_probe, > + .remove = tidss_remove, > + .driver = { > + .name = "tidss", > +#ifdef CONFIG_PM > + .pm = &tidss_pm_ops, > +#endif > + .of_match_table = tidss_of_table, > + .suppress_bind_attrs = true, > + }, > +}; > + > +module_platform_driver(tidss_platform_driver); > + > +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@xxxxxx>"); > +MODULE_DESCRIPTION("TI Keystone DSS Driver"); > +MODULE_LICENSE("GPL v2"); > diff --git a/drivers/gpu/drm/tidss/tidss_drv.h b/drivers/gpu/drm/tidss/tidss_drv.h > new file mode 100644 > index 0000000..ff055aa > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_drv.h > @@ -0,0 +1,40 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#ifndef __TIDSS_DRV_H__ > +#define __TIDSS_DRV_H__ > + > +#include <linux/spinlock.h> > + > +struct tidss_device { > + struct device *dev; /* Underlying DSS device */ > + struct drm_device *ddev; /* DRM device for DSS */ > + > + struct drm_fbdev_cma *fbdev; > + > + struct dispc_device *dispc; > + const struct tidss_dispc_ops *dispc_ops; > + > + const struct tidss_features *features; > + > + unsigned int num_crtcs; > + struct drm_crtc *crtcs[8]; > + > + unsigned int num_planes; > + struct drm_plane *planes[8]; > + > + spinlock_t wait_lock; /* protects the irq masks */ > + u64 irq_mask; /* enabled irqs in addition to wait_list */ > + u64 irq_uf_mask; /* underflow irq bits for all planes */ > + > + struct drm_atomic_state *saved_state; > +}; > + > +struct tidss_features { > + int (*dispc_init)(struct tidss_device *tidss); > +}; > + > +#endif > diff --git a/drivers/gpu/drm/tidss/tidss_encoder.c b/drivers/gpu/drm/tidss/tidss_encoder.c > new file mode 100644 > index 0000000..32ee294 > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_encoder.c > @@ -0,0 +1,70 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#include <linux/export.h> > + > +#include <drm/drmP.h> > +#include <drm/drm_crtc.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_panel.h> > +#include <drm/drm_of.h> > + > +#include "tidss_drv.h" > +#include "tidss_encoder.h" > +#include "tidss_crtc.h" > + > +/* ----------------------------------------------------------------------------- > + * Encoder > + */ > + > +static int tidss_encoder_atomic_check(struct drm_encoder *encoder, > + struct drm_crtc_state *crtc_state, > + struct drm_connector_state *conn_state) > +{ > + struct drm_device *ddev = encoder->dev; > + struct tidss_crtc_state *tcrtc_state = to_tidss_crtc_state(crtc_state); > + struct drm_display_info *di = &conn_state->connector->display_info; > + > + dev_dbg(ddev->dev, "%s\n", __func__); > + > + // XXX any cleaner way to set bus format and flags? > + tcrtc_state->bus_format = di->bus_formats[0]; > + tcrtc_state->bus_flags = di->bus_flags; > + > + return 0; > +} > + > +static const struct drm_encoder_helper_funcs encoder_helper_funcs = { > + .atomic_check = tidss_encoder_atomic_check, > +}; > + > +static const struct drm_encoder_funcs encoder_funcs = { > + .destroy = drm_encoder_cleanup, > +}; > + > +struct drm_encoder *tidss_encoder_create(struct tidss_device *tidss, > + u32 encoder_type, u32 possible_crtcs) > +{ > + struct drm_encoder *enc; > + int ret; > + > + enc = devm_kzalloc(tidss->dev, sizeof(*enc), GFP_KERNEL); > + if (!enc) > + return ERR_PTR(-ENOMEM); > + > + enc->possible_crtcs = possible_crtcs; > + > + ret = drm_encoder_init(tidss->ddev, enc, &encoder_funcs, > + encoder_type, NULL); > + if (ret < 0) > + return ERR_PTR(ret); > + > + drm_encoder_helper_add(enc, &encoder_helper_funcs); > + > + dev_dbg(tidss->dev, "Encoder create done\n"); > + > + return enc; > +} > diff --git a/drivers/gpu/drm/tidss/tidss_encoder.h b/drivers/gpu/drm/tidss/tidss_encoder.h > new file mode 100644 > index 0000000..06854d6 > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_encoder.h > @@ -0,0 +1,17 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#ifndef __TIDSS_ENCODER_H__ > +#define __TIDSS_ENCODER_H__ > + > +#include <drm/drm_encoder.h> > + > +struct tidss_device; > + > +struct drm_encoder *tidss_encoder_create(struct tidss_device *tidss, > + u32 encoder_type, u32 possible_crtcs); > + > +#endif > diff --git a/drivers/gpu/drm/tidss/tidss_irq.c b/drivers/gpu/drm/tidss/tidss_irq.c > new file mode 100644 > index 0000000..07c4e1f > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_irq.c > @@ -0,0 +1,193 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#include <drm/drmP.h> > + > +#include "tidss_irq.h" > +#include "tidss_drv.h" > +#include "tidss_dispc.h" > +#include "tidss_crtc.h" > +#include "tidss_plane.h" > + > +/* call with wait_lock and dispc runtime held */ > +static void tidss_irq_update(struct drm_device *ddev) > +{ > + struct tidss_device *tidss = ddev->dev_private; > + > + assert_spin_locked(&tidss->wait_lock); > + > + tidss->dispc_ops->write_irqenable(tidss->dispc, tidss->irq_mask); > +} > + > +void tidss_irq_enable_vblank(struct drm_crtc *crtc) > +{ > + struct drm_device *ddev = crtc->dev; > + struct tidss_device *tidss = ddev->dev_private; > + struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); > + u32 hw_videoport = tcrtc->hw_videoport; > + unsigned long flags; > + > + spin_lock_irqsave(&tidss->wait_lock, flags); > + tidss->irq_mask |= DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) | > + DSS_IRQ_VP_VSYNC_ODD(hw_videoport); > + tidss_irq_update(ddev); > + spin_unlock_irqrestore(&tidss->wait_lock, flags); > +} > + > +void tidss_irq_disable_vblank(struct drm_crtc *crtc) > +{ > + struct drm_device *ddev = crtc->dev; > + struct tidss_device *tidss = ddev->dev_private; > + struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); > + u32 hw_videoport = tcrtc->hw_videoport; > + unsigned long flags; > + > + spin_lock_irqsave(&tidss->wait_lock, flags); > + tidss->irq_mask &= ~(DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) | > + DSS_IRQ_VP_VSYNC_ODD(hw_videoport)); > + tidss_irq_update(ddev); > + spin_unlock_irqrestore(&tidss->wait_lock, flags); > +} > + > +static void tidss_irq_fifo_underflow(struct tidss_device *tidss, > + u64 irqstatus) > +{ > + static DEFINE_RATELIMIT_STATE(_rs, DEFAULT_RATELIMIT_INTERVAL, > + DEFAULT_RATELIMIT_BURST); > + unsigned int i; > + u64 masked; > + > + spin_lock(&tidss->wait_lock); > + masked = irqstatus & tidss->irq_uf_mask & tidss->irq_mask; > + spin_unlock(&tidss->wait_lock); > + > + if (!masked) > + return; > + > + if (!__ratelimit(&_rs)) > + return; > + > + DRM_ERROR("FIFO underflow on "); > + > + for (i = 0; i < DSS_MAX_PLANES; ++i) { > + if (masked & DSS_IRQ_PLANE_FIFO_UNDERFLOW(i)) > + pr_cont("%u:%s ", i, > + tidss->dispc_ops->plane_name(tidss->dispc, i)); > + } > + > + pr_cont("(%016llx)\n", irqstatus); > +} > + > +static void tidss_irq_ocp_error_handler(struct drm_device *ddev, > + u64 irqstatus) > +{ > + if (irqstatus & DSS_IRQ_DEVICE_OCP_ERR) > + dev_err_ratelimited(ddev->dev, "OCP error\n"); > +} > + > +irqreturn_t tidss_irq_handler(int irq, void *arg) > +{ > + struct drm_device *ddev = (struct drm_device *) arg; > + struct tidss_device *tidss = ddev->dev_private; > + unsigned int id; > + u64 irqstatus; > + > + if (WARN_ON(!ddev->irq_enabled)) > + return IRQ_NONE; > + > + irqstatus = tidss->dispc_ops->read_and_clear_irqstatus(tidss->dispc); > + > + for (id = 0; id < tidss->num_crtcs; id++) { > + struct drm_crtc *crtc = tidss->crtcs[id]; > + struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); > + u32 hw_videoport = tcrtc->hw_videoport; > + > + if (irqstatus & (DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) | > + DSS_IRQ_VP_VSYNC_ODD(hw_videoport))) > + tidss_crtc_vblank_irq(crtc); > + > + if (irqstatus & (DSS_IRQ_VP_FRAME_DONE(hw_videoport))) > + tidss_crtc_framedone_irq(crtc); > + > + if (irqstatus & DSS_IRQ_VP_SYNC_LOST(hw_videoport)) > + tidss_crtc_error_irq(crtc, irqstatus); > + } > + > + tidss_irq_ocp_error_handler(ddev, irqstatus); > + tidss_irq_fifo_underflow(tidss, irqstatus); > + > + return IRQ_HANDLED; > +} > + > +void tidss_irq_preinstall(struct drm_device *ddev) > +{ > + struct tidss_device *tidss = ddev->dev_private; > + > + spin_lock_init(&tidss->wait_lock); > + > + tidss->dispc_ops->runtime_get(tidss->dispc); > + > + tidss->dispc_ops->write_irqenable(tidss->dispc, 0); > + tidss->dispc_ops->read_and_clear_irqstatus(tidss->dispc); > + > + tidss->dispc_ops->runtime_put(tidss->dispc); > +} > + > +int tidss_irq_postinstall(struct drm_device *ddev) > +{ > + struct tidss_device *tidss = ddev->dev_private; > + unsigned int i; > + unsigned long flags; > + > + tidss->dispc_ops->runtime_get(tidss->dispc); > + > + spin_lock_irqsave(&tidss->wait_lock, flags); > + > + tidss->irq_mask = DSS_IRQ_DEVICE_OCP_ERR; > + > + tidss->irq_uf_mask = 0; > + for (i = 0; i < tidss->num_planes; ++i) { > + struct tidss_plane *tplane = to_tidss_plane(tidss->planes[i]); > + > + tidss->irq_uf_mask |= DSS_IRQ_PLANE_FIFO_UNDERFLOW(tplane->hw_plane_id); > + } > + tidss->irq_mask |= tidss->irq_uf_mask; > + > + for (i = 0; i < tidss->num_crtcs; ++i) { > + struct tidss_crtc *tcrtc = to_tidss_crtc(tidss->crtcs[i]); > + > + tidss->irq_mask |= DSS_IRQ_VP_SYNC_LOST(tcrtc->hw_videoport); > + > + tidss->irq_mask |= DSS_IRQ_VP_FRAME_DONE(tcrtc->hw_videoport); > + } > + > + tidss_irq_update(ddev); > + > + spin_unlock_irqrestore(&tidss->wait_lock, flags); > + > + tidss->dispc_ops->runtime_put(tidss->dispc); > + > + return 0; > +} > + > +void tidss_irq_uninstall(struct drm_device *ddev) > +{ > + struct tidss_device *tidss = ddev->dev_private; > + > + tidss->dispc_ops->runtime_get(tidss->dispc); > + tidss->dispc_ops->write_irqenable(tidss->dispc, 0); > + tidss->dispc_ops->runtime_put(tidss->dispc); > +} > + > +void tidss_irq_resume(struct drm_device *ddev) > +{ > + struct tidss_device *tidss = ddev->dev_private; > + unsigned long flags; > + > + spin_lock_irqsave(&tidss->wait_lock, flags); > + tidss_irq_update(ddev); > + spin_unlock_irqrestore(&tidss->wait_lock, flags); > +} > diff --git a/drivers/gpu/drm/tidss/tidss_irq.h b/drivers/gpu/drm/tidss/tidss_irq.h > new file mode 100644 > index 0000000..e150729 > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_irq.h > @@ -0,0 +1,25 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#ifndef __TIDSS_IRQ_H__ > +#define __TIDSS_IRQ_H__ > + > +#include <linux/types.h> > + > +struct drm_crtc; > +struct drm_device; > + > +void tidss_irq_enable_vblank(struct drm_crtc *crtc); > +void tidss_irq_disable_vblank(struct drm_crtc *crtc); > + > +void tidss_irq_preinstall(struct drm_device *ddev); > +int tidss_irq_postinstall(struct drm_device *ddev); > +void tidss_irq_uninstall(struct drm_device *ddev); > +irqreturn_t tidss_irq_handler(int irq, void *arg); > + > +void tidss_irq_resume(struct drm_device *ddev); > + > +#endif > diff --git a/drivers/gpu/drm/tidss/tidss_kms.c b/drivers/gpu/drm/tidss/tidss_kms.c > new file mode 100644 > index 0000000..bbf2c48 > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_kms.c > @@ -0,0 +1,95 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_fb_cma_helper.h> > +#include <drm/drm_fb_helper.h> > +#include <drm/drm_gem_framebuffer_helper.h> > + > +#include "tidss_crtc.h" > +#include "tidss_drv.h" > +#include "tidss_encoder.h" > +#include "tidss_kms.h" > +#include "tidss_plane.h" > + > +static void tidss_atomic_commit_tail(struct drm_atomic_state *old_state) > +{ > + struct drm_device *ddev = old_state->dev; > + struct tidss_device *tidss = ddev->dev_private; > + > + dev_dbg(ddev->dev, "%s\n", __func__); > + > + tidss->dispc_ops->runtime_get(tidss->dispc); > + > + drm_atomic_helper_commit_modeset_disables(ddev, old_state); > + drm_atomic_helper_commit_planes(ddev, old_state, 0); > + drm_atomic_helper_commit_modeset_enables(ddev, old_state); > + > + drm_atomic_helper_commit_hw_done(old_state); > + drm_atomic_helper_wait_for_flip_done(ddev, old_state); > + > + drm_atomic_helper_cleanup_planes(ddev, old_state); > + > + tidss->dispc_ops->runtime_put(tidss->dispc); > +} > + > +static const struct drm_mode_config_helper_funcs mode_config_helper_funcs = { > + .atomic_commit_tail = tidss_atomic_commit_tail, > +}; > + > +static const struct drm_mode_config_funcs mode_config_funcs = { > + .fb_create = drm_gem_fb_create, > + .atomic_check = drm_atomic_helper_check, > + .atomic_commit = drm_atomic_helper_commit, > +}; > + > +static int tidss_modeset_init_properties(struct tidss_device *tidss) > +{ > + return 0; > +} > + > +int tidss_modeset_init(struct tidss_device *tidss) > +{ > + struct drm_device *ddev = tidss->ddev; > + unsigned int i; > + int ret; > + > + dev_dbg(tidss->dev, "%s\n", __func__); > + > + drm_mode_config_init(ddev); > + > + ddev->mode_config.min_width = 8; > + ddev->mode_config.min_height = 8; > + ddev->mode_config.max_width = 8096; > + ddev->mode_config.max_height = 8096; > + ddev->mode_config.normalize_zpos = true; > + ddev->mode_config.funcs = &mode_config_funcs; > + ddev->mode_config.helper_private = &mode_config_helper_funcs; > + > + ret = tidss_modeset_init_properties(tidss); > + if (ret < 0) > + return ret; > + > + ret = tidss->dispc_ops->modeset_init(tidss->dispc); > + if (ret) > + return ret; > + > + ret = drm_vblank_init(ddev, tidss->num_crtcs); > + if (ret) > + return ret; > + > + /* Start with vertical blanking interrupt reporting disabled. */ > + for (i = 0; i < tidss->num_crtcs; ++i) > + drm_crtc_vblank_reset(tidss->crtcs[i]); > + > + drm_mode_config_reset(ddev); > + > + dev_dbg(tidss->dev, "%s done\n", __func__); > + > + return 0; > +} > diff --git a/drivers/gpu/drm/tidss/tidss_kms.h b/drivers/gpu/drm/tidss/tidss_kms.h > new file mode 100644 > index 0000000..99aaff09 > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_kms.h > @@ -0,0 +1,14 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#ifndef __TIDSS_KMS_H__ > +#define __TIDSS_KMS_H__ > + > +struct tidss_device; > + > +int tidss_modeset_init(struct tidss_device *tidss); > + > +#endif > diff --git a/drivers/gpu/drm/tidss/tidss_plane.c b/drivers/gpu/drm/tidss/tidss_plane.c > new file mode 100644 > index 0000000..c7d8c70 > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_plane.c > @@ -0,0 +1,240 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#include <drm/drmP.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_fb_cma_helper.h> > +#include <drm/drm_gem_cma_helper.h> > +#include <drm/drm_plane_helper.h> > + > +#include "tidss_crtc.h" > +#include "tidss_drv.h" > +#include "tidss_plane.h" > + > +dma_addr_t dispc7_plane_state_paddr(const struct drm_plane_state *state) > +{ > + struct drm_framebuffer *fb = state->fb; > + struct drm_gem_cma_object *gem; > + u32 x = state->src_x >> 16; > + u32 y = state->src_y >> 16; > + > + gem = drm_fb_cma_get_gem_obj(state->fb, 0); > + > + return gem->paddr + fb->offsets[0] + x * fb->format->cpp[0] + > + y * fb->pitches[0]; > +} > + > +dma_addr_t dispc7_plane_state_p_uv_addr(const struct drm_plane_state *state) > +{ > + struct drm_framebuffer *fb = state->fb; > + struct drm_gem_cma_object *gem; > + u32 x = state->src_x >> 16; > + u32 y = state->src_y >> 16; > + > + if (WARN_ON(state->fb->format->num_planes != 2)) > + return 0; > + > + gem = drm_fb_cma_get_gem_obj(fb, 1); > + > + return gem->paddr + fb->offsets[1] + > + (x * fb->format->cpp[1] / fb->format->hsub) + > + (y * fb->pitches[1] / fb->format->vsub); > +} > + > +static int tidss_plane_atomic_check(struct drm_plane *plane, > + struct drm_plane_state *state) > +{ > + struct drm_device *ddev = plane->dev; > + struct tidss_device *tidss = ddev->dev_private; > + struct drm_crtc_state *crtc_state; > + struct tidss_plane *tplane = to_tidss_plane(plane); > + const struct drm_format_info *finfo; > + u32 hw_videoport; > + int ret; > + > + dev_dbg(ddev->dev, "%s\n", __func__); > + > + if (!state->crtc) { > + /* > + * The visible field is not reset by the DRM core but only > + * updated by drm_plane_helper_check_state(), set it manually. > + */ > + state->visible = false; > + return 0; > + } > + > + crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc); > + if (IS_ERR(crtc_state)) > + return PTR_ERR(crtc_state); > + > + ret = drm_atomic_helper_check_plane_state(state, crtc_state, > + 0, > + INT_MAX, > + true, true); > + if (ret < 0) > + return ret; > + > + /* > + * The HW is only able to start drawing at subpixel boundary > + * (the two first checks bellow). At the end of a row the HW > + * can only jump integer number of subpixels forward to > + * beginning of next row. So we can only show picture with > + * integer subpixel width (the third check). However, after > + * reaching the end of the drawn picture the drawing starts > + * again at the absolute memory address where top left corner > + * position of the drawn picture is (so there is no need to > + * check for odd height). > + */ > + > + finfo = drm_format_info(state->fb->format->format); > + > + if ((state->src_x >> 16) % finfo->hsub != 0) { > + dev_dbg(ddev->dev, > + "%s: x-position %u not divisible subpixel size %u\n", > + __func__, (state->src_x >> 16), finfo->hsub); > + return -EINVAL; > + } > + > + if ((state->src_y >> 16) % finfo->vsub != 0) { > + dev_dbg(ddev->dev, > + "%s: y-position %u not divisible subpixel size %u\n", > + __func__, (state->src_y >> 16), finfo->vsub); > + return -EINVAL; > + } > + > + if ((state->src_w >> 16) % finfo->hsub != 0) { > + dev_dbg(ddev->dev, > + "%s: src width %u not divisible by subpixel size %u\n", > + __func__, (state->src_w >> 16), finfo->hsub); > + return -EINVAL; > + } > + > + if (!state->visible) > + return 0; > + > + hw_videoport = to_tidss_crtc(state->crtc)->hw_videoport; > + > + return tidss->dispc_ops->plane_check(tidss->dispc, > + tplane->hw_plane_id, > + state, hw_videoport); > +} > + > +static void tidss_plane_atomic_update(struct drm_plane *plane, > + struct drm_plane_state *old_state) > +{ > + struct drm_device *ddev = plane->dev; > + struct tidss_device *tidss = ddev->dev_private; > + struct tidss_plane *tplane = to_tidss_plane(plane); > + struct drm_plane_state *state = plane->state; > + u32 hw_videoport; > + int ret; > + > + dev_dbg(ddev->dev, "%s\n", __func__); > + > + if (!state->visible) { > + tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id, > + false); > + return; > + } > + > + hw_videoport = to_tidss_crtc(state->crtc)->hw_videoport; > + > + ret = tidss->dispc_ops->plane_setup(tidss->dispc, tplane->hw_plane_id, > + state, hw_videoport); > + > + if (ret) { > + dev_err(plane->dev->dev, "Failed to setup plane %d\n", > + tplane->hw_plane_id); > + tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id, > + false); > + return; > + } > + > + tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id, true); > +} > + > +static void tidss_plane_atomic_disable(struct drm_plane *plane, > + struct drm_plane_state *old_state) > +{ > + struct drm_device *ddev = plane->dev; > + struct tidss_device *tidss = ddev->dev_private; > + struct tidss_plane *tplane = to_tidss_plane(plane); > + > + dev_dbg(ddev->dev, "%s\n", __func__); > + > + tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id, false); > +} > + > +static const struct drm_plane_helper_funcs tidss_plane_helper_funcs = { > + .atomic_check = tidss_plane_atomic_check, > + .atomic_update = tidss_plane_atomic_update, > + .atomic_disable = tidss_plane_atomic_disable, > +}; > + > +static const struct drm_plane_funcs tidss_plane_funcs = { > + .update_plane = drm_atomic_helper_update_plane, > + .disable_plane = drm_atomic_helper_disable_plane, > + .reset = drm_atomic_helper_plane_reset, > + .destroy = drm_plane_cleanup, > + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, > +}; > + > +struct tidss_plane *tidss_plane_create(struct tidss_device *tidss, > + u32 hw_plane_id, u32 plane_type, > + u32 crtc_mask, const u32 *formats, > + u32 num_formats) > +{ > + const struct tidss_plane_feat *pfeat; > + struct tidss_plane *tplane; > + enum drm_plane_type type; > + u32 possible_crtcs; > + u32 num_planes = tidss->dispc_ops->get_num_planes(tidss->dispc); > + int ret; > + > + pfeat = tidss->dispc_ops->plane_feat(tidss->dispc, hw_plane_id); > + > + tplane = devm_kzalloc(tidss->dev, sizeof(*tplane), GFP_KERNEL); > + if (!tplane) > + return ERR_PTR(-ENOMEM); > + > + tplane->hw_plane_id = hw_plane_id; > + > + possible_crtcs = crtc_mask; > + type = plane_type; > + > + ret = drm_universal_plane_init(tidss->ddev, &tplane->plane, > + possible_crtcs, > + &tidss_plane_funcs, > + formats, num_formats, > + NULL, type, NULL); > + if (ret < 0) > + return ERR_PTR(ret); > + > + drm_plane_helper_add(&tplane->plane, &tidss_plane_helper_funcs); > + if (num_planes > 1) > + drm_plane_create_zpos_property(&tplane->plane, hw_plane_id, 0, > + num_planes - 1); > + > + ret = drm_plane_create_color_properties(&tplane->plane, > + pfeat->color.encodings, > + pfeat->color.ranges, > + pfeat->color.default_encoding, > + pfeat->color.default_range); > + if (ret) > + return ERR_PTR(ret); > + > + if (pfeat->blend.global_alpha) { > + ret = drm_plane_create_alpha_property(&tplane->plane); > + if (ret) > + return ERR_PTR(ret); > + } > + > + return tplane; > +} > diff --git a/drivers/gpu/drm/tidss/tidss_plane.h b/drivers/gpu/drm/tidss/tidss_plane.h > new file mode 100644 > index 0000000..ec600a9 > --- /dev/null > +++ b/drivers/gpu/drm/tidss/tidss_plane.h > @@ -0,0 +1,29 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ > + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> > + */ > + > +#ifndef __TIDSS_PLANE_H__ > +#define __TIDSS_PLANE_H__ > + > +#include "tidss_dispc.h" > + > +#define to_tidss_plane(p) container_of((p), struct tidss_plane, plane) > + > +struct tidss_plane { > + struct drm_plane plane; > + > + u32 hw_plane_id; > + > +}; > + > +struct tidss_plane *tidss_plane_create(struct tidss_device *tidss, > + u32 hw_plane_id, u32 plane_type, > + u32 crtc_mask, const u32 *formats, > + u32 num_formats); > + > +dma_addr_t dispc7_plane_state_paddr(const struct drm_plane_state *state); > +dma_addr_t dispc7_plane_state_p_uv_addr(const struct drm_plane_state *state); > + > +#endif > -- > Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki. Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki > -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel