Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,13 @@
config DRM_IMX8_DC
tristate "Freescale i.MX8 Display Controller Graphics"
depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
select DRM_CLIENT_SELECTION
select DRM_GEM_DMA_HELPER
select DRM_KMS_HELPER
select DRM_DISPLAY_HELPER
select DRM_BRIDGE_CONNECTOR
select GENERIC_IRQ_CHIP
select REGMAP
select REGMAP_MMIO
help
enable Freescale i.MX8 Display Controller(DC) graphics support
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
imx8-dc-drm-objs := dc-cf.o dc-crtc.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o \
dc-fu.o dc-fw.o dc-ic.o dc-kms.o dc-lb.o dc-pe.o \
dc-plane.o dc-tc.o
obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
+171
View File
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/component.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include "dc-drv.h"
#include "dc-pe.h"
#define STATICCONTROL 0x8
#define FRAMEDIMENSIONS 0xc
#define HEIGHT(x) FIELD_PREP(GENMASK(29, 16), ((x) - 1))
#define WIDTH(x) FIELD_PREP(GENMASK(13, 0), ((x) - 1))
#define CONSTANTCOLOR 0x10
#define BLUE(x) FIELD_PREP(GENMASK(15, 8), (x))
static const struct dc_subdev_info dc_cf_info[] = {
{ .reg_start = 0x56180960, .id = 0, },
{ .reg_start = 0x561809e0, .id = 1, },
{ .reg_start = 0x561809a0, .id = 4, },
{ .reg_start = 0x56180a20, .id = 5, },
};
static const struct regmap_range dc_cf_regmap_ranges[] = {
regmap_reg_range(STATICCONTROL, CONSTANTCOLOR),
};
static const struct regmap_access_table dc_cf_regmap_access_table = {
.yes_ranges = dc_cf_regmap_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_cf_regmap_ranges),
};
static const struct regmap_config dc_cf_cfg_regmap_config = {
.name = "cfg",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_cf_regmap_access_table,
.rd_table = &dc_cf_regmap_access_table,
.max_register = CONSTANTCOLOR,
};
static inline void dc_cf_enable_shden(struct dc_cf *cf)
{
regmap_write(cf->reg_cfg, STATICCONTROL, SHDEN);
}
enum dc_link_id dc_cf_get_link_id(struct dc_cf *cf)
{
return cf->link;
}
void dc_cf_framedimensions(struct dc_cf *cf, unsigned int w,
unsigned int h)
{
regmap_write(cf->reg_cfg, FRAMEDIMENSIONS, WIDTH(w) | HEIGHT(h));
}
void dc_cf_constantcolor_black(struct dc_cf *cf)
{
regmap_write(cf->reg_cfg, CONSTANTCOLOR, 0);
}
void dc_cf_constantcolor_blue(struct dc_cf *cf)
{
regmap_write(cf->reg_cfg, CONSTANTCOLOR, BLUE(0xff));
}
void dc_cf_init(struct dc_cf *cf)
{
dc_cf_enable_shden(cf);
}
static int dc_cf_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct dc_drm_device *dc_drm = data;
struct resource *res_pec;
void __iomem *base_cfg;
struct dc_cf *cf;
int id;
cf = devm_kzalloc(dev, sizeof(*cf), GFP_KERNEL);
if (!cf)
return -ENOMEM;
res_pec = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base_cfg = devm_platform_ioremap_resource_byname(pdev, "cfg");
if (IS_ERR(base_cfg))
return PTR_ERR(base_cfg);
cf->reg_cfg = devm_regmap_init_mmio(dev, base_cfg,
&dc_cf_cfg_regmap_config);
if (IS_ERR(cf->reg_cfg))
return PTR_ERR(cf->reg_cfg);
id = dc_subdev_get_id(dc_cf_info, ARRAY_SIZE(dc_cf_info), res_pec);
if (id < 0) {
dev_err(dev, "failed to get instance number: %d\n", id);
return id;
}
switch (id) {
case 0:
cf->link = LINK_ID_CONSTFRAME0;
dc_drm->cf_cont[0] = cf;
break;
case 1:
cf->link = LINK_ID_CONSTFRAME1;
dc_drm->cf_cont[1] = cf;
break;
case 4:
cf->link = LINK_ID_CONSTFRAME4;
dc_drm->cf_safe[0] = cf;
break;
case 5:
cf->link = LINK_ID_CONSTFRAME5;
dc_drm->cf_safe[1] = cf;
break;
}
return 0;
}
static const struct component_ops dc_cf_ops = {
.bind = dc_cf_bind,
};
static int dc_cf_probe(struct platform_device *pdev)
{
int ret;
ret = component_add(&pdev->dev, &dc_cf_ops);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to add component\n");
return 0;
}
static void dc_cf_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dc_cf_ops);
}
static const struct of_device_id dc_cf_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-dc-constframe" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dc_cf_dt_ids);
struct platform_driver dc_cf_driver = {
.probe = dc_cf_probe,
.remove = dc_cf_remove,
.driver = {
.name = "imx8-dc-constframe",
.suppress_bind_attrs = true,
.of_match_table = dc_cf_dt_ids,
},
};
@@ -0,0 +1,549 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/completion.h>
#include <linux/container_of.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/pm_runtime.h>
#include <linux/spinlock.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_modes.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_plane.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#include "dc-de.h"
#include "dc-drv.h"
#include "dc-kms.h"
#include "dc-pe.h"
#define dc_crtc_dbg(crtc, fmt, ...) \
do { \
struct drm_crtc *_crtc = (crtc); \
drm_dbg_kms(_crtc->dev, "[CRTC:%d:%s] " fmt, \
_crtc->base.id, _crtc->name, ##__VA_ARGS__); \
} while (0)
#define dc_crtc_err(crtc, fmt, ...) \
do { \
struct drm_crtc *_crtc = (crtc); \
drm_err(_crtc->dev, "[CRTC:%d:%s] " fmt, \
_crtc->base.id, _crtc->name, ##__VA_ARGS__); \
} while (0)
#define DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(c) \
do { \
unsigned long ret; \
ret = wait_for_completion_timeout(&dc_crtc->c, HZ); \
if (ret == 0) \
dc_crtc_err(crtc, "%s: wait for " #c " timeout\n", \
__func__); \
} while (0)
#define DC_CRTC_CHECK_FRAMEGEN_FIFO(fg) \
do { \
struct dc_fg *_fg = (fg); \
if (dc_fg_secondary_requests_to_read_empty_fifo(_fg)) { \
dc_fg_secondary_clear_channel_status(_fg); \
dc_crtc_err(crtc, "%s: FrameGen FIFO empty\n", \
__func__); \
} \
} while (0)
#define DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(fg) \
do { \
if (dc_fg_wait_for_secondary_syncup(fg)) \
dc_crtc_err(crtc, \
"%s: FrameGen secondary channel isn't syncup\n",\
__func__); \
} while (0)
static inline struct dc_crtc *to_dc_crtc(struct drm_crtc *crtc)
{
return container_of(crtc, struct dc_crtc, base);
}
static u32 dc_crtc_get_vblank_counter(struct drm_crtc *crtc)
{
struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
return dc_fg_get_frame_index(dc_crtc->fg);
}
static int dc_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
enable_irq(dc_crtc->irq_dec_framecomplete);
return 0;
}
static void dc_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
/* nosync due to atomic context */
disable_irq_nosync(dc_crtc->irq_dec_framecomplete);
}
static const struct drm_crtc_funcs dc_crtc_funcs = {
.reset = drm_atomic_helper_crtc_reset,
.destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.get_vblank_counter = dc_crtc_get_vblank_counter,
.enable_vblank = dc_crtc_enable_vblank,
.disable_vblank = dc_crtc_disable_vblank,
.get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
};
static void dc_crtc_queue_state_event(struct drm_crtc_state *crtc_state)
{
struct drm_crtc *crtc = crtc_state->crtc;
struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
spin_lock_irq(&crtc->dev->event_lock);
if (crtc_state->event) {
WARN_ON(drm_crtc_vblank_get(crtc));
WARN_ON(dc_crtc->event);
dc_crtc->event = crtc_state->event;
crtc_state->event = NULL;
}
spin_unlock_irq(&crtc->dev->event_lock);
}
static inline enum drm_mode_status
dc_crtc_check_clock(struct dc_crtc *dc_crtc, int clk_khz)
{
return dc_fg_check_clock(dc_crtc->fg, clk_khz);
}
static enum drm_mode_status
dc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
{
struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
enum drm_mode_status status;
status = dc_crtc_check_clock(dc_crtc, mode->clock);
if (status != MODE_OK)
return status;
if (mode->crtc_clock > DC_FRAMEGEN_MAX_CLOCK_KHZ)
return MODE_CLOCK_HIGH;
return MODE_OK;
}
static int
dc_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *state)
{
struct drm_crtc_state *new_crtc_state =
drm_atomic_get_new_crtc_state(state, crtc);
struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
enum drm_mode_status status;
status = dc_crtc_check_clock(dc_crtc, adj->clock);
if (status != MODE_OK)
return -EINVAL;
return 0;
}
static void
dc_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_commit *state)
{
struct drm_crtc_state *new_crtc_state =
drm_atomic_get_new_crtc_state(state, crtc);
struct dc_drm_device *dc_drm = to_dc_drm_device(crtc->dev);
int idx, ret;
if (!drm_atomic_crtc_needs_modeset(new_crtc_state) ||
!new_crtc_state->active)
return;
if (!drm_dev_enter(crtc->dev, &idx))
return;
/* request pixel engine power-on when CRTC starts to be active */
ret = pm_runtime_resume_and_get(dc_drm->pe->dev);
if (ret)
dc_crtc_err(crtc, "failed to get DC pixel engine RPM: %d\n",
ret);
drm_dev_exit(idx);
}
static void
dc_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_commit *state)
{
struct drm_crtc_state *old_crtc_state =
drm_atomic_get_old_crtc_state(state, crtc);
struct drm_crtc_state *new_crtc_state =
drm_atomic_get_new_crtc_state(state, crtc);
struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
int idx;
if (drm_atomic_crtc_needs_modeset(new_crtc_state) ||
(!old_crtc_state->active && !new_crtc_state->active))
return;
if (!drm_dev_enter(crtc->dev, &idx))
goto out;
enable_irq(dc_crtc->irq_ed_cont_shdload);
/* flush plane update out to display */
dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdload_done);
disable_irq(dc_crtc->irq_ed_cont_shdload);
DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
drm_dev_exit(idx);
out:
dc_crtc_queue_state_event(new_crtc_state);
}
static void
dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_commit *state)
{
struct drm_crtc_state *new_crtc_state =
drm_atomic_get_new_crtc_state(state, crtc);
struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
enum dc_link_id cf_link;
int idx, ret;
dc_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
drm_crtc_vblank_on(crtc);
if (!drm_dev_enter(crtc->dev, &idx))
goto out;
/* request display engine power-on when CRTC is enabled */
ret = pm_runtime_resume_and_get(dc_crtc->de->dev);
if (ret < 0)
dc_crtc_err(crtc, "failed to get DC display engine RPM: %d\n",
ret);
enable_irq(dc_crtc->irq_dec_shdload);
enable_irq(dc_crtc->irq_ed_cont_shdload);
enable_irq(dc_crtc->irq_ed_safe_shdload);
dc_fg_cfg_videomode(dc_crtc->fg, adj);
dc_cf_framedimensions(dc_crtc->cf_cont,
adj->crtc_hdisplay, adj->crtc_vdisplay);
dc_cf_framedimensions(dc_crtc->cf_safe,
adj->crtc_hdisplay, adj->crtc_vdisplay);
/* constframe in safety stream shows blue frame */
dc_cf_constantcolor_blue(dc_crtc->cf_safe);
cf_link = dc_cf_get_link_id(dc_crtc->cf_safe);
dc_ed_pec_src_sel(dc_crtc->ed_safe, cf_link);
/* show CRTC background if no plane is enabled */
if (new_crtc_state->plane_mask == 0) {
/* constframe in content stream shows black frame */
dc_cf_constantcolor_black(dc_crtc->cf_cont);
cf_link = dc_cf_get_link_id(dc_crtc->cf_cont);
dc_ed_pec_src_sel(dc_crtc->ed_cont, cf_link);
}
dc_fg_enable_clock(dc_crtc->fg);
dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
dc_ed_pec_sync_trigger(dc_crtc->ed_safe);
dc_fg_shdtokgen(dc_crtc->fg);
dc_fg_enable(dc_crtc->fg);
DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_safe_shdload_done);
DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdload_done);
DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_shdload_done);
disable_irq(dc_crtc->irq_ed_safe_shdload);
disable_irq(dc_crtc->irq_ed_cont_shdload);
disable_irq(dc_crtc->irq_dec_shdload);
DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(dc_crtc->fg);
DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
drm_dev_exit(idx);
out:
dc_crtc_queue_state_event(new_crtc_state);
}
static void
dc_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_commit *state)
{
struct drm_crtc_state *new_crtc_state =
drm_atomic_get_new_crtc_state(state, crtc);
struct dc_drm_device *dc_drm = to_dc_drm_device(crtc->dev);
struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
int idx;
if (!drm_dev_enter(crtc->dev, &idx))
goto out;
enable_irq(dc_crtc->irq_dec_seqcomplete);
dc_fg_disable(dc_crtc->fg);
DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_seqcomplete_done);
disable_irq(dc_crtc->irq_dec_seqcomplete);
dc_fg_disable_clock(dc_crtc->fg);
/* request pixel engine power-off as plane is off too */
pm_runtime_put(dc_drm->pe->dev);
/* request display engine power-off when CRTC is disabled */
pm_runtime_put(dc_crtc->de->dev);
drm_dev_exit(idx);
out:
drm_crtc_vblank_off(crtc);
spin_lock_irq(&crtc->dev->event_lock);
if (new_crtc_state->event && !new_crtc_state->active) {
drm_crtc_send_vblank_event(crtc, new_crtc_state->event);
new_crtc_state->event = NULL;
}
spin_unlock_irq(&crtc->dev->event_lock);
}
static bool dc_crtc_get_scanout_position(struct drm_crtc *crtc,
bool in_vblank_irq,
int *vpos, int *hpos,
ktime_t *stime, ktime_t *etime,
const struct drm_display_mode *mode)
{
struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
int vdisplay = mode->crtc_vdisplay;
int vtotal = mode->crtc_vtotal;
bool reliable;
int line;
int idx;
if (stime)
*stime = ktime_get();
if (!drm_dev_enter(crtc->dev, &idx)) {
reliable = false;
*vpos = 0;
*hpos = 0;
goto out;
}
/* line index starts with 0 for the first active output line */
line = dc_fg_get_line_index(dc_crtc->fg);
if (line < vdisplay)
/* active scanout area - positive */
*vpos = line + 1;
else
/* inside vblank - negative */
*vpos = line - (vtotal - 1);
*hpos = 0;
reliable = true;
drm_dev_exit(idx);
out:
if (etime)
*etime = ktime_get();
return reliable;
}
static const struct drm_crtc_helper_funcs dc_helper_funcs = {
.mode_valid = dc_crtc_mode_valid,
.atomic_check = dc_crtc_atomic_check,
.atomic_begin = dc_crtc_atomic_begin,
.atomic_flush = dc_crtc_atomic_flush,
.atomic_enable = dc_crtc_atomic_enable,
.atomic_disable = dc_crtc_atomic_disable,
.get_scanout_position = dc_crtc_get_scanout_position,
};
static irqreturn_t dc_crtc_irq_handler_dec_framecomplete(int irq, void *dev_id)
{
struct dc_crtc *dc_crtc = dev_id;
struct drm_crtc *crtc = &dc_crtc->base;
unsigned long flags;
drm_crtc_handle_vblank(crtc);
spin_lock_irqsave(&crtc->dev->event_lock, flags);
if (dc_crtc->event) {
drm_crtc_send_vblank_event(crtc, dc_crtc->event);
dc_crtc->event = NULL;
drm_crtc_vblank_put(crtc);
}
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
return IRQ_HANDLED;
}
static irqreturn_t
dc_crtc_irq_handler_dec_seqcomplete_done(int irq, void *dev_id)
{
struct dc_crtc *dc_crtc = dev_id;
complete(&dc_crtc->dec_seqcomplete_done);
return IRQ_HANDLED;
}
static irqreturn_t dc_crtc_irq_handler_dec_shdload_done(int irq, void *dev_id)
{
struct dc_crtc *dc_crtc = dev_id;
complete(&dc_crtc->dec_shdload_done);
return IRQ_HANDLED;
}
static irqreturn_t
dc_crtc_irq_handler_ed_cont_shdload_done(int irq, void *dev_id)
{
struct dc_crtc *dc_crtc = dev_id;
complete(&dc_crtc->ed_cont_shdload_done);
return IRQ_HANDLED;
}
static irqreturn_t
dc_crtc_irq_handler_ed_safe_shdload_done(int irq, void *dev_id)
{
struct dc_crtc *dc_crtc = dev_id;
complete(&dc_crtc->ed_safe_shdload_done);
return IRQ_HANDLED;
}
static int dc_crtc_request_irqs(struct drm_device *drm, struct dc_crtc *dc_crtc)
{
struct {
struct device *dev;
unsigned int irq;
irqreturn_t (*irq_handler)(int irq, void *dev_id);
} irqs[DC_CRTC_IRQS] = {
{
dc_crtc->de->dev,
dc_crtc->irq_dec_framecomplete,
dc_crtc_irq_handler_dec_framecomplete,
}, {
dc_crtc->de->dev,
dc_crtc->irq_dec_seqcomplete,
dc_crtc_irq_handler_dec_seqcomplete_done,
}, {
dc_crtc->de->dev,
dc_crtc->irq_dec_shdload,
dc_crtc_irq_handler_dec_shdload_done,
}, {
dc_crtc->ed_cont->dev,
dc_crtc->irq_ed_cont_shdload,
dc_crtc_irq_handler_ed_cont_shdload_done,
}, {
dc_crtc->ed_safe->dev,
dc_crtc->irq_ed_safe_shdload,
dc_crtc_irq_handler_ed_safe_shdload_done,
},
};
int i, ret;
for (i = 0; i < DC_CRTC_IRQS; i++) {
struct dc_crtc_irq *irq = &dc_crtc->irqs[i];
ret = devm_request_irq(irqs[i].dev, irqs[i].irq,
irqs[i].irq_handler, IRQF_NO_AUTOEN,
dev_name(irqs[i].dev), dc_crtc);
if (ret) {
dev_err(irqs[i].dev, "failed to request irq(%u): %d\n",
irqs[i].irq, ret);
return ret;
}
irq->dc_crtc = dc_crtc;
irq->irq = irqs[i].irq;
}
return 0;
}
int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index)
{
struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
struct drm_device *drm = &dc_drm->base;
struct dc_de *de = dc_drm->de[crtc_index];
struct dc_pe *pe = dc_drm->pe;
struct dc_plane *dc_primary;
int ret;
dc_crtc->de = de;
init_completion(&dc_crtc->dec_seqcomplete_done);
init_completion(&dc_crtc->dec_shdload_done);
init_completion(&dc_crtc->ed_cont_shdload_done);
init_completion(&dc_crtc->ed_safe_shdload_done);
dc_crtc->cf_cont = pe->cf_cont[crtc_index];
dc_crtc->cf_safe = pe->cf_safe[crtc_index];
dc_crtc->ed_cont = pe->ed_cont[crtc_index];
dc_crtc->ed_safe = pe->ed_safe[crtc_index];
dc_crtc->fg = de->fg;
dc_crtc->irq_dec_framecomplete = de->irq_framecomplete;
dc_crtc->irq_dec_seqcomplete = de->irq_seqcomplete;
dc_crtc->irq_dec_shdload = de->irq_shdload;
dc_crtc->irq_ed_safe_shdload = dc_crtc->ed_safe->irq_shdload;
dc_crtc->irq_ed_cont_shdload = dc_crtc->ed_cont->irq_shdload;
dc_primary = &dc_drm->dc_primary[crtc_index];
ret = dc_plane_init(dc_drm, dc_primary);
if (ret) {
dev_err(de->dev, "failed to initialize primary plane: %d\n",
ret);
return ret;
}
drm_crtc_helper_add(&dc_crtc->base, &dc_helper_funcs);
ret = drm_crtc_init_with_planes(drm, &dc_crtc->base, &dc_primary->base,
NULL, &dc_crtc_funcs, NULL);
if (ret)
dev_err(de->dev, "failed to add CRTC: %d\n", ret);
return ret;
}
int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index)
{
struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
struct drm_device *drm = &dc_drm->base;
return dc_crtc_request_irqs(drm, dc_crtc);
}
+176
View File
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/component.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include "dc-de.h"
#include "dc-drv.h"
#define POLARITYCTRL 0xc
#define POLEN_HIGH BIT(2)
static const struct dc_subdev_info dc_de_info[] = {
{ .reg_start = 0x5618b400, .id = 0, },
{ .reg_start = 0x5618b420, .id = 1, },
};
static const struct regmap_range dc_de_regmap_ranges[] = {
regmap_reg_range(POLARITYCTRL, POLARITYCTRL),
};
static const struct regmap_access_table dc_de_regmap_access_table = {
.yes_ranges = dc_de_regmap_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_de_regmap_ranges),
};
static const struct regmap_config dc_de_top_regmap_config = {
.name = "top",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_de_regmap_access_table,
.rd_table = &dc_de_regmap_access_table,
.max_register = POLARITYCTRL,
};
static inline void dc_dec_init(struct dc_de *de)
{
regmap_write_bits(de->reg_top, POLARITYCTRL, POLARITYCTRL, POLEN_HIGH);
}
static int dc_de_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct dc_drm_device *dc_drm = data;
struct resource *res_top;
void __iomem *base_top;
struct dc_de *de;
int ret, id;
de = devm_kzalloc(dev, sizeof(*de), GFP_KERNEL);
if (!de)
return -ENOMEM;
base_top = devm_platform_get_and_ioremap_resource(pdev, 0, &res_top);
if (IS_ERR(base_top))
return PTR_ERR(base_top);
de->reg_top = devm_regmap_init_mmio(dev, base_top,
&dc_de_top_regmap_config);
if (IS_ERR(de->reg_top))
return PTR_ERR(de->reg_top);
de->irq_shdload = platform_get_irq_byname(pdev, "shdload");
if (de->irq_shdload < 0)
return de->irq_shdload;
de->irq_framecomplete = platform_get_irq_byname(pdev, "framecomplete");
if (de->irq_framecomplete < 0)
return de->irq_framecomplete;
de->irq_seqcomplete = platform_get_irq_byname(pdev, "seqcomplete");
if (de->irq_seqcomplete < 0)
return de->irq_seqcomplete;
de->dev = dev;
dev_set_drvdata(dev, de);
ret = devm_pm_runtime_enable(dev);
if (ret)
return ret;
id = dc_subdev_get_id(dc_de_info, ARRAY_SIZE(dc_de_info), res_top);
if (id < 0) {
dev_err(dev, "failed to get instance number: %d\n", id);
return id;
}
dc_drm->de[id] = de;
return 0;
}
/*
* It's possible to get the child device pointers from the child component
* bind callbacks, but it depends on the component helper behavior to bind
* the display engine component first. To avoid the dependency, post bind
* to get the pointers from dc_drm in a safe manner.
*/
void dc_de_post_bind(struct dc_drm_device *dc_drm)
{
struct dc_de *de;
int i;
for (i = 0; i < DC_DISPLAYS; i++) {
de = dc_drm->de[i];
de->fg = dc_drm->fg[i];
de->tc = dc_drm->tc[i];
}
}
static const struct component_ops dc_de_ops = {
.bind = dc_de_bind,
};
static int dc_de_probe(struct platform_device *pdev)
{
int ret;
ret = devm_of_platform_populate(&pdev->dev);
if (ret < 0)
return ret;
ret = component_add(&pdev->dev, &dc_de_ops);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to add component\n");
return 0;
}
static void dc_de_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dc_de_ops);
}
static int dc_de_runtime_resume(struct device *dev)
{
struct dc_de *de = dev_get_drvdata(dev);
dc_dec_init(de);
dc_fg_init(de->fg);
dc_tc_init(de->tc);
return 0;
}
static const struct dev_pm_ops dc_de_pm_ops = {
RUNTIME_PM_OPS(NULL, dc_de_runtime_resume, NULL)
};
static const struct of_device_id dc_de_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-dc-display-engine" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dc_de_dt_ids);
struct platform_driver dc_de_driver = {
.probe = dc_de_probe,
.remove = dc_de_remove,
.driver = {
.name = "imx8-dc-display-engine",
.suppress_bind_attrs = true,
.of_match_table = dc_de_dt_ids,
.pm = pm_sleep_ptr(&dc_de_pm_ops),
},
};
@@ -0,0 +1,59 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2024 NXP
*/
#ifndef __DC_DISPLAY_ENGINE_H__
#define __DC_DISPLAY_ENGINE_H__
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include <drm/drm_modes.h>
#define DC_DISPLAYS 2
#define DC_FRAMEGEN_MAX_FRAME_INDEX 0x3ffff
#define DC_FRAMEGEN_MAX_CLOCK_KHZ 300000
struct dc_fg {
struct device *dev;
struct regmap *reg;
struct clk *clk_disp;
};
struct dc_tc {
struct device *dev;
struct regmap *reg;
};
struct dc_de {
struct device *dev;
struct regmap *reg_top;
struct dc_fg *fg;
struct dc_tc *tc;
int irq_shdload;
int irq_framecomplete;
int irq_seqcomplete;
};
/* Frame Generator Unit */
void dc_fg_cfg_videomode(struct dc_fg *fg, struct drm_display_mode *m);
void dc_fg_enable(struct dc_fg *fg);
void dc_fg_disable(struct dc_fg *fg);
void dc_fg_shdtokgen(struct dc_fg *fg);
u32 dc_fg_get_frame_index(struct dc_fg *fg);
u32 dc_fg_get_line_index(struct dc_fg *fg);
bool dc_fg_wait_for_frame_index_moving(struct dc_fg *fg);
bool dc_fg_secondary_requests_to_read_empty_fifo(struct dc_fg *fg);
void dc_fg_secondary_clear_channel_status(struct dc_fg *fg);
int dc_fg_wait_for_secondary_syncup(struct dc_fg *fg);
void dc_fg_enable_clock(struct dc_fg *fg);
void dc_fg_disable_clock(struct dc_fg *fg);
enum drm_mode_status dc_fg_check_clock(struct dc_fg *fg, int clk_khz);
void dc_fg_init(struct dc_fg *fg);
/* Timing Controller Unit */
void dc_tc_init(struct dc_tc *tc);
#endif /* __DC_DISPLAY_ENGINE_H__ */
+292
View File
@@ -0,0 +1,292 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_dma.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_managed.h>
#include <drm/drm_modeset_helper.h>
#include <drm/drm_of.h>
#include "dc-de.h"
#include "dc-drv.h"
#include "dc-pe.h"
struct dc_priv {
struct drm_device *drm;
struct clk *clk_cfg;
};
DEFINE_DRM_GEM_DMA_FOPS(dc_drm_driver_fops);
static struct drm_driver dc_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
DRM_GEM_DMA_DRIVER_OPS,
DRM_FBDEV_DMA_DRIVER_OPS,
.fops = &dc_drm_driver_fops,
.name = "imx8-dc",
.desc = "i.MX8 DC DRM graphics",
.major = 1,
.minor = 0,
.patchlevel = 0,
};
static void
dc_add_components(struct device *dev, struct component_match **matchptr)
{
struct device_node *child, *grandchild;
for_each_available_child_of_node(dev->of_node, child) {
/* The interrupt controller is not a component. */
if (of_device_is_compatible(child, "fsl,imx8qxp-dc-intc"))
continue;
drm_of_component_match_add(dev, matchptr, component_compare_of,
child);
for_each_available_child_of_node(child, grandchild)
drm_of_component_match_add(dev, matchptr,
component_compare_of,
grandchild);
}
}
static int dc_drm_component_bind_all(struct dc_drm_device *dc_drm)
{
struct drm_device *drm = &dc_drm->base;
int ret;
ret = component_bind_all(drm->dev, dc_drm);
if (ret)
return ret;
dc_de_post_bind(dc_drm);
dc_pe_post_bind(dc_drm);
return 0;
}
static void dc_drm_component_unbind_all(void *ptr)
{
struct dc_drm_device *dc_drm = ptr;
struct drm_device *drm = &dc_drm->base;
component_unbind_all(drm->dev, dc_drm);
}
static int dc_drm_bind(struct device *dev)
{
struct dc_priv *priv = dev_get_drvdata(dev);
struct dc_drm_device *dc_drm;
struct drm_device *drm;
int ret;
dc_drm = devm_drm_dev_alloc(dev, &dc_drm_driver, struct dc_drm_device,
base);
if (IS_ERR(dc_drm))
return PTR_ERR(dc_drm);
drm = &dc_drm->base;
ret = dc_drm_component_bind_all(dc_drm);
if (ret)
return ret;
ret = devm_add_action_or_reset(dev, dc_drm_component_unbind_all,
dc_drm);
if (ret)
return ret;
ret = dc_kms_init(dc_drm);
if (ret)
return ret;
ret = drm_dev_register(drm, 0);
if (ret) {
dev_err(dev, "failed to register drm device: %d\n", ret);
goto err;
}
drm_client_setup_with_fourcc(drm, DRM_FORMAT_XRGB8888);
priv->drm = drm;
return 0;
err:
dc_kms_uninit(dc_drm);
return ret;
}
static void dc_drm_unbind(struct device *dev)
{
struct dc_priv *priv = dev_get_drvdata(dev);
struct dc_drm_device *dc_drm = to_dc_drm_device(priv->drm);
struct drm_device *drm = &dc_drm->base;
priv->drm = NULL;
drm_dev_unplug(drm);
dc_kms_uninit(dc_drm);
drm_atomic_helper_shutdown(drm);
}
static const struct component_master_ops dc_drm_ops = {
.bind = dc_drm_bind,
.unbind = dc_drm_unbind,
};
static int dc_probe(struct platform_device *pdev)
{
struct component_match *match = NULL;
struct dc_priv *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->clk_cfg = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(priv->clk_cfg))
return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk_cfg),
"failed to get cfg clock\n");
dev_set_drvdata(&pdev->dev, priv);
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
ret = devm_pm_runtime_enable(&pdev->dev);
if (ret)
return ret;
ret = devm_of_platform_populate(&pdev->dev);
if (ret)
return ret;
dc_add_components(&pdev->dev, &match);
ret = component_master_add_with_match(&pdev->dev, &dc_drm_ops, match);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to add component master\n");
return 0;
}
static void dc_remove(struct platform_device *pdev)
{
component_master_del(&pdev->dev, &dc_drm_ops);
}
static int dc_runtime_suspend(struct device *dev)
{
struct dc_priv *priv = dev_get_drvdata(dev);
clk_disable_unprepare(priv->clk_cfg);
return 0;
}
static int dc_runtime_resume(struct device *dev)
{
struct dc_priv *priv = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(priv->clk_cfg);
if (ret)
dev_err(dev, "failed to enable cfg clock: %d\n", ret);
return ret;
}
static int dc_suspend(struct device *dev)
{
struct dc_priv *priv = dev_get_drvdata(dev);
return drm_mode_config_helper_suspend(priv->drm);
}
static int dc_resume(struct device *dev)
{
struct dc_priv *priv = dev_get_drvdata(dev);
return drm_mode_config_helper_resume(priv->drm);
}
static void dc_shutdown(struct platform_device *pdev)
{
struct dc_priv *priv = dev_get_drvdata(&pdev->dev);
drm_atomic_helper_shutdown(priv->drm);
}
static const struct dev_pm_ops dc_pm_ops = {
RUNTIME_PM_OPS(dc_runtime_suspend, dc_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(dc_suspend, dc_resume)
};
static const struct of_device_id dc_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-dc", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dc_dt_ids);
static struct platform_driver dc_driver = {
.probe = dc_probe,
.remove = dc_remove,
.shutdown = dc_shutdown,
.driver = {
.name = "imx8-dc",
.of_match_table = dc_dt_ids,
.pm = pm_sleep_ptr(&dc_pm_ops),
},
};
static struct platform_driver * const dc_drivers[] = {
&dc_cf_driver,
&dc_de_driver,
&dc_ed_driver,
&dc_fg_driver,
&dc_fl_driver,
&dc_fw_driver,
&dc_ic_driver,
&dc_lb_driver,
&dc_pe_driver,
&dc_tc_driver,
&dc_driver,
};
static int __init dc_drm_init(void)
{
return platform_register_drivers(dc_drivers, ARRAY_SIZE(dc_drivers));
}
static void __exit dc_drm_exit(void)
{
platform_unregister_drivers(dc_drivers, ARRAY_SIZE(dc_drivers));
}
module_init(dc_drm_init);
module_exit(dc_drm_exit);
MODULE_DESCRIPTION("i.MX8 Display Controller DRM Driver");
MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
MODULE_LICENSE("GPL");
+102
View File
@@ -0,0 +1,102 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2024 NXP
*/
#ifndef __DC_DRV_H__
#define __DC_DRV_H__
#include <linux/container_of.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <drm/drm_device.h>
#include <drm/drm_encoder.h>
#include "dc-de.h"
#include "dc-kms.h"
#include "dc-pe.h"
/**
* struct dc_drm_device - DC specific drm_device
*/
struct dc_drm_device {
/** @base: base drm_device structure */
struct drm_device base;
/** @dc_crtc: DC specific CRTC list */
struct dc_crtc dc_crtc[DC_DISPLAYS];
/** @dc_primary: DC specific primary plane list */
struct dc_plane dc_primary[DC_DISPLAYS];
/** @encoder: encoder list */
struct drm_encoder encoder[DC_DISPLAYS];
/** @cf_safe: constframe list(safety stream) */
struct dc_cf *cf_safe[DC_DISPLAYS];
/** @cf_cont: constframe list(content stream) */
struct dc_cf *cf_cont[DC_DISPLAYS];
/** @de: display engine list */
struct dc_de *de[DC_DISPLAYS];
/** @ed_safe: extdst list(safety stream) */
struct dc_ed *ed_safe[DC_DISPLAYS];
/** @ed_cont: extdst list(content stream) */
struct dc_ed *ed_cont[DC_DISPLAYS];
/** @fg: framegen list */
struct dc_fg *fg[DC_DISPLAYS];
/** @fu_disp: fetchunit list(used by display engine) */
struct dc_fu *fu_disp[DC_DISP_FU_CNT];
/** @lb: layerblend list */
struct dc_lb *lb[DC_LB_CNT];
/** @pe: pixel engine */
struct dc_pe *pe;
/** @tc: tcon list */
struct dc_tc *tc[DC_DISPLAYS];
};
struct dc_subdev_info {
resource_size_t reg_start;
int id;
};
static inline struct dc_drm_device *to_dc_drm_device(struct drm_device *drm)
{
return container_of(drm, struct dc_drm_device, base);
}
int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index);
int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index);
int dc_kms_init(struct dc_drm_device *dc_drm);
void dc_kms_uninit(struct dc_drm_device *dc_drm);
int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane);
extern struct platform_driver dc_cf_driver;
extern struct platform_driver dc_de_driver;
extern struct platform_driver dc_ed_driver;
extern struct platform_driver dc_fg_driver;
extern struct platform_driver dc_fl_driver;
extern struct platform_driver dc_fw_driver;
extern struct platform_driver dc_ic_driver;
extern struct platform_driver dc_lb_driver;
extern struct platform_driver dc_pe_driver;
extern struct platform_driver dc_tc_driver;
static inline int dc_subdev_get_id(const struct dc_subdev_info *info,
int info_cnt, struct resource *res)
{
int i;
if (!res)
return -EINVAL;
for (i = 0; i < info_cnt; i++)
if (info[i].reg_start == res->start)
return info[i].id;
return -EINVAL;
}
void dc_de_post_bind(struct dc_drm_device *dc_drm);
void dc_pe_post_bind(struct dc_drm_device *dc_drm);
#endif /* __DC_DRV_H__ */
+287
View File
@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/component.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include "dc-drv.h"
#include "dc-pe.h"
#define PIXENGCFG_STATIC 0x8
#define DIV_MASK GENMASK(23, 16)
#define DIV(x) FIELD_PREP(DIV_MASK, (x))
#define DIV_RESET 0x80
#define SYNC_MODE BIT(8)
#define SINGLE 0
#define POWERDOWN BIT(4)
#define PIXENGCFG_DYNAMIC 0xc
#define PIXENGCFG_TRIGGER 0x14
#define SYNC_TRIGGER BIT(0)
#define STATICCONTROL 0x8
#define PERFCOUNTMODE BIT(12)
#define KICK_MODE BIT(8)
#define EXTERNAL BIT(8)
#define CONTROL 0xc
#define GAMMAAPPLYENABLE BIT(0)
static const struct dc_subdev_info dc_ed_info[] = {
{ .reg_start = 0x56180980, .id = 0, },
{ .reg_start = 0x56180a00, .id = 1, },
{ .reg_start = 0x561809c0, .id = 4, },
{ .reg_start = 0x56180a40, .id = 5, },
};
static const struct regmap_range dc_ed_pec_regmap_write_ranges[] = {
regmap_reg_range(PIXENGCFG_STATIC, PIXENGCFG_STATIC),
regmap_reg_range(PIXENGCFG_DYNAMIC, PIXENGCFG_DYNAMIC),
regmap_reg_range(PIXENGCFG_TRIGGER, PIXENGCFG_TRIGGER),
};
static const struct regmap_access_table dc_ed_pec_regmap_write_table = {
.yes_ranges = dc_ed_pec_regmap_write_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_ed_pec_regmap_write_ranges),
};
static const struct regmap_range dc_ed_pec_regmap_read_ranges[] = {
regmap_reg_range(PIXENGCFG_STATIC, PIXENGCFG_STATIC),
regmap_reg_range(PIXENGCFG_DYNAMIC, PIXENGCFG_DYNAMIC),
};
static const struct regmap_access_table dc_ed_pec_regmap_read_table = {
.yes_ranges = dc_ed_pec_regmap_read_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_ed_pec_regmap_read_ranges),
};
static const struct regmap_range dc_ed_pec_regmap_volatile_ranges[] = {
regmap_reg_range(PIXENGCFG_TRIGGER, PIXENGCFG_TRIGGER),
};
static const struct regmap_access_table dc_ed_pec_regmap_volatile_table = {
.yes_ranges = dc_ed_pec_regmap_volatile_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_ed_pec_regmap_volatile_ranges),
};
static const struct regmap_config dc_ed_pec_regmap_config = {
.name = "pec",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_ed_pec_regmap_write_table,
.rd_table = &dc_ed_pec_regmap_read_table,
.volatile_table = &dc_ed_pec_regmap_volatile_table,
.max_register = PIXENGCFG_TRIGGER,
};
static const struct regmap_range dc_ed_regmap_ranges[] = {
regmap_reg_range(STATICCONTROL, STATICCONTROL),
regmap_reg_range(CONTROL, CONTROL),
};
static const struct regmap_access_table dc_ed_regmap_access_table = {
.yes_ranges = dc_ed_regmap_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_ed_regmap_ranges),
};
static const struct regmap_config dc_ed_cfg_regmap_config = {
.name = "cfg",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_ed_regmap_access_table,
.rd_table = &dc_ed_regmap_access_table,
.max_register = CONTROL,
};
static const enum dc_link_id src_sels[] = {
LINK_ID_NONE,
LINK_ID_CONSTFRAME0,
LINK_ID_CONSTFRAME1,
LINK_ID_CONSTFRAME4,
LINK_ID_CONSTFRAME5,
LINK_ID_LAYERBLEND3,
LINK_ID_LAYERBLEND2,
LINK_ID_LAYERBLEND1,
LINK_ID_LAYERBLEND0,
};
static inline void dc_ed_pec_enable_shden(struct dc_ed *ed)
{
regmap_write_bits(ed->reg_pec, PIXENGCFG_STATIC, SHDEN, SHDEN);
}
static inline void dc_ed_pec_poweron(struct dc_ed *ed)
{
regmap_write_bits(ed->reg_pec, PIXENGCFG_STATIC, POWERDOWN, 0);
}
static inline void dc_ed_pec_sync_mode_single(struct dc_ed *ed)
{
regmap_write_bits(ed->reg_pec, PIXENGCFG_STATIC, SYNC_MODE, SINGLE);
}
static inline void dc_ed_pec_div_reset(struct dc_ed *ed)
{
regmap_write_bits(ed->reg_pec, PIXENGCFG_STATIC, DIV_MASK,
DIV(DIV_RESET));
}
void dc_ed_pec_src_sel(struct dc_ed *ed, enum dc_link_id src)
{
int i;
for (i = 0; i < ARRAY_SIZE(src_sels); i++) {
if (src_sels[i] == src) {
regmap_write(ed->reg_pec, PIXENGCFG_DYNAMIC, src);
return;
}
}
}
void dc_ed_pec_sync_trigger(struct dc_ed *ed)
{
regmap_write(ed->reg_pec, PIXENGCFG_TRIGGER, SYNC_TRIGGER);
}
static inline void dc_ed_enable_shden(struct dc_ed *ed)
{
regmap_write_bits(ed->reg_cfg, STATICCONTROL, SHDEN, SHDEN);
}
static inline void dc_ed_kick_mode_external(struct dc_ed *ed)
{
regmap_write_bits(ed->reg_cfg, STATICCONTROL, KICK_MODE, EXTERNAL);
}
static inline void dc_ed_disable_perfcountmode(struct dc_ed *ed)
{
regmap_write_bits(ed->reg_cfg, STATICCONTROL, PERFCOUNTMODE, 0);
}
static inline void dc_ed_disable_gamma_apply(struct dc_ed *ed)
{
regmap_write_bits(ed->reg_cfg, CONTROL, GAMMAAPPLYENABLE, 0);
}
void dc_ed_init(struct dc_ed *ed)
{
dc_ed_pec_src_sel(ed, LINK_ID_NONE);
dc_ed_pec_enable_shden(ed);
dc_ed_pec_poweron(ed);
dc_ed_pec_sync_mode_single(ed);
dc_ed_pec_div_reset(ed);
dc_ed_enable_shden(ed);
dc_ed_disable_perfcountmode(ed);
dc_ed_kick_mode_external(ed);
dc_ed_disable_gamma_apply(ed);
}
static int dc_ed_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct dc_drm_device *dc_drm = data;
struct resource *res_pec;
void __iomem *base_pec;
void __iomem *base_cfg;
struct dc_ed *ed;
int id;
ed = devm_kzalloc(dev, sizeof(*ed), GFP_KERNEL);
if (!ed)
return -ENOMEM;
base_pec = devm_platform_get_and_ioremap_resource(pdev, 0, &res_pec);
if (IS_ERR(base_pec))
return PTR_ERR(base_pec);
base_cfg = devm_platform_ioremap_resource_byname(pdev, "cfg");
if (IS_ERR(base_cfg))
return PTR_ERR(base_cfg);
ed->reg_pec = devm_regmap_init_mmio(dev, base_pec,
&dc_ed_pec_regmap_config);
if (IS_ERR(ed->reg_pec))
return PTR_ERR(ed->reg_pec);
ed->reg_cfg = devm_regmap_init_mmio(dev, base_cfg,
&dc_ed_cfg_regmap_config);
if (IS_ERR(ed->reg_cfg))
return PTR_ERR(ed->reg_cfg);
ed->irq_shdload = platform_get_irq_byname(pdev, "shdload");
if (ed->irq_shdload < 0)
return ed->irq_shdload;
ed->dev = dev;
id = dc_subdev_get_id(dc_ed_info, ARRAY_SIZE(dc_ed_info), res_pec);
if (id < 0) {
dev_err(dev, "failed to get instance number: %d\n", id);
return id;
}
switch (id) {
case 0:
dc_drm->ed_cont[0] = ed;
break;
case 1:
dc_drm->ed_cont[1] = ed;
break;
case 4:
dc_drm->ed_safe[0] = ed;
break;
case 5:
dc_drm->ed_safe[1] = ed;
break;
}
return 0;
}
static const struct component_ops dc_ed_ops = {
.bind = dc_ed_bind,
};
static int dc_ed_probe(struct platform_device *pdev)
{
int ret;
ret = component_add(&pdev->dev, &dc_ed_ops);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to add component\n");
return 0;
}
static void dc_ed_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dc_ed_ops);
}
static const struct of_device_id dc_ed_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-dc-extdst" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dc_ed_dt_ids);
struct platform_driver dc_ed_driver = {
.probe = dc_ed_probe,
.remove = dc_ed_remove,
.driver = {
.name = "imx8-dc-extdst",
.suppress_bind_attrs = true,
.of_match_table = dc_ed_dt_ids,
},
};
+375
View File
@@ -0,0 +1,375 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/device.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/units.h>
#include <drm/drm_modes.h>
#include "dc-de.h"
#include "dc-drv.h"
#define FGSTCTRL 0x8
#define FGSYNCMODE_MASK GENMASK(2, 1)
#define FGSYNCMODE(x) FIELD_PREP(FGSYNCMODE_MASK, (x))
#define SHDEN BIT(0)
#define HTCFG1 0xc
#define HTOTAL(x) FIELD_PREP(GENMASK(29, 16), ((x) - 1))
#define HACT(x) FIELD_PREP(GENMASK(13, 0), (x))
#define HTCFG2 0x10
#define HSEN BIT(31)
#define HSBP(x) FIELD_PREP(GENMASK(29, 16), ((x) - 1))
#define HSYNC(x) FIELD_PREP(GENMASK(13, 0), ((x) - 1))
#define VTCFG1 0x14
#define VTOTAL(x) FIELD_PREP(GENMASK(29, 16), ((x) - 1))
#define VACT(x) FIELD_PREP(GENMASK(13, 0), (x))
#define VTCFG2 0x18
#define VSEN BIT(31)
#define VSBP(x) FIELD_PREP(GENMASK(29, 16), ((x) - 1))
#define VSYNC(x) FIELD_PREP(GENMASK(13, 0), ((x) - 1))
#define PKICKCONFIG 0x2c
#define SKICKCONFIG 0x30
#define EN BIT(31)
#define ROW(x) FIELD_PREP(GENMASK(29, 16), (x))
#define COL(x) FIELD_PREP(GENMASK(13, 0), (x))
#define PACFG 0x54
#define SACFG 0x58
#define STARTY(x) FIELD_PREP(GENMASK(29, 16), ((x) + 1))
#define STARTX(x) FIELD_PREP(GENMASK(13, 0), ((x) + 1))
#define FGINCTRL 0x5c
#define FGINCTRLPANIC 0x60
#define ENSECALPHA BIT(4)
#define ENPRIMALPHA BIT(3)
#define FGDM_MASK GENMASK(2, 0)
#define FGCCR 0x64
#define CCGREEN(x) FIELD_PREP(GENMASK(19, 10), (x))
#define FGENABLE 0x68
#define FGEN BIT(0)
#define FGSLR 0x6c
#define SHDTOKGEN BIT(0)
#define FGTIMESTAMP 0x74
#define FRAMEINDEX(x) FIELD_GET(GENMASK(31, 14), (x))
#define LINEINDEX(x) FIELD_GET(GENMASK(13, 0), (x))
#define FGCHSTAT 0x78
#define SECSYNCSTAT BIT(24)
#define SFIFOEMPTY BIT(16)
#define FGCHSTATCLR 0x7c
#define CLRSECSTAT BIT(16)
enum dc_fg_syncmode {
FG_SYNCMODE_OFF, /* No side-by-side synchronization. */
};
enum dc_fg_dm {
FG_DM_CONSTCOL = 0x1, /* Constant Color Background is shown. */
FG_DM_SEC_ON_TOP = 0x5, /* Both inputs overlaid with secondary on top. */
};
static const struct dc_subdev_info dc_fg_info[] = {
{ .reg_start = 0x5618b800, .id = 0, },
{ .reg_start = 0x5618d400, .id = 1, },
};
static const struct regmap_range dc_fg_regmap_write_ranges[] = {
regmap_reg_range(FGSTCTRL, VTCFG2),
regmap_reg_range(PKICKCONFIG, SKICKCONFIG),
regmap_reg_range(PACFG, FGSLR),
regmap_reg_range(FGCHSTATCLR, FGCHSTATCLR),
};
static const struct regmap_range dc_fg_regmap_read_ranges[] = {
regmap_reg_range(FGSTCTRL, VTCFG2),
regmap_reg_range(PKICKCONFIG, SKICKCONFIG),
regmap_reg_range(PACFG, FGENABLE),
regmap_reg_range(FGTIMESTAMP, FGCHSTAT),
};
static const struct regmap_access_table dc_fg_regmap_write_table = {
.yes_ranges = dc_fg_regmap_write_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_fg_regmap_write_ranges),
};
static const struct regmap_access_table dc_fg_regmap_read_table = {
.yes_ranges = dc_fg_regmap_read_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_fg_regmap_read_ranges),
};
static const struct regmap_config dc_fg_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_fg_regmap_write_table,
.rd_table = &dc_fg_regmap_read_table,
.max_register = FGCHSTATCLR,
};
static inline void dc_fg_enable_shden(struct dc_fg *fg)
{
regmap_write_bits(fg->reg, FGSTCTRL, SHDEN, SHDEN);
}
static inline void dc_fg_syncmode(struct dc_fg *fg, enum dc_fg_syncmode mode)
{
regmap_write_bits(fg->reg, FGSTCTRL, FGSYNCMODE_MASK, FGSYNCMODE(mode));
}
void dc_fg_cfg_videomode(struct dc_fg *fg, struct drm_display_mode *m)
{
u32 hact, htotal, hsync, hsbp;
u32 vact, vtotal, vsync, vsbp;
u32 kick_row, kick_col;
int ret;
hact = m->crtc_hdisplay;
htotal = m->crtc_htotal;
hsync = m->crtc_hsync_end - m->crtc_hsync_start;
hsbp = m->crtc_htotal - m->crtc_hsync_start;
vact = m->crtc_vdisplay;
vtotal = m->crtc_vtotal;
vsync = m->crtc_vsync_end - m->crtc_vsync_start;
vsbp = m->crtc_vtotal - m->crtc_vsync_start;
/* video mode */
regmap_write(fg->reg, HTCFG1, HACT(hact) | HTOTAL(htotal));
regmap_write(fg->reg, HTCFG2, HSYNC(hsync) | HSBP(hsbp) | HSEN);
regmap_write(fg->reg, VTCFG1, VACT(vact) | VTOTAL(vtotal));
regmap_write(fg->reg, VTCFG2, VSYNC(vsync) | VSBP(vsbp) | VSEN);
kick_col = hact + 1;
kick_row = vact;
/* pkickconfig */
regmap_write(fg->reg, PKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
/* skikconfig */
regmap_write(fg->reg, SKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
/* primary and secondary area position configuration */
regmap_write(fg->reg, PACFG, STARTX(0) | STARTY(0));
regmap_write(fg->reg, SACFG, STARTX(0) | STARTY(0));
/* alpha */
regmap_write_bits(fg->reg, FGINCTRL, ENPRIMALPHA | ENSECALPHA, 0);
regmap_write_bits(fg->reg, FGINCTRLPANIC, ENPRIMALPHA | ENSECALPHA, 0);
/* constant color is green(used in panic mode) */
regmap_write(fg->reg, FGCCR, CCGREEN(0x3ff));
ret = clk_set_rate(fg->clk_disp, m->clock * HZ_PER_KHZ);
if (ret < 0)
dev_err(fg->dev, "failed to set display clock rate: %d\n", ret);
}
static inline void dc_fg_displaymode(struct dc_fg *fg, enum dc_fg_dm mode)
{
regmap_write_bits(fg->reg, FGINCTRL, FGDM_MASK, mode);
}
static inline void dc_fg_panic_displaymode(struct dc_fg *fg, enum dc_fg_dm mode)
{
regmap_write_bits(fg->reg, FGINCTRLPANIC, FGDM_MASK, mode);
}
void dc_fg_enable(struct dc_fg *fg)
{
regmap_write(fg->reg, FGENABLE, FGEN);
}
void dc_fg_disable(struct dc_fg *fg)
{
regmap_write(fg->reg, FGENABLE, 0);
}
void dc_fg_shdtokgen(struct dc_fg *fg)
{
regmap_write(fg->reg, FGSLR, SHDTOKGEN);
}
u32 dc_fg_get_frame_index(struct dc_fg *fg)
{
u32 val;
regmap_read(fg->reg, FGTIMESTAMP, &val);
return FRAMEINDEX(val);
}
u32 dc_fg_get_line_index(struct dc_fg *fg)
{
u32 val;
regmap_read(fg->reg, FGTIMESTAMP, &val);
return LINEINDEX(val);
}
bool dc_fg_wait_for_frame_index_moving(struct dc_fg *fg)
{
unsigned long timeout = jiffies + msecs_to_jiffies(100);
u32 frame_index, last_frame_index;
frame_index = dc_fg_get_frame_index(fg);
do {
last_frame_index = frame_index;
frame_index = dc_fg_get_frame_index(fg);
} while (last_frame_index == frame_index &&
time_before(jiffies, timeout));
return last_frame_index != frame_index;
}
bool dc_fg_secondary_requests_to_read_empty_fifo(struct dc_fg *fg)
{
u32 val;
regmap_read(fg->reg, FGCHSTAT, &val);
return !!(val & SFIFOEMPTY);
}
void dc_fg_secondary_clear_channel_status(struct dc_fg *fg)
{
regmap_write(fg->reg, FGCHSTATCLR, CLRSECSTAT);
}
int dc_fg_wait_for_secondary_syncup(struct dc_fg *fg)
{
unsigned int val;
return regmap_read_poll_timeout(fg->reg, FGCHSTAT, val,
val & SECSYNCSTAT, 5, 100000);
}
void dc_fg_enable_clock(struct dc_fg *fg)
{
int ret;
ret = clk_prepare_enable(fg->clk_disp);
if (ret)
dev_err(fg->dev, "failed to enable display clock: %d\n", ret);
}
void dc_fg_disable_clock(struct dc_fg *fg)
{
clk_disable_unprepare(fg->clk_disp);
}
enum drm_mode_status dc_fg_check_clock(struct dc_fg *fg, int clk_khz)
{
unsigned long rounded_rate;
rounded_rate = clk_round_rate(fg->clk_disp, clk_khz * HZ_PER_KHZ);
if (rounded_rate != clk_khz * HZ_PER_KHZ)
return MODE_NOCLOCK;
return MODE_OK;
}
void dc_fg_init(struct dc_fg *fg)
{
dc_fg_enable_shden(fg);
dc_fg_syncmode(fg, FG_SYNCMODE_OFF);
dc_fg_displaymode(fg, FG_DM_SEC_ON_TOP);
dc_fg_panic_displaymode(fg, FG_DM_CONSTCOL);
}
static int dc_fg_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct dc_drm_device *dc_drm = data;
struct resource *res;
void __iomem *base;
struct dc_fg *fg;
int id;
fg = devm_kzalloc(dev, sizeof(*fg), GFP_KERNEL);
if (!fg)
return -ENOMEM;
base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(base))
return PTR_ERR(base);
fg->reg = devm_regmap_init_mmio(dev, base, &dc_fg_regmap_config);
if (IS_ERR(fg->reg))
return PTR_ERR(fg->reg);
fg->clk_disp = devm_clk_get(dev, NULL);
if (IS_ERR(fg->clk_disp))
return dev_err_probe(dev, PTR_ERR(fg->clk_disp),
"failed to get display clock\n");
id = dc_subdev_get_id(dc_fg_info, ARRAY_SIZE(dc_fg_info), res);
if (id < 0) {
dev_err(dev, "failed to get instance number: %d\n", id);
return id;
}
fg->dev = dev;
dc_drm->fg[id] = fg;
return 0;
}
static const struct component_ops dc_fg_ops = {
.bind = dc_fg_bind,
};
static int dc_fg_probe(struct platform_device *pdev)
{
int ret;
ret = component_add(&pdev->dev, &dc_fg_ops);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to add component\n");
return 0;
}
static void dc_fg_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dc_fg_ops);
}
static const struct of_device_id dc_fg_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-dc-framegen" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dc_fg_dt_ids);
struct platform_driver dc_fg_driver = {
.probe = dc_fg_probe,
.remove = dc_fg_remove,
.driver = {
.name = "imx8-dc-framegen",
.suppress_bind_attrs = true,
.of_match_table = dc_fg_dt_ids,
},
};
+184
View File
@@ -0,0 +1,184 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/component.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <drm/drm_fourcc.h>
#include "dc-drv.h"
#include "dc-fu.h"
#define BASEADDRESS(x) (0x10 + FRAC_OFFSET * (x))
#define SOURCEBUFFERATTRIBUTES(x) (0x14 + FRAC_OFFSET * (x))
#define SOURCEBUFFERDIMENSION(x) (0x18 + FRAC_OFFSET * (x))
#define COLORCOMPONENTBITS(x) (0x1c + FRAC_OFFSET * (x))
#define COLORCOMPONENTSHIFT(x) (0x20 + FRAC_OFFSET * (x))
#define LAYEROFFSET(x) (0x24 + FRAC_OFFSET * (x))
#define CLIPWINDOWOFFSET(x) (0x28 + FRAC_OFFSET * (x))
#define CLIPWINDOWDIMENSIONS(x) (0x2c + FRAC_OFFSET * (x))
#define CONSTANTCOLOR(x) (0x30 + FRAC_OFFSET * (x))
#define LAYERPROPERTY(x) (0x34 + FRAC_OFFSET * (x))
#define FRAMEDIMENSIONS 0x150
struct dc_fl {
struct dc_fu fu;
};
static const struct dc_subdev_info dc_fl_info[] = {
{ .reg_start = 0x56180ac0, .id = 0, },
};
static const struct regmap_range dc_fl_regmap_ranges[] = {
regmap_reg_range(STATICCONTROL, FRAMEDIMENSIONS),
};
static const struct regmap_access_table dc_fl_regmap_access_table = {
.yes_ranges = dc_fl_regmap_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_fl_regmap_ranges),
};
static const struct regmap_config dc_fl_cfg_regmap_config = {
.name = "cfg",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_fl_regmap_access_table,
.rd_table = &dc_fl_regmap_access_table,
.max_register = FRAMEDIMENSIONS,
};
static void dc_fl_set_fmt(struct dc_fu *fu, enum dc_fu_frac frac,
const struct drm_format_info *format)
{
u32 bits = 0, shifts = 0;
dc_fu_set_src_bpp(fu, frac, format->cpp[0] * 8);
regmap_write_bits(fu->reg_cfg, LAYERPROPERTY(frac),
YUVCONVERSIONMODE_MASK,
YUVCONVERSIONMODE(YUVCONVERSIONMODE_OFF));
dc_fu_get_pixel_format_bits(fu, format->format, &bits);
dc_fu_get_pixel_format_shifts(fu, format->format, &shifts);
regmap_write(fu->reg_cfg, COLORCOMPONENTBITS(frac), bits);
regmap_write(fu->reg_cfg, COLORCOMPONENTSHIFT(frac), shifts);
}
static void dc_fl_set_framedimensions(struct dc_fu *fu, int w, int h)
{
regmap_write(fu->reg_cfg, FRAMEDIMENSIONS,
FRAMEWIDTH(w) | FRAMEHEIGHT(h));
}
static void dc_fl_init(struct dc_fu *fu)
{
dc_fu_common_hw_init(fu);
dc_fu_shdldreq_sticky(fu, 0xff);
}
static void dc_fl_set_ops(struct dc_fu *fu)
{
memcpy(&fu->ops, &dc_fu_common_ops, sizeof(dc_fu_common_ops));
fu->ops.init = dc_fl_init;
fu->ops.set_fmt = dc_fl_set_fmt;
fu->ops.set_framedimensions = dc_fl_set_framedimensions;
}
static int dc_fl_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct dc_drm_device *dc_drm = data;
struct resource *res_pec;
void __iomem *base_cfg;
struct dc_fl *fl;
struct dc_fu *fu;
int i, id;
fl = devm_kzalloc(dev, sizeof(*fl), GFP_KERNEL);
if (!fl)
return -ENOMEM;
fu = &fl->fu;
res_pec = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base_cfg = devm_platform_ioremap_resource_byname(pdev, "cfg");
if (IS_ERR(base_cfg))
return PTR_ERR(base_cfg);
fu->reg_cfg = devm_regmap_init_mmio(dev, base_cfg,
&dc_fl_cfg_regmap_config);
if (IS_ERR(fu->reg_cfg))
return PTR_ERR(fu->reg_cfg);
id = dc_subdev_get_id(dc_fl_info, ARRAY_SIZE(dc_fl_info), res_pec);
if (id < 0) {
dev_err(dev, "failed to get instance number: %d\n", id);
return id;
}
fu->link_id = LINK_ID_FETCHLAYER0;
fu->id = DC_FETCHUNIT_FL0;
for (i = 0; i < DC_FETCHUNIT_FRAC_NUM; i++) {
fu->reg_baseaddr[i] = BASEADDRESS(i);
fu->reg_sourcebufferattributes[i] = SOURCEBUFFERATTRIBUTES(i);
fu->reg_sourcebufferdimension[i] = SOURCEBUFFERDIMENSION(i);
fu->reg_layeroffset[i] = LAYEROFFSET(i);
fu->reg_clipwindowoffset[i] = CLIPWINDOWOFFSET(i);
fu->reg_clipwindowdimensions[i] = CLIPWINDOWDIMENSIONS(i);
fu->reg_constantcolor[i] = CONSTANTCOLOR(i);
fu->reg_layerproperty[i] = LAYERPROPERTY(i);
}
snprintf(fu->name, sizeof(fu->name), "FetchLayer%d", id);
dc_fl_set_ops(fu);
dc_drm->fu_disp[fu->id] = fu;
return 0;
}
static const struct component_ops dc_fl_ops = {
.bind = dc_fl_bind,
};
static int dc_fl_probe(struct platform_device *pdev)
{
int ret;
ret = component_add(&pdev->dev, &dc_fl_ops);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to add component\n");
return 0;
}
static void dc_fl_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dc_fl_ops);
}
static const struct of_device_id dc_fl_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-dc-fetchlayer" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dc_fl_dt_ids);
struct platform_driver dc_fl_driver = {
.probe = dc_fl_probe,
.remove = dc_fl_remove,
.driver = {
.name = "imx8-dc-fetchlayer",
.suppress_bind_attrs = true,
.of_match_table = dc_fl_dt_ids,
},
};
+258
View File
@@ -0,0 +1,258 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/math.h>
#include "dc-fu.h"
#include "dc-pe.h"
/* STATICCONTROL */
#define SHDLDREQSTICKY_MASK GENMASK(31, 24)
#define SHDLDREQSTICKY(x) FIELD_PREP(SHDLDREQSTICKY_MASK, (x))
#define BASEADDRESSAUTOUPDATE_MASK GENMASK(23, 16)
#define BASEADDRESSAUTOUPDATE(x) FIELD_PREP(BASEADDRESSAUTOUPDATE_MASK, (x))
/* BURSTBUFFERMANAGEMENT */
#define LINEMODE_MASK BIT(31)
#define SETBURSTLENGTH_MASK GENMASK(12, 8)
#define SETBURSTLENGTH(x) FIELD_PREP(SETBURSTLENGTH_MASK, (x))
#define SETNUMBUFFERS_MASK GENMASK(7, 0)
#define SETNUMBUFFERS(x) FIELD_PREP(SETNUMBUFFERS_MASK, (x))
/* SOURCEBUFFERATTRIBUTES */
#define BITSPERPIXEL_MASK GENMASK(21, 16)
#define BITSPERPIXEL(x) FIELD_PREP(BITSPERPIXEL_MASK, (x))
#define STRIDE_MASK GENMASK(15, 0)
#define STRIDE(x) FIELD_PREP(STRIDE_MASK, (x) - 1)
/* SOURCEBUFFERDIMENSION */
#define LINECOUNT(x) FIELD_PREP(GENMASK(29, 16), (x))
#define LINEWIDTH(x) FIELD_PREP(GENMASK(13, 0), (x))
/* LAYEROFFSET */
#define LAYERYOFFSET(x) FIELD_PREP(GENMASK(30, 16), (x))
#define LAYERXOFFSET(x) FIELD_PREP(GENMASK(14, 0), (x))
/* CLIPWINDOWOFFSET */
#define CLIPWINDOWYOFFSET(x) FIELD_PREP(GENMASK(30, 16), (x))
#define CLIPWINDOWXOFFSET(x) FIELD_PREP(GENMASK(14, 0), (x))
/* CLIPWINDOWDIMENSIONS */
#define CLIPWINDOWHEIGHT(x) FIELD_PREP(GENMASK(29, 16), (x) - 1)
#define CLIPWINDOWWIDTH(x) FIELD_PREP(GENMASK(13, 0), (x) - 1)
enum dc_linemode {
/*
* Mandatory setting for operation in the Display Controller.
* Works also for Blit Engine with marginal performance impact.
*/
LINEMODE_DISPLAY = 0,
};
struct dc_fu_pixel_format {
u32 pixel_format;
u32 bits;
u32 shifts;
};
static const struct dc_fu_pixel_format pixel_formats[] = {
{
DRM_FORMAT_XRGB8888,
R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0),
R_SHIFT(16) | G_SHIFT(8) | B_SHIFT(0) | A_SHIFT(0),
},
};
void dc_fu_get_pixel_format_bits(struct dc_fu *fu, u32 format, u32 *bits)
{
int i;
for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) {
if (pixel_formats[i].pixel_format == format) {
*bits = pixel_formats[i].bits;
return;
}
}
}
void
dc_fu_get_pixel_format_shifts(struct dc_fu *fu, u32 format, u32 *shifts)
{
int i;
for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) {
if (pixel_formats[i].pixel_format == format) {
*shifts = pixel_formats[i].shifts;
return;
}
}
}
static inline void dc_fu_enable_shden(struct dc_fu *fu)
{
regmap_write_bits(fu->reg_cfg, STATICCONTROL, SHDEN, SHDEN);
}
static inline void dc_fu_baddr_autoupdate(struct dc_fu *fu, u8 layer_mask)
{
regmap_write_bits(fu->reg_cfg, STATICCONTROL,
BASEADDRESSAUTOUPDATE_MASK,
BASEADDRESSAUTOUPDATE(layer_mask));
}
void dc_fu_shdldreq_sticky(struct dc_fu *fu, u8 layer_mask)
{
regmap_write_bits(fu->reg_cfg, STATICCONTROL, SHDLDREQSTICKY_MASK,
SHDLDREQSTICKY(layer_mask));
}
static inline void dc_fu_set_linemode(struct dc_fu *fu, enum dc_linemode mode)
{
regmap_write_bits(fu->reg_cfg, BURSTBUFFERMANAGEMENT, LINEMODE_MASK,
mode);
}
static inline void dc_fu_set_numbuffers(struct dc_fu *fu, unsigned int num)
{
regmap_write_bits(fu->reg_cfg, BURSTBUFFERMANAGEMENT,
SETNUMBUFFERS_MASK, SETNUMBUFFERS(num));
}
static void dc_fu_set_burstlength(struct dc_fu *fu, dma_addr_t baddr)
{
unsigned int burst_size, burst_length;
burst_size = 1 << __ffs(baddr);
burst_size = round_up(burst_size, 8);
burst_size = min(burst_size, 128U);
burst_length = burst_size / 8;
regmap_write_bits(fu->reg_cfg, BURSTBUFFERMANAGEMENT,
SETBURSTLENGTH_MASK, SETBURSTLENGTH(burst_length));
}
static void dc_fu_set_baseaddress(struct dc_fu *fu, enum dc_fu_frac frac,
dma_addr_t baddr)
{
regmap_write(fu->reg_cfg, fu->reg_baseaddr[frac], baddr);
}
void dc_fu_set_src_bpp(struct dc_fu *fu, enum dc_fu_frac frac, unsigned int bpp)
{
regmap_write_bits(fu->reg_cfg, fu->reg_sourcebufferattributes[frac],
BITSPERPIXEL_MASK, BITSPERPIXEL(bpp));
}
static void dc_fu_set_src_stride(struct dc_fu *fu, enum dc_fu_frac frac,
unsigned int stride)
{
regmap_write_bits(fu->reg_cfg, fu->reg_sourcebufferattributes[frac],
STRIDE_MASK, STRIDE(stride));
}
static void dc_fu_set_src_buf_dimensions(struct dc_fu *fu, enum dc_fu_frac frac,
int w, int h)
{
regmap_write(fu->reg_cfg, fu->reg_sourcebufferdimension[frac],
LINEWIDTH(w) | LINECOUNT(h));
}
static inline void dc_fu_layeroffset(struct dc_fu *fu, enum dc_fu_frac frac,
unsigned int x, unsigned int y)
{
regmap_write(fu->reg_cfg, fu->reg_layeroffset[frac],
LAYERXOFFSET(x) | LAYERYOFFSET(y));
}
static inline void dc_fu_clipoffset(struct dc_fu *fu, enum dc_fu_frac frac,
unsigned int x, unsigned int y)
{
regmap_write(fu->reg_cfg, fu->reg_clipwindowoffset[frac],
CLIPWINDOWXOFFSET(x) | CLIPWINDOWYOFFSET(y));
}
static inline void dc_fu_clipdimensions(struct dc_fu *fu, enum dc_fu_frac frac,
unsigned int w, unsigned int h)
{
regmap_write(fu->reg_cfg, fu->reg_clipwindowdimensions[frac],
CLIPWINDOWWIDTH(w) | CLIPWINDOWHEIGHT(h));
}
static inline void
dc_fu_set_pixel_blend_mode(struct dc_fu *fu, enum dc_fu_frac frac)
{
regmap_write(fu->reg_cfg, fu->reg_layerproperty[frac], 0);
regmap_write(fu->reg_cfg, fu->reg_constantcolor[frac], 0);
}
static void dc_fu_enable_src_buf(struct dc_fu *fu, enum dc_fu_frac frac)
{
regmap_write_bits(fu->reg_cfg, fu->reg_layerproperty[frac],
SOURCEBUFFERENABLE, SOURCEBUFFERENABLE);
}
static void dc_fu_disable_src_buf(struct dc_fu *fu, enum dc_fu_frac frac)
{
regmap_write_bits(fu->reg_cfg, fu->reg_layerproperty[frac],
SOURCEBUFFERENABLE, 0);
if (fu->lb) {
dc_lb_pec_clken(fu->lb, CLKEN_DISABLE);
dc_lb_mode(fu->lb, LB_NEUTRAL);
}
}
static void dc_fu_set_layerblend(struct dc_fu *fu, struct dc_lb *lb)
{
fu->lb = lb;
}
static enum dc_link_id dc_fu_get_link_id(struct dc_fu *fu)
{
return fu->link_id;
}
static const char *dc_fu_get_name(struct dc_fu *fu)
{
return fu->name;
}
const struct dc_fu_ops dc_fu_common_ops = {
.set_burstlength = dc_fu_set_burstlength,
.set_baseaddress = dc_fu_set_baseaddress,
.set_src_stride = dc_fu_set_src_stride,
.set_src_buf_dimensions = dc_fu_set_src_buf_dimensions,
.enable_src_buf = dc_fu_enable_src_buf,
.disable_src_buf = dc_fu_disable_src_buf,
.set_layerblend = dc_fu_set_layerblend,
.get_link_id = dc_fu_get_link_id,
.get_name = dc_fu_get_name,
};
const struct dc_fu_ops *dc_fu_get_ops(struct dc_fu *fu)
{
return &fu->ops;
}
void dc_fu_common_hw_init(struct dc_fu *fu)
{
enum dc_fu_frac i;
dc_fu_baddr_autoupdate(fu, 0x0);
dc_fu_enable_shden(fu);
dc_fu_set_linemode(fu, LINEMODE_DISPLAY);
dc_fu_set_numbuffers(fu, 16);
for (i = DC_FETCHUNIT_FRAC0; i < DC_FETCHUNIT_FRAC_NUM; i++) {
dc_fu_layeroffset(fu, i, 0, 0);
dc_fu_clipoffset(fu, i, 0, 0);
dc_fu_clipdimensions(fu, i, 1, 1);
dc_fu_disable_src_buf(fu, i);
dc_fu_set_pixel_blend_mode(fu, i);
}
}
+129
View File
@@ -0,0 +1,129 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2024 NXP
*/
#ifndef __DC_FETCHUNIT_H__
#define __DC_FETCHUNIT_H__
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/regmap.h>
#include <linux/types.h>
#include <drm/drm_fourcc.h>
#include "dc-pe.h"
#define FRAC_OFFSET 0x28
#define STATICCONTROL 0x8
#define BURSTBUFFERMANAGEMENT 0xc
/* COLORCOMPONENTBITS */
#define R_BITS(x) FIELD_PREP_CONST(GENMASK(27, 24), (x))
#define G_BITS(x) FIELD_PREP_CONST(GENMASK(19, 16), (x))
#define B_BITS(x) FIELD_PREP_CONST(GENMASK(11, 8), (x))
#define A_BITS(x) FIELD_PREP_CONST(GENMASK(3, 0), (x))
/* COLORCOMPONENTSHIFT */
#define R_SHIFT(x) FIELD_PREP_CONST(GENMASK(28, 24), (x))
#define G_SHIFT(x) FIELD_PREP_CONST(GENMASK(20, 16), (x))
#define B_SHIFT(x) FIELD_PREP_CONST(GENMASK(12, 8), (x))
#define A_SHIFT(x) FIELD_PREP_CONST(GENMASK(4, 0), (x))
/* LAYERPROPERTY */
#define SOURCEBUFFERENABLE BIT(31)
#define YUVCONVERSIONMODE_MASK GENMASK(18, 17)
#define YUVCONVERSIONMODE(x) FIELD_PREP(YUVCONVERSIONMODE_MASK, (x))
/* FRAMEDIMENSIONS */
#define FRAMEHEIGHT(x) FIELD_PREP(GENMASK(29, 16), (x))
#define FRAMEWIDTH(x) FIELD_PREP(GENMASK(13, 0), (x))
/* CONTROL */
#define INPUTSELECT_MASK GENMASK(4, 3)
#define INPUTSELECT(x) FIELD_PREP(INPUTSELECT_MASK, (x))
#define RASTERMODE_MASK GENMASK(2, 0)
#define RASTERMODE(x) FIELD_PREP(RASTERMODE_MASK, (x))
enum dc_yuvconversionmode {
YUVCONVERSIONMODE_OFF,
};
enum dc_inputselect {
INPUTSELECT_INACTIVE,
};
enum dc_rastermode {
RASTERMODE_NORMAL,
};
enum {
DC_FETCHUNIT_FL0,
DC_FETCHUNIT_FW2,
};
enum dc_fu_frac {
DC_FETCHUNIT_FRAC0,
DC_FETCHUNIT_FRAC1,
DC_FETCHUNIT_FRAC2,
DC_FETCHUNIT_FRAC3,
DC_FETCHUNIT_FRAC4,
DC_FETCHUNIT_FRAC5,
DC_FETCHUNIT_FRAC6,
DC_FETCHUNIT_FRAC7,
DC_FETCHUNIT_FRAC_NUM
};
struct dc_fu;
struct dc_lb;
struct dc_fu_ops {
void (*init)(struct dc_fu *fu);
void (*set_burstlength)(struct dc_fu *fu, dma_addr_t baddr);
void (*set_baseaddress)(struct dc_fu *fu, enum dc_fu_frac frac,
dma_addr_t baddr);
void (*set_src_stride)(struct dc_fu *fu, enum dc_fu_frac frac,
unsigned int stride);
void (*set_src_buf_dimensions)(struct dc_fu *fu, enum dc_fu_frac frac,
int w, int h);
void (*set_fmt)(struct dc_fu *fu, enum dc_fu_frac frac,
const struct drm_format_info *format);
void (*enable_src_buf)(struct dc_fu *fu, enum dc_fu_frac frac);
void (*disable_src_buf)(struct dc_fu *fu, enum dc_fu_frac frac);
void (*set_framedimensions)(struct dc_fu *fu, int w, int h);
void (*set_layerblend)(struct dc_fu *fu, struct dc_lb *lb);
enum dc_link_id (*get_link_id)(struct dc_fu *fu);
const char *(*get_name)(struct dc_fu *fu);
};
struct dc_fu {
struct regmap *reg_pec;
struct regmap *reg_cfg;
char name[21];
u32 reg_baseaddr[DC_FETCHUNIT_FRAC_NUM];
u32 reg_sourcebufferattributes[DC_FETCHUNIT_FRAC_NUM];
u32 reg_sourcebufferdimension[DC_FETCHUNIT_FRAC_NUM];
u32 reg_layeroffset[DC_FETCHUNIT_FRAC_NUM];
u32 reg_clipwindowoffset[DC_FETCHUNIT_FRAC_NUM];
u32 reg_clipwindowdimensions[DC_FETCHUNIT_FRAC_NUM];
u32 reg_constantcolor[DC_FETCHUNIT_FRAC_NUM];
u32 reg_layerproperty[DC_FETCHUNIT_FRAC_NUM];
unsigned int id;
enum dc_link_id link_id;
struct dc_fu_ops ops;
struct dc_lb *lb;
};
extern const struct dc_fu_ops dc_fu_common_ops;
void dc_fu_get_pixel_format_bits(struct dc_fu *fu, u32 format, u32 *bits);
void dc_fu_get_pixel_format_shifts(struct dc_fu *fu, u32 format, u32 *shifts);
void dc_fu_shdldreq_sticky(struct dc_fu *fu, u8 layer_mask);
void dc_fu_set_src_bpp(struct dc_fu *fu, enum dc_fu_frac frac, unsigned int bpp);
void dc_fu_common_hw_init(struct dc_fu *fu);
const struct dc_fu_ops *dc_fu_get_ops(struct dc_fu *fu);
#endif /* __DC_FETCHUNIT_H__ */
+221
View File
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/component.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <drm/drm_fourcc.h>
#include "dc-drv.h"
#include "dc-fu.h"
#define PIXENGCFG_DYNAMIC 0x8
#define BASEADDRESS(x) (0x10 + FRAC_OFFSET * (x))
#define SOURCEBUFFERATTRIBUTES(x) (0x14 + FRAC_OFFSET * (x))
#define SOURCEBUFFERDIMENSION(x) (0x18 + FRAC_OFFSET * (x))
#define COLORCOMPONENTBITS(x) (0x1c + FRAC_OFFSET * (x))
#define COLORCOMPONENTSHIFT(x) (0x20 + FRAC_OFFSET * (x))
#define LAYEROFFSET(x) (0x24 + FRAC_OFFSET * (x))
#define CLIPWINDOWOFFSET(x) (0x28 + FRAC_OFFSET * (x))
#define CLIPWINDOWDIMENSIONS(x) (0x2c + FRAC_OFFSET * (x))
#define CONSTANTCOLOR(x) (0x30 + FRAC_OFFSET * (x))
#define LAYERPROPERTY(x) (0x34 + FRAC_OFFSET * (x))
#define FRAMEDIMENSIONS 0x150
#define CONTROL 0x170
struct dc_fw {
struct dc_fu fu;
};
static const struct dc_subdev_info dc_fw_info[] = {
{ .reg_start = 0x56180a60, .id = 2, },
};
static const struct regmap_range dc_fw_pec_regmap_access_ranges[] = {
regmap_reg_range(PIXENGCFG_DYNAMIC, PIXENGCFG_DYNAMIC),
};
static const struct regmap_access_table dc_fw_pec_regmap_access_table = {
.yes_ranges = dc_fw_pec_regmap_access_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_fw_pec_regmap_access_ranges),
};
static const struct regmap_config dc_fw_pec_regmap_config = {
.name = "pec",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_fw_pec_regmap_access_table,
.rd_table = &dc_fw_pec_regmap_access_table,
.max_register = PIXENGCFG_DYNAMIC,
};
static const struct regmap_range dc_fw_regmap_ranges[] = {
regmap_reg_range(STATICCONTROL, FRAMEDIMENSIONS),
regmap_reg_range(CONTROL, CONTROL),
};
static const struct regmap_access_table dc_fw_regmap_access_table = {
.yes_ranges = dc_fw_regmap_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_fw_regmap_ranges),
};
static const struct regmap_config dc_fw_cfg_regmap_config = {
.name = "cfg",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_fw_regmap_access_table,
.rd_table = &dc_fw_regmap_access_table,
.max_register = CONTROL,
};
static void dc_fw_set_fmt(struct dc_fu *fu, enum dc_fu_frac frac,
const struct drm_format_info *format)
{
u32 bits = 0, shifts = 0;
dc_fu_set_src_bpp(fu, frac, format->cpp[0] * 8);
regmap_write_bits(fu->reg_cfg, CONTROL, INPUTSELECT_MASK,
INPUTSELECT(INPUTSELECT_INACTIVE));
regmap_write_bits(fu->reg_cfg, CONTROL, RASTERMODE_MASK,
RASTERMODE(RASTERMODE_NORMAL));
regmap_write_bits(fu->reg_cfg, LAYERPROPERTY(frac),
YUVCONVERSIONMODE_MASK,
YUVCONVERSIONMODE(YUVCONVERSIONMODE_OFF));
dc_fu_get_pixel_format_bits(fu, format->format, &bits);
dc_fu_get_pixel_format_shifts(fu, format->format, &shifts);
regmap_write(fu->reg_cfg, COLORCOMPONENTBITS(frac), bits);
regmap_write(fu->reg_cfg, COLORCOMPONENTSHIFT(frac), shifts);
}
static void dc_fw_set_framedimensions(struct dc_fu *fu, int w, int h)
{
regmap_write(fu->reg_cfg, FRAMEDIMENSIONS,
FRAMEWIDTH(w) | FRAMEHEIGHT(h));
}
static void dc_fw_init(struct dc_fu *fu)
{
regmap_write(fu->reg_pec, PIXENGCFG_DYNAMIC, LINK_ID_NONE);
dc_fu_common_hw_init(fu);
dc_fu_shdldreq_sticky(fu, 0xff);
}
static void dc_fw_set_ops(struct dc_fu *fu)
{
memcpy(&fu->ops, &dc_fu_common_ops, sizeof(dc_fu_common_ops));
fu->ops.init = dc_fw_init;
fu->ops.set_fmt = dc_fw_set_fmt;
fu->ops.set_framedimensions = dc_fw_set_framedimensions;
}
static int dc_fw_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct dc_drm_device *dc_drm = data;
struct resource *res_pec;
void __iomem *base_pec;
void __iomem *base_cfg;
struct dc_fw *fw;
struct dc_fu *fu;
int i, id;
fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL);
if (!fw)
return -ENOMEM;
fu = &fw->fu;
base_pec = devm_platform_get_and_ioremap_resource(pdev, 0, &res_pec);
if (IS_ERR(base_pec))
return PTR_ERR(base_pec);
base_cfg = devm_platform_ioremap_resource_byname(pdev, "cfg");
if (IS_ERR(base_cfg))
return PTR_ERR(base_cfg);
fu->reg_pec = devm_regmap_init_mmio(dev, base_pec,
&dc_fw_pec_regmap_config);
if (IS_ERR(fu->reg_pec))
return PTR_ERR(fu->reg_pec);
fu->reg_cfg = devm_regmap_init_mmio(dev, base_cfg,
&dc_fw_cfg_regmap_config);
if (IS_ERR(fu->reg_cfg))
return PTR_ERR(fu->reg_cfg);
id = dc_subdev_get_id(dc_fw_info, ARRAY_SIZE(dc_fw_info), res_pec);
if (id < 0) {
dev_err(dev, "failed to get instance number: %d\n", id);
return id;
}
fu->link_id = LINK_ID_FETCHWARP2;
fu->id = DC_FETCHUNIT_FW2;
for (i = 0; i < DC_FETCHUNIT_FRAC_NUM; i++) {
fu->reg_baseaddr[i] = BASEADDRESS(i);
fu->reg_sourcebufferattributes[i] = SOURCEBUFFERATTRIBUTES(i);
fu->reg_sourcebufferdimension[i] = SOURCEBUFFERDIMENSION(i);
fu->reg_layeroffset[i] = LAYEROFFSET(i);
fu->reg_clipwindowoffset[i] = CLIPWINDOWOFFSET(i);
fu->reg_clipwindowdimensions[i] = CLIPWINDOWDIMENSIONS(i);
fu->reg_constantcolor[i] = CONSTANTCOLOR(i);
fu->reg_layerproperty[i] = LAYERPROPERTY(i);
}
snprintf(fu->name, sizeof(fu->name), "FetchWarp%d", id);
dc_fw_set_ops(fu);
dc_drm->fu_disp[fu->id] = fu;
return 0;
}
static const struct component_ops dc_fw_ops = {
.bind = dc_fw_bind,
};
static int dc_fw_probe(struct platform_device *pdev)
{
int ret;
ret = component_add(&pdev->dev, &dc_fw_ops);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to add component\n");
return 0;
}
static void dc_fw_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dc_fw_ops);
}
static const struct of_device_id dc_fw_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-dc-fetchwarp" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dc_fw_dt_ids);
struct platform_driver dc_fw_driver = {
.probe = dc_fw_probe,
.remove = dc_fw_remove,
.driver = {
.name = "imx8-dc-fetchwarp",
.suppress_bind_attrs = true,
.of_match_table = dc_fw_dt_ids,
},
};
+282
View File
@@ -0,0 +1,282 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#define USERINTERRUPTMASK(n) (0x8 + 4 * (n))
#define INTERRUPTENABLE(n) (0x10 + 4 * (n))
#define INTERRUPTPRESET(n) (0x18 + 4 * (n))
#define INTERRUPTCLEAR(n) (0x20 + 4 * (n))
#define INTERRUPTSTATUS(n) (0x28 + 4 * (n))
#define USERINTERRUPTENABLE(n) (0x40 + 4 * (n))
#define USERINTERRUPTPRESET(n) (0x48 + 4 * (n))
#define USERINTERRUPTCLEAR(n) (0x50 + 4 * (n))
#define USERINTERRUPTSTATUS(n) (0x58 + 4 * (n))
#define IRQ_COUNT 49
#define IRQ_RESERVED 35
#define REG_NUM 2
struct dc_ic_data {
struct regmap *regs;
struct clk *clk_axi;
int irq[IRQ_COUNT];
struct irq_domain *domain;
};
struct dc_ic_entry {
struct dc_ic_data *data;
int irq;
};
static const struct regmap_range dc_ic_regmap_write_ranges[] = {
regmap_reg_range(USERINTERRUPTMASK(0), INTERRUPTCLEAR(1)),
regmap_reg_range(USERINTERRUPTENABLE(0), USERINTERRUPTCLEAR(1)),
};
static const struct regmap_access_table dc_ic_regmap_write_table = {
.yes_ranges = dc_ic_regmap_write_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_ic_regmap_write_ranges),
};
static const struct regmap_range dc_ic_regmap_read_ranges[] = {
regmap_reg_range(USERINTERRUPTMASK(0), INTERRUPTENABLE(1)),
regmap_reg_range(INTERRUPTSTATUS(0), INTERRUPTSTATUS(1)),
regmap_reg_range(USERINTERRUPTENABLE(0), USERINTERRUPTENABLE(1)),
regmap_reg_range(USERINTERRUPTSTATUS(0), USERINTERRUPTSTATUS(1)),
};
static const struct regmap_access_table dc_ic_regmap_read_table = {
.yes_ranges = dc_ic_regmap_read_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_ic_regmap_read_ranges),
};
static const struct regmap_range dc_ic_regmap_volatile_ranges[] = {
regmap_reg_range(INTERRUPTPRESET(0), INTERRUPTCLEAR(1)),
regmap_reg_range(USERINTERRUPTPRESET(0), USERINTERRUPTCLEAR(1)),
};
static const struct regmap_access_table dc_ic_regmap_volatile_table = {
.yes_ranges = dc_ic_regmap_volatile_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_ic_regmap_volatile_ranges),
};
static const struct regmap_config dc_ic_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_ic_regmap_write_table,
.rd_table = &dc_ic_regmap_read_table,
.volatile_table = &dc_ic_regmap_volatile_table,
.max_register = USERINTERRUPTSTATUS(1),
};
static void dc_ic_irq_handler(struct irq_desc *desc)
{
struct dc_ic_entry *entry = irq_desc_get_handler_data(desc);
struct dc_ic_data *data = entry->data;
unsigned int status, enable;
unsigned int virq;
chained_irq_enter(irq_desc_get_chip(desc), desc);
regmap_read(data->regs, USERINTERRUPTSTATUS(entry->irq / 32), &status);
regmap_read(data->regs, USERINTERRUPTENABLE(entry->irq / 32), &enable);
status &= enable;
if (status & BIT(entry->irq % 32)) {
virq = irq_find_mapping(data->domain, entry->irq);
if (virq)
generic_handle_irq(virq);
}
chained_irq_exit(irq_desc_get_chip(desc), desc);
}
static const unsigned long unused_irq[REG_NUM] = {0x00000000, 0xfffe0008};
static int dc_ic_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct irq_chip_generic *gc;
struct dc_ic_entry *entry;
struct irq_chip_type *ct;
struct dc_ic_data *data;
void __iomem *base;
int i, ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
entry = devm_kcalloc(dev, IRQ_COUNT, sizeof(*entry), GFP_KERNEL);
if (!entry)
return -ENOMEM;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base)) {
dev_err(dev, "failed to initialize reg\n");
return PTR_ERR(base);
}
data->regs = devm_regmap_init_mmio(dev, base, &dc_ic_regmap_config);
if (IS_ERR(data->regs))
return PTR_ERR(data->regs);
data->clk_axi = devm_clk_get(dev, NULL);
if (IS_ERR(data->clk_axi))
return dev_err_probe(dev, PTR_ERR(data->clk_axi),
"failed to get AXI clock\n");
for (i = 0; i < IRQ_COUNT; i++) {
/* skip the reserved IRQ */
if (i == IRQ_RESERVED)
continue;
ret = platform_get_irq(pdev, i);
if (ret < 0)
return ret;
}
dev_set_drvdata(dev, data);
ret = devm_pm_runtime_enable(dev);
if (ret)
return ret;
ret = pm_runtime_resume_and_get(dev);
if (ret < 0) {
dev_err(dev, "failed to get runtime PM sync: %d\n", ret);
return ret;
}
for (i = 0; i < REG_NUM; i++) {
/* mask and clear all interrupts */
regmap_write(data->regs, USERINTERRUPTENABLE(i), 0x0);
regmap_write(data->regs, INTERRUPTENABLE(i), 0x0);
regmap_write(data->regs, USERINTERRUPTCLEAR(i), 0xffffffff);
regmap_write(data->regs, INTERRUPTCLEAR(i), 0xffffffff);
/* set all interrupts to user mode */
regmap_write(data->regs, USERINTERRUPTMASK(i), 0xffffffff);
}
data->domain = irq_domain_add_linear(dev->of_node, IRQ_COUNT,
&irq_generic_chip_ops, data);
if (!data->domain) {
dev_err(dev, "failed to create IRQ domain\n");
pm_runtime_put(dev);
return -ENOMEM;
}
irq_domain_set_pm_device(data->domain, dev);
ret = irq_alloc_domain_generic_chips(data->domain, 32, 1, "DC",
handle_level_irq, 0, 0, 0);
if (ret) {
dev_err(dev, "failed to alloc generic IRQ chips: %d\n", ret);
irq_domain_remove(data->domain);
pm_runtime_put(dev);
return ret;
}
for (i = 0; i < IRQ_COUNT; i += 32) {
gc = irq_get_domain_generic_chip(data->domain, i);
gc->reg_base = base;
gc->unused = unused_irq[i / 32];
ct = gc->chip_types;
ct->chip.irq_ack = irq_gc_ack_set_bit;
ct->chip.irq_mask = irq_gc_mask_clr_bit;
ct->chip.irq_unmask = irq_gc_mask_set_bit;
ct->regs.ack = USERINTERRUPTCLEAR(i / 32);
ct->regs.mask = USERINTERRUPTENABLE(i / 32);
}
for (i = 0; i < IRQ_COUNT; i++) {
/* skip the reserved IRQ */
if (i == IRQ_RESERVED)
continue;
data->irq[i] = irq_of_parse_and_map(dev->of_node, i);
entry[i].data = data;
entry[i].irq = i;
irq_set_chained_handler_and_data(data->irq[i],
dc_ic_irq_handler, &entry[i]);
}
return 0;
}
static void dc_ic_remove(struct platform_device *pdev)
{
struct dc_ic_data *data = dev_get_drvdata(&pdev->dev);
int i;
for (i = 0; i < IRQ_COUNT; i++) {
if (i == IRQ_RESERVED)
continue;
irq_set_chained_handler_and_data(data->irq[i], NULL, NULL);
}
irq_domain_remove(data->domain);
pm_runtime_put_sync(&pdev->dev);
}
static int dc_ic_runtime_suspend(struct device *dev)
{
struct dc_ic_data *data = dev_get_drvdata(dev);
clk_disable_unprepare(data->clk_axi);
return 0;
}
static int dc_ic_runtime_resume(struct device *dev)
{
struct dc_ic_data *data = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(data->clk_axi);
if (ret)
dev_err(dev, "failed to enable AXI clock: %d\n", ret);
return ret;
}
static const struct dev_pm_ops dc_ic_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
RUNTIME_PM_OPS(dc_ic_runtime_suspend, dc_ic_runtime_resume, NULL)
};
static const struct of_device_id dc_ic_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-dc-intc", },
{ /* sentinel */ }
};
struct platform_driver dc_ic_driver = {
.probe = dc_ic_probe,
.remove = dc_ic_remove,
.driver = {
.name = "imx8-dc-intc",
.suppress_bind_attrs = true,
.of_match_table = dc_ic_dt_ids,
.pm = pm_sleep_ptr(&dc_ic_pm_ops),
},
};
+137
View File
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/of.h>
#include <linux/of_graph.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_device.h>
#include <drm/drm_encoder.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_mode_config.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_vblank.h>
#include "dc-de.h"
#include "dc-drv.h"
#include "dc-kms.h"
static const struct drm_mode_config_funcs dc_drm_mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm,
int crtc_index)
{
struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
struct drm_device *drm = &dc_drm->base;
struct drm_crtc *crtc = &dc_crtc->base;
struct drm_connector *connector;
struct device *dev = drm->dev;
struct drm_encoder *encoder;
struct drm_bridge *bridge;
int ret;
bridge = devm_drm_of_get_bridge(dev, dc_crtc->de->tc->dev->of_node,
0, 0);
if (IS_ERR(bridge)) {
ret = PTR_ERR(bridge);
if (ret == -ENODEV)
return 0;
return dev_err_probe(dev, ret,
"failed to find bridge for CRTC%u\n",
crtc->index);
}
encoder = &dc_drm->encoder[crtc_index];
ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
if (ret) {
dev_err(dev, "failed to initialize encoder for CRTC%u: %d\n",
crtc->index, ret);
return ret;
}
encoder->possible_crtcs = drm_crtc_mask(crtc);
ret = drm_bridge_attach(encoder, bridge, NULL,
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
dev_err(dev,
"failed to attach bridge to encoder for CRTC%u: %d\n",
crtc->index, ret);
return ret;
}
connector = drm_bridge_connector_init(drm, encoder);
if (IS_ERR(connector)) {
ret = PTR_ERR(connector);
dev_err(dev, "failed to init bridge connector for CRTC%u: %d\n",
crtc->index, ret);
return ret;
}
return 0;
}
int dc_kms_init(struct dc_drm_device *dc_drm)
{
struct drm_device *drm = &dc_drm->base;
int ret, i;
ret = drmm_mode_config_init(drm);
if (ret)
return ret;
drm->mode_config.min_width = 60;
drm->mode_config.min_height = 60;
drm->mode_config.max_width = 8192;
drm->mode_config.max_height = 8192;
drm->mode_config.funcs = &dc_drm_mode_config_funcs;
drm->vblank_disable_immediate = true;
drm->max_vblank_count = DC_FRAMEGEN_MAX_FRAME_INDEX;
for (i = 0; i < DC_DISPLAYS; i++) {
ret = dc_crtc_init(dc_drm, i);
if (ret)
return ret;
ret = dc_kms_init_encoder_per_crtc(dc_drm, i);
if (ret)
return ret;
}
for (i = 0; i < DC_DISPLAYS; i++) {
ret = dc_crtc_post_init(dc_drm, i);
if (ret)
return ret;
}
ret = drm_vblank_init(drm, DC_DISPLAYS);
if (ret) {
dev_err(drm->dev, "failed to init vblank support: %d\n", ret);
return ret;
}
drm_mode_config_reset(drm);
drm_kms_helper_poll_init(drm);
return 0;
}
void dc_kms_uninit(struct dc_drm_device *dc_drm)
{
drm_kms_helper_poll_fini(&dc_drm->base);
}
+131
View File
@@ -0,0 +1,131 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2024 NXP
*/
#ifndef __DC_KMS_H__
#define __DC_KMS_H__
#include <linux/completion.h>
#include <drm/drm_crtc.h>
#include <drm/drm_plane.h>
#include <drm/drm_vblank.h>
#include "dc-de.h"
#include "dc-fu.h"
#include "dc-pe.h"
#define DC_CRTC_IRQS 5
struct dc_crtc_irq {
struct dc_crtc *dc_crtc;
unsigned int irq;
};
/**
* struct dc_crtc - DC specific drm_crtc
*
* Each display controller contains one content stream and one safety stream.
* In general, the two streams have the same functionality. One stream is
* overlaid on the other by @fg. This driver chooses to generate black constant
* color from the content stream as background color, build plane(s) on the
* content stream by using layerblend(s) and always generate a constant color
* from the safety stream. Note that due to the decoupled timing, the safety
* stream still works to show the constant color properly even when the content
* stream has completely hung up due to mal-function of this driver.
*/
struct dc_crtc {
/** @base: base drm_crtc structure */
struct drm_crtc base;
/** @de: display engine */
struct dc_de *de;
/** @cf_cont: content stream constframe */
struct dc_cf *cf_cont;
/** @cf_safe: safety stream constframe */
struct dc_cf *cf_safe;
/** @ed_cont: content stream extdst */
struct dc_ed *ed_cont;
/** @ed_safe: safety stream extdst */
struct dc_ed *ed_safe;
/** @fg: framegen */
struct dc_fg *fg;
/**
* @irq_dec_framecomplete:
*
* display engine configuration frame complete interrupt
*/
unsigned int irq_dec_framecomplete;
/**
* @irq_dec_seqcomplete:
*
* display engine configuration sequence complete interrupt
*/
unsigned int irq_dec_seqcomplete;
/**
* @irq_dec_shdload:
*
* display engine configuration shadow load interrupt
*/
unsigned int irq_dec_shdload;
/**
* @irq_ed_cont_shdload:
*
* content stream extdst shadow load interrupt
*/
unsigned int irq_ed_cont_shdload;
/**
* @irq_ed_safe_shdload:
*
* safety stream extdst shadow load interrupt
*/
unsigned int irq_ed_safe_shdload;
/**
* @dec_seqcomplete_done:
*
* display engine configuration sequence completion
*/
struct completion dec_seqcomplete_done;
/**
* @dec_shdload_done:
*
* display engine configuration shadow load completion
*/
struct completion dec_shdload_done;
/**
* @ed_cont_shdload_done:
*
* content stream extdst shadow load completion
*/
struct completion ed_cont_shdload_done;
/**
* @ed_safe_shdload_done:
*
* safety stream extdst shadow load completion
*/
struct completion ed_safe_shdload_done;
/** @event: cached pending vblank event */
struct drm_pending_vblank_event *event;
/** @irqs: interrupt list */
struct dc_crtc_irq irqs[DC_CRTC_IRQS];
};
/**
* struct dc_plane - DC specific drm_plane
*
* Build a plane on content stream with a fetchunit and a layerblend.
*/
struct dc_plane {
/** @base: base drm_plane structure */
struct drm_plane base;
/** @fu: fetchunit */
struct dc_fu *fu;
/** @cf: content stream constframe */
struct dc_cf *cf;
/** @lb: layerblend */
struct dc_lb *lb;
/** @ed: content stream extdst */
struct dc_ed *ed;
};
#endif /* __DC_KMS_H__ */
+324
View File
@@ -0,0 +1,324 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/component.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <drm/drm_blend.h>
#include "dc-drv.h"
#include "dc-pe.h"
#define PIXENGCFG_DYNAMIC 0x8
#define PIXENGCFG_DYNAMIC_SEC_SEL_MASK GENMASK(13, 8)
#define PIXENGCFG_DYNAMIC_SEC_SEL(x) \
FIELD_PREP(PIXENGCFG_DYNAMIC_SEC_SEL_MASK, (x))
#define PIXENGCFG_DYNAMIC_PRIM_SEL_MASK GENMASK(5, 0)
#define PIXENGCFG_DYNAMIC_PRIM_SEL(x) \
FIELD_PREP(PIXENGCFG_DYNAMIC_PRIM_SEL_MASK, (x))
#define STATICCONTROL 0x8
#define SHDTOKSEL_MASK GENMASK(4, 3)
#define SHDTOKSEL(x) FIELD_PREP(SHDTOKSEL_MASK, (x))
#define SHDLDSEL_MASK GENMASK(2, 1)
#define SHDLDSEL(x) FIELD_PREP(SHDLDSEL_MASK, (x))
#define CONTROL 0xc
#define CTRL_MODE_MASK BIT(0)
#define CTRL_MODE(x) FIELD_PREP(CTRL_MODE_MASK, (x))
#define BLENDCONTROL 0x10
#define ALPHA_MASK GENMASK(23, 16)
#define ALPHA(x) FIELD_PREP(ALPHA_MASK, (x))
#define SEC_A_BLD_FUNC_MASK GENMASK(14, 12)
#define SEC_A_BLD_FUNC(x) \
FIELD_PREP(SEC_A_BLD_FUNC_MASK, (x))
#define PRIM_A_BLD_FUNC_MASK GENMASK(10, 8)
#define PRIM_A_BLD_FUNC(x) \
FIELD_PREP(PRIM_A_BLD_FUNC_MASK, (x))
#define SEC_C_BLD_FUNC_MASK GENMASK(6, 4)
#define SEC_C_BLD_FUNC(x) \
FIELD_PREP(SEC_C_BLD_FUNC_MASK, (x))
#define PRIM_C_BLD_FUNC_MASK GENMASK(2, 0)
#define PRIM_C_BLD_FUNC(x) \
FIELD_PREP(PRIM_C_BLD_FUNC_MASK, (x))
#define POSITION 0x14
#define YPOS_MASK GENMASK(31, 16)
#define YPOS(x) FIELD_PREP(YPOS_MASK, (x))
#define XPOS_MASK GENMASK(15, 0)
#define XPOS(x) FIELD_PREP(XPOS_MASK, (x))
enum dc_lb_blend_func {
DC_LAYERBLEND_BLEND_ZERO,
DC_LAYERBLEND_BLEND_ONE,
DC_LAYERBLEND_BLEND_PRIM_ALPHA,
DC_LAYERBLEND_BLEND_ONE_MINUS_PRIM_ALPHA,
DC_LAYERBLEND_BLEND_SEC_ALPHA,
DC_LAYERBLEND_BLEND_ONE_MINUS_SEC_ALPHA,
DC_LAYERBLEND_BLEND_CONST_ALPHA,
DC_LAYERBLEND_BLEND_ONE_MINUS_CONST_ALPHA,
};
enum dc_lb_shadow_sel {
BOTH = 0x2,
};
static const struct dc_subdev_info dc_lb_info[] = {
{ .reg_start = 0x56180ba0, .id = 0, },
{ .reg_start = 0x56180bc0, .id = 1, },
{ .reg_start = 0x56180be0, .id = 2, },
{ .reg_start = 0x56180c00, .id = 3, },
};
static const struct regmap_range dc_lb_pec_regmap_access_ranges[] = {
regmap_reg_range(PIXENGCFG_DYNAMIC, PIXENGCFG_DYNAMIC),
};
static const struct regmap_access_table dc_lb_pec_regmap_access_table = {
.yes_ranges = dc_lb_pec_regmap_access_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_lb_pec_regmap_access_ranges),
};
static const struct regmap_config dc_lb_pec_regmap_config = {
.name = "pec",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_lb_pec_regmap_access_table,
.rd_table = &dc_lb_pec_regmap_access_table,
.max_register = PIXENGCFG_DYNAMIC,
};
static const struct regmap_range dc_lb_regmap_ranges[] = {
regmap_reg_range(STATICCONTROL, POSITION),
};
static const struct regmap_access_table dc_lb_regmap_access_table = {
.yes_ranges = dc_lb_regmap_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_lb_regmap_ranges),
};
static const struct regmap_config dc_lb_cfg_regmap_config = {
.name = "cfg",
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_lb_regmap_access_table,
.rd_table = &dc_lb_regmap_access_table,
.max_register = POSITION,
};
static const enum dc_link_id prim_sels[] = {
/* common options */
LINK_ID_NONE,
LINK_ID_CONSTFRAME0,
LINK_ID_CONSTFRAME1,
LINK_ID_CONSTFRAME4,
LINK_ID_CONSTFRAME5,
/*
* special options:
* layerblend(n) has n special options,
* from layerblend0 to layerblend(n - 1), e.g.,
* layerblend3 has 3 special options -
* layerblend0/1/2.
*/
LINK_ID_LAYERBLEND0,
LINK_ID_LAYERBLEND1,
LINK_ID_LAYERBLEND2,
LINK_ID_LAYERBLEND3,
};
static const enum dc_link_id sec_sels[] = {
LINK_ID_NONE,
LINK_ID_FETCHWARP2,
LINK_ID_FETCHLAYER0,
};
enum dc_link_id dc_lb_get_link_id(struct dc_lb *lb)
{
return lb->link;
}
void dc_lb_pec_dynamic_prim_sel(struct dc_lb *lb, enum dc_link_id prim)
{
int fixed_sels_num = ARRAY_SIZE(prim_sels) - 4;
int i;
for (i = 0; i < fixed_sels_num + lb->id; i++) {
if (prim_sels[i] == prim) {
regmap_write_bits(lb->reg_pec, PIXENGCFG_DYNAMIC,
PIXENGCFG_DYNAMIC_PRIM_SEL_MASK,
PIXENGCFG_DYNAMIC_PRIM_SEL(prim));
return;
}
}
dev_warn(lb->dev, "invalid primary input selection:%d\n", prim);
}
void dc_lb_pec_dynamic_sec_sel(struct dc_lb *lb, enum dc_link_id sec)
{
int i;
for (i = 0; i < ARRAY_SIZE(sec_sels); i++) {
if (sec_sels[i] == sec) {
regmap_write_bits(lb->reg_pec, PIXENGCFG_DYNAMIC,
PIXENGCFG_DYNAMIC_SEC_SEL_MASK,
PIXENGCFG_DYNAMIC_SEC_SEL(sec));
return;
}
}
dev_warn(lb->dev, "invalid secondary input selection:%d\n", sec);
}
void dc_lb_pec_clken(struct dc_lb *lb, enum dc_pec_clken clken)
{
regmap_write_bits(lb->reg_pec, PIXENGCFG_DYNAMIC, CLKEN_MASK,
CLKEN(clken));
}
static inline void dc_lb_enable_shden(struct dc_lb *lb)
{
regmap_write_bits(lb->reg_cfg, STATICCONTROL, SHDEN, SHDEN);
}
static inline void dc_lb_shdtoksel(struct dc_lb *lb, enum dc_lb_shadow_sel sel)
{
regmap_write_bits(lb->reg_cfg, STATICCONTROL, SHDTOKSEL_MASK,
SHDTOKSEL(sel));
}
static inline void dc_lb_shdldsel(struct dc_lb *lb, enum dc_lb_shadow_sel sel)
{
regmap_write_bits(lb->reg_cfg, STATICCONTROL, SHDLDSEL_MASK,
SHDLDSEL(sel));
}
void dc_lb_mode(struct dc_lb *lb, enum dc_lb_mode mode)
{
regmap_write_bits(lb->reg_cfg, CONTROL, CTRL_MODE_MASK, mode);
}
static inline void dc_lb_blendcontrol(struct dc_lb *lb)
{
u32 val = PRIM_A_BLD_FUNC(DC_LAYERBLEND_BLEND_ZERO) |
SEC_A_BLD_FUNC(DC_LAYERBLEND_BLEND_ZERO) |
PRIM_C_BLD_FUNC(DC_LAYERBLEND_BLEND_ZERO) |
SEC_C_BLD_FUNC(DC_LAYERBLEND_BLEND_CONST_ALPHA) |
ALPHA(DRM_BLEND_ALPHA_OPAQUE >> 8);
regmap_write(lb->reg_cfg, BLENDCONTROL, val);
}
void dc_lb_position(struct dc_lb *lb, int x, int y)
{
regmap_write(lb->reg_cfg, POSITION, XPOS(x) | YPOS(y));
}
int dc_lb_get_id(struct dc_lb *lb)
{
return lb->id;
}
void dc_lb_init(struct dc_lb *lb)
{
dc_lb_pec_dynamic_prim_sel(lb, LINK_ID_NONE);
dc_lb_pec_dynamic_sec_sel(lb, LINK_ID_NONE);
dc_lb_pec_clken(lb, CLKEN_DISABLE);
dc_lb_shdldsel(lb, BOTH);
dc_lb_shdtoksel(lb, BOTH);
dc_lb_blendcontrol(lb);
dc_lb_enable_shden(lb);
}
static int dc_lb_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct dc_drm_device *dc_drm = data;
struct resource *res_pec;
void __iomem *base_pec;
void __iomem *base_cfg;
struct dc_lb *lb;
lb = devm_kzalloc(dev, sizeof(*lb), GFP_KERNEL);
if (!lb)
return -ENOMEM;
base_pec = devm_platform_get_and_ioremap_resource(pdev, 0, &res_pec);
if (IS_ERR(base_pec))
return PTR_ERR(base_pec);
base_cfg = devm_platform_ioremap_resource_byname(pdev, "cfg");
if (IS_ERR(base_cfg))
return PTR_ERR(base_cfg);
lb->reg_pec = devm_regmap_init_mmio(dev, base_pec,
&dc_lb_pec_regmap_config);
if (IS_ERR(lb->reg_pec))
return PTR_ERR(lb->reg_pec);
lb->reg_cfg = devm_regmap_init_mmio(dev, base_cfg,
&dc_lb_cfg_regmap_config);
if (IS_ERR(lb->reg_cfg))
return PTR_ERR(lb->reg_cfg);
lb->id = dc_subdev_get_id(dc_lb_info, ARRAY_SIZE(dc_lb_info), res_pec);
if (lb->id < 0) {
dev_err(dev, "failed to get instance number: %d\n", lb->id);
return lb->id;
}
lb->dev = dev;
lb->link = LINK_ID_LAYERBLEND0 + lb->id;
dc_drm->lb[lb->id] = lb;
return 0;
}
static const struct component_ops dc_lb_ops = {
.bind = dc_lb_bind,
};
static int dc_lb_probe(struct platform_device *pdev)
{
int ret;
ret = component_add(&pdev->dev, &dc_lb_ops);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to add component\n");
return 0;
}
static void dc_lb_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dc_lb_ops);
}
static const struct of_device_id dc_lb_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-dc-layerblend" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dc_lb_dt_ids);
struct platform_driver dc_lb_driver = {
.probe = dc_lb_probe,
.remove = dc_lb_remove,
.driver = {
.name = "imx8-dc-layerblend",
.suppress_bind_attrs = true,
.of_match_table = dc_lb_dt_ids,
},
};
+157
View File
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include "dc-drv.h"
#include "dc-fu.h"
#include "dc-pe.h"
static int dc_pe_bind(struct device *dev, struct device *master, void *data)
{
struct dc_drm_device *dc_drm = data;
struct dc_pe *pe;
int ret;
pe = devm_kzalloc(dev, sizeof(*pe), GFP_KERNEL);
if (!pe)
return -ENOMEM;
pe->clk_axi = devm_clk_get(dev, NULL);
if (IS_ERR(pe->clk_axi))
return dev_err_probe(dev, PTR_ERR(pe->clk_axi),
"failed to get AXI clock\n");
pe->dev = dev;
dev_set_drvdata(dev, pe);
ret = devm_pm_runtime_enable(dev);
if (ret)
return ret;
dc_drm->pe = pe;
return 0;
}
/*
* It's possible to get the child device pointers from the child component
* bind callbacks, but it depends on the component helper behavior to bind
* the pixel engine component first. To avoid the dependency, post bind to
* get the pointers from dc_drm in a safe manner.
*/
void dc_pe_post_bind(struct dc_drm_device *dc_drm)
{
struct dc_pe *pe = dc_drm->pe;
int i;
for (i = 0; i < DC_DISPLAYS; i++) {
pe->cf_safe[i] = dc_drm->cf_safe[i];
pe->cf_cont[i] = dc_drm->cf_cont[i];
pe->ed_safe[i] = dc_drm->ed_safe[i];
pe->ed_cont[i] = dc_drm->ed_cont[i];
}
for (i = 0; i < DC_DISP_FU_CNT; i++)
pe->fu_disp[i] = dc_drm->fu_disp[i];
for (i = 0; i < DC_LB_CNT; i++)
pe->lb[i] = dc_drm->lb[i];
}
static const struct component_ops dc_pe_ops = {
.bind = dc_pe_bind,
};
static int dc_pe_probe(struct platform_device *pdev)
{
int ret;
ret = devm_of_platform_populate(&pdev->dev);
if (ret < 0)
return ret;
ret = component_add(&pdev->dev, &dc_pe_ops);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to add component\n");
return 0;
}
static void dc_pe_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dc_pe_ops);
}
static int dc_pe_runtime_suspend(struct device *dev)
{
struct dc_pe *pe = dev_get_drvdata(dev);
clk_disable_unprepare(pe->clk_axi);
return 0;
}
static int dc_pe_runtime_resume(struct device *dev)
{
struct dc_pe *pe = dev_get_drvdata(dev);
int i, ret;
ret = clk_prepare_enable(pe->clk_axi);
if (ret) {
dev_err(dev, "failed to enable AXI clock: %d\n", ret);
return ret;
}
for (i = 0; i < ARRAY_SIZE(pe->cf_safe); i++)
dc_cf_init(pe->cf_safe[i]);
for (i = 0; i < ARRAY_SIZE(pe->cf_cont); i++)
dc_cf_init(pe->cf_cont[i]);
for (i = 0; i < ARRAY_SIZE(pe->ed_safe); i++)
dc_ed_init(pe->ed_safe[i]);
for (i = 0; i < ARRAY_SIZE(pe->ed_cont); i++)
dc_ed_init(pe->ed_cont[i]);
for (i = 0; i < ARRAY_SIZE(pe->fu_disp); i++)
pe->fu_disp[i]->ops.init(pe->fu_disp[i]);
for (i = 0; i < ARRAY_SIZE(pe->lb); i++)
dc_lb_init(pe->lb[i]);
return 0;
}
static const struct dev_pm_ops dc_pe_pm_ops = {
RUNTIME_PM_OPS(dc_pe_runtime_suspend, dc_pe_runtime_resume, NULL)
};
static const struct of_device_id dc_pe_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-dc-pixel-engine", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dc_pe_dt_ids);
struct platform_driver dc_pe_driver = {
.probe = dc_pe_probe,
.remove = dc_pe_remove,
.driver = {
.name = "imx8-dc-pixel-engine",
.suppress_bind_attrs = true,
.of_match_table = dc_pe_dt_ids,
.pm = pm_sleep_ptr(&dc_pe_pm_ops),
},
};
+101
View File
@@ -0,0 +1,101 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2024 NXP
*/
#ifndef __DC_PIXEL_ENGINE_H__
#define __DC_PIXEL_ENGINE_H__
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include "dc-de.h"
#define SHDEN BIT(0)
#define CLKEN_MASK_SHIFT 24
#define CLKEN_MASK (0x3 << CLKEN_MASK_SHIFT)
#define CLKEN(n) ((n) << CLKEN_MASK_SHIFT)
#define DC_DISP_FU_CNT 2
#define DC_LB_CNT 4
enum dc_link_id {
LINK_ID_NONE = 0x00,
LINK_ID_CONSTFRAME0 = 0x0c,
LINK_ID_CONSTFRAME4 = 0x0e,
LINK_ID_CONSTFRAME1 = 0x10,
LINK_ID_CONSTFRAME5 = 0x12,
LINK_ID_FETCHWARP2 = 0x14,
LINK_ID_FETCHLAYER0 = 0x1a,
LINK_ID_LAYERBLEND0 = 0x21,
LINK_ID_LAYERBLEND1 = 0x22,
LINK_ID_LAYERBLEND2 = 0x23,
LINK_ID_LAYERBLEND3 = 0x24,
};
enum dc_lb_mode {
LB_NEUTRAL, /* Output is same as primary input. */
LB_BLEND,
};
enum dc_pec_clken {
CLKEN_DISABLE,
CLKEN_AUTOMATIC,
};
struct dc_cf {
struct regmap *reg_cfg;
enum dc_link_id link;
};
struct dc_ed {
struct device *dev;
struct regmap *reg_pec;
struct regmap *reg_cfg;
int irq_shdload;
};
struct dc_lb {
struct device *dev;
struct regmap *reg_pec;
struct regmap *reg_cfg;
int id;
enum dc_link_id link;
};
struct dc_pe {
struct device *dev;
struct clk *clk_axi;
struct dc_cf *cf_safe[DC_DISPLAYS];
struct dc_cf *cf_cont[DC_DISPLAYS];
struct dc_ed *ed_safe[DC_DISPLAYS];
struct dc_ed *ed_cont[DC_DISPLAYS];
struct dc_fu *fu_disp[DC_DISP_FU_CNT];
struct dc_lb *lb[DC_LB_CNT];
};
/* Constant Frame Unit */
enum dc_link_id dc_cf_get_link_id(struct dc_cf *cf);
void dc_cf_framedimensions(struct dc_cf *cf, unsigned int w, unsigned int h);
void dc_cf_constantcolor_black(struct dc_cf *cf);
void dc_cf_constantcolor_blue(struct dc_cf *cf);
void dc_cf_init(struct dc_cf *cf);
/* External Destination Unit */
void dc_ed_pec_src_sel(struct dc_ed *ed, enum dc_link_id src);
void dc_ed_pec_sync_trigger(struct dc_ed *ed);
void dc_ed_init(struct dc_ed *ed);
/* Layer Blend Unit */
enum dc_link_id dc_lb_get_link_id(struct dc_lb *lb);
void dc_lb_pec_dynamic_prim_sel(struct dc_lb *lb, enum dc_link_id prim);
void dc_lb_pec_dynamic_sec_sel(struct dc_lb *lb, enum dc_link_id sec);
void dc_lb_pec_clken(struct dc_lb *lb, enum dc_pec_clken clken);
void dc_lb_mode(struct dc_lb *lb, enum dc_lb_mode mode);
void dc_lb_position(struct dc_lb *lb, int x, int y);
int dc_lb_get_id(struct dc_lb *lb);
void dc_lb_init(struct dc_lb *lb);
#endif /* __DC_PIXEL_ENGINE_H__ */
@@ -0,0 +1,224 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/container_of.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_print.h>
#include "dc-drv.h"
#include "dc-fu.h"
#include "dc-kms.h"
#define DC_PLANE_MAX_PITCH 0x10000
#define DC_PLANE_MAX_PIX_CNT 8192
#define dc_plane_dbg(plane, fmt, ...) \
do { \
struct drm_plane *_plane = (plane); \
drm_dbg_kms(_plane->dev, "[PLANE:%d:%s] " fmt, \
_plane->base.id, _plane->name, ##__VA_ARGS__); \
} while (0)
static const uint32_t dc_plane_formats[] = {
DRM_FORMAT_XRGB8888,
};
static const struct drm_plane_funcs dc_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
static inline struct dc_plane *to_dc_plane(struct drm_plane *plane)
{
return container_of(plane, struct dc_plane, base);
}
static int dc_plane_check_max_source_resolution(struct drm_plane_state *state)
{
int src_h = drm_rect_height(&state->src) >> 16;
int src_w = drm_rect_width(&state->src) >> 16;
if (src_w > DC_PLANE_MAX_PIX_CNT || src_h > DC_PLANE_MAX_PIX_CNT) {
dc_plane_dbg(state->plane, "invalid source resolution\n");
return -EINVAL;
}
return 0;
}
static int dc_plane_check_fb(struct drm_plane_state *state)
{
struct drm_framebuffer *fb = state->fb;
dma_addr_t baseaddr = drm_fb_dma_get_gem_addr(fb, state, 0);
/* base address alignment */
if (baseaddr & 0x3) {
dc_plane_dbg(state->plane, "fb bad baddr alignment\n");
return -EINVAL;
}
/* pitches[0] range */
if (fb->pitches[0] > DC_PLANE_MAX_PITCH) {
dc_plane_dbg(state->plane, "fb pitches[0] is out of range\n");
return -EINVAL;
}
/* pitches[0] alignment */
if (fb->pitches[0] & 0x3) {
dc_plane_dbg(state->plane, "fb bad pitches[0] alignment\n");
return -EINVAL;
}
return 0;
}
static int
dc_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *state)
{
struct drm_plane_state *plane_state =
drm_atomic_get_new_plane_state(state, plane);
struct drm_crtc_state *crtc_state;
int ret;
/* ok to disable */
if (!plane_state->fb)
return 0;
if (!plane_state->crtc) {
dc_plane_dbg(plane, "no CRTC in plane state\n");
return -EINVAL;
}
crtc_state =
drm_atomic_get_new_crtc_state(state, plane_state->crtc);
if (WARN_ON(!crtc_state))
return -EINVAL;
ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
DRM_PLANE_NO_SCALING,
DRM_PLANE_NO_SCALING,
true, false);
if (ret) {
dc_plane_dbg(plane, "failed to check plane state: %d\n", ret);
return ret;
}
ret = dc_plane_check_max_source_resolution(plane_state);
if (ret)
return ret;
return dc_plane_check_fb(plane_state);
}
static void
dc_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_commit *state)
{
struct drm_plane_state *new_state =
drm_atomic_get_new_plane_state(state, plane);
struct dc_plane *dplane = to_dc_plane(plane);
struct drm_framebuffer *fb = new_state->fb;
const struct dc_fu_ops *fu_ops;
struct dc_lb *lb = dplane->lb;
struct dc_fu *fu = dplane->fu;
dma_addr_t baseaddr;
int src_w, src_h;
int idx;
if (!drm_dev_enter(plane->dev, &idx))
return;
src_w = drm_rect_width(&new_state->src) >> 16;
src_h = drm_rect_height(&new_state->src) >> 16;
baseaddr = drm_fb_dma_get_gem_addr(fb, new_state, 0);
fu_ops = dc_fu_get_ops(dplane->fu);
fu_ops->set_layerblend(fu, lb);
fu_ops->set_burstlength(fu, baseaddr);
fu_ops->set_src_stride(fu, DC_FETCHUNIT_FRAC0, fb->pitches[0]);
fu_ops->set_src_buf_dimensions(fu, DC_FETCHUNIT_FRAC0, src_w, src_h);
fu_ops->set_fmt(fu, DC_FETCHUNIT_FRAC0, fb->format);
fu_ops->set_framedimensions(fu, src_w, src_h);
fu_ops->set_baseaddress(fu, DC_FETCHUNIT_FRAC0, baseaddr);
fu_ops->enable_src_buf(fu, DC_FETCHUNIT_FRAC0);
dc_plane_dbg(plane, "uses %s\n", fu_ops->get_name(fu));
dc_lb_pec_dynamic_prim_sel(lb, dc_cf_get_link_id(dplane->cf));
dc_lb_pec_dynamic_sec_sel(lb, fu_ops->get_link_id(fu));
dc_lb_mode(lb, LB_BLEND);
dc_lb_position(lb, new_state->dst.x1, new_state->dst.y1);
dc_lb_pec_clken(lb, CLKEN_AUTOMATIC);
dc_plane_dbg(plane, "uses LayerBlend%d\n", dc_lb_get_id(lb));
/* set ExtDst's source to LayerBlend */
dc_ed_pec_src_sel(dplane->ed, dc_lb_get_link_id(lb));
drm_dev_exit(idx);
}
static void dc_plane_atomic_disable(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct dc_plane *dplane = to_dc_plane(plane);
const struct dc_fu_ops *fu_ops;
int idx;
if (!drm_dev_enter(plane->dev, &idx))
return;
/* disable fetchunit in shadow */
fu_ops = dc_fu_get_ops(dplane->fu);
fu_ops->disable_src_buf(dplane->fu, DC_FETCHUNIT_FRAC0);
/* set ExtDst's source to ConstFrame */
dc_ed_pec_src_sel(dplane->ed, dc_cf_get_link_id(dplane->cf));
drm_dev_exit(idx);
}
static const struct drm_plane_helper_funcs dc_plane_helper_funcs = {
.atomic_check = dc_plane_atomic_check,
.atomic_update = dc_plane_atomic_update,
.atomic_disable = dc_plane_atomic_disable,
};
int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane)
{
struct drm_plane *plane = &dc_plane->base;
int ret;
ret = drm_universal_plane_init(&dc_drm->base, plane, 0, &dc_plane_funcs,
dc_plane_formats,
ARRAY_SIZE(dc_plane_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
if (ret)
return ret;
drm_plane_helper_add(plane, &dc_plane_helper_funcs);
dc_plane->fu = dc_drm->pe->fu_disp[plane->index];
dc_plane->cf = dc_drm->pe->cf_cont[plane->index];
dc_plane->lb = dc_drm->pe->lb[plane->index];
dc_plane->ed = dc_drm->pe->ed_cont[plane->index];
return 0;
}
+140
View File
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2024 NXP
*/
#include <linux/component.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include "dc-drv.h"
#include "dc-de.h"
#define TCON_CTRL 0x410
#define CTRL_RST_VAL 0x01401408
/* red: MAPBIT 29-20, green: MAPBIT 19-10, blue: MAPBIT 9-0 */
#define MAPBIT3_0 0x418
#define MAPBIT7_4 0x41c
#define MAPBIT11_8 0x420
#define MAPBIT15_12 0x424
#define MAPBIT19_16 0x428
#define MAPBIT23_20 0x42c
#define MAPBIT27_24 0x430
#define MAPBIT31_28 0x434
static const struct dc_subdev_info dc_tc_info[] = {
{ .reg_start = 0x5618c800, .id = 0, },
{ .reg_start = 0x5618e400, .id = 1, },
};
static const struct regmap_range dc_tc_regmap_ranges[] = {
regmap_reg_range(TCON_CTRL, TCON_CTRL),
regmap_reg_range(MAPBIT3_0, MAPBIT31_28),
};
static const struct regmap_access_table dc_tc_regmap_access_table = {
.yes_ranges = dc_tc_regmap_ranges,
.n_yes_ranges = ARRAY_SIZE(dc_tc_regmap_ranges),
};
static const struct regmap_config dc_tc_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
.wr_table = &dc_tc_regmap_access_table,
.rd_table = &dc_tc_regmap_access_table,
.max_register = MAPBIT31_28,
};
/*
* The pixels reach TCON are always in 30-bit BGR format.
* The first bridge always receives pixels in 30-bit RGB format.
* So, map the format to MEDIA_BUS_FMT_RGB101010_1X30.
*/
static const u32 dc_tc_mapbit[] = {
0x17161514, 0x1b1a1918, 0x0b0a1d1c, 0x0f0e0d0c,
0x13121110, 0x03020100, 0x07060504, 0x00000908,
};
void dc_tc_init(struct dc_tc *tc)
{
/* reset TCON_CTRL to POR default so that TCON works in bypass mode */
regmap_write(tc->reg, TCON_CTRL, CTRL_RST_VAL);
/* set format */
regmap_bulk_write(tc->reg, MAPBIT3_0, dc_tc_mapbit,
ARRAY_SIZE(dc_tc_mapbit));
}
static int dc_tc_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct dc_drm_device *dc_drm = data;
struct resource *res;
void __iomem *base;
struct dc_tc *tc;
int id;
tc = devm_kzalloc(dev, sizeof(*tc), GFP_KERNEL);
if (!tc)
return -ENOMEM;
base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(base))
return PTR_ERR(base);
tc->reg = devm_regmap_init_mmio(dev, base, &dc_tc_regmap_config);
if (IS_ERR(tc->reg))
return PTR_ERR(tc->reg);
id = dc_subdev_get_id(dc_tc_info, ARRAY_SIZE(dc_tc_info), res);
if (id < 0) {
dev_err(dev, "failed to get instance number: %d\n", id);
return id;
}
tc->dev = dev;
dc_drm->tc[id] = tc;
return 0;
}
static const struct component_ops dc_tc_ops = {
.bind = dc_tc_bind,
};
static int dc_tc_probe(struct platform_device *pdev)
{
int ret;
ret = component_add(&pdev->dev, &dc_tc_ops);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"failed to add component\n");
return 0;
}
static void dc_tc_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dc_tc_ops);
}
static const struct of_device_id dc_tc_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-dc-tcon" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dc_tc_dt_ids);
struct platform_driver dc_tc_driver = {
.probe = dc_tc_probe,
.remove = dc_tc_remove,
.driver = {
.name = "imx8-dc-tcon",
.suppress_bind_attrs = true,
.of_match_table = dc_tc_dt_ids,
},
};