| /* |
| * Copyright 2017 NXP |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or (at your |
| * option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * for more details. |
| */ |
| |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/platform_device.h> |
| #include <linux/types.h> |
| #include <video/dpu.h> |
| #include "dpu-prv.h" |
| |
| #define BASEADDRESS0 0x10 |
| #define SOURCEBUFFERATTRIBUTES0 0x14 |
| #define SOURCEBUFFERDIMENSION0 0x18 |
| #define COLORCOMPONENTBITS0 0x1C |
| #define COLORCOMPONENTSHIFT0 0x20 |
| #define LAYEROFFSET0 0x24 |
| #define CLIPWINDOWOFFSET0 0x28 |
| #define CLIPWINDOWDIMENSIONS0 0x2C |
| #define CONSTANTCOLOR0 0x30 |
| #define LAYERPROPERTY0 0x34 |
| #define FRAMEDIMENSIONS 0x38 |
| #define FRAMERESAMPLING 0x3C |
| #define CONTROL 0x40 |
| #define CONTROLTRIGGER 0x44 |
| #define START 0x48 |
| #define FETCHTYPE 0x4C |
| #define BURSTBUFFERPROPERTIES 0x50 |
| #define HIDDENSTATUS 0x54 |
| |
| struct dpu_fetcheco { |
| void __iomem *pec_base; |
| void __iomem *base; |
| struct mutex mutex; |
| int id; |
| bool inuse; |
| bool pin_off; |
| struct dpu_soc *dpu; |
| /* see DPU_PLANE_SRC_xxx */ |
| unsigned int stream_id; |
| }; |
| |
| static inline u32 dpu_pec_fe_read(struct dpu_fetcheco *fe, unsigned int offset) |
| { |
| return readl(fe->pec_base + offset); |
| } |
| |
| static inline void dpu_pec_fe_write(struct dpu_fetcheco *fe, u32 value, |
| unsigned int offset) |
| { |
| writel(value, fe->pec_base + offset); |
| } |
| |
| static inline u32 dpu_fe_read(struct dpu_fetcheco *fe, unsigned int offset) |
| { |
| return readl(fe->base + offset); |
| } |
| |
| static inline void dpu_fe_write(struct dpu_fetcheco *fe, u32 value, |
| unsigned int offset) |
| { |
| writel(value, fe->base + offset); |
| } |
| |
| void fetcheco_shden(struct dpu_fetcheco *fe, bool enable) |
| { |
| u32 val; |
| |
| mutex_lock(&fe->mutex); |
| val = dpu_fe_read(fe, STATICCONTROL); |
| if (enable) |
| val |= SHDEN; |
| else |
| val &= ~SHDEN; |
| dpu_fe_write(fe, val, STATICCONTROL); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_shden); |
| |
| void fetcheco_set_burstlength(struct dpu_fetcheco *fe, dma_addr_t baddr, |
| bool use_prefetch) |
| { |
| struct dpu_soc *dpu = fe->dpu; |
| unsigned int burst_size, burst_length; |
| u32 val; |
| |
| if (use_prefetch) { |
| /* |
| * address TKT343664: |
| * fetch unit base address has to align to burst size |
| */ |
| burst_size = 1 << (ffs(baddr) - 1); |
| burst_size = min(burst_size, 128U); |
| burst_length = burst_size / 8; |
| } else { |
| burst_length = 16; |
| } |
| |
| mutex_lock(&fe->mutex); |
| val = dpu_fe_read(fe, BURSTBUFFERMANAGEMENT); |
| val &= ~SETBURSTLENGTH_MASK; |
| val |= SETBURSTLENGTH(burst_length); |
| dpu_fe_write(fe, val, BURSTBUFFERMANAGEMENT); |
| mutex_unlock(&fe->mutex); |
| |
| dev_dbg(dpu->dev, "FetchEco%d burst length is %u\n", |
| fe->id, burst_length); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_set_burstlength); |
| |
| void fetcheco_baseaddress(struct dpu_fetcheco *fe, dma_addr_t paddr) |
| { |
| mutex_lock(&fe->mutex); |
| dpu_fe_write(fe, paddr, BASEADDRESS0); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_baseaddress); |
| |
| void fetcheco_source_bpp(struct dpu_fetcheco *fe, int bpp) |
| { |
| u32 val; |
| |
| mutex_lock(&fe->mutex); |
| val = dpu_fe_read(fe, SOURCEBUFFERATTRIBUTES0); |
| val &= ~0x3f0000; |
| val |= BITSPERPIXEL(bpp); |
| dpu_fe_write(fe, val, SOURCEBUFFERATTRIBUTES0); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_source_bpp); |
| |
| /* |
| * The arguments width and bpp are valid only when use_prefetch is true. |
| * Since the pixel format has to be NV12 or NV21 when use_prefetch is true, |
| * we assume width stands for how many UV we have in bytes for one line, |
| * while bpp should be 8bits for every U or V component. |
| */ |
| void fetcheco_source_stride(struct dpu_fetcheco *fe, unsigned int width, |
| int bpp, unsigned int stride, |
| dma_addr_t baddr, bool use_prefetch) |
| { |
| unsigned int burst_size; |
| u32 val; |
| |
| if (use_prefetch) { |
| /* |
| * address TKT343664: |
| * fetch unit base address has to align to burst size |
| */ |
| burst_size = 1 << (ffs(baddr) - 1); |
| burst_size = min(burst_size, 128U); |
| |
| stride = width * (bpp >> 3); |
| /* |
| * address TKT339017: |
| * fixup for burst size vs stride mismatch |
| */ |
| stride = round_up(stride, burst_size); |
| } |
| |
| mutex_lock(&fe->mutex); |
| val = dpu_fe_read(fe, SOURCEBUFFERATTRIBUTES0); |
| val &= ~0xffff; |
| val |= STRIDE(stride); |
| dpu_fe_write(fe, val, SOURCEBUFFERATTRIBUTES0); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_source_stride); |
| |
| void fetcheco_src_buf_dimensions(struct dpu_fetcheco *fe, unsigned int w, |
| unsigned int h, u32 fmt) |
| { |
| int width = dpu_format_plane_width(w, fmt, 1); |
| int height = dpu_format_plane_height(h, fmt, 1); |
| u32 val; |
| |
| switch (fmt) { |
| case DRM_FORMAT_NV12: |
| case DRM_FORMAT_NV21: |
| case DRM_FORMAT_NV16: |
| case DRM_FORMAT_NV61: |
| case DRM_FORMAT_NV24: |
| case DRM_FORMAT_NV42: |
| break; |
| default: |
| WARN(1, "Unsupported FetchEco pixel format 0x%08x\n", fmt); |
| return; |
| } |
| |
| val = LINEWIDTH(width) | LINECOUNT(height); |
| |
| mutex_lock(&fe->mutex); |
| dpu_fe_write(fe, val, SOURCEBUFFERDIMENSION0); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_src_buf_dimensions); |
| |
| void fetcheco_set_fmt(struct dpu_fetcheco *fe, u32 fmt) |
| { |
| u32 val, bits, shift; |
| int i, hsub, vsub; |
| unsigned int x, y; |
| |
| switch (fmt) { |
| case DRM_FORMAT_NV12: |
| case DRM_FORMAT_NV21: |
| case DRM_FORMAT_NV16: |
| case DRM_FORMAT_NV61: |
| case DRM_FORMAT_NV24: |
| case DRM_FORMAT_NV42: |
| break; |
| default: |
| WARN(1, "Unsupported FetchEco pixel format 0x%08x\n", fmt); |
| return; |
| } |
| |
| hsub = dpu_format_horz_chroma_subsampling(fmt); |
| switch (hsub) { |
| case 1: |
| x = 0x4; |
| break; |
| case 2: |
| x = 0x2; |
| break; |
| default: |
| WARN_ON(1); |
| return; |
| } |
| |
| vsub = dpu_format_vert_chroma_subsampling(fmt); |
| switch (vsub) { |
| case 1: |
| y = 0x4; |
| break; |
| case 2: |
| y = 0x2; |
| break; |
| default: |
| WARN_ON(1); |
| return; |
| } |
| |
| mutex_lock(&fe->mutex); |
| val = dpu_fe_read(fe, FRAMERESAMPLING); |
| val &= ~(DELTAX_MASK | DELTAY_MASK); |
| val |= DELTAX(x) | DELTAY(y); |
| dpu_fe_write(fe, val, FRAMERESAMPLING); |
| |
| val = dpu_fe_read(fe, CONTROL); |
| val &= ~RASTERMODE_MASK; |
| val |= RASTERMODE(RASTERMODE__NORMAL); |
| dpu_fe_write(fe, val, CONTROL); |
| mutex_unlock(&fe->mutex); |
| |
| for (i = 0; i < ARRAY_SIZE(dpu_pixel_format_matrix); i++) { |
| if (dpu_pixel_format_matrix[i].pixel_format == fmt) { |
| bits = dpu_pixel_format_matrix[i].bits; |
| shift = dpu_pixel_format_matrix[i].shift; |
| |
| bits &= ~Y_BITS_MASK; |
| shift &= ~Y_SHIFT_MASK; |
| |
| mutex_lock(&fe->mutex); |
| dpu_fe_write(fe, bits, COLORCOMPONENTBITS0); |
| dpu_fe_write(fe, shift, COLORCOMPONENTSHIFT0); |
| mutex_unlock(&fe->mutex); |
| return; |
| } |
| } |
| |
| WARN_ON(1); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_set_fmt); |
| |
| void fetcheco_layeroffset(struct dpu_fetcheco *fe, unsigned int x, |
| unsigned int y) |
| { |
| u32 val; |
| |
| val = LAYERXOFFSET(x) | LAYERYOFFSET(y); |
| |
| mutex_lock(&fe->mutex); |
| dpu_fe_write(fe, val, LAYEROFFSET0); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_layeroffset); |
| |
| void fetcheco_clipoffset(struct dpu_fetcheco *fe, unsigned int x, |
| unsigned int y) |
| { |
| u32 val; |
| |
| val = CLIPWINDOWXOFFSET(x) | CLIPWINDOWYOFFSET(y); |
| |
| mutex_lock(&fe->mutex); |
| dpu_fe_write(fe, val, CLIPWINDOWOFFSET0); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_clipoffset); |
| |
| void fetcheco_clipdimensions(struct dpu_fetcheco *fe, unsigned int w, |
| unsigned int h) |
| { |
| u32 val; |
| |
| val = CLIPWINDOWWIDTH(w) | CLIPWINDOWHEIGHT(h); |
| |
| mutex_lock(&fe->mutex); |
| dpu_fe_write(fe, val, CLIPWINDOWDIMENSIONS0); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_clipdimensions); |
| |
| void fetcheco_source_buffer_enable(struct dpu_fetcheco *fe) |
| { |
| u32 val; |
| |
| mutex_lock(&fe->mutex); |
| val = dpu_fe_read(fe, LAYERPROPERTY0); |
| val |= SOURCEBUFFERENABLE; |
| dpu_fe_write(fe, val, LAYERPROPERTY0); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_source_buffer_enable); |
| |
| void fetcheco_source_buffer_disable(struct dpu_fetcheco *fe) |
| { |
| u32 val; |
| |
| mutex_lock(&fe->mutex); |
| val = dpu_fe_read(fe, LAYERPROPERTY0); |
| val &= ~SOURCEBUFFERENABLE; |
| dpu_fe_write(fe, val, LAYERPROPERTY0); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_source_buffer_disable); |
| |
| bool fetcheco_is_enabled(struct dpu_fetcheco *fe) |
| { |
| u32 val; |
| |
| mutex_lock(&fe->mutex); |
| val = dpu_fe_read(fe, LAYERPROPERTY0); |
| mutex_unlock(&fe->mutex); |
| |
| return !!(val & SOURCEBUFFERENABLE); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_is_enabled); |
| |
| void fetcheco_framedimensions(struct dpu_fetcheco *fe, unsigned int w, |
| unsigned int h) |
| { |
| u32 val; |
| |
| val = FRAMEWIDTH(w) | FRAMEHEIGHT(h); |
| |
| mutex_lock(&fe->mutex); |
| dpu_fe_write(fe, val, FRAMEDIMENSIONS); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_framedimensions); |
| |
| void fetcheco_frameresampling(struct dpu_fetcheco *fe, unsigned int x, |
| unsigned int y) |
| { |
| u32 val; |
| |
| mutex_lock(&fe->mutex); |
| val = dpu_fe_read(fe, FRAMERESAMPLING); |
| val &= ~(DELTAX_MASK | DELTAY_MASK); |
| val |= DELTAX(x) | DELTAY(y); |
| dpu_fe_write(fe, val, FRAMERESAMPLING); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_frameresampling); |
| |
| void fetcheco_controltrigger(struct dpu_fetcheco *fe, bool trigger) |
| { |
| u32 val; |
| |
| val = trigger ? SHDTOKGEN : 0; |
| |
| mutex_lock(&fe->mutex); |
| dpu_fe_write(fe, val, CONTROLTRIGGER); |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_controltrigger); |
| |
| int fetcheco_fetchtype(struct dpu_fetcheco *fe, fetchtype_t *type) |
| { |
| struct dpu_soc *dpu = fe->dpu; |
| u32 val; |
| |
| mutex_lock(&fe->mutex); |
| val = dpu_fe_read(fe, FETCHTYPE); |
| val &= FETCHTYPE_MASK; |
| mutex_unlock(&fe->mutex); |
| |
| switch (val) { |
| case FETCHTYPE__DECODE: |
| dev_dbg(dpu->dev, "FetchEco%d with RL and RLAD decoder\n", |
| fe->id); |
| break; |
| case FETCHTYPE__LAYER: |
| dev_dbg(dpu->dev, "FetchEco%d with fractional " |
| "plane(8 layers)\n", fe->id); |
| break; |
| case FETCHTYPE__WARP: |
| dev_dbg(dpu->dev, "FetchEco%d with arbitrary warping and " |
| "fractional plane(8 layers)\n", fe->id); |
| break; |
| case FETCHTYPE__ECO: |
| dev_dbg(dpu->dev, "FetchEco%d with minimum feature set for " |
| "alpha, chroma and coordinate planes\n", |
| fe->id); |
| break; |
| case FETCHTYPE__PERSP: |
| dev_dbg(dpu->dev, "FetchEco%d with affine, perspective and " |
| "arbitrary warping\n", fe->id); |
| break; |
| case FETCHTYPE__ROT: |
| dev_dbg(dpu->dev, "FetchEco%d with affine and arbitrary " |
| "warping\n", fe->id); |
| break; |
| case FETCHTYPE__DECODEL: |
| dev_dbg(dpu->dev, "FetchEco%d with RL and RLAD decoder, " |
| "reduced feature set\n", fe->id); |
| break; |
| case FETCHTYPE__LAYERL: |
| dev_dbg(dpu->dev, "FetchEco%d with fractional " |
| "plane(8 layers), reduced feature set\n", |
| fe->id); |
| break; |
| case FETCHTYPE__ROTL: |
| dev_dbg(dpu->dev, "FetchEco%d with affine and arbitrary " |
| "warping, reduced feature set\n", fe->id); |
| break; |
| default: |
| dev_warn(dpu->dev, "Invalid fetch type %u for FetchEco%d\n", |
| val, fe->id); |
| return -EINVAL; |
| } |
| |
| *type = val; |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_fetchtype); |
| |
| dpu_block_id_t fetcheco_get_block_id(struct dpu_fetcheco *fe) |
| { |
| switch (fe->id) { |
| case 0: |
| return ID_FETCHECO0; |
| case 1: |
| return ID_FETCHECO1; |
| case 2: |
| return ID_FETCHECO2; |
| case 9: |
| return ID_FETCHECO9; |
| default: |
| WARN_ON(1); |
| } |
| |
| return ID_NONE; |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_get_block_id); |
| |
| unsigned int fetcheco_get_stream_id(struct dpu_fetcheco *fe) |
| { |
| return fe->stream_id; |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_get_stream_id); |
| |
| void fetcheco_set_stream_id(struct dpu_fetcheco *fe, unsigned int id) |
| { |
| switch (id) { |
| case DPU_PLANE_SRC_TO_DISP_STREAM0: |
| case DPU_PLANE_SRC_TO_DISP_STREAM1: |
| case DPU_PLANE_SRC_DISABLED: |
| fe->stream_id = id; |
| break; |
| default: |
| WARN_ON(1); |
| } |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_set_stream_id); |
| |
| void fetcheco_pin_off(struct dpu_fetcheco *fe) |
| { |
| fe->pin_off = true; |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_pin_off); |
| |
| void fetcheco_unpin_off(struct dpu_fetcheco *fe) |
| { |
| fe->pin_off = false; |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_unpin_off); |
| |
| bool fetcheco_is_pinned_off(struct dpu_fetcheco *fe) |
| { |
| return fe->pin_off; |
| } |
| EXPORT_SYMBOL_GPL(fetcheco_is_pinned_off); |
| |
| struct dpu_fetcheco *dpu_fe_get(struct dpu_soc *dpu, int id) |
| { |
| struct dpu_fetcheco *fe; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(fe_ids); i++) |
| if (fe_ids[i] == id) |
| break; |
| |
| if (i == ARRAY_SIZE(fe_ids)) |
| return ERR_PTR(-EINVAL); |
| |
| fe = dpu->fe_priv[i]; |
| |
| mutex_lock(&fe->mutex); |
| |
| if (fe->inuse) { |
| fe = ERR_PTR(-EBUSY); |
| goto out; |
| } |
| |
| fe->inuse = true; |
| out: |
| mutex_unlock(&fe->mutex); |
| |
| return fe; |
| } |
| EXPORT_SYMBOL_GPL(dpu_fe_get); |
| |
| void dpu_fe_put(struct dpu_fetcheco *fe) |
| { |
| mutex_lock(&fe->mutex); |
| |
| fe->inuse = false; |
| |
| mutex_unlock(&fe->mutex); |
| } |
| EXPORT_SYMBOL_GPL(dpu_fe_put); |
| |
| void _dpu_fe_init(struct dpu_soc *dpu, unsigned int id) |
| { |
| struct dpu_fetcheco *fe; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(fe_ids); i++) |
| if (fe_ids[i] == id) |
| break; |
| |
| if (WARN_ON(i == ARRAY_SIZE(fe_ids))) |
| return; |
| |
| fe = dpu->fe_priv[i]; |
| |
| fetcheco_shden(fe, true); |
| |
| mutex_lock(&fe->mutex); |
| dpu_fe_write(fe, SETNUMBUFFERS(16) | SETBURSTLENGTH(16), |
| BURSTBUFFERMANAGEMENT); |
| mutex_unlock(&fe->mutex); |
| } |
| |
| int dpu_fe_init(struct dpu_soc *dpu, unsigned int id, |
| unsigned long pec_base, unsigned long base) |
| { |
| struct dpu_fetcheco *fe; |
| int i; |
| |
| fe = devm_kzalloc(dpu->dev, sizeof(*fe), GFP_KERNEL); |
| if (!fe) |
| return -ENOMEM; |
| |
| for (i = 0; i < ARRAY_SIZE(fe_ids); i++) |
| if (fe_ids[i] == id) |
| break; |
| |
| dpu->fe_priv[i] = fe; |
| |
| fe->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_16); |
| if (!fe->pec_base) |
| return -ENOMEM; |
| |
| fe->base = devm_ioremap(dpu->dev, base, SZ_128); |
| if (!fe->base) |
| return -ENOMEM; |
| |
| fe->dpu = dpu; |
| fe->id = id; |
| mutex_init(&fe->mutex); |
| |
| _dpu_fe_init(dpu, id); |
| |
| return 0; |
| } |