![Linus Torvalds](/assets/img/avatar_default.png)
uapi: - dma-buf heaps added (and fixed) - command line add support for panel oreientation - command line allow overriding penguin count drm: - mipi dsi definition updates - lockdep annotations for dma_resv - remove dma-buf kmap/kunmap support - constify fb_ops in all fbdev drivers - MST fix for daisy chained hotplug- - CTA-861-G modes with VIC >= 193 added - fix drm_panel_of_backlight export - LVDS decoder support - more device based logging support - scanline alighment for dumb buffers - MST DSC helpers scheduler: - documentation fixes - job distribution improvements panel: - Logic PD type 28 panel support - Jimax8729d MIPI-DSI - igenic JZ4770 - generic DSI devicetree bindings - sony acx424AKP panel - Leadtek LTK500HD1829 - xinpeng XPP055C272 - AUO B116XAK01 - GiantPlus GPM940B0 - BOE NV140FHM-N49 - Satoz SAT050AT40H12R2 - Sharp LS020B1DD01D panels. ttm: - use blocking WW lock i915: - hw/uapi state separation - Lock annotation improvements - selftest improvements - ICL/TGL DSI VDSC support - VBT parsing improvments - Display refactoring - DSI updates + fixes - HDCP 2.2 for CFL - CML PCI ID fixes - GLK+ fbc fix - PSR fixes - GEN/GT refactor improvments - DP MST fixes - switch context id alloc to xarray - workaround updates - LMEM debugfs support - tiled monitor fixes - ICL+ clock gating programming removed - DP MST disable sequence fixed - LMEM discontiguous object maps - prefaulting for discontiguous objects - use LMEM for dumb buffers if possible - add LMEM mmap support amdgpu: - enable sync object timelines for vulkan - MST atomic routines - enable MST DSC support - add DMCUB display microengine support - DC OEM i2c support - Renoir DC fixes - Initial HDCP 2.x support - BACO support for Arcturus - Use BACO for runtime PM power save - gfxoff on navi10 - gfx10 golden updates and fixes - DCN support on POWER - GFXOFF for raven1 refresh - MM engine idle handlers cleanup - 10bpc EDP panel fixes - renoir watermark fixes - SR-IOV fixes - Arcturus VCN fixes - GDDR6 training fixes - freesync fixes - Pollock support amdkfd: - unify more codepath with amdgpu - use KIQ to setup HIQ rather than MMIO radeon: - fix vma fault handler race - PPC DMA fix - register check fixes for r100/r200 nouveau: - mmap_sem vs dma_resv fix - rewrite the ACR secure boot code for Turing - TU10x graphics engine support (TU11x pending) - Page kind mapping for turing - 10-bit LUT support - GP10B Tegra fixes - HD audio regression fix hisilicon/hibmc: - use generic fbdev code and helpers rockchip: - dsi/px30 support virtio: - fb damage support - static some functions vc4: - use dma_resv lock wrappers msm: - use dma_resv lock wrappers - sc7180 display + DSI support - a618 support - UBWC support improvements vmwgfx: - updates + new logging uapi exynos: - enable/disable callback cleanups etnaviv: - use dma_resv lock wrappers atmel-hlcdc: - clock fixes mediatek: - cmdq support - non-smooth cursor fixes - ctm property support sun4i: - suspend support - A64 mipi dsi support rcar-du: - Color management module support - LVDS encoder dual-link support - R8A77980 support analogic: - add support for an6345 ast: - atomic modeset support - primary plane garbage fix arcgpu: - fixes for fourcc handling tegra: - minor fixes and improvments mcde: - vblank support meson: - OSD1 plane AFBC commit gma500: - add pageflip support - reomve global drm_dev komeda: - tweak debugfs output - d32 support - runtime PM suppotr udl: - use generic shmem helpers - cleanup and fixes -----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJeMm6RAAoJEAx081l5xIa+vN8P/0j4jEOv+KIinAhoH+LG3EpD m2TUuu5OQIoBrcCoWOgFBk3wqYpw6PdMBdkXh+5sE5lfeBynp8oC3Bin+QsHJE05 eGBpZtHe+70MQb0Eha+Aic0hchvBKzRnq6i0MYSIHn6afs76dLmF8knTjycxrvV5 Xu1Z3WDmjzqgWF9ja5JCD6fby11seP5RrwObYKVikO35QQyJJwGSGKgu5rq/pByK /n0PCnCOINuL0Lz6J9qexdh/0/XYFQilRC31GJNlKbDSFuECF0GOEzEE/xUBW/pI dLh2YwIIygm18Gar9PgvMwXJn3BfzQ0qEJsf+HlQeNw9iLgbHpp2AsTxHTE87OGe R/y85taW3jGjPsNOKZOeLpvg/Ro8l8ZipLApvDCG2O22DThg/cd6NDjZxl1FJfRH acDG/JdgPo5MbdRAH/cM1WuFS9gEM+0BeSQ5gCjtPakF+X4Vz+ABFDLMRJoaejkJ q8DG32TQXELQx0RMghsqK7YCWGfl+2alA1u9w6TgJh9Rq4iVckvpDeqAZnK1Adkc 87g957Tl0n6FA4wJj/t5jrceiLRMJAm/rBK+R3GZNfWrgx4bHbCmb4fZDZsrFzph nbAjNJ5kOchrFCaRR47ULby6+Q14MAFbkWq4Crfu4YDdzUkTPpep6pi2GIe8w0rV P0hdYOYJf6LUda0utuQX =oFrI -----END PGP SIGNATURE----- Merge tag 'drm-next-2020-01-30' of git://anongit.freedesktop.org/drm/drm Pull drm updates from Davbe Airlie: "This is the main pull request for graphics for 5.6. Usual selection of changes all over. I've got one outstanding vmwgfx pull that touches mm so kept it separate until after all of this lands. I'll try and get it to you soon after this, but it might be early next week (nothing wrong with code, just my schedule is messy) This also hits a lot of fbdev drivers with some cleanups. Other notables: - vulkan timeline semaphore support added to syncobjs - nouveau turing secureboot/graphics support - Displayport MST display stream compression support Detailed summary: uapi: - dma-buf heaps added (and fixed) - command line add support for panel oreientation - command line allow overriding penguin count drm: - mipi dsi definition updates - lockdep annotations for dma_resv - remove dma-buf kmap/kunmap support - constify fb_ops in all fbdev drivers - MST fix for daisy chained hotplug- - CTA-861-G modes with VIC >= 193 added - fix drm_panel_of_backlight export - LVDS decoder support - more device based logging support - scanline alighment for dumb buffers - MST DSC helpers scheduler: - documentation fixes - job distribution improvements panel: - Logic PD type 28 panel support - Jimax8729d MIPI-DSI - igenic JZ4770 - generic DSI devicetree bindings - sony acx424AKP panel - Leadtek LTK500HD1829 - xinpeng XPP055C272 - AUO B116XAK01 - GiantPlus GPM940B0 - BOE NV140FHM-N49 - Satoz SAT050AT40H12R2 - Sharp LS020B1DD01D panels. ttm: - use blocking WW lock i915: - hw/uapi state separation - Lock annotation improvements - selftest improvements - ICL/TGL DSI VDSC support - VBT parsing improvments - Display refactoring - DSI updates + fixes - HDCP 2.2 for CFL - CML PCI ID fixes - GLK+ fbc fix - PSR fixes - GEN/GT refactor improvments - DP MST fixes - switch context id alloc to xarray - workaround updates - LMEM debugfs support - tiled monitor fixes - ICL+ clock gating programming removed - DP MST disable sequence fixed - LMEM discontiguous object maps - prefaulting for discontiguous objects - use LMEM for dumb buffers if possible - add LMEM mmap support amdgpu: - enable sync object timelines for vulkan - MST atomic routines - enable MST DSC support - add DMCUB display microengine support - DC OEM i2c support - Renoir DC fixes - Initial HDCP 2.x support - BACO support for Arcturus - Use BACO for runtime PM power save - gfxoff on navi10 - gfx10 golden updates and fixes - DCN support on POWER - GFXOFF for raven1 refresh - MM engine idle handlers cleanup - 10bpc EDP panel fixes - renoir watermark fixes - SR-IOV fixes - Arcturus VCN fixes - GDDR6 training fixes - freesync fixes - Pollock support amdkfd: - unify more codepath with amdgpu - use KIQ to setup HIQ rather than MMIO radeon: - fix vma fault handler race - PPC DMA fix - register check fixes for r100/r200 nouveau: - mmap_sem vs dma_resv fix - rewrite the ACR secure boot code for Turing - TU10x graphics engine support (TU11x pending) - Page kind mapping for turing - 10-bit LUT support - GP10B Tegra fixes - HD audio regression fix hisilicon/hibmc: - use generic fbdev code and helpers rockchip: - dsi/px30 support virtio: - fb damage support - static some functions vc4: - use dma_resv lock wrappers msm: - use dma_resv lock wrappers - sc7180 display + DSI support - a618 support - UBWC support improvements vmwgfx: - updates + new logging uapi exynos: - enable/disable callback cleanups etnaviv: - use dma_resv lock wrappers atmel-hlcdc: - clock fixes mediatek: - cmdq support - non-smooth cursor fixes - ctm property support sun4i: - suspend support - A64 mipi dsi support rcar-du: - Color management module support - LVDS encoder dual-link support - R8A77980 support analogic: - add support for an6345 ast: - atomic modeset support - primary plane garbage fix arcgpu: - fixes for fourcc handling tegra: - minor fixes and improvments mcde: - vblank support meson: - OSD1 plane AFBC commit gma500: - add pageflip support - reomve global drm_dev komeda: - tweak debugfs output - d32 support - runtime PM suppotr udl: - use generic shmem helpers - cleanup and fixes" * tag 'drm-next-2020-01-30' of git://anongit.freedesktop.org/drm/drm: (1998 commits) drm/nouveau/fb/gp102-: allow module to load even when scrubber binary is missing drm/nouveau/acr: return error when registering LSF if ACR not supported drm/nouveau/disp/gv100-: not all channel types support reporting error codes drm/nouveau/disp/nv50-: prevent oops when no channel method map provided drm/nouveau: support synchronous pushbuf submission drm/nouveau: signal pending fences when channel has been killed drm/nouveau: reject attempts to submit to dead channels drm/nouveau: zero vma pointer even if we only unreference it rather than free drm/nouveau: Add HD-audio component notifier support drm/nouveau: fix build error without CONFIG_IOMMU_API drm/nouveau/kms/nv04: remove set but not used variable 'width' drm/nouveau/kms/nv50: remove set but not unused variable 'nv_connector' drm/nouveau/mmu: fix comptag memory leak drm/nouveau/gr/gp10b: Use gp100_grctx and gp100_gr_zbc drm/nouveau/pmu/gm20b,gp10b: Fix Falcon bootstrapping drm/exynos: Rename Exynos to lowercase drm/exynos: change callback names drm/mst: Don't do atomic checks over disabled managers drm/amdgpu: add the lost mutex_init back drm/amd/display: skip opp blank or unblank if test pattern enabled ...
2668 lines
66 KiB
C
2668 lines
66 KiB
C
/*
|
|
* SuperH Mobile LCDC Framebuffer
|
|
*
|
|
* Copyright (c) 2008 Magnus Damm
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
* for more details.
|
|
*/
|
|
|
|
#include <linux/atomic.h>
|
|
#include <linux/backlight.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/console.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/fbcon.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/videodev2.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <video/sh_mobile_lcdc.h>
|
|
|
|
#include "sh_mobile_lcdcfb.h"
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
* Overlay register definitions
|
|
*/
|
|
|
|
#define LDBCR 0xb00
|
|
#define LDBCR_UPC(n) (1 << ((n) + 16))
|
|
#define LDBCR_UPF(n) (1 << ((n) + 8))
|
|
#define LDBCR_UPD(n) (1 << ((n) + 0))
|
|
#define LDBnBSIFR(n) (0xb20 + (n) * 0x20 + 0x00)
|
|
#define LDBBSIFR_EN (1 << 31)
|
|
#define LDBBSIFR_VS (1 << 29)
|
|
#define LDBBSIFR_BRSEL (1 << 28)
|
|
#define LDBBSIFR_MX (1 << 27)
|
|
#define LDBBSIFR_MY (1 << 26)
|
|
#define LDBBSIFR_CV3 (3 << 24)
|
|
#define LDBBSIFR_CV2 (2 << 24)
|
|
#define LDBBSIFR_CV1 (1 << 24)
|
|
#define LDBBSIFR_CV0 (0 << 24)
|
|
#define LDBBSIFR_CV_MASK (3 << 24)
|
|
#define LDBBSIFR_LAY_MASK (0xff << 16)
|
|
#define LDBBSIFR_LAY_SHIFT 16
|
|
#define LDBBSIFR_ROP3_MASK (0xff << 16)
|
|
#define LDBBSIFR_ROP3_SHIFT 16
|
|
#define LDBBSIFR_AL_PL8 (3 << 14)
|
|
#define LDBBSIFR_AL_PL1 (2 << 14)
|
|
#define LDBBSIFR_AL_PK (1 << 14)
|
|
#define LDBBSIFR_AL_1 (0 << 14)
|
|
#define LDBBSIFR_AL_MASK (3 << 14)
|
|
#define LDBBSIFR_SWPL (1 << 10)
|
|
#define LDBBSIFR_SWPW (1 << 9)
|
|
#define LDBBSIFR_SWPB (1 << 8)
|
|
#define LDBBSIFR_RY (1 << 7)
|
|
#define LDBBSIFR_CHRR_420 (2 << 0)
|
|
#define LDBBSIFR_CHRR_422 (1 << 0)
|
|
#define LDBBSIFR_CHRR_444 (0 << 0)
|
|
#define LDBBSIFR_RPKF_ARGB32 (0x00 << 0)
|
|
#define LDBBSIFR_RPKF_RGB16 (0x03 << 0)
|
|
#define LDBBSIFR_RPKF_RGB24 (0x0b << 0)
|
|
#define LDBBSIFR_RPKF_MASK (0x1f << 0)
|
|
#define LDBnBSSZR(n) (0xb20 + (n) * 0x20 + 0x04)
|
|
#define LDBBSSZR_BVSS_MASK (0xfff << 16)
|
|
#define LDBBSSZR_BVSS_SHIFT 16
|
|
#define LDBBSSZR_BHSS_MASK (0xfff << 0)
|
|
#define LDBBSSZR_BHSS_SHIFT 0
|
|
#define LDBnBLOCR(n) (0xb20 + (n) * 0x20 + 0x08)
|
|
#define LDBBLOCR_CVLC_MASK (0xfff << 16)
|
|
#define LDBBLOCR_CVLC_SHIFT 16
|
|
#define LDBBLOCR_CHLC_MASK (0xfff << 0)
|
|
#define LDBBLOCR_CHLC_SHIFT 0
|
|
#define LDBnBSMWR(n) (0xb20 + (n) * 0x20 + 0x0c)
|
|
#define LDBBSMWR_BSMWA_MASK (0xffff << 16)
|
|
#define LDBBSMWR_BSMWA_SHIFT 16
|
|
#define LDBBSMWR_BSMW_MASK (0xffff << 0)
|
|
#define LDBBSMWR_BSMW_SHIFT 0
|
|
#define LDBnBSAYR(n) (0xb20 + (n) * 0x20 + 0x10)
|
|
#define LDBBSAYR_FG1A_MASK (0xff << 24)
|
|
#define LDBBSAYR_FG1A_SHIFT 24
|
|
#define LDBBSAYR_FG1R_MASK (0xff << 16)
|
|
#define LDBBSAYR_FG1R_SHIFT 16
|
|
#define LDBBSAYR_FG1G_MASK (0xff << 8)
|
|
#define LDBBSAYR_FG1G_SHIFT 8
|
|
#define LDBBSAYR_FG1B_MASK (0xff << 0)
|
|
#define LDBBSAYR_FG1B_SHIFT 0
|
|
#define LDBnBSACR(n) (0xb20 + (n) * 0x20 + 0x14)
|
|
#define LDBBSACR_FG2A_MASK (0xff << 24)
|
|
#define LDBBSACR_FG2A_SHIFT 24
|
|
#define LDBBSACR_FG2R_MASK (0xff << 16)
|
|
#define LDBBSACR_FG2R_SHIFT 16
|
|
#define LDBBSACR_FG2G_MASK (0xff << 8)
|
|
#define LDBBSACR_FG2G_SHIFT 8
|
|
#define LDBBSACR_FG2B_MASK (0xff << 0)
|
|
#define LDBBSACR_FG2B_SHIFT 0
|
|
#define LDBnBSAAR(n) (0xb20 + (n) * 0x20 + 0x18)
|
|
#define LDBBSAAR_AP_MASK (0xff << 24)
|
|
#define LDBBSAAR_AP_SHIFT 24
|
|
#define LDBBSAAR_R_MASK (0xff << 16)
|
|
#define LDBBSAAR_R_SHIFT 16
|
|
#define LDBBSAAR_GY_MASK (0xff << 8)
|
|
#define LDBBSAAR_GY_SHIFT 8
|
|
#define LDBBSAAR_B_MASK (0xff << 0)
|
|
#define LDBBSAAR_B_SHIFT 0
|
|
#define LDBnBPPCR(n) (0xb20 + (n) * 0x20 + 0x1c)
|
|
#define LDBBPPCR_AP_MASK (0xff << 24)
|
|
#define LDBBPPCR_AP_SHIFT 24
|
|
#define LDBBPPCR_R_MASK (0xff << 16)
|
|
#define LDBBPPCR_R_SHIFT 16
|
|
#define LDBBPPCR_GY_MASK (0xff << 8)
|
|
#define LDBBPPCR_GY_SHIFT 8
|
|
#define LDBBPPCR_B_MASK (0xff << 0)
|
|
#define LDBBPPCR_B_SHIFT 0
|
|
#define LDBnBBGCL(n) (0xb10 + (n) * 0x04)
|
|
#define LDBBBGCL_BGA_MASK (0xff << 24)
|
|
#define LDBBBGCL_BGA_SHIFT 24
|
|
#define LDBBBGCL_BGR_MASK (0xff << 16)
|
|
#define LDBBBGCL_BGR_SHIFT 16
|
|
#define LDBBBGCL_BGG_MASK (0xff << 8)
|
|
#define LDBBBGCL_BGG_SHIFT 8
|
|
#define LDBBBGCL_BGB_MASK (0xff << 0)
|
|
#define LDBBBGCL_BGB_SHIFT 0
|
|
|
|
#define SIDE_B_OFFSET 0x1000
|
|
#define MIRROR_OFFSET 0x2000
|
|
|
|
#define MAX_XRES 1920
|
|
#define MAX_YRES 1080
|
|
|
|
enum sh_mobile_lcdc_overlay_mode {
|
|
LCDC_OVERLAY_BLEND,
|
|
LCDC_OVERLAY_ROP3,
|
|
};
|
|
|
|
/*
|
|
* struct sh_mobile_lcdc_overlay - LCDC display overlay
|
|
*
|
|
* @channel: LCDC channel this overlay belongs to
|
|
* @cfg: Overlay configuration
|
|
* @info: Frame buffer device
|
|
* @index: Overlay index (0-3)
|
|
* @base: Overlay registers base address
|
|
* @enabled: True if the overlay is enabled
|
|
* @mode: Overlay blending mode (alpha blend or ROP3)
|
|
* @alpha: Global alpha blending value (0-255, for alpha blending mode)
|
|
* @rop3: Raster operation (for ROP3 mode)
|
|
* @fb_mem: Frame buffer virtual memory address
|
|
* @fb_size: Frame buffer size in bytes
|
|
* @dma_handle: Frame buffer DMA address
|
|
* @base_addr_y: Overlay base address (RGB or luma component)
|
|
* @base_addr_c: Overlay base address (chroma component)
|
|
* @pan_y_offset: Panning linear offset in bytes (luma component)
|
|
* @format: Current pixelf format
|
|
* @xres: Horizontal visible resolution
|
|
* @xres_virtual: Horizontal total resolution
|
|
* @yres: Vertical visible resolution
|
|
* @yres_virtual: Vertical total resolution
|
|
* @pitch: Overlay line pitch
|
|
* @pos_x: Horizontal overlay position
|
|
* @pos_y: Vertical overlay position
|
|
*/
|
|
struct sh_mobile_lcdc_overlay {
|
|
struct sh_mobile_lcdc_chan *channel;
|
|
|
|
const struct sh_mobile_lcdc_overlay_cfg *cfg;
|
|
struct fb_info *info;
|
|
|
|
unsigned int index;
|
|
unsigned long base;
|
|
|
|
bool enabled;
|
|
enum sh_mobile_lcdc_overlay_mode mode;
|
|
unsigned int alpha;
|
|
unsigned int rop3;
|
|
|
|
void *fb_mem;
|
|
unsigned long fb_size;
|
|
|
|
dma_addr_t dma_handle;
|
|
unsigned long base_addr_y;
|
|
unsigned long base_addr_c;
|
|
unsigned long pan_y_offset;
|
|
|
|
const struct sh_mobile_lcdc_format_info *format;
|
|
unsigned int xres;
|
|
unsigned int xres_virtual;
|
|
unsigned int yres;
|
|
unsigned int yres_virtual;
|
|
unsigned int pitch;
|
|
int pos_x;
|
|
int pos_y;
|
|
};
|
|
|
|
struct sh_mobile_lcdc_priv {
|
|
void __iomem *base;
|
|
int irq;
|
|
atomic_t hw_usecnt;
|
|
struct device *dev;
|
|
struct clk *dot_clk;
|
|
unsigned long lddckr;
|
|
|
|
struct sh_mobile_lcdc_chan ch[2];
|
|
struct sh_mobile_lcdc_overlay overlays[4];
|
|
|
|
int started;
|
|
int forced_fourcc; /* 2 channel LCDC must share fourcc setting */
|
|
};
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Registers access
|
|
*/
|
|
|
|
static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = {
|
|
[LDDCKPAT1R] = 0x400,
|
|
[LDDCKPAT2R] = 0x404,
|
|
[LDMT1R] = 0x418,
|
|
[LDMT2R] = 0x41c,
|
|
[LDMT3R] = 0x420,
|
|
[LDDFR] = 0x424,
|
|
[LDSM1R] = 0x428,
|
|
[LDSM2R] = 0x42c,
|
|
[LDSA1R] = 0x430,
|
|
[LDSA2R] = 0x434,
|
|
[LDMLSR] = 0x438,
|
|
[LDHCNR] = 0x448,
|
|
[LDHSYNR] = 0x44c,
|
|
[LDVLNR] = 0x450,
|
|
[LDVSYNR] = 0x454,
|
|
[LDPMR] = 0x460,
|
|
[LDHAJR] = 0x4a0,
|
|
};
|
|
|
|
static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = {
|
|
[LDDCKPAT1R] = 0x408,
|
|
[LDDCKPAT2R] = 0x40c,
|
|
[LDMT1R] = 0x600,
|
|
[LDMT2R] = 0x604,
|
|
[LDMT3R] = 0x608,
|
|
[LDDFR] = 0x60c,
|
|
[LDSM1R] = 0x610,
|
|
[LDSM2R] = 0x614,
|
|
[LDSA1R] = 0x618,
|
|
[LDMLSR] = 0x620,
|
|
[LDHCNR] = 0x624,
|
|
[LDHSYNR] = 0x628,
|
|
[LDVLNR] = 0x62c,
|
|
[LDVSYNR] = 0x630,
|
|
[LDPMR] = 0x63c,
|
|
};
|
|
|
|
static bool banked(int reg_nr)
|
|
{
|
|
switch (reg_nr) {
|
|
case LDMT1R:
|
|
case LDMT2R:
|
|
case LDMT3R:
|
|
case LDDFR:
|
|
case LDSM1R:
|
|
case LDSA1R:
|
|
case LDSA2R:
|
|
case LDMLSR:
|
|
case LDHCNR:
|
|
case LDHSYNR:
|
|
case LDVLNR:
|
|
case LDVSYNR:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int lcdc_chan_is_sublcd(struct sh_mobile_lcdc_chan *chan)
|
|
{
|
|
return chan->cfg->chan == LCDC_CHAN_SUBLCD;
|
|
}
|
|
|
|
static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan,
|
|
int reg_nr, unsigned long data)
|
|
{
|
|
iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr]);
|
|
if (banked(reg_nr))
|
|
iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] +
|
|
SIDE_B_OFFSET);
|
|
}
|
|
|
|
static void lcdc_write_chan_mirror(struct sh_mobile_lcdc_chan *chan,
|
|
int reg_nr, unsigned long data)
|
|
{
|
|
iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] +
|
|
MIRROR_OFFSET);
|
|
}
|
|
|
|
static unsigned long lcdc_read_chan(struct sh_mobile_lcdc_chan *chan,
|
|
int reg_nr)
|
|
{
|
|
return ioread32(chan->lcdc->base + chan->reg_offs[reg_nr]);
|
|
}
|
|
|
|
static void lcdc_write_overlay(struct sh_mobile_lcdc_overlay *ovl,
|
|
int reg, unsigned long data)
|
|
{
|
|
iowrite32(data, ovl->channel->lcdc->base + reg);
|
|
iowrite32(data, ovl->channel->lcdc->base + reg + SIDE_B_OFFSET);
|
|
}
|
|
|
|
static void lcdc_write(struct sh_mobile_lcdc_priv *priv,
|
|
unsigned long reg_offs, unsigned long data)
|
|
{
|
|
iowrite32(data, priv->base + reg_offs);
|
|
}
|
|
|
|
static unsigned long lcdc_read(struct sh_mobile_lcdc_priv *priv,
|
|
unsigned long reg_offs)
|
|
{
|
|
return ioread32(priv->base + reg_offs);
|
|
}
|
|
|
|
static void lcdc_wait_bit(struct sh_mobile_lcdc_priv *priv,
|
|
unsigned long reg_offs,
|
|
unsigned long mask, unsigned long until)
|
|
{
|
|
while ((lcdc_read(priv, reg_offs) & mask) != until)
|
|
cpu_relax();
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Clock management
|
|
*/
|
|
|
|
static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv)
|
|
{
|
|
if (atomic_inc_and_test(&priv->hw_usecnt)) {
|
|
if (priv->dot_clk)
|
|
clk_prepare_enable(priv->dot_clk);
|
|
pm_runtime_get_sync(priv->dev);
|
|
}
|
|
}
|
|
|
|
static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv)
|
|
{
|
|
if (atomic_sub_return(1, &priv->hw_usecnt) == -1) {
|
|
pm_runtime_put(priv->dev);
|
|
if (priv->dot_clk)
|
|
clk_disable_unprepare(priv->dot_clk);
|
|
}
|
|
}
|
|
|
|
static int sh_mobile_lcdc_setup_clocks(struct sh_mobile_lcdc_priv *priv,
|
|
int clock_source)
|
|
{
|
|
struct clk *clk;
|
|
char *str;
|
|
|
|
switch (clock_source) {
|
|
case LCDC_CLK_BUS:
|
|
str = "bus_clk";
|
|
priv->lddckr = LDDCKR_ICKSEL_BUS;
|
|
break;
|
|
case LCDC_CLK_PERIPHERAL:
|
|
str = "peripheral_clk";
|
|
priv->lddckr = LDDCKR_ICKSEL_MIPI;
|
|
break;
|
|
case LCDC_CLK_EXTERNAL:
|
|
str = NULL;
|
|
priv->lddckr = LDDCKR_ICKSEL_HDMI;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (str == NULL)
|
|
return 0;
|
|
|
|
clk = clk_get(priv->dev, str);
|
|
if (IS_ERR(clk)) {
|
|
dev_err(priv->dev, "cannot get dot clock %s\n", str);
|
|
return PTR_ERR(clk);
|
|
}
|
|
|
|
priv->dot_clk = clk;
|
|
return 0;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Display, panel and deferred I/O
|
|
*/
|
|
|
|
static void lcdc_sys_write_index(void *handle, unsigned long data)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = handle;
|
|
|
|
lcdc_write(ch->lcdc, _LDDWD0R, data | LDDWDxR_WDACT);
|
|
lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
|
|
lcdc_write(ch->lcdc, _LDDWAR, LDDWAR_WA |
|
|
(lcdc_chan_is_sublcd(ch) ? 2 : 0));
|
|
lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
|
|
}
|
|
|
|
static void lcdc_sys_write_data(void *handle, unsigned long data)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = handle;
|
|
|
|
lcdc_write(ch->lcdc, _LDDWD0R, data | LDDWDxR_WDACT | LDDWDxR_RSW);
|
|
lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
|
|
lcdc_write(ch->lcdc, _LDDWAR, LDDWAR_WA |
|
|
(lcdc_chan_is_sublcd(ch) ? 2 : 0));
|
|
lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
|
|
}
|
|
|
|
static unsigned long lcdc_sys_read_data(void *handle)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = handle;
|
|
|
|
lcdc_write(ch->lcdc, _LDDRDR, LDDRDR_RSR);
|
|
lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
|
|
lcdc_write(ch->lcdc, _LDDRAR, LDDRAR_RA |
|
|
(lcdc_chan_is_sublcd(ch) ? 2 : 0));
|
|
udelay(1);
|
|
lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0);
|
|
|
|
return lcdc_read(ch->lcdc, _LDDRDR) & LDDRDR_DRD_MASK;
|
|
}
|
|
|
|
static struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = {
|
|
.write_index = lcdc_sys_write_index,
|
|
.write_data = lcdc_sys_write_data,
|
|
.read_data = lcdc_sys_read_data,
|
|
};
|
|
|
|
static int sh_mobile_lcdc_sginit(struct fb_info *info,
|
|
struct list_head *pagelist)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
|
unsigned int nr_pages_max = ch->fb_size >> PAGE_SHIFT;
|
|
struct page *page;
|
|
int nr_pages = 0;
|
|
|
|
sg_init_table(ch->sglist, nr_pages_max);
|
|
|
|
list_for_each_entry(page, pagelist, lru)
|
|
sg_set_page(&ch->sglist[nr_pages++], page, PAGE_SIZE, 0);
|
|
|
|
return nr_pages;
|
|
}
|
|
|
|
static void sh_mobile_lcdc_deferred_io(struct fb_info *info,
|
|
struct list_head *pagelist)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
|
const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg;
|
|
|
|
/* enable clocks before accessing hardware */
|
|
sh_mobile_lcdc_clk_on(ch->lcdc);
|
|
|
|
/*
|
|
* It's possible to get here without anything on the pagelist via
|
|
* sh_mobile_lcdc_deferred_io_touch() or via a userspace fsync()
|
|
* invocation. In the former case, the acceleration routines are
|
|
* stepped in to when using the framebuffer console causing the
|
|
* workqueue to be scheduled without any dirty pages on the list.
|
|
*
|
|
* Despite this, a panel update is still needed given that the
|
|
* acceleration routines have their own methods for writing in
|
|
* that still need to be updated.
|
|
*
|
|
* The fsync() and empty pagelist case could be optimized for,
|
|
* but we don't bother, as any application exhibiting such
|
|
* behaviour is fundamentally broken anyways.
|
|
*/
|
|
if (!list_empty(pagelist)) {
|
|
unsigned int nr_pages = sh_mobile_lcdc_sginit(info, pagelist);
|
|
|
|
/* trigger panel update */
|
|
dma_map_sg(ch->lcdc->dev, ch->sglist, nr_pages, DMA_TO_DEVICE);
|
|
if (panel->start_transfer)
|
|
panel->start_transfer(ch, &sh_mobile_lcdc_sys_bus_ops);
|
|
lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG);
|
|
dma_unmap_sg(ch->lcdc->dev, ch->sglist, nr_pages,
|
|
DMA_TO_DEVICE);
|
|
} else {
|
|
if (panel->start_transfer)
|
|
panel->start_transfer(ch, &sh_mobile_lcdc_sys_bus_ops);
|
|
lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG);
|
|
}
|
|
}
|
|
|
|
static void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info)
|
|
{
|
|
struct fb_deferred_io *fbdefio = info->fbdefio;
|
|
|
|
if (fbdefio)
|
|
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
|
|
}
|
|
|
|
static void sh_mobile_lcdc_display_on(struct sh_mobile_lcdc_chan *ch)
|
|
{
|
|
const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg;
|
|
|
|
if (ch->tx_dev) {
|
|
int ret;
|
|
|
|
ret = ch->tx_dev->ops->display_on(ch->tx_dev);
|
|
if (ret < 0)
|
|
return;
|
|
|
|
if (ret == SH_MOBILE_LCDC_DISPLAY_DISCONNECTED)
|
|
ch->info->state = FBINFO_STATE_SUSPENDED;
|
|
}
|
|
|
|
/* HDMI must be enabled before LCDC configuration */
|
|
if (panel->display_on)
|
|
panel->display_on();
|
|
}
|
|
|
|
static void sh_mobile_lcdc_display_off(struct sh_mobile_lcdc_chan *ch)
|
|
{
|
|
const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg;
|
|
|
|
if (panel->display_off)
|
|
panel->display_off();
|
|
|
|
if (ch->tx_dev)
|
|
ch->tx_dev->ops->display_off(ch->tx_dev);
|
|
}
|
|
|
|
static int sh_mobile_lcdc_check_var(struct fb_var_screeninfo *var,
|
|
struct fb_info *info);
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Format helpers
|
|
*/
|
|
|
|
struct sh_mobile_lcdc_format_info {
|
|
u32 fourcc;
|
|
unsigned int bpp;
|
|
bool yuv;
|
|
u32 lddfr;
|
|
};
|
|
|
|
static const struct sh_mobile_lcdc_format_info sh_mobile_format_infos[] = {
|
|
{
|
|
.fourcc = V4L2_PIX_FMT_RGB565,
|
|
.bpp = 16,
|
|
.yuv = false,
|
|
.lddfr = LDDFR_PKF_RGB16,
|
|
}, {
|
|
.fourcc = V4L2_PIX_FMT_BGR24,
|
|
.bpp = 24,
|
|
.yuv = false,
|
|
.lddfr = LDDFR_PKF_RGB24,
|
|
}, {
|
|
.fourcc = V4L2_PIX_FMT_BGR32,
|
|
.bpp = 32,
|
|
.yuv = false,
|
|
.lddfr = LDDFR_PKF_ARGB32,
|
|
}, {
|
|
.fourcc = V4L2_PIX_FMT_NV12,
|
|
.bpp = 12,
|
|
.yuv = true,
|
|
.lddfr = LDDFR_CC | LDDFR_YF_420,
|
|
}, {
|
|
.fourcc = V4L2_PIX_FMT_NV21,
|
|
.bpp = 12,
|
|
.yuv = true,
|
|
.lddfr = LDDFR_CC | LDDFR_YF_420,
|
|
}, {
|
|
.fourcc = V4L2_PIX_FMT_NV16,
|
|
.bpp = 16,
|
|
.yuv = true,
|
|
.lddfr = LDDFR_CC | LDDFR_YF_422,
|
|
}, {
|
|
.fourcc = V4L2_PIX_FMT_NV61,
|
|
.bpp = 16,
|
|
.yuv = true,
|
|
.lddfr = LDDFR_CC | LDDFR_YF_422,
|
|
}, {
|
|
.fourcc = V4L2_PIX_FMT_NV24,
|
|
.bpp = 24,
|
|
.yuv = true,
|
|
.lddfr = LDDFR_CC | LDDFR_YF_444,
|
|
}, {
|
|
.fourcc = V4L2_PIX_FMT_NV42,
|
|
.bpp = 24,
|
|
.yuv = true,
|
|
.lddfr = LDDFR_CC | LDDFR_YF_444,
|
|
},
|
|
};
|
|
|
|
static const struct sh_mobile_lcdc_format_info *
|
|
sh_mobile_format_info(u32 fourcc)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sh_mobile_format_infos); ++i) {
|
|
if (sh_mobile_format_infos[i].fourcc == fourcc)
|
|
return &sh_mobile_format_infos[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int sh_mobile_format_fourcc(const struct fb_var_screeninfo *var)
|
|
{
|
|
if (var->grayscale > 1)
|
|
return var->grayscale;
|
|
|
|
switch (var->bits_per_pixel) {
|
|
case 16:
|
|
return V4L2_PIX_FMT_RGB565;
|
|
case 24:
|
|
return V4L2_PIX_FMT_BGR24;
|
|
case 32:
|
|
return V4L2_PIX_FMT_BGR32;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int sh_mobile_format_is_fourcc(const struct fb_var_screeninfo *var)
|
|
{
|
|
return var->grayscale > 1;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Start, stop and IRQ
|
|
*/
|
|
|
|
static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data)
|
|
{
|
|
struct sh_mobile_lcdc_priv *priv = data;
|
|
struct sh_mobile_lcdc_chan *ch;
|
|
unsigned long ldintr;
|
|
int is_sub;
|
|
int k;
|
|
|
|
/* Acknowledge interrupts and disable further VSYNC End IRQs. */
|
|
ldintr = lcdc_read(priv, _LDINTR);
|
|
lcdc_write(priv, _LDINTR, (ldintr ^ LDINTR_STATUS_MASK) & ~LDINTR_VEE);
|
|
|
|
/* figure out if this interrupt is for main or sub lcd */
|
|
is_sub = (lcdc_read(priv, _LDSR) & LDSR_MSS) ? 1 : 0;
|
|
|
|
/* wake up channel and disable clocks */
|
|
for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
|
|
ch = &priv->ch[k];
|
|
|
|
if (!ch->enabled)
|
|
continue;
|
|
|
|
/* Frame End */
|
|
if (ldintr & LDINTR_FS) {
|
|
if (is_sub == lcdc_chan_is_sublcd(ch)) {
|
|
ch->frame_end = 1;
|
|
wake_up(&ch->frame_end_wait);
|
|
|
|
sh_mobile_lcdc_clk_off(priv);
|
|
}
|
|
}
|
|
|
|
/* VSYNC End */
|
|
if (ldintr & LDINTR_VES)
|
|
complete(&ch->vsync_completion);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int sh_mobile_lcdc_wait_for_vsync(struct sh_mobile_lcdc_chan *ch)
|
|
{
|
|
unsigned long ldintr;
|
|
int ret;
|
|
|
|
/* Enable VSync End interrupt and be careful not to acknowledge any
|
|
* pending interrupt.
|
|
*/
|
|
ldintr = lcdc_read(ch->lcdc, _LDINTR);
|
|
ldintr |= LDINTR_VEE | LDINTR_STATUS_MASK;
|
|
lcdc_write(ch->lcdc, _LDINTR, ldintr);
|
|
|
|
ret = wait_for_completion_interruptible_timeout(&ch->vsync_completion,
|
|
msecs_to_jiffies(100));
|
|
if (!ret)
|
|
return -ETIMEDOUT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv,
|
|
int start)
|
|
{
|
|
unsigned long tmp = lcdc_read(priv, _LDCNT2R);
|
|
int k;
|
|
|
|
/* start or stop the lcdc */
|
|
if (start)
|
|
lcdc_write(priv, _LDCNT2R, tmp | LDCNT2R_DO);
|
|
else
|
|
lcdc_write(priv, _LDCNT2R, tmp & ~LDCNT2R_DO);
|
|
|
|
/* wait until power is applied/stopped on all channels */
|
|
for (k = 0; k < ARRAY_SIZE(priv->ch); k++)
|
|
if (lcdc_read(priv, _LDCNT2R) & priv->ch[k].enabled)
|
|
while (1) {
|
|
tmp = lcdc_read_chan(&priv->ch[k], LDPMR)
|
|
& LDPMR_LPS;
|
|
if (start && tmp == LDPMR_LPS)
|
|
break;
|
|
if (!start && tmp == 0)
|
|
break;
|
|
cpu_relax();
|
|
}
|
|
|
|
if (!start)
|
|
lcdc_write(priv, _LDDCKSTPR, 1); /* stop dotclock */
|
|
}
|
|
|
|
static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch)
|
|
{
|
|
const struct fb_var_screeninfo *var = &ch->info->var;
|
|
const struct fb_videomode *mode = &ch->display.mode;
|
|
unsigned long h_total, hsync_pos, display_h_total;
|
|
u32 tmp;
|
|
|
|
tmp = ch->ldmt1r_value;
|
|
tmp |= (var->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : LDMT1R_VPOL;
|
|
tmp |= (var->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : LDMT1R_HPOL;
|
|
tmp |= (ch->cfg->flags & LCDC_FLAGS_DWPOL) ? LDMT1R_DWPOL : 0;
|
|
tmp |= (ch->cfg->flags & LCDC_FLAGS_DIPOL) ? LDMT1R_DIPOL : 0;
|
|
tmp |= (ch->cfg->flags & LCDC_FLAGS_DAPOL) ? LDMT1R_DAPOL : 0;
|
|
tmp |= (ch->cfg->flags & LCDC_FLAGS_HSCNT) ? LDMT1R_HSCNT : 0;
|
|
tmp |= (ch->cfg->flags & LCDC_FLAGS_DWCNT) ? LDMT1R_DWCNT : 0;
|
|
lcdc_write_chan(ch, LDMT1R, tmp);
|
|
|
|
/* setup SYS bus */
|
|
lcdc_write_chan(ch, LDMT2R, ch->cfg->sys_bus_cfg.ldmt2r);
|
|
lcdc_write_chan(ch, LDMT3R, ch->cfg->sys_bus_cfg.ldmt3r);
|
|
|
|
/* horizontal configuration */
|
|
h_total = mode->xres + mode->hsync_len + mode->left_margin
|
|
+ mode->right_margin;
|
|
tmp = h_total / 8; /* HTCN */
|
|
tmp |= (min(mode->xres, ch->xres) / 8) << 16; /* HDCN */
|
|
lcdc_write_chan(ch, LDHCNR, tmp);
|
|
|
|
hsync_pos = mode->xres + mode->right_margin;
|
|
tmp = hsync_pos / 8; /* HSYNP */
|
|
tmp |= (mode->hsync_len / 8) << 16; /* HSYNW */
|
|
lcdc_write_chan(ch, LDHSYNR, tmp);
|
|
|
|
/* vertical configuration */
|
|
tmp = mode->yres + mode->vsync_len + mode->upper_margin
|
|
+ mode->lower_margin; /* VTLN */
|
|
tmp |= min(mode->yres, ch->yres) << 16; /* VDLN */
|
|
lcdc_write_chan(ch, LDVLNR, tmp);
|
|
|
|
tmp = mode->yres + mode->lower_margin; /* VSYNP */
|
|
tmp |= mode->vsync_len << 16; /* VSYNW */
|
|
lcdc_write_chan(ch, LDVSYNR, tmp);
|
|
|
|
/* Adjust horizontal synchronisation for HDMI */
|
|
display_h_total = mode->xres + mode->hsync_len + mode->left_margin
|
|
+ mode->right_margin;
|
|
tmp = ((mode->xres & 7) << 24) | ((display_h_total & 7) << 16)
|
|
| ((mode->hsync_len & 7) << 8) | (hsync_pos & 7);
|
|
lcdc_write_chan(ch, LDHAJR, tmp);
|
|
lcdc_write_chan_mirror(ch, LDHAJR, tmp);
|
|
}
|
|
|
|
static void sh_mobile_lcdc_overlay_setup(struct sh_mobile_lcdc_overlay *ovl)
|
|
{
|
|
u32 format = 0;
|
|
|
|
if (!ovl->enabled) {
|
|
lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index));
|
|
lcdc_write_overlay(ovl, LDBnBSIFR(ovl->index), 0);
|
|
lcdc_write(ovl->channel->lcdc, LDBCR,
|
|
LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index));
|
|
return;
|
|
}
|
|
|
|
ovl->base_addr_y = ovl->dma_handle;
|
|
ovl->base_addr_c = ovl->dma_handle
|
|
+ ovl->xres_virtual * ovl->yres_virtual;
|
|
|
|
switch (ovl->mode) {
|
|
case LCDC_OVERLAY_BLEND:
|
|
format = LDBBSIFR_EN | (ovl->alpha << LDBBSIFR_LAY_SHIFT);
|
|
break;
|
|
|
|
case LCDC_OVERLAY_ROP3:
|
|
format = LDBBSIFR_EN | LDBBSIFR_BRSEL
|
|
| (ovl->rop3 << LDBBSIFR_ROP3_SHIFT);
|
|
break;
|
|
}
|
|
|
|
switch (ovl->format->fourcc) {
|
|
case V4L2_PIX_FMT_RGB565:
|
|
case V4L2_PIX_FMT_NV21:
|
|
case V4L2_PIX_FMT_NV61:
|
|
case V4L2_PIX_FMT_NV42:
|
|
format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW;
|
|
break;
|
|
case V4L2_PIX_FMT_BGR24:
|
|
case V4L2_PIX_FMT_NV12:
|
|
case V4L2_PIX_FMT_NV16:
|
|
case V4L2_PIX_FMT_NV24:
|
|
format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW | LDBBSIFR_SWPB;
|
|
break;
|
|
case V4L2_PIX_FMT_BGR32:
|
|
default:
|
|
format |= LDBBSIFR_SWPL;
|
|
break;
|
|
}
|
|
|
|
switch (ovl->format->fourcc) {
|
|
case V4L2_PIX_FMT_RGB565:
|
|
format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB16;
|
|
break;
|
|
case V4L2_PIX_FMT_BGR24:
|
|
format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB24;
|
|
break;
|
|
case V4L2_PIX_FMT_BGR32:
|
|
format |= LDBBSIFR_AL_PK | LDBBSIFR_RY | LDDFR_PKF_ARGB32;
|
|
break;
|
|
case V4L2_PIX_FMT_NV12:
|
|
case V4L2_PIX_FMT_NV21:
|
|
format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_420;
|
|
break;
|
|
case V4L2_PIX_FMT_NV16:
|
|
case V4L2_PIX_FMT_NV61:
|
|
format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_422;
|
|
break;
|
|
case V4L2_PIX_FMT_NV24:
|
|
case V4L2_PIX_FMT_NV42:
|
|
format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_444;
|
|
break;
|
|
}
|
|
|
|
lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index));
|
|
|
|
lcdc_write_overlay(ovl, LDBnBSIFR(ovl->index), format);
|
|
|
|
lcdc_write_overlay(ovl, LDBnBSSZR(ovl->index),
|
|
(ovl->yres << LDBBSSZR_BVSS_SHIFT) |
|
|
(ovl->xres << LDBBSSZR_BHSS_SHIFT));
|
|
lcdc_write_overlay(ovl, LDBnBLOCR(ovl->index),
|
|
(ovl->pos_y << LDBBLOCR_CVLC_SHIFT) |
|
|
(ovl->pos_x << LDBBLOCR_CHLC_SHIFT));
|
|
lcdc_write_overlay(ovl, LDBnBSMWR(ovl->index),
|
|
ovl->pitch << LDBBSMWR_BSMW_SHIFT);
|
|
|
|
lcdc_write_overlay(ovl, LDBnBSAYR(ovl->index), ovl->base_addr_y);
|
|
lcdc_write_overlay(ovl, LDBnBSACR(ovl->index), ovl->base_addr_c);
|
|
|
|
lcdc_write(ovl->channel->lcdc, LDBCR,
|
|
LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index));
|
|
}
|
|
|
|
/*
|
|
* __sh_mobile_lcdc_start - Configure and start the LCDC
|
|
* @priv: LCDC device
|
|
*
|
|
* Configure all enabled channels and start the LCDC device. All external
|
|
* devices (clocks, MERAM, panels, ...) are not touched by this function.
|
|
*/
|
|
static void __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch;
|
|
unsigned long tmp;
|
|
int k, m;
|
|
|
|
/* Enable LCDC channels. Read data from external memory, avoid using the
|
|
* BEU for now.
|
|
*/
|
|
lcdc_write(priv, _LDCNT2R, priv->ch[0].enabled | priv->ch[1].enabled);
|
|
|
|
/* Stop the LCDC first and disable all interrupts. */
|
|
sh_mobile_lcdc_start_stop(priv, 0);
|
|
lcdc_write(priv, _LDINTR, 0);
|
|
|
|
/* Configure power supply, dot clocks and start them. */
|
|
tmp = priv->lddckr;
|
|
for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
|
|
ch = &priv->ch[k];
|
|
if (!ch->enabled)
|
|
continue;
|
|
|
|
/* Power supply */
|
|
lcdc_write_chan(ch, LDPMR, 0);
|
|
|
|
m = ch->cfg->clock_divider;
|
|
if (!m)
|
|
continue;
|
|
|
|
/* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider
|
|
* denominator.
|
|
*/
|
|
lcdc_write_chan(ch, LDDCKPAT1R, 0);
|
|
lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1);
|
|
|
|
if (m == 1)
|
|
m = LDDCKR_MOSEL;
|
|
tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0);
|
|
}
|
|
|
|
lcdc_write(priv, _LDDCKR, tmp);
|
|
lcdc_write(priv, _LDDCKSTPR, 0);
|
|
lcdc_wait_bit(priv, _LDDCKSTPR, ~0, 0);
|
|
|
|
/* Setup geometry, format, frame buffer memory and operation mode. */
|
|
for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
|
|
ch = &priv->ch[k];
|
|
if (!ch->enabled)
|
|
continue;
|
|
|
|
sh_mobile_lcdc_geometry(ch);
|
|
|
|
tmp = ch->format->lddfr;
|
|
|
|
if (ch->format->yuv) {
|
|
switch (ch->colorspace) {
|
|
case V4L2_COLORSPACE_REC709:
|
|
tmp |= LDDFR_CF1;
|
|
break;
|
|
case V4L2_COLORSPACE_JPEG:
|
|
tmp |= LDDFR_CF0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
lcdc_write_chan(ch, LDDFR, tmp);
|
|
lcdc_write_chan(ch, LDMLSR, ch->line_size);
|
|
lcdc_write_chan(ch, LDSA1R, ch->base_addr_y);
|
|
if (ch->format->yuv)
|
|
lcdc_write_chan(ch, LDSA2R, ch->base_addr_c);
|
|
|
|
/* When using deferred I/O mode, configure the LCDC for one-shot
|
|
* operation and enable the frame end interrupt. Otherwise use
|
|
* continuous read mode.
|
|
*/
|
|
if (ch->ldmt1r_value & LDMT1R_IFM &&
|
|
ch->cfg->sys_bus_cfg.deferred_io_msec) {
|
|
lcdc_write_chan(ch, LDSM1R, LDSM1R_OS);
|
|
lcdc_write(priv, _LDINTR, LDINTR_FE);
|
|
} else {
|
|
lcdc_write_chan(ch, LDSM1R, 0);
|
|
}
|
|
}
|
|
|
|
/* Word and long word swap. */
|
|
switch (priv->ch[0].format->fourcc) {
|
|
case V4L2_PIX_FMT_RGB565:
|
|
case V4L2_PIX_FMT_NV21:
|
|
case V4L2_PIX_FMT_NV61:
|
|
case V4L2_PIX_FMT_NV42:
|
|
tmp = LDDDSR_LS | LDDDSR_WS;
|
|
break;
|
|
case V4L2_PIX_FMT_BGR24:
|
|
case V4L2_PIX_FMT_NV12:
|
|
case V4L2_PIX_FMT_NV16:
|
|
case V4L2_PIX_FMT_NV24:
|
|
tmp = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS;
|
|
break;
|
|
case V4L2_PIX_FMT_BGR32:
|
|
default:
|
|
tmp = LDDDSR_LS;
|
|
break;
|
|
}
|
|
lcdc_write(priv, _LDDDSR, tmp);
|
|
|
|
/* Enable the display output. */
|
|
lcdc_write(priv, _LDCNT1R, LDCNT1R_DE);
|
|
sh_mobile_lcdc_start_stop(priv, 1);
|
|
priv->started = 1;
|
|
}
|
|
|
|
static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch;
|
|
unsigned long tmp;
|
|
int ret;
|
|
int k;
|
|
|
|
/* enable clocks before accessing the hardware */
|
|
for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
|
|
if (priv->ch[k].enabled)
|
|
sh_mobile_lcdc_clk_on(priv);
|
|
}
|
|
|
|
/* reset */
|
|
lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LDCNT2R_BR);
|
|
lcdc_wait_bit(priv, _LDCNT2R, LDCNT2R_BR, 0);
|
|
|
|
for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
|
|
const struct sh_mobile_lcdc_panel_cfg *panel;
|
|
|
|
ch = &priv->ch[k];
|
|
if (!ch->enabled)
|
|
continue;
|
|
|
|
panel = &ch->cfg->panel_cfg;
|
|
if (panel->setup_sys) {
|
|
ret = panel->setup_sys(ch, &sh_mobile_lcdc_sys_bus_ops);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Compute frame buffer base address and pitch for each channel. */
|
|
for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
|
|
ch = &priv->ch[k];
|
|
if (!ch->enabled)
|
|
continue;
|
|
|
|
ch->base_addr_y = ch->dma_handle;
|
|
ch->base_addr_c = ch->dma_handle
|
|
+ ch->xres_virtual * ch->yres_virtual;
|
|
ch->line_size = ch->pitch;
|
|
}
|
|
|
|
for (k = 0; k < ARRAY_SIZE(priv->overlays); ++k) {
|
|
struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[k];
|
|
sh_mobile_lcdc_overlay_setup(ovl);
|
|
}
|
|
|
|
/* Start the LCDC. */
|
|
__sh_mobile_lcdc_start(priv);
|
|
|
|
/* Setup deferred I/O, tell the board code to enable the panels, and
|
|
* turn backlight on.
|
|
*/
|
|
for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
|
|
ch = &priv->ch[k];
|
|
if (!ch->enabled)
|
|
continue;
|
|
|
|
tmp = ch->cfg->sys_bus_cfg.deferred_io_msec;
|
|
if (ch->ldmt1r_value & LDMT1R_IFM && tmp) {
|
|
ch->defio.deferred_io = sh_mobile_lcdc_deferred_io;
|
|
ch->defio.delay = msecs_to_jiffies(tmp);
|
|
ch->info->fbdefio = &ch->defio;
|
|
fb_deferred_io_init(ch->info);
|
|
}
|
|
|
|
sh_mobile_lcdc_display_on(ch);
|
|
|
|
if (ch->bl) {
|
|
ch->bl->props.power = FB_BLANK_UNBLANK;
|
|
backlight_update_status(ch->bl);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch;
|
|
int k;
|
|
|
|
/* clean up deferred io and ask board code to disable panel */
|
|
for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
|
|
ch = &priv->ch[k];
|
|
if (!ch->enabled)
|
|
continue;
|
|
|
|
/* deferred io mode:
|
|
* flush frame, and wait for frame end interrupt
|
|
* clean up deferred io and enable clock
|
|
*/
|
|
if (ch->info && ch->info->fbdefio) {
|
|
ch->frame_end = 0;
|
|
schedule_delayed_work(&ch->info->deferred_work, 0);
|
|
wait_event(ch->frame_end_wait, ch->frame_end);
|
|
fb_deferred_io_cleanup(ch->info);
|
|
ch->info->fbdefio = NULL;
|
|
sh_mobile_lcdc_clk_on(priv);
|
|
}
|
|
|
|
if (ch->bl) {
|
|
ch->bl->props.power = FB_BLANK_POWERDOWN;
|
|
backlight_update_status(ch->bl);
|
|
}
|
|
|
|
sh_mobile_lcdc_display_off(ch);
|
|
}
|
|
|
|
/* stop the lcdc */
|
|
if (priv->started) {
|
|
sh_mobile_lcdc_start_stop(priv, 0);
|
|
priv->started = 0;
|
|
}
|
|
|
|
/* stop clocks */
|
|
for (k = 0; k < ARRAY_SIZE(priv->ch); k++)
|
|
if (priv->ch[k].enabled)
|
|
sh_mobile_lcdc_clk_off(priv);
|
|
}
|
|
|
|
static int __sh_mobile_lcdc_check_var(struct fb_var_screeninfo *var,
|
|
struct fb_info *info)
|
|
{
|
|
if (var->xres > MAX_XRES || var->yres > MAX_YRES)
|
|
return -EINVAL;
|
|
|
|
/* Make sure the virtual resolution is at least as big as the visible
|
|
* resolution.
|
|
*/
|
|
if (var->xres_virtual < var->xres)
|
|
var->xres_virtual = var->xres;
|
|
if (var->yres_virtual < var->yres)
|
|
var->yres_virtual = var->yres;
|
|
|
|
if (sh_mobile_format_is_fourcc(var)) {
|
|
const struct sh_mobile_lcdc_format_info *format;
|
|
|
|
format = sh_mobile_format_info(var->grayscale);
|
|
if (format == NULL)
|
|
return -EINVAL;
|
|
var->bits_per_pixel = format->bpp;
|
|
|
|
/* Default to RGB and JPEG color-spaces for RGB and YUV formats
|
|
* respectively.
|
|
*/
|
|
if (!format->yuv)
|
|
var->colorspace = V4L2_COLORSPACE_SRGB;
|
|
else if (var->colorspace != V4L2_COLORSPACE_REC709)
|
|
var->colorspace = V4L2_COLORSPACE_JPEG;
|
|
} else {
|
|
if (var->bits_per_pixel <= 16) { /* RGB 565 */
|
|
var->bits_per_pixel = 16;
|
|
var->red.offset = 11;
|
|
var->red.length = 5;
|
|
var->green.offset = 5;
|
|
var->green.length = 6;
|
|
var->blue.offset = 0;
|
|
var->blue.length = 5;
|
|
var->transp.offset = 0;
|
|
var->transp.length = 0;
|
|
} else if (var->bits_per_pixel <= 24) { /* RGB 888 */
|
|
var->bits_per_pixel = 24;
|
|
var->red.offset = 16;
|
|
var->red.length = 8;
|
|
var->green.offset = 8;
|
|
var->green.length = 8;
|
|
var->blue.offset = 0;
|
|
var->blue.length = 8;
|
|
var->transp.offset = 0;
|
|
var->transp.length = 0;
|
|
} else if (var->bits_per_pixel <= 32) { /* RGBA 888 */
|
|
var->bits_per_pixel = 32;
|
|
var->red.offset = 16;
|
|
var->red.length = 8;
|
|
var->green.offset = 8;
|
|
var->green.length = 8;
|
|
var->blue.offset = 0;
|
|
var->blue.length = 8;
|
|
var->transp.offset = 24;
|
|
var->transp.length = 8;
|
|
} else
|
|
return -EINVAL;
|
|
|
|
var->red.msb_right = 0;
|
|
var->green.msb_right = 0;
|
|
var->blue.msb_right = 0;
|
|
var->transp.msb_right = 0;
|
|
}
|
|
|
|
/* Make sure we don't exceed our allocated memory. */
|
|
if (var->xres_virtual * var->yres_virtual * var->bits_per_pixel / 8 >
|
|
info->fix.smem_len)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Frame buffer operations - Overlays
|
|
*/
|
|
|
|
static ssize_t
|
|
overlay_alpha_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct fb_info *info = dev_get_drvdata(dev);
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%u\n", ovl->alpha);
|
|
}
|
|
|
|
static ssize_t
|
|
overlay_alpha_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct fb_info *info = dev_get_drvdata(dev);
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
unsigned int alpha;
|
|
char *endp;
|
|
|
|
alpha = simple_strtoul(buf, &endp, 10);
|
|
if (isspace(*endp))
|
|
endp++;
|
|
|
|
if (endp - buf != count)
|
|
return -EINVAL;
|
|
|
|
if (alpha > 255)
|
|
return -EINVAL;
|
|
|
|
if (ovl->alpha != alpha) {
|
|
ovl->alpha = alpha;
|
|
|
|
if (ovl->mode == LCDC_OVERLAY_BLEND && ovl->enabled)
|
|
sh_mobile_lcdc_overlay_setup(ovl);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
overlay_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct fb_info *info = dev_get_drvdata(dev);
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%u\n", ovl->mode);
|
|
}
|
|
|
|
static ssize_t
|
|
overlay_mode_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct fb_info *info = dev_get_drvdata(dev);
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
unsigned int mode;
|
|
char *endp;
|
|
|
|
mode = simple_strtoul(buf, &endp, 10);
|
|
if (isspace(*endp))
|
|
endp++;
|
|
|
|
if (endp - buf != count)
|
|
return -EINVAL;
|
|
|
|
if (mode != LCDC_OVERLAY_BLEND && mode != LCDC_OVERLAY_ROP3)
|
|
return -EINVAL;
|
|
|
|
if (ovl->mode != mode) {
|
|
ovl->mode = mode;
|
|
|
|
if (ovl->enabled)
|
|
sh_mobile_lcdc_overlay_setup(ovl);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
overlay_position_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct fb_info *info = dev_get_drvdata(dev);
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%d,%d\n", ovl->pos_x, ovl->pos_y);
|
|
}
|
|
|
|
static ssize_t
|
|
overlay_position_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct fb_info *info = dev_get_drvdata(dev);
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
char *endp;
|
|
int pos_x;
|
|
int pos_y;
|
|
|
|
pos_x = simple_strtol(buf, &endp, 10);
|
|
if (*endp != ',')
|
|
return -EINVAL;
|
|
|
|
pos_y = simple_strtol(endp + 1, &endp, 10);
|
|
if (isspace(*endp))
|
|
endp++;
|
|
|
|
if (endp - buf != count)
|
|
return -EINVAL;
|
|
|
|
if (ovl->pos_x != pos_x || ovl->pos_y != pos_y) {
|
|
ovl->pos_x = pos_x;
|
|
ovl->pos_y = pos_y;
|
|
|
|
if (ovl->enabled)
|
|
sh_mobile_lcdc_overlay_setup(ovl);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
overlay_rop3_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct fb_info *info = dev_get_drvdata(dev);
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%u\n", ovl->rop3);
|
|
}
|
|
|
|
static ssize_t
|
|
overlay_rop3_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct fb_info *info = dev_get_drvdata(dev);
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
unsigned int rop3;
|
|
char *endp;
|
|
|
|
rop3 = simple_strtoul(buf, &endp, 10);
|
|
if (isspace(*endp))
|
|
endp++;
|
|
|
|
if (endp - buf != count)
|
|
return -EINVAL;
|
|
|
|
if (rop3 > 255)
|
|
return -EINVAL;
|
|
|
|
if (ovl->rop3 != rop3) {
|
|
ovl->rop3 = rop3;
|
|
|
|
if (ovl->mode == LCDC_OVERLAY_ROP3 && ovl->enabled)
|
|
sh_mobile_lcdc_overlay_setup(ovl);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct device_attribute overlay_sysfs_attrs[] = {
|
|
__ATTR(ovl_alpha, S_IRUGO|S_IWUSR,
|
|
overlay_alpha_show, overlay_alpha_store),
|
|
__ATTR(ovl_mode, S_IRUGO|S_IWUSR,
|
|
overlay_mode_show, overlay_mode_store),
|
|
__ATTR(ovl_position, S_IRUGO|S_IWUSR,
|
|
overlay_position_show, overlay_position_store),
|
|
__ATTR(ovl_rop3, S_IRUGO|S_IWUSR,
|
|
overlay_rop3_show, overlay_rop3_store),
|
|
};
|
|
|
|
static const struct fb_fix_screeninfo sh_mobile_lcdc_overlay_fix = {
|
|
.id = "SH Mobile LCDC",
|
|
.type = FB_TYPE_PACKED_PIXELS,
|
|
.visual = FB_VISUAL_TRUECOLOR,
|
|
.accel = FB_ACCEL_NONE,
|
|
.xpanstep = 1,
|
|
.ypanstep = 1,
|
|
.ywrapstep = 0,
|
|
.capabilities = FB_CAP_FOURCC,
|
|
};
|
|
|
|
static int sh_mobile_lcdc_overlay_pan(struct fb_var_screeninfo *var,
|
|
struct fb_info *info)
|
|
{
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
unsigned long base_addr_y;
|
|
unsigned long base_addr_c;
|
|
unsigned long y_offset;
|
|
unsigned long c_offset;
|
|
|
|
if (!ovl->format->yuv) {
|
|
y_offset = (var->yoffset * ovl->xres_virtual + var->xoffset)
|
|
* ovl->format->bpp / 8;
|
|
c_offset = 0;
|
|
} else {
|
|
unsigned int xsub = ovl->format->bpp < 24 ? 2 : 1;
|
|
unsigned int ysub = ovl->format->bpp < 16 ? 2 : 1;
|
|
|
|
y_offset = var->yoffset * ovl->xres_virtual + var->xoffset;
|
|
c_offset = var->yoffset / ysub * ovl->xres_virtual * 2 / xsub
|
|
+ var->xoffset * 2 / xsub;
|
|
}
|
|
|
|
/* If the Y offset hasn't changed, the C offset hasn't either. There's
|
|
* nothing to do in that case.
|
|
*/
|
|
if (y_offset == ovl->pan_y_offset)
|
|
return 0;
|
|
|
|
/* Set the source address for the next refresh */
|
|
base_addr_y = ovl->dma_handle + y_offset;
|
|
base_addr_c = ovl->dma_handle + ovl->xres_virtual * ovl->yres_virtual
|
|
+ c_offset;
|
|
|
|
ovl->base_addr_y = base_addr_y;
|
|
ovl->base_addr_c = base_addr_c;
|
|
ovl->pan_y_offset = y_offset;
|
|
|
|
lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index));
|
|
|
|
lcdc_write_overlay(ovl, LDBnBSAYR(ovl->index), ovl->base_addr_y);
|
|
lcdc_write_overlay(ovl, LDBnBSACR(ovl->index), ovl->base_addr_c);
|
|
|
|
lcdc_write(ovl->channel->lcdc, LDBCR,
|
|
LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sh_mobile_lcdc_overlay_ioctl(struct fb_info *info, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
|
|
switch (cmd) {
|
|
case FBIO_WAITFORVSYNC:
|
|
return sh_mobile_lcdc_wait_for_vsync(ovl->channel);
|
|
|
|
default:
|
|
return -ENOIOCTLCMD;
|
|
}
|
|
}
|
|
|
|
static int sh_mobile_lcdc_overlay_check_var(struct fb_var_screeninfo *var,
|
|
struct fb_info *info)
|
|
{
|
|
return __sh_mobile_lcdc_check_var(var, info);
|
|
}
|
|
|
|
static int sh_mobile_lcdc_overlay_set_par(struct fb_info *info)
|
|
{
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
|
|
ovl->format =
|
|
sh_mobile_format_info(sh_mobile_format_fourcc(&info->var));
|
|
|
|
ovl->xres = info->var.xres;
|
|
ovl->xres_virtual = info->var.xres_virtual;
|
|
ovl->yres = info->var.yres;
|
|
ovl->yres_virtual = info->var.yres_virtual;
|
|
|
|
if (ovl->format->yuv)
|
|
ovl->pitch = info->var.xres_virtual;
|
|
else
|
|
ovl->pitch = info->var.xres_virtual * ovl->format->bpp / 8;
|
|
|
|
sh_mobile_lcdc_overlay_setup(ovl);
|
|
|
|
info->fix.line_length = ovl->pitch;
|
|
|
|
if (sh_mobile_format_is_fourcc(&info->var)) {
|
|
info->fix.type = FB_TYPE_FOURCC;
|
|
info->fix.visual = FB_VISUAL_FOURCC;
|
|
} else {
|
|
info->fix.type = FB_TYPE_PACKED_PIXELS;
|
|
info->fix.visual = FB_VISUAL_TRUECOLOR;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Overlay blanking. Disable the overlay when blanked. */
|
|
static int sh_mobile_lcdc_overlay_blank(int blank, struct fb_info *info)
|
|
{
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
|
|
ovl->enabled = !blank;
|
|
sh_mobile_lcdc_overlay_setup(ovl);
|
|
|
|
/* Prevent the backlight from receiving a blanking event by returning
|
|
* a non-zero value.
|
|
*/
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
sh_mobile_lcdc_overlay_mmap(struct fb_info *info, struct vm_area_struct *vma)
|
|
{
|
|
struct sh_mobile_lcdc_overlay *ovl = info->par;
|
|
|
|
return dma_mmap_coherent(ovl->channel->lcdc->dev, vma, ovl->fb_mem,
|
|
ovl->dma_handle, ovl->fb_size);
|
|
}
|
|
|
|
static const struct fb_ops sh_mobile_lcdc_overlay_ops = {
|
|
.owner = THIS_MODULE,
|
|
.fb_read = fb_sys_read,
|
|
.fb_write = fb_sys_write,
|
|
.fb_fillrect = sys_fillrect,
|
|
.fb_copyarea = sys_copyarea,
|
|
.fb_imageblit = sys_imageblit,
|
|
.fb_blank = sh_mobile_lcdc_overlay_blank,
|
|
.fb_pan_display = sh_mobile_lcdc_overlay_pan,
|
|
.fb_ioctl = sh_mobile_lcdc_overlay_ioctl,
|
|
.fb_check_var = sh_mobile_lcdc_overlay_check_var,
|
|
.fb_set_par = sh_mobile_lcdc_overlay_set_par,
|
|
.fb_mmap = sh_mobile_lcdc_overlay_mmap,
|
|
};
|
|
|
|
static void
|
|
sh_mobile_lcdc_overlay_fb_unregister(struct sh_mobile_lcdc_overlay *ovl)
|
|
{
|
|
struct fb_info *info = ovl->info;
|
|
|
|
if (info == NULL || info->dev == NULL)
|
|
return;
|
|
|
|
unregister_framebuffer(ovl->info);
|
|
}
|
|
|
|
static int
|
|
sh_mobile_lcdc_overlay_fb_register(struct sh_mobile_lcdc_overlay *ovl)
|
|
{
|
|
struct sh_mobile_lcdc_priv *lcdc = ovl->channel->lcdc;
|
|
struct fb_info *info = ovl->info;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
if (info == NULL)
|
|
return 0;
|
|
|
|
ret = register_framebuffer(info);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dev_info(lcdc->dev, "registered %s/overlay %u as %dx%d %dbpp.\n",
|
|
dev_name(lcdc->dev), ovl->index, info->var.xres,
|
|
info->var.yres, info->var.bits_per_pixel);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(overlay_sysfs_attrs); ++i) {
|
|
ret = device_create_file(info->dev, &overlay_sysfs_attrs[i]);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
sh_mobile_lcdc_overlay_fb_cleanup(struct sh_mobile_lcdc_overlay *ovl)
|
|
{
|
|
struct fb_info *info = ovl->info;
|
|
|
|
if (info == NULL || info->device == NULL)
|
|
return;
|
|
|
|
framebuffer_release(info);
|
|
}
|
|
|
|
static int
|
|
sh_mobile_lcdc_overlay_fb_init(struct sh_mobile_lcdc_overlay *ovl)
|
|
{
|
|
struct sh_mobile_lcdc_priv *priv = ovl->channel->lcdc;
|
|
struct fb_var_screeninfo *var;
|
|
struct fb_info *info;
|
|
|
|
/* Allocate and initialize the frame buffer device. */
|
|
info = framebuffer_alloc(0, priv->dev);
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
ovl->info = info;
|
|
|
|
info->flags = FBINFO_FLAG_DEFAULT;
|
|
info->fbops = &sh_mobile_lcdc_overlay_ops;
|
|
info->device = priv->dev;
|
|
info->screen_base = ovl->fb_mem;
|
|
info->par = ovl;
|
|
|
|
/* Initialize fixed screen information. Restrict pan to 2 lines steps
|
|
* for NV12 and NV21.
|
|
*/
|
|
info->fix = sh_mobile_lcdc_overlay_fix;
|
|
snprintf(info->fix.id, sizeof(info->fix.id),
|
|
"SH Mobile LCDC Overlay %u", ovl->index);
|
|
info->fix.smem_start = ovl->dma_handle;
|
|
info->fix.smem_len = ovl->fb_size;
|
|
info->fix.line_length = ovl->pitch;
|
|
|
|
if (ovl->format->yuv)
|
|
info->fix.visual = FB_VISUAL_FOURCC;
|
|
else
|
|
info->fix.visual = FB_VISUAL_TRUECOLOR;
|
|
|
|
switch (ovl->format->fourcc) {
|
|
case V4L2_PIX_FMT_NV12:
|
|
case V4L2_PIX_FMT_NV21:
|
|
info->fix.ypanstep = 2;
|
|
/* Fall through */
|
|
case V4L2_PIX_FMT_NV16:
|
|
case V4L2_PIX_FMT_NV61:
|
|
info->fix.xpanstep = 2;
|
|
}
|
|
|
|
/* Initialize variable screen information. */
|
|
var = &info->var;
|
|
memset(var, 0, sizeof(*var));
|
|
var->xres = ovl->xres;
|
|
var->yres = ovl->yres;
|
|
var->xres_virtual = ovl->xres_virtual;
|
|
var->yres_virtual = ovl->yres_virtual;
|
|
var->activate = FB_ACTIVATE_NOW;
|
|
|
|
/* Use the legacy API by default for RGB formats, and the FOURCC API
|
|
* for YUV formats.
|
|
*/
|
|
if (!ovl->format->yuv)
|
|
var->bits_per_pixel = ovl->format->bpp;
|
|
else
|
|
var->grayscale = ovl->format->fourcc;
|
|
|
|
return sh_mobile_lcdc_overlay_check_var(var, info);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Frame buffer operations - main frame buffer
|
|
*/
|
|
|
|
static int sh_mobile_lcdc_setcolreg(u_int regno,
|
|
u_int red, u_int green, u_int blue,
|
|
u_int transp, struct fb_info *info)
|
|
{
|
|
u32 *palette = info->pseudo_palette;
|
|
|
|
if (regno >= PALETTE_NR)
|
|
return -EINVAL;
|
|
|
|
/* only FB_VISUAL_TRUECOLOR supported */
|
|
|
|
red >>= 16 - info->var.red.length;
|
|
green >>= 16 - info->var.green.length;
|
|
blue >>= 16 - info->var.blue.length;
|
|
transp >>= 16 - info->var.transp.length;
|
|
|
|
palette[regno] = (red << info->var.red.offset) |
|
|
(green << info->var.green.offset) |
|
|
(blue << info->var.blue.offset) |
|
|
(transp << info->var.transp.offset);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct fb_fix_screeninfo sh_mobile_lcdc_fix = {
|
|
.id = "SH Mobile LCDC",
|
|
.type = FB_TYPE_PACKED_PIXELS,
|
|
.visual = FB_VISUAL_TRUECOLOR,
|
|
.accel = FB_ACCEL_NONE,
|
|
.xpanstep = 1,
|
|
.ypanstep = 1,
|
|
.ywrapstep = 0,
|
|
.capabilities = FB_CAP_FOURCC,
|
|
};
|
|
|
|
static void sh_mobile_lcdc_fillrect(struct fb_info *info,
|
|
const struct fb_fillrect *rect)
|
|
{
|
|
sys_fillrect(info, rect);
|
|
sh_mobile_lcdc_deferred_io_touch(info);
|
|
}
|
|
|
|
static void sh_mobile_lcdc_copyarea(struct fb_info *info,
|
|
const struct fb_copyarea *area)
|
|
{
|
|
sys_copyarea(info, area);
|
|
sh_mobile_lcdc_deferred_io_touch(info);
|
|
}
|
|
|
|
static void sh_mobile_lcdc_imageblit(struct fb_info *info,
|
|
const struct fb_image *image)
|
|
{
|
|
sys_imageblit(info, image);
|
|
sh_mobile_lcdc_deferred_io_touch(info);
|
|
}
|
|
|
|
static int sh_mobile_lcdc_pan(struct fb_var_screeninfo *var,
|
|
struct fb_info *info)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
|
struct sh_mobile_lcdc_priv *priv = ch->lcdc;
|
|
unsigned long ldrcntr;
|
|
unsigned long base_addr_y, base_addr_c;
|
|
unsigned long y_offset;
|
|
unsigned long c_offset;
|
|
|
|
if (!ch->format->yuv) {
|
|
y_offset = (var->yoffset * ch->xres_virtual + var->xoffset)
|
|
* ch->format->bpp / 8;
|
|
c_offset = 0;
|
|
} else {
|
|
unsigned int xsub = ch->format->bpp < 24 ? 2 : 1;
|
|
unsigned int ysub = ch->format->bpp < 16 ? 2 : 1;
|
|
|
|
y_offset = var->yoffset * ch->xres_virtual + var->xoffset;
|
|
c_offset = var->yoffset / ysub * ch->xres_virtual * 2 / xsub
|
|
+ var->xoffset * 2 / xsub;
|
|
}
|
|
|
|
/* If the Y offset hasn't changed, the C offset hasn't either. There's
|
|
* nothing to do in that case.
|
|
*/
|
|
if (y_offset == ch->pan_y_offset)
|
|
return 0;
|
|
|
|
/* Set the source address for the next refresh */
|
|
base_addr_y = ch->dma_handle + y_offset;
|
|
base_addr_c = ch->dma_handle + ch->xres_virtual * ch->yres_virtual
|
|
+ c_offset;
|
|
|
|
ch->base_addr_y = base_addr_y;
|
|
ch->base_addr_c = base_addr_c;
|
|
ch->pan_y_offset = y_offset;
|
|
|
|
lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y);
|
|
if (ch->format->yuv)
|
|
lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c);
|
|
|
|
ldrcntr = lcdc_read(priv, _LDRCNTR);
|
|
if (lcdc_chan_is_sublcd(ch))
|
|
lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS);
|
|
else
|
|
lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_MRS);
|
|
|
|
|
|
sh_mobile_lcdc_deferred_io_touch(info);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sh_mobile_lcdc_ioctl(struct fb_info *info, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
|
int retval;
|
|
|
|
switch (cmd) {
|
|
case FBIO_WAITFORVSYNC:
|
|
retval = sh_mobile_lcdc_wait_for_vsync(ch);
|
|
break;
|
|
|
|
default:
|
|
retval = -ENOIOCTLCMD;
|
|
break;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
static void sh_mobile_fb_reconfig(struct fb_info *info)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
|
struct fb_var_screeninfo var;
|
|
struct fb_videomode mode;
|
|
|
|
if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par))
|
|
/* More framebuffer users are active */
|
|
return;
|
|
|
|
fb_var_to_videomode(&mode, &info->var);
|
|
|
|
if (fb_mode_is_equal(&ch->display.mode, &mode))
|
|
return;
|
|
|
|
/* Display has been re-plugged, framebuffer is free now, reconfigure */
|
|
var = info->var;
|
|
fb_videomode_to_var(&var, &ch->display.mode);
|
|
var.width = ch->display.width;
|
|
var.height = ch->display.height;
|
|
var.activate = FB_ACTIVATE_NOW;
|
|
|
|
if (fb_set_var(info, &var) < 0)
|
|
/* Couldn't reconfigure, hopefully, can continue as before */
|
|
return;
|
|
|
|
fbcon_update_vcs(info, true);
|
|
}
|
|
|
|
/*
|
|
* Locking: both .fb_release() and .fb_open() are called with info->lock held if
|
|
* user == 1, or with console sem held, if user == 0.
|
|
*/
|
|
static int sh_mobile_lcdc_release(struct fb_info *info, int user)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
|
|
|
mutex_lock(&ch->open_lock);
|
|
dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count);
|
|
|
|
ch->use_count--;
|
|
|
|
/* Nothing to reconfigure, when called from fbcon */
|
|
if (user) {
|
|
console_lock();
|
|
sh_mobile_fb_reconfig(info);
|
|
console_unlock();
|
|
}
|
|
|
|
mutex_unlock(&ch->open_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sh_mobile_lcdc_open(struct fb_info *info, int user)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
|
|
|
mutex_lock(&ch->open_lock);
|
|
ch->use_count++;
|
|
|
|
dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count);
|
|
mutex_unlock(&ch->open_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sh_mobile_lcdc_check_var(struct fb_var_screeninfo *var,
|
|
struct fb_info *info)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
|
struct sh_mobile_lcdc_priv *p = ch->lcdc;
|
|
unsigned int best_dist = (unsigned int)-1;
|
|
unsigned int best_xres = 0;
|
|
unsigned int best_yres = 0;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
/* If board code provides us with a list of available modes, make sure
|
|
* we use one of them. Find the mode closest to the requested one. The
|
|
* distance between two modes is defined as the size of the
|
|
* non-overlapping parts of the two rectangles.
|
|
*/
|
|
for (i = 0; i < ch->cfg->num_modes; ++i) {
|
|
const struct fb_videomode *mode = &ch->cfg->lcd_modes[i];
|
|
unsigned int dist;
|
|
|
|
/* We can only round up. */
|
|
if (var->xres > mode->xres || var->yres > mode->yres)
|
|
continue;
|
|
|
|
dist = var->xres * var->yres + mode->xres * mode->yres
|
|
- 2 * min(var->xres, mode->xres)
|
|
* min(var->yres, mode->yres);
|
|
|
|
if (dist < best_dist) {
|
|
best_xres = mode->xres;
|
|
best_yres = mode->yres;
|
|
best_dist = dist;
|
|
}
|
|
}
|
|
|
|
/* If no available mode can be used, return an error. */
|
|
if (ch->cfg->num_modes != 0) {
|
|
if (best_dist == (unsigned int)-1)
|
|
return -EINVAL;
|
|
|
|
var->xres = best_xres;
|
|
var->yres = best_yres;
|
|
}
|
|
|
|
ret = __sh_mobile_lcdc_check_var(var, info);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* only accept the forced_fourcc for dual channel configurations */
|
|
if (p->forced_fourcc &&
|
|
p->forced_fourcc != sh_mobile_format_fourcc(var))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sh_mobile_lcdc_set_par(struct fb_info *info)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
|
int ret;
|
|
|
|
sh_mobile_lcdc_stop(ch->lcdc);
|
|
|
|
ch->format = sh_mobile_format_info(sh_mobile_format_fourcc(&info->var));
|
|
ch->colorspace = info->var.colorspace;
|
|
|
|
ch->xres = info->var.xres;
|
|
ch->xres_virtual = info->var.xres_virtual;
|
|
ch->yres = info->var.yres;
|
|
ch->yres_virtual = info->var.yres_virtual;
|
|
|
|
if (ch->format->yuv)
|
|
ch->pitch = info->var.xres_virtual;
|
|
else
|
|
ch->pitch = info->var.xres_virtual * ch->format->bpp / 8;
|
|
|
|
ret = sh_mobile_lcdc_start(ch->lcdc);
|
|
if (ret < 0)
|
|
dev_err(info->dev, "%s: unable to restart LCDC\n", __func__);
|
|
|
|
info->fix.line_length = ch->pitch;
|
|
|
|
if (sh_mobile_format_is_fourcc(&info->var)) {
|
|
info->fix.type = FB_TYPE_FOURCC;
|
|
info->fix.visual = FB_VISUAL_FOURCC;
|
|
} else {
|
|
info->fix.type = FB_TYPE_PACKED_PIXELS;
|
|
info->fix.visual = FB_VISUAL_TRUECOLOR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Screen blanking. Behavior is as follows:
|
|
* FB_BLANK_UNBLANK: screen unblanked, clocks enabled
|
|
* FB_BLANK_NORMAL: screen blanked, clocks enabled
|
|
* FB_BLANK_VSYNC,
|
|
* FB_BLANK_HSYNC,
|
|
* FB_BLANK_POWEROFF: screen blanked, clocks disabled
|
|
*/
|
|
static int sh_mobile_lcdc_blank(int blank, struct fb_info *info)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
|
struct sh_mobile_lcdc_priv *p = ch->lcdc;
|
|
|
|
/* blank the screen? */
|
|
if (blank > FB_BLANK_UNBLANK && ch->blank_status == FB_BLANK_UNBLANK) {
|
|
struct fb_fillrect rect = {
|
|
.width = ch->xres,
|
|
.height = ch->yres,
|
|
};
|
|
sh_mobile_lcdc_fillrect(info, &rect);
|
|
}
|
|
/* turn clocks on? */
|
|
if (blank <= FB_BLANK_NORMAL && ch->blank_status > FB_BLANK_NORMAL) {
|
|
sh_mobile_lcdc_clk_on(p);
|
|
}
|
|
/* turn clocks off? */
|
|
if (blank > FB_BLANK_NORMAL && ch->blank_status <= FB_BLANK_NORMAL) {
|
|
/* make sure the screen is updated with the black fill before
|
|
* switching the clocks off. one vsync is not enough since
|
|
* blanking may occur in the middle of a refresh. deferred io
|
|
* mode will reenable the clocks and update the screen in time,
|
|
* so it does not need this. */
|
|
if (!info->fbdefio) {
|
|
sh_mobile_lcdc_wait_for_vsync(ch);
|
|
sh_mobile_lcdc_wait_for_vsync(ch);
|
|
}
|
|
sh_mobile_lcdc_clk_off(p);
|
|
}
|
|
|
|
ch->blank_status = blank;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sh_mobile_lcdc_mmap(struct fb_info *info, struct vm_area_struct *vma)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
|
|
|
return dma_mmap_coherent(ch->lcdc->dev, vma, ch->fb_mem,
|
|
ch->dma_handle, ch->fb_size);
|
|
}
|
|
|
|
static const struct fb_ops sh_mobile_lcdc_ops = {
|
|
.owner = THIS_MODULE,
|
|
.fb_setcolreg = sh_mobile_lcdc_setcolreg,
|
|
.fb_read = fb_sys_read,
|
|
.fb_write = fb_sys_write,
|
|
.fb_fillrect = sh_mobile_lcdc_fillrect,
|
|
.fb_copyarea = sh_mobile_lcdc_copyarea,
|
|
.fb_imageblit = sh_mobile_lcdc_imageblit,
|
|
.fb_blank = sh_mobile_lcdc_blank,
|
|
.fb_pan_display = sh_mobile_lcdc_pan,
|
|
.fb_ioctl = sh_mobile_lcdc_ioctl,
|
|
.fb_open = sh_mobile_lcdc_open,
|
|
.fb_release = sh_mobile_lcdc_release,
|
|
.fb_check_var = sh_mobile_lcdc_check_var,
|
|
.fb_set_par = sh_mobile_lcdc_set_par,
|
|
.fb_mmap = sh_mobile_lcdc_mmap,
|
|
};
|
|
|
|
static void
|
|
sh_mobile_lcdc_channel_fb_unregister(struct sh_mobile_lcdc_chan *ch)
|
|
{
|
|
if (ch->info && ch->info->dev)
|
|
unregister_framebuffer(ch->info);
|
|
}
|
|
|
|
static int
|
|
sh_mobile_lcdc_channel_fb_register(struct sh_mobile_lcdc_chan *ch)
|
|
{
|
|
struct fb_info *info = ch->info;
|
|
int ret;
|
|
|
|
if (info->fbdefio) {
|
|
ch->sglist = vmalloc(sizeof(struct scatterlist) *
|
|
ch->fb_size >> PAGE_SHIFT);
|
|
if (!ch->sglist)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
info->bl_dev = ch->bl;
|
|
|
|
ret = register_framebuffer(info);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dev_info(ch->lcdc->dev, "registered %s/%s as %dx%d %dbpp.\n",
|
|
dev_name(ch->lcdc->dev), (ch->cfg->chan == LCDC_CHAN_MAINLCD) ?
|
|
"mainlcd" : "sublcd", info->var.xres, info->var.yres,
|
|
info->var.bits_per_pixel);
|
|
|
|
/* deferred io mode: disable clock to save power */
|
|
if (info->fbdefio || info->state == FBINFO_STATE_SUSPENDED)
|
|
sh_mobile_lcdc_clk_off(ch->lcdc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
sh_mobile_lcdc_channel_fb_cleanup(struct sh_mobile_lcdc_chan *ch)
|
|
{
|
|
struct fb_info *info = ch->info;
|
|
|
|
if (!info || !info->device)
|
|
return;
|
|
|
|
vfree(ch->sglist);
|
|
|
|
fb_dealloc_cmap(&info->cmap);
|
|
framebuffer_release(info);
|
|
}
|
|
|
|
static int
|
|
sh_mobile_lcdc_channel_fb_init(struct sh_mobile_lcdc_chan *ch,
|
|
const struct fb_videomode *modes,
|
|
unsigned int num_modes)
|
|
{
|
|
struct sh_mobile_lcdc_priv *priv = ch->lcdc;
|
|
struct fb_var_screeninfo *var;
|
|
struct fb_info *info;
|
|
int ret;
|
|
|
|
/* Allocate and initialize the frame buffer device. Create the modes
|
|
* list and allocate the color map.
|
|
*/
|
|
info = framebuffer_alloc(0, priv->dev);
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
ch->info = info;
|
|
|
|
info->flags = FBINFO_FLAG_DEFAULT;
|
|
info->fbops = &sh_mobile_lcdc_ops;
|
|
info->device = priv->dev;
|
|
info->screen_base = ch->fb_mem;
|
|
info->pseudo_palette = &ch->pseudo_palette;
|
|
info->par = ch;
|
|
|
|
fb_videomode_to_modelist(modes, num_modes, &info->modelist);
|
|
|
|
ret = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0);
|
|
if (ret < 0) {
|
|
dev_err(priv->dev, "unable to allocate cmap\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Initialize fixed screen information. Restrict pan to 2 lines steps
|
|
* for NV12 and NV21.
|
|
*/
|
|
info->fix = sh_mobile_lcdc_fix;
|
|
info->fix.smem_start = ch->dma_handle;
|
|
info->fix.smem_len = ch->fb_size;
|
|
info->fix.line_length = ch->pitch;
|
|
|
|
if (ch->format->yuv)
|
|
info->fix.visual = FB_VISUAL_FOURCC;
|
|
else
|
|
info->fix.visual = FB_VISUAL_TRUECOLOR;
|
|
|
|
switch (ch->format->fourcc) {
|
|
case V4L2_PIX_FMT_NV12:
|
|
case V4L2_PIX_FMT_NV21:
|
|
info->fix.ypanstep = 2;
|
|
/* Fall through */
|
|
case V4L2_PIX_FMT_NV16:
|
|
case V4L2_PIX_FMT_NV61:
|
|
info->fix.xpanstep = 2;
|
|
}
|
|
|
|
/* Initialize variable screen information using the first mode as
|
|
* default.
|
|
*/
|
|
var = &info->var;
|
|
fb_videomode_to_var(var, modes);
|
|
var->width = ch->display.width;
|
|
var->height = ch->display.height;
|
|
var->xres_virtual = ch->xres_virtual;
|
|
var->yres_virtual = ch->yres_virtual;
|
|
var->activate = FB_ACTIVATE_NOW;
|
|
|
|
/* Use the legacy API by default for RGB formats, and the FOURCC API
|
|
* for YUV formats.
|
|
*/
|
|
if (!ch->format->yuv)
|
|
var->bits_per_pixel = ch->format->bpp;
|
|
else
|
|
var->grayscale = ch->format->fourcc;
|
|
|
|
ret = sh_mobile_lcdc_check_var(var, info);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Backlight
|
|
*/
|
|
|
|
static int sh_mobile_lcdc_update_bl(struct backlight_device *bdev)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev);
|
|
int brightness = bdev->props.brightness;
|
|
|
|
if (bdev->props.power != FB_BLANK_UNBLANK ||
|
|
bdev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
|
|
brightness = 0;
|
|
|
|
ch->bl_brightness = brightness;
|
|
return ch->cfg->bl_info.set_brightness(brightness);
|
|
}
|
|
|
|
static int sh_mobile_lcdc_get_brightness(struct backlight_device *bdev)
|
|
{
|
|
struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev);
|
|
|
|
return ch->bl_brightness;
|
|
}
|
|
|
|
static int sh_mobile_lcdc_check_fb(struct backlight_device *bdev,
|
|
struct fb_info *info)
|
|
{
|
|
return (info->bl_dev == bdev);
|
|
}
|
|
|
|
static const struct backlight_ops sh_mobile_lcdc_bl_ops = {
|
|
.options = BL_CORE_SUSPENDRESUME,
|
|
.update_status = sh_mobile_lcdc_update_bl,
|
|
.get_brightness = sh_mobile_lcdc_get_brightness,
|
|
.check_fb = sh_mobile_lcdc_check_fb,
|
|
};
|
|
|
|
static struct backlight_device *sh_mobile_lcdc_bl_probe(struct device *parent,
|
|
struct sh_mobile_lcdc_chan *ch)
|
|
{
|
|
struct backlight_device *bl;
|
|
|
|
bl = backlight_device_register(ch->cfg->bl_info.name, parent, ch,
|
|
&sh_mobile_lcdc_bl_ops, NULL);
|
|
if (IS_ERR(bl)) {
|
|
dev_err(parent, "unable to register backlight device: %ld\n",
|
|
PTR_ERR(bl));
|
|
return NULL;
|
|
}
|
|
|
|
bl->props.max_brightness = ch->cfg->bl_info.max_brightness;
|
|
bl->props.brightness = bl->props.max_brightness;
|
|
backlight_update_status(bl);
|
|
|
|
return bl;
|
|
}
|
|
|
|
static void sh_mobile_lcdc_bl_remove(struct backlight_device *bdev)
|
|
{
|
|
backlight_device_unregister(bdev);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Power management
|
|
*/
|
|
|
|
static int sh_mobile_lcdc_suspend(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
sh_mobile_lcdc_stop(platform_get_drvdata(pdev));
|
|
return 0;
|
|
}
|
|
|
|
static int sh_mobile_lcdc_resume(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
|
|
return sh_mobile_lcdc_start(platform_get_drvdata(pdev));
|
|
}
|
|
|
|
static int sh_mobile_lcdc_runtime_suspend(struct device *dev)
|
|
{
|
|
struct sh_mobile_lcdc_priv *priv = dev_get_drvdata(dev);
|
|
|
|
/* turn off LCDC hardware */
|
|
lcdc_write(priv, _LDCNT1R, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sh_mobile_lcdc_runtime_resume(struct device *dev)
|
|
{
|
|
struct sh_mobile_lcdc_priv *priv = dev_get_drvdata(dev);
|
|
|
|
__sh_mobile_lcdc_start(priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = {
|
|
.suspend = sh_mobile_lcdc_suspend,
|
|
.resume = sh_mobile_lcdc_resume,
|
|
.runtime_suspend = sh_mobile_lcdc_runtime_suspend,
|
|
.runtime_resume = sh_mobile_lcdc_runtime_resume,
|
|
};
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Framebuffer notifier
|
|
*/
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Probe/remove and driver init/exit
|
|
*/
|
|
|
|
static const struct fb_videomode default_720p = {
|
|
.name = "HDMI 720p",
|
|
.xres = 1280,
|
|
.yres = 720,
|
|
|
|
.left_margin = 220,
|
|
.right_margin = 110,
|
|
.hsync_len = 40,
|
|
|
|
.upper_margin = 20,
|
|
.lower_margin = 5,
|
|
.vsync_len = 5,
|
|
|
|
.pixclock = 13468,
|
|
.refresh = 60,
|
|
.sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
|
|
};
|
|
|
|
static int sh_mobile_lcdc_remove(struct platform_device *pdev)
|
|
{
|
|
struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev);
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(priv->overlays); i++)
|
|
sh_mobile_lcdc_overlay_fb_unregister(&priv->overlays[i]);
|
|
for (i = 0; i < ARRAY_SIZE(priv->ch); i++)
|
|
sh_mobile_lcdc_channel_fb_unregister(&priv->ch[i]);
|
|
|
|
sh_mobile_lcdc_stop(priv);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(priv->overlays); i++) {
|
|
struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i];
|
|
|
|
sh_mobile_lcdc_overlay_fb_cleanup(ovl);
|
|
|
|
if (ovl->fb_mem)
|
|
dma_free_coherent(&pdev->dev, ovl->fb_size,
|
|
ovl->fb_mem, ovl->dma_handle);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(priv->ch); i++) {
|
|
struct sh_mobile_lcdc_chan *ch = &priv->ch[i];
|
|
|
|
if (ch->tx_dev) {
|
|
ch->tx_dev->lcdc = NULL;
|
|
module_put(ch->cfg->tx_dev->dev.driver->owner);
|
|
}
|
|
|
|
sh_mobile_lcdc_channel_fb_cleanup(ch);
|
|
|
|
if (ch->fb_mem)
|
|
dma_free_coherent(&pdev->dev, ch->fb_size,
|
|
ch->fb_mem, ch->dma_handle);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(priv->ch); i++) {
|
|
struct sh_mobile_lcdc_chan *ch = &priv->ch[i];
|
|
|
|
if (ch->bl)
|
|
sh_mobile_lcdc_bl_remove(ch->bl);
|
|
mutex_destroy(&ch->open_lock);
|
|
}
|
|
|
|
if (priv->dot_clk) {
|
|
pm_runtime_disable(&pdev->dev);
|
|
clk_put(priv->dot_clk);
|
|
}
|
|
|
|
if (priv->base)
|
|
iounmap(priv->base);
|
|
|
|
if (priv->irq)
|
|
free_irq(priv->irq, priv);
|
|
kfree(priv);
|
|
return 0;
|
|
}
|
|
|
|
static int sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch)
|
|
{
|
|
int interface_type = ch->cfg->interface_type;
|
|
|
|
switch (interface_type) {
|
|
case RGB8:
|
|
case RGB9:
|
|
case RGB12A:
|
|
case RGB12B:
|
|
case RGB16:
|
|
case RGB18:
|
|
case RGB24:
|
|
case SYS8A:
|
|
case SYS8B:
|
|
case SYS8C:
|
|
case SYS8D:
|
|
case SYS9:
|
|
case SYS12:
|
|
case SYS16A:
|
|
case SYS16B:
|
|
case SYS16C:
|
|
case SYS18:
|
|
case SYS24:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* SUBLCD only supports SYS interface */
|
|
if (lcdc_chan_is_sublcd(ch)) {
|
|
if (!(interface_type & LDMT1R_IFM))
|
|
return -EINVAL;
|
|
|
|
interface_type &= ~LDMT1R_IFM;
|
|
}
|
|
|
|
ch->ldmt1r_value = interface_type;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sh_mobile_lcdc_overlay_init(struct sh_mobile_lcdc_overlay *ovl)
|
|
{
|
|
const struct sh_mobile_lcdc_format_info *format;
|
|
struct device *dev = ovl->channel->lcdc->dev;
|
|
int ret;
|
|
|
|
if (ovl->cfg->fourcc == 0)
|
|
return 0;
|
|
|
|
/* Validate the format. */
|
|
format = sh_mobile_format_info(ovl->cfg->fourcc);
|
|
if (format == NULL) {
|
|
dev_err(dev, "Invalid FOURCC %08x\n", ovl->cfg->fourcc);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ovl->enabled = false;
|
|
ovl->mode = LCDC_OVERLAY_BLEND;
|
|
ovl->alpha = 255;
|
|
ovl->rop3 = 0;
|
|
ovl->pos_x = 0;
|
|
ovl->pos_y = 0;
|
|
|
|
/* The default Y virtual resolution is twice the panel size to allow for
|
|
* double-buffering.
|
|
*/
|
|
ovl->format = format;
|
|
ovl->xres = ovl->cfg->max_xres;
|
|
ovl->xres_virtual = ovl->xres;
|
|
ovl->yres = ovl->cfg->max_yres;
|
|
ovl->yres_virtual = ovl->yres * 2;
|
|
|
|
if (!format->yuv)
|
|
ovl->pitch = ovl->xres_virtual * format->bpp / 8;
|
|
else
|
|
ovl->pitch = ovl->xres_virtual;
|
|
|
|
/* Allocate frame buffer memory. */
|
|
ovl->fb_size = ovl->cfg->max_xres * ovl->cfg->max_yres
|
|
* format->bpp / 8 * 2;
|
|
ovl->fb_mem = dma_alloc_coherent(dev, ovl->fb_size, &ovl->dma_handle,
|
|
GFP_KERNEL);
|
|
if (!ovl->fb_mem) {
|
|
dev_err(dev, "unable to allocate buffer\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = sh_mobile_lcdc_overlay_fb_init(ovl);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sh_mobile_lcdc_channel_init(struct sh_mobile_lcdc_chan *ch)
|
|
{
|
|
const struct sh_mobile_lcdc_format_info *format;
|
|
const struct sh_mobile_lcdc_chan_cfg *cfg = ch->cfg;
|
|
struct device *dev = ch->lcdc->dev;
|
|
const struct fb_videomode *max_mode;
|
|
const struct fb_videomode *mode;
|
|
unsigned int num_modes;
|
|
unsigned int max_size;
|
|
unsigned int i;
|
|
|
|
/* Validate the format. */
|
|
format = sh_mobile_format_info(cfg->fourcc);
|
|
if (format == NULL) {
|
|
dev_err(dev, "Invalid FOURCC %08x.\n", cfg->fourcc);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Iterate through the modes to validate them and find the highest
|
|
* resolution.
|
|
*/
|
|
max_mode = NULL;
|
|
max_size = 0;
|
|
|
|
for (i = 0, mode = cfg->lcd_modes; i < cfg->num_modes; i++, mode++) {
|
|
unsigned int size = mode->yres * mode->xres;
|
|
|
|
/* NV12/NV21 buffers must have even number of lines */
|
|
if ((cfg->fourcc == V4L2_PIX_FMT_NV12 ||
|
|
cfg->fourcc == V4L2_PIX_FMT_NV21) && (mode->yres & 0x1)) {
|
|
dev_err(dev, "yres must be multiple of 2 for "
|
|
"YCbCr420 mode.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (size > max_size) {
|
|
max_mode = mode;
|
|
max_size = size;
|
|
}
|
|
}
|
|
|
|
if (!max_size)
|
|
max_size = MAX_XRES * MAX_YRES;
|
|
else
|
|
dev_dbg(dev, "Found largest videomode %ux%u\n",
|
|
max_mode->xres, max_mode->yres);
|
|
|
|
if (cfg->lcd_modes == NULL) {
|
|
mode = &default_720p;
|
|
num_modes = 1;
|
|
} else {
|
|
mode = cfg->lcd_modes;
|
|
num_modes = cfg->num_modes;
|
|
}
|
|
|
|
/* Use the first mode as default. The default Y virtual resolution is
|
|
* twice the panel size to allow for double-buffering.
|
|
*/
|
|
ch->format = format;
|
|
ch->xres = mode->xres;
|
|
ch->xres_virtual = mode->xres;
|
|
ch->yres = mode->yres;
|
|
ch->yres_virtual = mode->yres * 2;
|
|
|
|
if (!format->yuv) {
|
|
ch->colorspace = V4L2_COLORSPACE_SRGB;
|
|
ch->pitch = ch->xres_virtual * format->bpp / 8;
|
|
} else {
|
|
ch->colorspace = V4L2_COLORSPACE_REC709;
|
|
ch->pitch = ch->xres_virtual;
|
|
}
|
|
|
|
ch->display.width = cfg->panel_cfg.width;
|
|
ch->display.height = cfg->panel_cfg.height;
|
|
ch->display.mode = *mode;
|
|
|
|
/* Allocate frame buffer memory. */
|
|
ch->fb_size = max_size * format->bpp / 8 * 2;
|
|
ch->fb_mem = dma_alloc_coherent(dev, ch->fb_size, &ch->dma_handle,
|
|
GFP_KERNEL);
|
|
if (ch->fb_mem == NULL) {
|
|
dev_err(dev, "unable to allocate buffer\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Initialize the transmitter device if present. */
|
|
if (cfg->tx_dev) {
|
|
if (!cfg->tx_dev->dev.driver ||
|
|
!try_module_get(cfg->tx_dev->dev.driver->owner)) {
|
|
dev_warn(dev, "unable to get transmitter device\n");
|
|
return -EINVAL;
|
|
}
|
|
ch->tx_dev = platform_get_drvdata(cfg->tx_dev);
|
|
ch->tx_dev->lcdc = ch;
|
|
ch->tx_dev->def_mode = *mode;
|
|
}
|
|
|
|
return sh_mobile_lcdc_channel_fb_init(ch, mode, num_modes);
|
|
}
|
|
|
|
static int sh_mobile_lcdc_probe(struct platform_device *pdev)
|
|
{
|
|
struct sh_mobile_lcdc_info *pdata = pdev->dev.platform_data;
|
|
struct sh_mobile_lcdc_priv *priv;
|
|
struct resource *res;
|
|
int num_channels;
|
|
int error;
|
|
int irq, i;
|
|
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "no platform data defined\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (!res || irq < 0) {
|
|
dev_err(&pdev->dev, "cannot get platform resources\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
priv->dev = &pdev->dev;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(priv->ch); i++)
|
|
mutex_init(&priv->ch[i].open_lock);
|
|
platform_set_drvdata(pdev, priv);
|
|
|
|
error = request_irq(irq, sh_mobile_lcdc_irq, 0,
|
|
dev_name(&pdev->dev), priv);
|
|
if (error) {
|
|
dev_err(&pdev->dev, "unable to request irq\n");
|
|
goto err1;
|
|
}
|
|
|
|
priv->irq = irq;
|
|
atomic_set(&priv->hw_usecnt, -1);
|
|
|
|
for (i = 0, num_channels = 0; i < ARRAY_SIZE(pdata->ch); i++) {
|
|
struct sh_mobile_lcdc_chan *ch = priv->ch + num_channels;
|
|
|
|
ch->lcdc = priv;
|
|
ch->cfg = &pdata->ch[i];
|
|
|
|
error = sh_mobile_lcdc_check_interface(ch);
|
|
if (error) {
|
|
dev_err(&pdev->dev, "unsupported interface type\n");
|
|
goto err1;
|
|
}
|
|
init_waitqueue_head(&ch->frame_end_wait);
|
|
init_completion(&ch->vsync_completion);
|
|
|
|
/* probe the backlight is there is one defined */
|
|
if (ch->cfg->bl_info.max_brightness)
|
|
ch->bl = sh_mobile_lcdc_bl_probe(&pdev->dev, ch);
|
|
|
|
switch (pdata->ch[i].chan) {
|
|
case LCDC_CHAN_MAINLCD:
|
|
ch->enabled = LDCNT2R_ME;
|
|
ch->reg_offs = lcdc_offs_mainlcd;
|
|
num_channels++;
|
|
break;
|
|
case LCDC_CHAN_SUBLCD:
|
|
ch->enabled = LDCNT2R_SE;
|
|
ch->reg_offs = lcdc_offs_sublcd;
|
|
num_channels++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!num_channels) {
|
|
dev_err(&pdev->dev, "no channels defined\n");
|
|
error = -EINVAL;
|
|
goto err1;
|
|
}
|
|
|
|
/* for dual channel LCDC (MAIN + SUB) force shared format setting */
|
|
if (num_channels == 2)
|
|
priv->forced_fourcc = pdata->ch[0].fourcc;
|
|
|
|
priv->base = ioremap(res->start, resource_size(res));
|
|
if (!priv->base) {
|
|
error = -ENOMEM;
|
|
goto err1;
|
|
}
|
|
|
|
error = sh_mobile_lcdc_setup_clocks(priv, pdata->clock_source);
|
|
if (error) {
|
|
dev_err(&pdev->dev, "unable to setup clocks\n");
|
|
goto err1;
|
|
}
|
|
|
|
/* Enable runtime PM. */
|
|
pm_runtime_enable(&pdev->dev);
|
|
|
|
for (i = 0; i < num_channels; i++) {
|
|
struct sh_mobile_lcdc_chan *ch = &priv->ch[i];
|
|
|
|
error = sh_mobile_lcdc_channel_init(ch);
|
|
if (error)
|
|
goto err1;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pdata->overlays); i++) {
|
|
struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i];
|
|
|
|
ovl->cfg = &pdata->overlays[i];
|
|
ovl->channel = &priv->ch[0];
|
|
|
|
error = sh_mobile_lcdc_overlay_init(ovl);
|
|
if (error)
|
|
goto err1;
|
|
}
|
|
|
|
error = sh_mobile_lcdc_start(priv);
|
|
if (error) {
|
|
dev_err(&pdev->dev, "unable to start hardware\n");
|
|
goto err1;
|
|
}
|
|
|
|
for (i = 0; i < num_channels; i++) {
|
|
struct sh_mobile_lcdc_chan *ch = priv->ch + i;
|
|
|
|
error = sh_mobile_lcdc_channel_fb_register(ch);
|
|
if (error)
|
|
goto err1;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pdata->overlays); i++) {
|
|
struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i];
|
|
|
|
error = sh_mobile_lcdc_overlay_fb_register(ovl);
|
|
if (error)
|
|
goto err1;
|
|
}
|
|
|
|
return 0;
|
|
err1:
|
|
sh_mobile_lcdc_remove(pdev);
|
|
|
|
return error;
|
|
}
|
|
|
|
static struct platform_driver sh_mobile_lcdc_driver = {
|
|
.driver = {
|
|
.name = "sh_mobile_lcdc_fb",
|
|
.pm = &sh_mobile_lcdc_dev_pm_ops,
|
|
},
|
|
.probe = sh_mobile_lcdc_probe,
|
|
.remove = sh_mobile_lcdc_remove,
|
|
};
|
|
|
|
module_platform_driver(sh_mobile_lcdc_driver);
|
|
|
|
MODULE_DESCRIPTION("SuperH Mobile LCDC Framebuffer driver");
|
|
MODULE_AUTHOR("Magnus Damm <damm@opensource.se>");
|
|
MODULE_LICENSE("GPL v2");
|