162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * SuperH Mobile LCDC Framebuffer 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (c) 2008 Magnus Damm 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public 762306a36Sopenharmony_ci * License. See the file "COPYING" in the main directory of this archive 862306a36Sopenharmony_ci * for more details. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/atomic.h> 1262306a36Sopenharmony_ci#include <linux/backlight.h> 1362306a36Sopenharmony_ci#include <linux/clk.h> 1462306a36Sopenharmony_ci#include <linux/console.h> 1562306a36Sopenharmony_ci#include <linux/ctype.h> 1662306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1762306a36Sopenharmony_ci#include <linux/delay.h> 1862306a36Sopenharmony_ci#include <linux/fbcon.h> 1962306a36Sopenharmony_ci#include <linux/init.h> 2062306a36Sopenharmony_ci#include <linux/interrupt.h> 2162306a36Sopenharmony_ci#include <linux/ioctl.h> 2262306a36Sopenharmony_ci#include <linux/kernel.h> 2362306a36Sopenharmony_ci#include <linux/mm.h> 2462306a36Sopenharmony_ci#include <linux/module.h> 2562306a36Sopenharmony_ci#include <linux/platform_device.h> 2662306a36Sopenharmony_ci#include <linux/pm_runtime.h> 2762306a36Sopenharmony_ci#include <linux/slab.h> 2862306a36Sopenharmony_ci#include <linux/videodev2.h> 2962306a36Sopenharmony_ci#include <linux/vmalloc.h> 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#include <video/sh_mobile_lcdc.h> 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#include "sh_mobile_lcdcfb.h" 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/* ---------------------------------------------------------------------------- 3662306a36Sopenharmony_ci * Overlay register definitions 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#define LDBCR 0xb00 4062306a36Sopenharmony_ci#define LDBCR_UPC(n) (1 << ((n) + 16)) 4162306a36Sopenharmony_ci#define LDBCR_UPF(n) (1 << ((n) + 8)) 4262306a36Sopenharmony_ci#define LDBCR_UPD(n) (1 << ((n) + 0)) 4362306a36Sopenharmony_ci#define LDBnBSIFR(n) (0xb20 + (n) * 0x20 + 0x00) 4462306a36Sopenharmony_ci#define LDBBSIFR_EN (1 << 31) 4562306a36Sopenharmony_ci#define LDBBSIFR_VS (1 << 29) 4662306a36Sopenharmony_ci#define LDBBSIFR_BRSEL (1 << 28) 4762306a36Sopenharmony_ci#define LDBBSIFR_MX (1 << 27) 4862306a36Sopenharmony_ci#define LDBBSIFR_MY (1 << 26) 4962306a36Sopenharmony_ci#define LDBBSIFR_CV3 (3 << 24) 5062306a36Sopenharmony_ci#define LDBBSIFR_CV2 (2 << 24) 5162306a36Sopenharmony_ci#define LDBBSIFR_CV1 (1 << 24) 5262306a36Sopenharmony_ci#define LDBBSIFR_CV0 (0 << 24) 5362306a36Sopenharmony_ci#define LDBBSIFR_CV_MASK (3 << 24) 5462306a36Sopenharmony_ci#define LDBBSIFR_LAY_MASK (0xff << 16) 5562306a36Sopenharmony_ci#define LDBBSIFR_LAY_SHIFT 16 5662306a36Sopenharmony_ci#define LDBBSIFR_ROP3_MASK (0xff << 16) 5762306a36Sopenharmony_ci#define LDBBSIFR_ROP3_SHIFT 16 5862306a36Sopenharmony_ci#define LDBBSIFR_AL_PL8 (3 << 14) 5962306a36Sopenharmony_ci#define LDBBSIFR_AL_PL1 (2 << 14) 6062306a36Sopenharmony_ci#define LDBBSIFR_AL_PK (1 << 14) 6162306a36Sopenharmony_ci#define LDBBSIFR_AL_1 (0 << 14) 6262306a36Sopenharmony_ci#define LDBBSIFR_AL_MASK (3 << 14) 6362306a36Sopenharmony_ci#define LDBBSIFR_SWPL (1 << 10) 6462306a36Sopenharmony_ci#define LDBBSIFR_SWPW (1 << 9) 6562306a36Sopenharmony_ci#define LDBBSIFR_SWPB (1 << 8) 6662306a36Sopenharmony_ci#define LDBBSIFR_RY (1 << 7) 6762306a36Sopenharmony_ci#define LDBBSIFR_CHRR_420 (2 << 0) 6862306a36Sopenharmony_ci#define LDBBSIFR_CHRR_422 (1 << 0) 6962306a36Sopenharmony_ci#define LDBBSIFR_CHRR_444 (0 << 0) 7062306a36Sopenharmony_ci#define LDBBSIFR_RPKF_ARGB32 (0x00 << 0) 7162306a36Sopenharmony_ci#define LDBBSIFR_RPKF_RGB16 (0x03 << 0) 7262306a36Sopenharmony_ci#define LDBBSIFR_RPKF_RGB24 (0x0b << 0) 7362306a36Sopenharmony_ci#define LDBBSIFR_RPKF_MASK (0x1f << 0) 7462306a36Sopenharmony_ci#define LDBnBSSZR(n) (0xb20 + (n) * 0x20 + 0x04) 7562306a36Sopenharmony_ci#define LDBBSSZR_BVSS_MASK (0xfff << 16) 7662306a36Sopenharmony_ci#define LDBBSSZR_BVSS_SHIFT 16 7762306a36Sopenharmony_ci#define LDBBSSZR_BHSS_MASK (0xfff << 0) 7862306a36Sopenharmony_ci#define LDBBSSZR_BHSS_SHIFT 0 7962306a36Sopenharmony_ci#define LDBnBLOCR(n) (0xb20 + (n) * 0x20 + 0x08) 8062306a36Sopenharmony_ci#define LDBBLOCR_CVLC_MASK (0xfff << 16) 8162306a36Sopenharmony_ci#define LDBBLOCR_CVLC_SHIFT 16 8262306a36Sopenharmony_ci#define LDBBLOCR_CHLC_MASK (0xfff << 0) 8362306a36Sopenharmony_ci#define LDBBLOCR_CHLC_SHIFT 0 8462306a36Sopenharmony_ci#define LDBnBSMWR(n) (0xb20 + (n) * 0x20 + 0x0c) 8562306a36Sopenharmony_ci#define LDBBSMWR_BSMWA_MASK (0xffff << 16) 8662306a36Sopenharmony_ci#define LDBBSMWR_BSMWA_SHIFT 16 8762306a36Sopenharmony_ci#define LDBBSMWR_BSMW_MASK (0xffff << 0) 8862306a36Sopenharmony_ci#define LDBBSMWR_BSMW_SHIFT 0 8962306a36Sopenharmony_ci#define LDBnBSAYR(n) (0xb20 + (n) * 0x20 + 0x10) 9062306a36Sopenharmony_ci#define LDBBSAYR_FG1A_MASK (0xff << 24) 9162306a36Sopenharmony_ci#define LDBBSAYR_FG1A_SHIFT 24 9262306a36Sopenharmony_ci#define LDBBSAYR_FG1R_MASK (0xff << 16) 9362306a36Sopenharmony_ci#define LDBBSAYR_FG1R_SHIFT 16 9462306a36Sopenharmony_ci#define LDBBSAYR_FG1G_MASK (0xff << 8) 9562306a36Sopenharmony_ci#define LDBBSAYR_FG1G_SHIFT 8 9662306a36Sopenharmony_ci#define LDBBSAYR_FG1B_MASK (0xff << 0) 9762306a36Sopenharmony_ci#define LDBBSAYR_FG1B_SHIFT 0 9862306a36Sopenharmony_ci#define LDBnBSACR(n) (0xb20 + (n) * 0x20 + 0x14) 9962306a36Sopenharmony_ci#define LDBBSACR_FG2A_MASK (0xff << 24) 10062306a36Sopenharmony_ci#define LDBBSACR_FG2A_SHIFT 24 10162306a36Sopenharmony_ci#define LDBBSACR_FG2R_MASK (0xff << 16) 10262306a36Sopenharmony_ci#define LDBBSACR_FG2R_SHIFT 16 10362306a36Sopenharmony_ci#define LDBBSACR_FG2G_MASK (0xff << 8) 10462306a36Sopenharmony_ci#define LDBBSACR_FG2G_SHIFT 8 10562306a36Sopenharmony_ci#define LDBBSACR_FG2B_MASK (0xff << 0) 10662306a36Sopenharmony_ci#define LDBBSACR_FG2B_SHIFT 0 10762306a36Sopenharmony_ci#define LDBnBSAAR(n) (0xb20 + (n) * 0x20 + 0x18) 10862306a36Sopenharmony_ci#define LDBBSAAR_AP_MASK (0xff << 24) 10962306a36Sopenharmony_ci#define LDBBSAAR_AP_SHIFT 24 11062306a36Sopenharmony_ci#define LDBBSAAR_R_MASK (0xff << 16) 11162306a36Sopenharmony_ci#define LDBBSAAR_R_SHIFT 16 11262306a36Sopenharmony_ci#define LDBBSAAR_GY_MASK (0xff << 8) 11362306a36Sopenharmony_ci#define LDBBSAAR_GY_SHIFT 8 11462306a36Sopenharmony_ci#define LDBBSAAR_B_MASK (0xff << 0) 11562306a36Sopenharmony_ci#define LDBBSAAR_B_SHIFT 0 11662306a36Sopenharmony_ci#define LDBnBPPCR(n) (0xb20 + (n) * 0x20 + 0x1c) 11762306a36Sopenharmony_ci#define LDBBPPCR_AP_MASK (0xff << 24) 11862306a36Sopenharmony_ci#define LDBBPPCR_AP_SHIFT 24 11962306a36Sopenharmony_ci#define LDBBPPCR_R_MASK (0xff << 16) 12062306a36Sopenharmony_ci#define LDBBPPCR_R_SHIFT 16 12162306a36Sopenharmony_ci#define LDBBPPCR_GY_MASK (0xff << 8) 12262306a36Sopenharmony_ci#define LDBBPPCR_GY_SHIFT 8 12362306a36Sopenharmony_ci#define LDBBPPCR_B_MASK (0xff << 0) 12462306a36Sopenharmony_ci#define LDBBPPCR_B_SHIFT 0 12562306a36Sopenharmony_ci#define LDBnBBGCL(n) (0xb10 + (n) * 0x04) 12662306a36Sopenharmony_ci#define LDBBBGCL_BGA_MASK (0xff << 24) 12762306a36Sopenharmony_ci#define LDBBBGCL_BGA_SHIFT 24 12862306a36Sopenharmony_ci#define LDBBBGCL_BGR_MASK (0xff << 16) 12962306a36Sopenharmony_ci#define LDBBBGCL_BGR_SHIFT 16 13062306a36Sopenharmony_ci#define LDBBBGCL_BGG_MASK (0xff << 8) 13162306a36Sopenharmony_ci#define LDBBBGCL_BGG_SHIFT 8 13262306a36Sopenharmony_ci#define LDBBBGCL_BGB_MASK (0xff << 0) 13362306a36Sopenharmony_ci#define LDBBBGCL_BGB_SHIFT 0 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci#define SIDE_B_OFFSET 0x1000 13662306a36Sopenharmony_ci#define MIRROR_OFFSET 0x2000 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci#define MAX_XRES 1920 13962306a36Sopenharmony_ci#define MAX_YRES 1080 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cienum sh_mobile_lcdc_overlay_mode { 14262306a36Sopenharmony_ci LCDC_OVERLAY_BLEND, 14362306a36Sopenharmony_ci LCDC_OVERLAY_ROP3, 14462306a36Sopenharmony_ci}; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci/* 14762306a36Sopenharmony_ci * struct sh_mobile_lcdc_overlay - LCDC display overlay 14862306a36Sopenharmony_ci * 14962306a36Sopenharmony_ci * @channel: LCDC channel this overlay belongs to 15062306a36Sopenharmony_ci * @cfg: Overlay configuration 15162306a36Sopenharmony_ci * @info: Frame buffer device 15262306a36Sopenharmony_ci * @index: Overlay index (0-3) 15362306a36Sopenharmony_ci * @base: Overlay registers base address 15462306a36Sopenharmony_ci * @enabled: True if the overlay is enabled 15562306a36Sopenharmony_ci * @mode: Overlay blending mode (alpha blend or ROP3) 15662306a36Sopenharmony_ci * @alpha: Global alpha blending value (0-255, for alpha blending mode) 15762306a36Sopenharmony_ci * @rop3: Raster operation (for ROP3 mode) 15862306a36Sopenharmony_ci * @fb_mem: Frame buffer virtual memory address 15962306a36Sopenharmony_ci * @fb_size: Frame buffer size in bytes 16062306a36Sopenharmony_ci * @dma_handle: Frame buffer DMA address 16162306a36Sopenharmony_ci * @base_addr_y: Overlay base address (RGB or luma component) 16262306a36Sopenharmony_ci * @base_addr_c: Overlay base address (chroma component) 16362306a36Sopenharmony_ci * @pan_y_offset: Panning linear offset in bytes (luma component) 16462306a36Sopenharmony_ci * @format: Current pixelf format 16562306a36Sopenharmony_ci * @xres: Horizontal visible resolution 16662306a36Sopenharmony_ci * @xres_virtual: Horizontal total resolution 16762306a36Sopenharmony_ci * @yres: Vertical visible resolution 16862306a36Sopenharmony_ci * @yres_virtual: Vertical total resolution 16962306a36Sopenharmony_ci * @pitch: Overlay line pitch 17062306a36Sopenharmony_ci * @pos_x: Horizontal overlay position 17162306a36Sopenharmony_ci * @pos_y: Vertical overlay position 17262306a36Sopenharmony_ci */ 17362306a36Sopenharmony_cistruct sh_mobile_lcdc_overlay { 17462306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *channel; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci const struct sh_mobile_lcdc_overlay_cfg *cfg; 17762306a36Sopenharmony_ci struct fb_info *info; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci unsigned int index; 18062306a36Sopenharmony_ci unsigned long base; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci bool enabled; 18362306a36Sopenharmony_ci enum sh_mobile_lcdc_overlay_mode mode; 18462306a36Sopenharmony_ci unsigned int alpha; 18562306a36Sopenharmony_ci unsigned int rop3; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci void *fb_mem; 18862306a36Sopenharmony_ci unsigned long fb_size; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci dma_addr_t dma_handle; 19162306a36Sopenharmony_ci unsigned long base_addr_y; 19262306a36Sopenharmony_ci unsigned long base_addr_c; 19362306a36Sopenharmony_ci unsigned long pan_y_offset; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci const struct sh_mobile_lcdc_format_info *format; 19662306a36Sopenharmony_ci unsigned int xres; 19762306a36Sopenharmony_ci unsigned int xres_virtual; 19862306a36Sopenharmony_ci unsigned int yres; 19962306a36Sopenharmony_ci unsigned int yres_virtual; 20062306a36Sopenharmony_ci unsigned int pitch; 20162306a36Sopenharmony_ci int pos_x; 20262306a36Sopenharmony_ci int pos_y; 20362306a36Sopenharmony_ci}; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistruct sh_mobile_lcdc_priv { 20662306a36Sopenharmony_ci void __iomem *base; 20762306a36Sopenharmony_ci int irq; 20862306a36Sopenharmony_ci atomic_t hw_usecnt; 20962306a36Sopenharmony_ci struct device *dev; 21062306a36Sopenharmony_ci struct clk *dot_clk; 21162306a36Sopenharmony_ci unsigned long lddckr; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci struct sh_mobile_lcdc_chan ch[2]; 21462306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay overlays[4]; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci int started; 21762306a36Sopenharmony_ci int forced_fourcc; /* 2 channel LCDC must share fourcc setting */ 21862306a36Sopenharmony_ci}; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 22162306a36Sopenharmony_ci * Registers access 22262306a36Sopenharmony_ci */ 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistatic unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { 22562306a36Sopenharmony_ci [LDDCKPAT1R] = 0x400, 22662306a36Sopenharmony_ci [LDDCKPAT2R] = 0x404, 22762306a36Sopenharmony_ci [LDMT1R] = 0x418, 22862306a36Sopenharmony_ci [LDMT2R] = 0x41c, 22962306a36Sopenharmony_ci [LDMT3R] = 0x420, 23062306a36Sopenharmony_ci [LDDFR] = 0x424, 23162306a36Sopenharmony_ci [LDSM1R] = 0x428, 23262306a36Sopenharmony_ci [LDSM2R] = 0x42c, 23362306a36Sopenharmony_ci [LDSA1R] = 0x430, 23462306a36Sopenharmony_ci [LDSA2R] = 0x434, 23562306a36Sopenharmony_ci [LDMLSR] = 0x438, 23662306a36Sopenharmony_ci [LDHCNR] = 0x448, 23762306a36Sopenharmony_ci [LDHSYNR] = 0x44c, 23862306a36Sopenharmony_ci [LDVLNR] = 0x450, 23962306a36Sopenharmony_ci [LDVSYNR] = 0x454, 24062306a36Sopenharmony_ci [LDPMR] = 0x460, 24162306a36Sopenharmony_ci [LDHAJR] = 0x4a0, 24262306a36Sopenharmony_ci}; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cistatic unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { 24562306a36Sopenharmony_ci [LDDCKPAT1R] = 0x408, 24662306a36Sopenharmony_ci [LDDCKPAT2R] = 0x40c, 24762306a36Sopenharmony_ci [LDMT1R] = 0x600, 24862306a36Sopenharmony_ci [LDMT2R] = 0x604, 24962306a36Sopenharmony_ci [LDMT3R] = 0x608, 25062306a36Sopenharmony_ci [LDDFR] = 0x60c, 25162306a36Sopenharmony_ci [LDSM1R] = 0x610, 25262306a36Sopenharmony_ci [LDSM2R] = 0x614, 25362306a36Sopenharmony_ci [LDSA1R] = 0x618, 25462306a36Sopenharmony_ci [LDMLSR] = 0x620, 25562306a36Sopenharmony_ci [LDHCNR] = 0x624, 25662306a36Sopenharmony_ci [LDHSYNR] = 0x628, 25762306a36Sopenharmony_ci [LDVLNR] = 0x62c, 25862306a36Sopenharmony_ci [LDVSYNR] = 0x630, 25962306a36Sopenharmony_ci [LDPMR] = 0x63c, 26062306a36Sopenharmony_ci}; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_cistatic bool banked(int reg_nr) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci switch (reg_nr) { 26562306a36Sopenharmony_ci case LDMT1R: 26662306a36Sopenharmony_ci case LDMT2R: 26762306a36Sopenharmony_ci case LDMT3R: 26862306a36Sopenharmony_ci case LDDFR: 26962306a36Sopenharmony_ci case LDSM1R: 27062306a36Sopenharmony_ci case LDSA1R: 27162306a36Sopenharmony_ci case LDSA2R: 27262306a36Sopenharmony_ci case LDMLSR: 27362306a36Sopenharmony_ci case LDHCNR: 27462306a36Sopenharmony_ci case LDHSYNR: 27562306a36Sopenharmony_ci case LDVLNR: 27662306a36Sopenharmony_ci case LDVSYNR: 27762306a36Sopenharmony_ci return true; 27862306a36Sopenharmony_ci } 27962306a36Sopenharmony_ci return false; 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic int lcdc_chan_is_sublcd(struct sh_mobile_lcdc_chan *chan) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci return chan->cfg->chan == LCDC_CHAN_SUBLCD; 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cistatic void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan, 28862306a36Sopenharmony_ci int reg_nr, unsigned long data) 28962306a36Sopenharmony_ci{ 29062306a36Sopenharmony_ci iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr]); 29162306a36Sopenharmony_ci if (banked(reg_nr)) 29262306a36Sopenharmony_ci iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] + 29362306a36Sopenharmony_ci SIDE_B_OFFSET); 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic void lcdc_write_chan_mirror(struct sh_mobile_lcdc_chan *chan, 29762306a36Sopenharmony_ci int reg_nr, unsigned long data) 29862306a36Sopenharmony_ci{ 29962306a36Sopenharmony_ci iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] + 30062306a36Sopenharmony_ci MIRROR_OFFSET); 30162306a36Sopenharmony_ci} 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_cistatic unsigned long lcdc_read_chan(struct sh_mobile_lcdc_chan *chan, 30462306a36Sopenharmony_ci int reg_nr) 30562306a36Sopenharmony_ci{ 30662306a36Sopenharmony_ci return ioread32(chan->lcdc->base + chan->reg_offs[reg_nr]); 30762306a36Sopenharmony_ci} 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_cistatic void lcdc_write_overlay(struct sh_mobile_lcdc_overlay *ovl, 31062306a36Sopenharmony_ci int reg, unsigned long data) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci iowrite32(data, ovl->channel->lcdc->base + reg); 31362306a36Sopenharmony_ci iowrite32(data, ovl->channel->lcdc->base + reg + SIDE_B_OFFSET); 31462306a36Sopenharmony_ci} 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_cistatic void lcdc_write(struct sh_mobile_lcdc_priv *priv, 31762306a36Sopenharmony_ci unsigned long reg_offs, unsigned long data) 31862306a36Sopenharmony_ci{ 31962306a36Sopenharmony_ci iowrite32(data, priv->base + reg_offs); 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_cistatic unsigned long lcdc_read(struct sh_mobile_lcdc_priv *priv, 32362306a36Sopenharmony_ci unsigned long reg_offs) 32462306a36Sopenharmony_ci{ 32562306a36Sopenharmony_ci return ioread32(priv->base + reg_offs); 32662306a36Sopenharmony_ci} 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cistatic void lcdc_wait_bit(struct sh_mobile_lcdc_priv *priv, 32962306a36Sopenharmony_ci unsigned long reg_offs, 33062306a36Sopenharmony_ci unsigned long mask, unsigned long until) 33162306a36Sopenharmony_ci{ 33262306a36Sopenharmony_ci while ((lcdc_read(priv, reg_offs) & mask) != until) 33362306a36Sopenharmony_ci cpu_relax(); 33462306a36Sopenharmony_ci} 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 33762306a36Sopenharmony_ci * Clock management 33862306a36Sopenharmony_ci */ 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_cistatic void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) 34162306a36Sopenharmony_ci{ 34262306a36Sopenharmony_ci if (atomic_inc_and_test(&priv->hw_usecnt)) { 34362306a36Sopenharmony_ci clk_prepare_enable(priv->dot_clk); 34462306a36Sopenharmony_ci pm_runtime_get_sync(priv->dev); 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) 34962306a36Sopenharmony_ci{ 35062306a36Sopenharmony_ci if (atomic_sub_return(1, &priv->hw_usecnt) == -1) { 35162306a36Sopenharmony_ci pm_runtime_put(priv->dev); 35262306a36Sopenharmony_ci clk_disable_unprepare(priv->dot_clk); 35362306a36Sopenharmony_ci } 35462306a36Sopenharmony_ci} 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_cistatic int sh_mobile_lcdc_setup_clocks(struct sh_mobile_lcdc_priv *priv, 35762306a36Sopenharmony_ci int clock_source) 35862306a36Sopenharmony_ci{ 35962306a36Sopenharmony_ci struct clk *clk; 36062306a36Sopenharmony_ci char *str; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci switch (clock_source) { 36362306a36Sopenharmony_ci case LCDC_CLK_BUS: 36462306a36Sopenharmony_ci str = "bus_clk"; 36562306a36Sopenharmony_ci priv->lddckr = LDDCKR_ICKSEL_BUS; 36662306a36Sopenharmony_ci break; 36762306a36Sopenharmony_ci case LCDC_CLK_PERIPHERAL: 36862306a36Sopenharmony_ci str = "peripheral_clk"; 36962306a36Sopenharmony_ci priv->lddckr = LDDCKR_ICKSEL_MIPI; 37062306a36Sopenharmony_ci break; 37162306a36Sopenharmony_ci case LCDC_CLK_EXTERNAL: 37262306a36Sopenharmony_ci str = NULL; 37362306a36Sopenharmony_ci priv->lddckr = LDDCKR_ICKSEL_HDMI; 37462306a36Sopenharmony_ci break; 37562306a36Sopenharmony_ci default: 37662306a36Sopenharmony_ci return -EINVAL; 37762306a36Sopenharmony_ci } 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci if (str == NULL) 38062306a36Sopenharmony_ci return 0; 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci clk = clk_get(priv->dev, str); 38362306a36Sopenharmony_ci if (IS_ERR(clk)) { 38462306a36Sopenharmony_ci dev_err(priv->dev, "cannot get dot clock %s\n", str); 38562306a36Sopenharmony_ci return PTR_ERR(clk); 38662306a36Sopenharmony_ci } 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci priv->dot_clk = clk; 38962306a36Sopenharmony_ci return 0; 39062306a36Sopenharmony_ci} 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 39362306a36Sopenharmony_ci * Display, panel and deferred I/O 39462306a36Sopenharmony_ci */ 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_cistatic void lcdc_sys_write_index(void *handle, unsigned long data) 39762306a36Sopenharmony_ci{ 39862306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = handle; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci lcdc_write(ch->lcdc, _LDDWD0R, data | LDDWDxR_WDACT); 40162306a36Sopenharmony_ci lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); 40262306a36Sopenharmony_ci lcdc_write(ch->lcdc, _LDDWAR, LDDWAR_WA | 40362306a36Sopenharmony_ci (lcdc_chan_is_sublcd(ch) ? 2 : 0)); 40462306a36Sopenharmony_ci lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); 40562306a36Sopenharmony_ci} 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_cistatic void lcdc_sys_write_data(void *handle, unsigned long data) 40862306a36Sopenharmony_ci{ 40962306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = handle; 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci lcdc_write(ch->lcdc, _LDDWD0R, data | LDDWDxR_WDACT | LDDWDxR_RSW); 41262306a36Sopenharmony_ci lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); 41362306a36Sopenharmony_ci lcdc_write(ch->lcdc, _LDDWAR, LDDWAR_WA | 41462306a36Sopenharmony_ci (lcdc_chan_is_sublcd(ch) ? 2 : 0)); 41562306a36Sopenharmony_ci lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); 41662306a36Sopenharmony_ci} 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_cistatic unsigned long lcdc_sys_read_data(void *handle) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = handle; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci lcdc_write(ch->lcdc, _LDDRDR, LDDRDR_RSR); 42362306a36Sopenharmony_ci lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); 42462306a36Sopenharmony_ci lcdc_write(ch->lcdc, _LDDRAR, LDDRAR_RA | 42562306a36Sopenharmony_ci (lcdc_chan_is_sublcd(ch) ? 2 : 0)); 42662306a36Sopenharmony_ci udelay(1); 42762306a36Sopenharmony_ci lcdc_wait_bit(ch->lcdc, _LDSR, LDSR_AS, 0); 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci return lcdc_read(ch->lcdc, _LDDRDR) & LDDRDR_DRD_MASK; 43062306a36Sopenharmony_ci} 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_cistatic struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { 43362306a36Sopenharmony_ci .write_index = lcdc_sys_write_index, 43462306a36Sopenharmony_ci .write_data = lcdc_sys_write_data, 43562306a36Sopenharmony_ci .read_data = lcdc_sys_read_data, 43662306a36Sopenharmony_ci}; 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_cistatic int sh_mobile_lcdc_sginit(struct fb_info *info, struct list_head *pagereflist) 43962306a36Sopenharmony_ci{ 44062306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = info->par; 44162306a36Sopenharmony_ci unsigned int nr_pages_max = ch->fb_size >> PAGE_SHIFT; 44262306a36Sopenharmony_ci struct fb_deferred_io_pageref *pageref; 44362306a36Sopenharmony_ci int nr_pages = 0; 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci sg_init_table(ch->sglist, nr_pages_max); 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci list_for_each_entry(pageref, pagereflist, list) { 44862306a36Sopenharmony_ci sg_set_page(&ch->sglist[nr_pages++], pageref->page, PAGE_SIZE, 0); 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci return nr_pages; 45262306a36Sopenharmony_ci} 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_cistatic void sh_mobile_lcdc_deferred_io(struct fb_info *info, struct list_head *pagereflist) 45562306a36Sopenharmony_ci{ 45662306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = info->par; 45762306a36Sopenharmony_ci const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci /* enable clocks before accessing hardware */ 46062306a36Sopenharmony_ci sh_mobile_lcdc_clk_on(ch->lcdc); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci /* 46362306a36Sopenharmony_ci * It's possible to get here without anything on the pagereflist via 46462306a36Sopenharmony_ci * sh_mobile_lcdc_deferred_io_touch() or via a userspace fsync() 46562306a36Sopenharmony_ci * invocation. In the former case, the acceleration routines are 46662306a36Sopenharmony_ci * stepped in to when using the framebuffer console causing the 46762306a36Sopenharmony_ci * workqueue to be scheduled without any dirty pages on the list. 46862306a36Sopenharmony_ci * 46962306a36Sopenharmony_ci * Despite this, a panel update is still needed given that the 47062306a36Sopenharmony_ci * acceleration routines have their own methods for writing in 47162306a36Sopenharmony_ci * that still need to be updated. 47262306a36Sopenharmony_ci * 47362306a36Sopenharmony_ci * The fsync() and empty pagereflist case could be optimized for, 47462306a36Sopenharmony_ci * but we don't bother, as any application exhibiting such 47562306a36Sopenharmony_ci * behaviour is fundamentally broken anyways. 47662306a36Sopenharmony_ci */ 47762306a36Sopenharmony_ci if (!list_empty(pagereflist)) { 47862306a36Sopenharmony_ci unsigned int nr_pages = sh_mobile_lcdc_sginit(info, pagereflist); 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci /* trigger panel update */ 48162306a36Sopenharmony_ci dma_map_sg(ch->lcdc->dev, ch->sglist, nr_pages, DMA_TO_DEVICE); 48262306a36Sopenharmony_ci if (panel->start_transfer) 48362306a36Sopenharmony_ci panel->start_transfer(ch, &sh_mobile_lcdc_sys_bus_ops); 48462306a36Sopenharmony_ci lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG); 48562306a36Sopenharmony_ci dma_unmap_sg(ch->lcdc->dev, ch->sglist, nr_pages, 48662306a36Sopenharmony_ci DMA_TO_DEVICE); 48762306a36Sopenharmony_ci } else { 48862306a36Sopenharmony_ci if (panel->start_transfer) 48962306a36Sopenharmony_ci panel->start_transfer(ch, &sh_mobile_lcdc_sys_bus_ops); 49062306a36Sopenharmony_ci lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG); 49162306a36Sopenharmony_ci } 49262306a36Sopenharmony_ci} 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_cistatic void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info) 49562306a36Sopenharmony_ci{ 49662306a36Sopenharmony_ci struct fb_deferred_io *fbdefio = info->fbdefio; 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci if (fbdefio) 49962306a36Sopenharmony_ci schedule_delayed_work(&info->deferred_work, fbdefio->delay); 50062306a36Sopenharmony_ci} 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_cistatic void sh_mobile_lcdc_display_on(struct sh_mobile_lcdc_chan *ch) 50362306a36Sopenharmony_ci{ 50462306a36Sopenharmony_ci const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg; 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci if (ch->tx_dev) { 50762306a36Sopenharmony_ci int ret; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci ret = ch->tx_dev->ops->display_on(ch->tx_dev); 51062306a36Sopenharmony_ci if (ret < 0) 51162306a36Sopenharmony_ci return; 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci if (ret == SH_MOBILE_LCDC_DISPLAY_DISCONNECTED) 51462306a36Sopenharmony_ci ch->info->state = FBINFO_STATE_SUSPENDED; 51562306a36Sopenharmony_ci } 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci /* HDMI must be enabled before LCDC configuration */ 51862306a36Sopenharmony_ci if (panel->display_on) 51962306a36Sopenharmony_ci panel->display_on(); 52062306a36Sopenharmony_ci} 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_cistatic void sh_mobile_lcdc_display_off(struct sh_mobile_lcdc_chan *ch) 52362306a36Sopenharmony_ci{ 52462306a36Sopenharmony_ci const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg; 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci if (panel->display_off) 52762306a36Sopenharmony_ci panel->display_off(); 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci if (ch->tx_dev) 53062306a36Sopenharmony_ci ch->tx_dev->ops->display_off(ch->tx_dev); 53162306a36Sopenharmony_ci} 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 53462306a36Sopenharmony_ci * Format helpers 53562306a36Sopenharmony_ci */ 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_cistruct sh_mobile_lcdc_format_info { 53862306a36Sopenharmony_ci u32 fourcc; 53962306a36Sopenharmony_ci unsigned int bpp; 54062306a36Sopenharmony_ci bool yuv; 54162306a36Sopenharmony_ci u32 lddfr; 54262306a36Sopenharmony_ci}; 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_cistatic const struct sh_mobile_lcdc_format_info sh_mobile_format_infos[] = { 54562306a36Sopenharmony_ci { 54662306a36Sopenharmony_ci .fourcc = V4L2_PIX_FMT_RGB565, 54762306a36Sopenharmony_ci .bpp = 16, 54862306a36Sopenharmony_ci .yuv = false, 54962306a36Sopenharmony_ci .lddfr = LDDFR_PKF_RGB16, 55062306a36Sopenharmony_ci }, { 55162306a36Sopenharmony_ci .fourcc = V4L2_PIX_FMT_BGR24, 55262306a36Sopenharmony_ci .bpp = 24, 55362306a36Sopenharmony_ci .yuv = false, 55462306a36Sopenharmony_ci .lddfr = LDDFR_PKF_RGB24, 55562306a36Sopenharmony_ci }, { 55662306a36Sopenharmony_ci .fourcc = V4L2_PIX_FMT_BGR32, 55762306a36Sopenharmony_ci .bpp = 32, 55862306a36Sopenharmony_ci .yuv = false, 55962306a36Sopenharmony_ci .lddfr = LDDFR_PKF_ARGB32, 56062306a36Sopenharmony_ci }, { 56162306a36Sopenharmony_ci .fourcc = V4L2_PIX_FMT_NV12, 56262306a36Sopenharmony_ci .bpp = 12, 56362306a36Sopenharmony_ci .yuv = true, 56462306a36Sopenharmony_ci .lddfr = LDDFR_CC | LDDFR_YF_420, 56562306a36Sopenharmony_ci }, { 56662306a36Sopenharmony_ci .fourcc = V4L2_PIX_FMT_NV21, 56762306a36Sopenharmony_ci .bpp = 12, 56862306a36Sopenharmony_ci .yuv = true, 56962306a36Sopenharmony_ci .lddfr = LDDFR_CC | LDDFR_YF_420, 57062306a36Sopenharmony_ci }, { 57162306a36Sopenharmony_ci .fourcc = V4L2_PIX_FMT_NV16, 57262306a36Sopenharmony_ci .bpp = 16, 57362306a36Sopenharmony_ci .yuv = true, 57462306a36Sopenharmony_ci .lddfr = LDDFR_CC | LDDFR_YF_422, 57562306a36Sopenharmony_ci }, { 57662306a36Sopenharmony_ci .fourcc = V4L2_PIX_FMT_NV61, 57762306a36Sopenharmony_ci .bpp = 16, 57862306a36Sopenharmony_ci .yuv = true, 57962306a36Sopenharmony_ci .lddfr = LDDFR_CC | LDDFR_YF_422, 58062306a36Sopenharmony_ci }, { 58162306a36Sopenharmony_ci .fourcc = V4L2_PIX_FMT_NV24, 58262306a36Sopenharmony_ci .bpp = 24, 58362306a36Sopenharmony_ci .yuv = true, 58462306a36Sopenharmony_ci .lddfr = LDDFR_CC | LDDFR_YF_444, 58562306a36Sopenharmony_ci }, { 58662306a36Sopenharmony_ci .fourcc = V4L2_PIX_FMT_NV42, 58762306a36Sopenharmony_ci .bpp = 24, 58862306a36Sopenharmony_ci .yuv = true, 58962306a36Sopenharmony_ci .lddfr = LDDFR_CC | LDDFR_YF_444, 59062306a36Sopenharmony_ci }, 59162306a36Sopenharmony_ci}; 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_cistatic const struct sh_mobile_lcdc_format_info * 59462306a36Sopenharmony_cish_mobile_format_info(u32 fourcc) 59562306a36Sopenharmony_ci{ 59662306a36Sopenharmony_ci unsigned int i; 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(sh_mobile_format_infos); ++i) { 59962306a36Sopenharmony_ci if (sh_mobile_format_infos[i].fourcc == fourcc) 60062306a36Sopenharmony_ci return &sh_mobile_format_infos[i]; 60162306a36Sopenharmony_ci } 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_ci return NULL; 60462306a36Sopenharmony_ci} 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_cistatic int sh_mobile_format_fourcc(const struct fb_var_screeninfo *var) 60762306a36Sopenharmony_ci{ 60862306a36Sopenharmony_ci if (var->grayscale > 1) 60962306a36Sopenharmony_ci return var->grayscale; 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci switch (var->bits_per_pixel) { 61262306a36Sopenharmony_ci case 16: 61362306a36Sopenharmony_ci return V4L2_PIX_FMT_RGB565; 61462306a36Sopenharmony_ci case 24: 61562306a36Sopenharmony_ci return V4L2_PIX_FMT_BGR24; 61662306a36Sopenharmony_ci case 32: 61762306a36Sopenharmony_ci return V4L2_PIX_FMT_BGR32; 61862306a36Sopenharmony_ci default: 61962306a36Sopenharmony_ci return 0; 62062306a36Sopenharmony_ci } 62162306a36Sopenharmony_ci} 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_cistatic int sh_mobile_format_is_fourcc(const struct fb_var_screeninfo *var) 62462306a36Sopenharmony_ci{ 62562306a36Sopenharmony_ci return var->grayscale > 1; 62662306a36Sopenharmony_ci} 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 62962306a36Sopenharmony_ci * Start, stop and IRQ 63062306a36Sopenharmony_ci */ 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_cistatic irqreturn_t sh_mobile_lcdc_irq(int irq, void *data) 63362306a36Sopenharmony_ci{ 63462306a36Sopenharmony_ci struct sh_mobile_lcdc_priv *priv = data; 63562306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch; 63662306a36Sopenharmony_ci unsigned long ldintr; 63762306a36Sopenharmony_ci int is_sub; 63862306a36Sopenharmony_ci int k; 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci /* Acknowledge interrupts and disable further VSYNC End IRQs. */ 64162306a36Sopenharmony_ci ldintr = lcdc_read(priv, _LDINTR); 64262306a36Sopenharmony_ci lcdc_write(priv, _LDINTR, (ldintr ^ LDINTR_STATUS_MASK) & ~LDINTR_VEE); 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci /* figure out if this interrupt is for main or sub lcd */ 64562306a36Sopenharmony_ci is_sub = (lcdc_read(priv, _LDSR) & LDSR_MSS) ? 1 : 0; 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci /* wake up channel and disable clocks */ 64862306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { 64962306a36Sopenharmony_ci ch = &priv->ch[k]; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci if (!ch->enabled) 65262306a36Sopenharmony_ci continue; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci /* Frame End */ 65562306a36Sopenharmony_ci if (ldintr & LDINTR_FS) { 65662306a36Sopenharmony_ci if (is_sub == lcdc_chan_is_sublcd(ch)) { 65762306a36Sopenharmony_ci ch->frame_end = 1; 65862306a36Sopenharmony_ci wake_up(&ch->frame_end_wait); 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci sh_mobile_lcdc_clk_off(priv); 66162306a36Sopenharmony_ci } 66262306a36Sopenharmony_ci } 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci /* VSYNC End */ 66562306a36Sopenharmony_ci if (ldintr & LDINTR_VES) 66662306a36Sopenharmony_ci complete(&ch->vsync_completion); 66762306a36Sopenharmony_ci } 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci return IRQ_HANDLED; 67062306a36Sopenharmony_ci} 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_cistatic int sh_mobile_lcdc_wait_for_vsync(struct sh_mobile_lcdc_chan *ch) 67362306a36Sopenharmony_ci{ 67462306a36Sopenharmony_ci unsigned long ldintr; 67562306a36Sopenharmony_ci int ret; 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci /* Enable VSync End interrupt and be careful not to acknowledge any 67862306a36Sopenharmony_ci * pending interrupt. 67962306a36Sopenharmony_ci */ 68062306a36Sopenharmony_ci ldintr = lcdc_read(ch->lcdc, _LDINTR); 68162306a36Sopenharmony_ci ldintr |= LDINTR_VEE | LDINTR_STATUS_MASK; 68262306a36Sopenharmony_ci lcdc_write(ch->lcdc, _LDINTR, ldintr); 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci ret = wait_for_completion_interruptible_timeout(&ch->vsync_completion, 68562306a36Sopenharmony_ci msecs_to_jiffies(100)); 68662306a36Sopenharmony_ci if (!ret) 68762306a36Sopenharmony_ci return -ETIMEDOUT; 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci return 0; 69062306a36Sopenharmony_ci} 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_cistatic void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, 69362306a36Sopenharmony_ci int start) 69462306a36Sopenharmony_ci{ 69562306a36Sopenharmony_ci unsigned long tmp = lcdc_read(priv, _LDCNT2R); 69662306a36Sopenharmony_ci int k; 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci /* start or stop the lcdc */ 69962306a36Sopenharmony_ci if (start) 70062306a36Sopenharmony_ci lcdc_write(priv, _LDCNT2R, tmp | LDCNT2R_DO); 70162306a36Sopenharmony_ci else 70262306a36Sopenharmony_ci lcdc_write(priv, _LDCNT2R, tmp & ~LDCNT2R_DO); 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ci /* wait until power is applied/stopped on all channels */ 70562306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(priv->ch); k++) 70662306a36Sopenharmony_ci if (lcdc_read(priv, _LDCNT2R) & priv->ch[k].enabled) 70762306a36Sopenharmony_ci while (1) { 70862306a36Sopenharmony_ci tmp = lcdc_read_chan(&priv->ch[k], LDPMR) 70962306a36Sopenharmony_ci & LDPMR_LPS; 71062306a36Sopenharmony_ci if (start && tmp == LDPMR_LPS) 71162306a36Sopenharmony_ci break; 71262306a36Sopenharmony_ci if (!start && tmp == 0) 71362306a36Sopenharmony_ci break; 71462306a36Sopenharmony_ci cpu_relax(); 71562306a36Sopenharmony_ci } 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_ci if (!start) 71862306a36Sopenharmony_ci lcdc_write(priv, _LDDCKSTPR, 1); /* stop dotclock */ 71962306a36Sopenharmony_ci} 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_cistatic void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) 72262306a36Sopenharmony_ci{ 72362306a36Sopenharmony_ci const struct fb_var_screeninfo *var = &ch->info->var; 72462306a36Sopenharmony_ci const struct fb_videomode *mode = &ch->display.mode; 72562306a36Sopenharmony_ci unsigned long h_total, hsync_pos, display_h_total; 72662306a36Sopenharmony_ci u32 tmp; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci tmp = ch->ldmt1r_value; 72962306a36Sopenharmony_ci tmp |= (var->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : LDMT1R_VPOL; 73062306a36Sopenharmony_ci tmp |= (var->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : LDMT1R_HPOL; 73162306a36Sopenharmony_ci tmp |= (ch->cfg->flags & LCDC_FLAGS_DWPOL) ? LDMT1R_DWPOL : 0; 73262306a36Sopenharmony_ci tmp |= (ch->cfg->flags & LCDC_FLAGS_DIPOL) ? LDMT1R_DIPOL : 0; 73362306a36Sopenharmony_ci tmp |= (ch->cfg->flags & LCDC_FLAGS_DAPOL) ? LDMT1R_DAPOL : 0; 73462306a36Sopenharmony_ci tmp |= (ch->cfg->flags & LCDC_FLAGS_HSCNT) ? LDMT1R_HSCNT : 0; 73562306a36Sopenharmony_ci tmp |= (ch->cfg->flags & LCDC_FLAGS_DWCNT) ? LDMT1R_DWCNT : 0; 73662306a36Sopenharmony_ci lcdc_write_chan(ch, LDMT1R, tmp); 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci /* setup SYS bus */ 73962306a36Sopenharmony_ci lcdc_write_chan(ch, LDMT2R, ch->cfg->sys_bus_cfg.ldmt2r); 74062306a36Sopenharmony_ci lcdc_write_chan(ch, LDMT3R, ch->cfg->sys_bus_cfg.ldmt3r); 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci /* horizontal configuration */ 74362306a36Sopenharmony_ci h_total = mode->xres + mode->hsync_len + mode->left_margin 74462306a36Sopenharmony_ci + mode->right_margin; 74562306a36Sopenharmony_ci tmp = h_total / 8; /* HTCN */ 74662306a36Sopenharmony_ci tmp |= (min(mode->xres, ch->xres) / 8) << 16; /* HDCN */ 74762306a36Sopenharmony_ci lcdc_write_chan(ch, LDHCNR, tmp); 74862306a36Sopenharmony_ci 74962306a36Sopenharmony_ci hsync_pos = mode->xres + mode->right_margin; 75062306a36Sopenharmony_ci tmp = hsync_pos / 8; /* HSYNP */ 75162306a36Sopenharmony_ci tmp |= (mode->hsync_len / 8) << 16; /* HSYNW */ 75262306a36Sopenharmony_ci lcdc_write_chan(ch, LDHSYNR, tmp); 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci /* vertical configuration */ 75562306a36Sopenharmony_ci tmp = mode->yres + mode->vsync_len + mode->upper_margin 75662306a36Sopenharmony_ci + mode->lower_margin; /* VTLN */ 75762306a36Sopenharmony_ci tmp |= min(mode->yres, ch->yres) << 16; /* VDLN */ 75862306a36Sopenharmony_ci lcdc_write_chan(ch, LDVLNR, tmp); 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci tmp = mode->yres + mode->lower_margin; /* VSYNP */ 76162306a36Sopenharmony_ci tmp |= mode->vsync_len << 16; /* VSYNW */ 76262306a36Sopenharmony_ci lcdc_write_chan(ch, LDVSYNR, tmp); 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_ci /* Adjust horizontal synchronisation for HDMI */ 76562306a36Sopenharmony_ci display_h_total = mode->xres + mode->hsync_len + mode->left_margin 76662306a36Sopenharmony_ci + mode->right_margin; 76762306a36Sopenharmony_ci tmp = ((mode->xres & 7) << 24) | ((display_h_total & 7) << 16) 76862306a36Sopenharmony_ci | ((mode->hsync_len & 7) << 8) | (hsync_pos & 7); 76962306a36Sopenharmony_ci lcdc_write_chan(ch, LDHAJR, tmp); 77062306a36Sopenharmony_ci lcdc_write_chan_mirror(ch, LDHAJR, tmp); 77162306a36Sopenharmony_ci} 77262306a36Sopenharmony_ci 77362306a36Sopenharmony_cistatic void sh_mobile_lcdc_overlay_setup(struct sh_mobile_lcdc_overlay *ovl) 77462306a36Sopenharmony_ci{ 77562306a36Sopenharmony_ci u32 format = 0; 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_ci if (!ovl->enabled) { 77862306a36Sopenharmony_ci lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index)); 77962306a36Sopenharmony_ci lcdc_write_overlay(ovl, LDBnBSIFR(ovl->index), 0); 78062306a36Sopenharmony_ci lcdc_write(ovl->channel->lcdc, LDBCR, 78162306a36Sopenharmony_ci LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index)); 78262306a36Sopenharmony_ci return; 78362306a36Sopenharmony_ci } 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_ci ovl->base_addr_y = ovl->dma_handle; 78662306a36Sopenharmony_ci ovl->base_addr_c = ovl->dma_handle 78762306a36Sopenharmony_ci + ovl->xres_virtual * ovl->yres_virtual; 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci switch (ovl->mode) { 79062306a36Sopenharmony_ci case LCDC_OVERLAY_BLEND: 79162306a36Sopenharmony_ci format = LDBBSIFR_EN | (ovl->alpha << LDBBSIFR_LAY_SHIFT); 79262306a36Sopenharmony_ci break; 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci case LCDC_OVERLAY_ROP3: 79562306a36Sopenharmony_ci format = LDBBSIFR_EN | LDBBSIFR_BRSEL 79662306a36Sopenharmony_ci | (ovl->rop3 << LDBBSIFR_ROP3_SHIFT); 79762306a36Sopenharmony_ci break; 79862306a36Sopenharmony_ci } 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_ci switch (ovl->format->fourcc) { 80162306a36Sopenharmony_ci case V4L2_PIX_FMT_RGB565: 80262306a36Sopenharmony_ci case V4L2_PIX_FMT_NV21: 80362306a36Sopenharmony_ci case V4L2_PIX_FMT_NV61: 80462306a36Sopenharmony_ci case V4L2_PIX_FMT_NV42: 80562306a36Sopenharmony_ci format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW; 80662306a36Sopenharmony_ci break; 80762306a36Sopenharmony_ci case V4L2_PIX_FMT_BGR24: 80862306a36Sopenharmony_ci case V4L2_PIX_FMT_NV12: 80962306a36Sopenharmony_ci case V4L2_PIX_FMT_NV16: 81062306a36Sopenharmony_ci case V4L2_PIX_FMT_NV24: 81162306a36Sopenharmony_ci format |= LDBBSIFR_SWPL | LDBBSIFR_SWPW | LDBBSIFR_SWPB; 81262306a36Sopenharmony_ci break; 81362306a36Sopenharmony_ci case V4L2_PIX_FMT_BGR32: 81462306a36Sopenharmony_ci default: 81562306a36Sopenharmony_ci format |= LDBBSIFR_SWPL; 81662306a36Sopenharmony_ci break; 81762306a36Sopenharmony_ci } 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_ci switch (ovl->format->fourcc) { 82062306a36Sopenharmony_ci case V4L2_PIX_FMT_RGB565: 82162306a36Sopenharmony_ci format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB16; 82262306a36Sopenharmony_ci break; 82362306a36Sopenharmony_ci case V4L2_PIX_FMT_BGR24: 82462306a36Sopenharmony_ci format |= LDBBSIFR_AL_1 | LDBBSIFR_RY | LDBBSIFR_RPKF_RGB24; 82562306a36Sopenharmony_ci break; 82662306a36Sopenharmony_ci case V4L2_PIX_FMT_BGR32: 82762306a36Sopenharmony_ci format |= LDBBSIFR_AL_PK | LDBBSIFR_RY | LDBBSIFR_RPKF_ARGB32; 82862306a36Sopenharmony_ci break; 82962306a36Sopenharmony_ci case V4L2_PIX_FMT_NV12: 83062306a36Sopenharmony_ci case V4L2_PIX_FMT_NV21: 83162306a36Sopenharmony_ci format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_420; 83262306a36Sopenharmony_ci break; 83362306a36Sopenharmony_ci case V4L2_PIX_FMT_NV16: 83462306a36Sopenharmony_ci case V4L2_PIX_FMT_NV61: 83562306a36Sopenharmony_ci format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_422; 83662306a36Sopenharmony_ci break; 83762306a36Sopenharmony_ci case V4L2_PIX_FMT_NV24: 83862306a36Sopenharmony_ci case V4L2_PIX_FMT_NV42: 83962306a36Sopenharmony_ci format |= LDBBSIFR_AL_1 | LDBBSIFR_CHRR_444; 84062306a36Sopenharmony_ci break; 84162306a36Sopenharmony_ci } 84262306a36Sopenharmony_ci 84362306a36Sopenharmony_ci lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index)); 84462306a36Sopenharmony_ci 84562306a36Sopenharmony_ci lcdc_write_overlay(ovl, LDBnBSIFR(ovl->index), format); 84662306a36Sopenharmony_ci 84762306a36Sopenharmony_ci lcdc_write_overlay(ovl, LDBnBSSZR(ovl->index), 84862306a36Sopenharmony_ci (ovl->yres << LDBBSSZR_BVSS_SHIFT) | 84962306a36Sopenharmony_ci (ovl->xres << LDBBSSZR_BHSS_SHIFT)); 85062306a36Sopenharmony_ci lcdc_write_overlay(ovl, LDBnBLOCR(ovl->index), 85162306a36Sopenharmony_ci (ovl->pos_y << LDBBLOCR_CVLC_SHIFT) | 85262306a36Sopenharmony_ci (ovl->pos_x << LDBBLOCR_CHLC_SHIFT)); 85362306a36Sopenharmony_ci lcdc_write_overlay(ovl, LDBnBSMWR(ovl->index), 85462306a36Sopenharmony_ci ovl->pitch << LDBBSMWR_BSMW_SHIFT); 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_ci lcdc_write_overlay(ovl, LDBnBSAYR(ovl->index), ovl->base_addr_y); 85762306a36Sopenharmony_ci lcdc_write_overlay(ovl, LDBnBSACR(ovl->index), ovl->base_addr_c); 85862306a36Sopenharmony_ci 85962306a36Sopenharmony_ci lcdc_write(ovl->channel->lcdc, LDBCR, 86062306a36Sopenharmony_ci LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index)); 86162306a36Sopenharmony_ci} 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_ci/* 86462306a36Sopenharmony_ci * __sh_mobile_lcdc_start - Configure and start the LCDC 86562306a36Sopenharmony_ci * @priv: LCDC device 86662306a36Sopenharmony_ci * 86762306a36Sopenharmony_ci * Configure all enabled channels and start the LCDC device. All external 86862306a36Sopenharmony_ci * devices (clocks, MERAM, panels, ...) are not touched by this function. 86962306a36Sopenharmony_ci */ 87062306a36Sopenharmony_cistatic void __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) 87162306a36Sopenharmony_ci{ 87262306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch; 87362306a36Sopenharmony_ci unsigned long tmp; 87462306a36Sopenharmony_ci int k, m; 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci /* Enable LCDC channels. Read data from external memory, avoid using the 87762306a36Sopenharmony_ci * BEU for now. 87862306a36Sopenharmony_ci */ 87962306a36Sopenharmony_ci lcdc_write(priv, _LDCNT2R, priv->ch[0].enabled | priv->ch[1].enabled); 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_ci /* Stop the LCDC first and disable all interrupts. */ 88262306a36Sopenharmony_ci sh_mobile_lcdc_start_stop(priv, 0); 88362306a36Sopenharmony_ci lcdc_write(priv, _LDINTR, 0); 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_ci /* Configure power supply, dot clocks and start them. */ 88662306a36Sopenharmony_ci tmp = priv->lddckr; 88762306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { 88862306a36Sopenharmony_ci ch = &priv->ch[k]; 88962306a36Sopenharmony_ci if (!ch->enabled) 89062306a36Sopenharmony_ci continue; 89162306a36Sopenharmony_ci 89262306a36Sopenharmony_ci /* Power supply */ 89362306a36Sopenharmony_ci lcdc_write_chan(ch, LDPMR, 0); 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ci m = ch->cfg->clock_divider; 89662306a36Sopenharmony_ci if (!m) 89762306a36Sopenharmony_ci continue; 89862306a36Sopenharmony_ci 89962306a36Sopenharmony_ci /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider 90062306a36Sopenharmony_ci * denominator. 90162306a36Sopenharmony_ci */ 90262306a36Sopenharmony_ci lcdc_write_chan(ch, LDDCKPAT1R, 0); 90362306a36Sopenharmony_ci lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1); 90462306a36Sopenharmony_ci 90562306a36Sopenharmony_ci if (m == 1) 90662306a36Sopenharmony_ci m = LDDCKR_MOSEL; 90762306a36Sopenharmony_ci tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0); 90862306a36Sopenharmony_ci } 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_ci lcdc_write(priv, _LDDCKR, tmp); 91162306a36Sopenharmony_ci lcdc_write(priv, _LDDCKSTPR, 0); 91262306a36Sopenharmony_ci lcdc_wait_bit(priv, _LDDCKSTPR, ~0, 0); 91362306a36Sopenharmony_ci 91462306a36Sopenharmony_ci /* Setup geometry, format, frame buffer memory and operation mode. */ 91562306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { 91662306a36Sopenharmony_ci ch = &priv->ch[k]; 91762306a36Sopenharmony_ci if (!ch->enabled) 91862306a36Sopenharmony_ci continue; 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_ci sh_mobile_lcdc_geometry(ch); 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci tmp = ch->format->lddfr; 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_ci if (ch->format->yuv) { 92562306a36Sopenharmony_ci switch (ch->colorspace) { 92662306a36Sopenharmony_ci case V4L2_COLORSPACE_REC709: 92762306a36Sopenharmony_ci tmp |= LDDFR_CF1; 92862306a36Sopenharmony_ci break; 92962306a36Sopenharmony_ci case V4L2_COLORSPACE_JPEG: 93062306a36Sopenharmony_ci tmp |= LDDFR_CF0; 93162306a36Sopenharmony_ci break; 93262306a36Sopenharmony_ci } 93362306a36Sopenharmony_ci } 93462306a36Sopenharmony_ci 93562306a36Sopenharmony_ci lcdc_write_chan(ch, LDDFR, tmp); 93662306a36Sopenharmony_ci lcdc_write_chan(ch, LDMLSR, ch->line_size); 93762306a36Sopenharmony_ci lcdc_write_chan(ch, LDSA1R, ch->base_addr_y); 93862306a36Sopenharmony_ci if (ch->format->yuv) 93962306a36Sopenharmony_ci lcdc_write_chan(ch, LDSA2R, ch->base_addr_c); 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_ci /* When using deferred I/O mode, configure the LCDC for one-shot 94262306a36Sopenharmony_ci * operation and enable the frame end interrupt. Otherwise use 94362306a36Sopenharmony_ci * continuous read mode. 94462306a36Sopenharmony_ci */ 94562306a36Sopenharmony_ci if (ch->ldmt1r_value & LDMT1R_IFM && 94662306a36Sopenharmony_ci ch->cfg->sys_bus_cfg.deferred_io_msec) { 94762306a36Sopenharmony_ci lcdc_write_chan(ch, LDSM1R, LDSM1R_OS); 94862306a36Sopenharmony_ci lcdc_write(priv, _LDINTR, LDINTR_FE); 94962306a36Sopenharmony_ci } else { 95062306a36Sopenharmony_ci lcdc_write_chan(ch, LDSM1R, 0); 95162306a36Sopenharmony_ci } 95262306a36Sopenharmony_ci } 95362306a36Sopenharmony_ci 95462306a36Sopenharmony_ci /* Word and long word swap. */ 95562306a36Sopenharmony_ci switch (priv->ch[0].format->fourcc) { 95662306a36Sopenharmony_ci case V4L2_PIX_FMT_RGB565: 95762306a36Sopenharmony_ci case V4L2_PIX_FMT_NV21: 95862306a36Sopenharmony_ci case V4L2_PIX_FMT_NV61: 95962306a36Sopenharmony_ci case V4L2_PIX_FMT_NV42: 96062306a36Sopenharmony_ci tmp = LDDDSR_LS | LDDDSR_WS; 96162306a36Sopenharmony_ci break; 96262306a36Sopenharmony_ci case V4L2_PIX_FMT_BGR24: 96362306a36Sopenharmony_ci case V4L2_PIX_FMT_NV12: 96462306a36Sopenharmony_ci case V4L2_PIX_FMT_NV16: 96562306a36Sopenharmony_ci case V4L2_PIX_FMT_NV24: 96662306a36Sopenharmony_ci tmp = LDDDSR_LS | LDDDSR_WS | LDDDSR_BS; 96762306a36Sopenharmony_ci break; 96862306a36Sopenharmony_ci case V4L2_PIX_FMT_BGR32: 96962306a36Sopenharmony_ci default: 97062306a36Sopenharmony_ci tmp = LDDDSR_LS; 97162306a36Sopenharmony_ci break; 97262306a36Sopenharmony_ci } 97362306a36Sopenharmony_ci lcdc_write(priv, _LDDDSR, tmp); 97462306a36Sopenharmony_ci 97562306a36Sopenharmony_ci /* Enable the display output. */ 97662306a36Sopenharmony_ci lcdc_write(priv, _LDCNT1R, LDCNT1R_DE); 97762306a36Sopenharmony_ci sh_mobile_lcdc_start_stop(priv, 1); 97862306a36Sopenharmony_ci priv->started = 1; 97962306a36Sopenharmony_ci} 98062306a36Sopenharmony_ci 98162306a36Sopenharmony_cistatic int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) 98262306a36Sopenharmony_ci{ 98362306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch; 98462306a36Sopenharmony_ci unsigned long tmp; 98562306a36Sopenharmony_ci int ret; 98662306a36Sopenharmony_ci int k; 98762306a36Sopenharmony_ci 98862306a36Sopenharmony_ci /* enable clocks before accessing the hardware */ 98962306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { 99062306a36Sopenharmony_ci if (priv->ch[k].enabled) 99162306a36Sopenharmony_ci sh_mobile_lcdc_clk_on(priv); 99262306a36Sopenharmony_ci } 99362306a36Sopenharmony_ci 99462306a36Sopenharmony_ci /* reset */ 99562306a36Sopenharmony_ci lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LDCNT2R_BR); 99662306a36Sopenharmony_ci lcdc_wait_bit(priv, _LDCNT2R, LDCNT2R_BR, 0); 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { 99962306a36Sopenharmony_ci const struct sh_mobile_lcdc_panel_cfg *panel; 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_ci ch = &priv->ch[k]; 100262306a36Sopenharmony_ci if (!ch->enabled) 100362306a36Sopenharmony_ci continue; 100462306a36Sopenharmony_ci 100562306a36Sopenharmony_ci panel = &ch->cfg->panel_cfg; 100662306a36Sopenharmony_ci if (panel->setup_sys) { 100762306a36Sopenharmony_ci ret = panel->setup_sys(ch, &sh_mobile_lcdc_sys_bus_ops); 100862306a36Sopenharmony_ci if (ret) 100962306a36Sopenharmony_ci return ret; 101062306a36Sopenharmony_ci } 101162306a36Sopenharmony_ci } 101262306a36Sopenharmony_ci 101362306a36Sopenharmony_ci /* Compute frame buffer base address and pitch for each channel. */ 101462306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { 101562306a36Sopenharmony_ci ch = &priv->ch[k]; 101662306a36Sopenharmony_ci if (!ch->enabled) 101762306a36Sopenharmony_ci continue; 101862306a36Sopenharmony_ci 101962306a36Sopenharmony_ci ch->base_addr_y = ch->dma_handle; 102062306a36Sopenharmony_ci ch->base_addr_c = ch->dma_handle 102162306a36Sopenharmony_ci + ch->xres_virtual * ch->yres_virtual; 102262306a36Sopenharmony_ci ch->line_size = ch->pitch; 102362306a36Sopenharmony_ci } 102462306a36Sopenharmony_ci 102562306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(priv->overlays); ++k) { 102662306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[k]; 102762306a36Sopenharmony_ci sh_mobile_lcdc_overlay_setup(ovl); 102862306a36Sopenharmony_ci } 102962306a36Sopenharmony_ci 103062306a36Sopenharmony_ci /* Start the LCDC. */ 103162306a36Sopenharmony_ci __sh_mobile_lcdc_start(priv); 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_ci /* Setup deferred I/O, tell the board code to enable the panels, and 103462306a36Sopenharmony_ci * turn backlight on. 103562306a36Sopenharmony_ci */ 103662306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { 103762306a36Sopenharmony_ci ch = &priv->ch[k]; 103862306a36Sopenharmony_ci if (!ch->enabled) 103962306a36Sopenharmony_ci continue; 104062306a36Sopenharmony_ci 104162306a36Sopenharmony_ci tmp = ch->cfg->sys_bus_cfg.deferred_io_msec; 104262306a36Sopenharmony_ci if (ch->ldmt1r_value & LDMT1R_IFM && tmp) { 104362306a36Sopenharmony_ci ch->defio.deferred_io = sh_mobile_lcdc_deferred_io; 104462306a36Sopenharmony_ci ch->defio.delay = msecs_to_jiffies(tmp); 104562306a36Sopenharmony_ci ch->info->fbdefio = &ch->defio; 104662306a36Sopenharmony_ci fb_deferred_io_init(ch->info); 104762306a36Sopenharmony_ci } 104862306a36Sopenharmony_ci 104962306a36Sopenharmony_ci sh_mobile_lcdc_display_on(ch); 105062306a36Sopenharmony_ci 105162306a36Sopenharmony_ci if (ch->bl) { 105262306a36Sopenharmony_ci ch->bl->props.power = FB_BLANK_UNBLANK; 105362306a36Sopenharmony_ci backlight_update_status(ch->bl); 105462306a36Sopenharmony_ci } 105562306a36Sopenharmony_ci } 105662306a36Sopenharmony_ci 105762306a36Sopenharmony_ci return 0; 105862306a36Sopenharmony_ci} 105962306a36Sopenharmony_ci 106062306a36Sopenharmony_cistatic void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) 106162306a36Sopenharmony_ci{ 106262306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch; 106362306a36Sopenharmony_ci int k; 106462306a36Sopenharmony_ci 106562306a36Sopenharmony_ci /* clean up deferred io and ask board code to disable panel */ 106662306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { 106762306a36Sopenharmony_ci ch = &priv->ch[k]; 106862306a36Sopenharmony_ci if (!ch->enabled) 106962306a36Sopenharmony_ci continue; 107062306a36Sopenharmony_ci 107162306a36Sopenharmony_ci /* deferred io mode: 107262306a36Sopenharmony_ci * flush frame, and wait for frame end interrupt 107362306a36Sopenharmony_ci * clean up deferred io and enable clock 107462306a36Sopenharmony_ci */ 107562306a36Sopenharmony_ci if (ch->info && ch->info->fbdefio) { 107662306a36Sopenharmony_ci ch->frame_end = 0; 107762306a36Sopenharmony_ci schedule_delayed_work(&ch->info->deferred_work, 0); 107862306a36Sopenharmony_ci wait_event(ch->frame_end_wait, ch->frame_end); 107962306a36Sopenharmony_ci fb_deferred_io_cleanup(ch->info); 108062306a36Sopenharmony_ci ch->info->fbdefio = NULL; 108162306a36Sopenharmony_ci sh_mobile_lcdc_clk_on(priv); 108262306a36Sopenharmony_ci } 108362306a36Sopenharmony_ci 108462306a36Sopenharmony_ci if (ch->bl) { 108562306a36Sopenharmony_ci ch->bl->props.power = FB_BLANK_POWERDOWN; 108662306a36Sopenharmony_ci backlight_update_status(ch->bl); 108762306a36Sopenharmony_ci } 108862306a36Sopenharmony_ci 108962306a36Sopenharmony_ci sh_mobile_lcdc_display_off(ch); 109062306a36Sopenharmony_ci } 109162306a36Sopenharmony_ci 109262306a36Sopenharmony_ci /* stop the lcdc */ 109362306a36Sopenharmony_ci if (priv->started) { 109462306a36Sopenharmony_ci sh_mobile_lcdc_start_stop(priv, 0); 109562306a36Sopenharmony_ci priv->started = 0; 109662306a36Sopenharmony_ci } 109762306a36Sopenharmony_ci 109862306a36Sopenharmony_ci /* stop clocks */ 109962306a36Sopenharmony_ci for (k = 0; k < ARRAY_SIZE(priv->ch); k++) 110062306a36Sopenharmony_ci if (priv->ch[k].enabled) 110162306a36Sopenharmony_ci sh_mobile_lcdc_clk_off(priv); 110262306a36Sopenharmony_ci} 110362306a36Sopenharmony_ci 110462306a36Sopenharmony_cistatic int __sh_mobile_lcdc_check_var(struct fb_var_screeninfo *var, 110562306a36Sopenharmony_ci struct fb_info *info) 110662306a36Sopenharmony_ci{ 110762306a36Sopenharmony_ci if (var->xres > MAX_XRES || var->yres > MAX_YRES) 110862306a36Sopenharmony_ci return -EINVAL; 110962306a36Sopenharmony_ci 111062306a36Sopenharmony_ci /* Make sure the virtual resolution is at least as big as the visible 111162306a36Sopenharmony_ci * resolution. 111262306a36Sopenharmony_ci */ 111362306a36Sopenharmony_ci if (var->xres_virtual < var->xres) 111462306a36Sopenharmony_ci var->xres_virtual = var->xres; 111562306a36Sopenharmony_ci if (var->yres_virtual < var->yres) 111662306a36Sopenharmony_ci var->yres_virtual = var->yres; 111762306a36Sopenharmony_ci 111862306a36Sopenharmony_ci if (sh_mobile_format_is_fourcc(var)) { 111962306a36Sopenharmony_ci const struct sh_mobile_lcdc_format_info *format; 112062306a36Sopenharmony_ci 112162306a36Sopenharmony_ci format = sh_mobile_format_info(var->grayscale); 112262306a36Sopenharmony_ci if (format == NULL) 112362306a36Sopenharmony_ci return -EINVAL; 112462306a36Sopenharmony_ci var->bits_per_pixel = format->bpp; 112562306a36Sopenharmony_ci 112662306a36Sopenharmony_ci /* Default to RGB and JPEG color-spaces for RGB and YUV formats 112762306a36Sopenharmony_ci * respectively. 112862306a36Sopenharmony_ci */ 112962306a36Sopenharmony_ci if (!format->yuv) 113062306a36Sopenharmony_ci var->colorspace = V4L2_COLORSPACE_SRGB; 113162306a36Sopenharmony_ci else if (var->colorspace != V4L2_COLORSPACE_REC709) 113262306a36Sopenharmony_ci var->colorspace = V4L2_COLORSPACE_JPEG; 113362306a36Sopenharmony_ci } else { 113462306a36Sopenharmony_ci if (var->bits_per_pixel <= 16) { /* RGB 565 */ 113562306a36Sopenharmony_ci var->bits_per_pixel = 16; 113662306a36Sopenharmony_ci var->red.offset = 11; 113762306a36Sopenharmony_ci var->red.length = 5; 113862306a36Sopenharmony_ci var->green.offset = 5; 113962306a36Sopenharmony_ci var->green.length = 6; 114062306a36Sopenharmony_ci var->blue.offset = 0; 114162306a36Sopenharmony_ci var->blue.length = 5; 114262306a36Sopenharmony_ci var->transp.offset = 0; 114362306a36Sopenharmony_ci var->transp.length = 0; 114462306a36Sopenharmony_ci } else if (var->bits_per_pixel <= 24) { /* RGB 888 */ 114562306a36Sopenharmony_ci var->bits_per_pixel = 24; 114662306a36Sopenharmony_ci var->red.offset = 16; 114762306a36Sopenharmony_ci var->red.length = 8; 114862306a36Sopenharmony_ci var->green.offset = 8; 114962306a36Sopenharmony_ci var->green.length = 8; 115062306a36Sopenharmony_ci var->blue.offset = 0; 115162306a36Sopenharmony_ci var->blue.length = 8; 115262306a36Sopenharmony_ci var->transp.offset = 0; 115362306a36Sopenharmony_ci var->transp.length = 0; 115462306a36Sopenharmony_ci } else if (var->bits_per_pixel <= 32) { /* RGBA 888 */ 115562306a36Sopenharmony_ci var->bits_per_pixel = 32; 115662306a36Sopenharmony_ci var->red.offset = 16; 115762306a36Sopenharmony_ci var->red.length = 8; 115862306a36Sopenharmony_ci var->green.offset = 8; 115962306a36Sopenharmony_ci var->green.length = 8; 116062306a36Sopenharmony_ci var->blue.offset = 0; 116162306a36Sopenharmony_ci var->blue.length = 8; 116262306a36Sopenharmony_ci var->transp.offset = 24; 116362306a36Sopenharmony_ci var->transp.length = 8; 116462306a36Sopenharmony_ci } else 116562306a36Sopenharmony_ci return -EINVAL; 116662306a36Sopenharmony_ci 116762306a36Sopenharmony_ci var->red.msb_right = 0; 116862306a36Sopenharmony_ci var->green.msb_right = 0; 116962306a36Sopenharmony_ci var->blue.msb_right = 0; 117062306a36Sopenharmony_ci var->transp.msb_right = 0; 117162306a36Sopenharmony_ci } 117262306a36Sopenharmony_ci 117362306a36Sopenharmony_ci /* Make sure we don't exceed our allocated memory. */ 117462306a36Sopenharmony_ci if (var->xres_virtual * var->yres_virtual * var->bits_per_pixel / 8 > 117562306a36Sopenharmony_ci info->fix.smem_len) 117662306a36Sopenharmony_ci return -EINVAL; 117762306a36Sopenharmony_ci 117862306a36Sopenharmony_ci return 0; 117962306a36Sopenharmony_ci} 118062306a36Sopenharmony_ci 118162306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 118262306a36Sopenharmony_ci * Frame buffer operations - Overlays 118362306a36Sopenharmony_ci */ 118462306a36Sopenharmony_ci 118562306a36Sopenharmony_cistatic ssize_t 118662306a36Sopenharmony_cioverlay_alpha_show(struct device *dev, struct device_attribute *attr, char *buf) 118762306a36Sopenharmony_ci{ 118862306a36Sopenharmony_ci struct fb_info *info = dev_get_drvdata(dev); 118962306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 119062306a36Sopenharmony_ci 119162306a36Sopenharmony_ci return sysfs_emit(buf, "%u\n", ovl->alpha); 119262306a36Sopenharmony_ci} 119362306a36Sopenharmony_ci 119462306a36Sopenharmony_cistatic ssize_t 119562306a36Sopenharmony_cioverlay_alpha_store(struct device *dev, struct device_attribute *attr, 119662306a36Sopenharmony_ci const char *buf, size_t count) 119762306a36Sopenharmony_ci{ 119862306a36Sopenharmony_ci struct fb_info *info = dev_get_drvdata(dev); 119962306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 120062306a36Sopenharmony_ci unsigned int alpha; 120162306a36Sopenharmony_ci char *endp; 120262306a36Sopenharmony_ci 120362306a36Sopenharmony_ci alpha = simple_strtoul(buf, &endp, 10); 120462306a36Sopenharmony_ci if (isspace(*endp)) 120562306a36Sopenharmony_ci endp++; 120662306a36Sopenharmony_ci 120762306a36Sopenharmony_ci if (endp - buf != count) 120862306a36Sopenharmony_ci return -EINVAL; 120962306a36Sopenharmony_ci 121062306a36Sopenharmony_ci if (alpha > 255) 121162306a36Sopenharmony_ci return -EINVAL; 121262306a36Sopenharmony_ci 121362306a36Sopenharmony_ci if (ovl->alpha != alpha) { 121462306a36Sopenharmony_ci ovl->alpha = alpha; 121562306a36Sopenharmony_ci 121662306a36Sopenharmony_ci if (ovl->mode == LCDC_OVERLAY_BLEND && ovl->enabled) 121762306a36Sopenharmony_ci sh_mobile_lcdc_overlay_setup(ovl); 121862306a36Sopenharmony_ci } 121962306a36Sopenharmony_ci 122062306a36Sopenharmony_ci return count; 122162306a36Sopenharmony_ci} 122262306a36Sopenharmony_ci 122362306a36Sopenharmony_cistatic ssize_t 122462306a36Sopenharmony_cioverlay_mode_show(struct device *dev, struct device_attribute *attr, char *buf) 122562306a36Sopenharmony_ci{ 122662306a36Sopenharmony_ci struct fb_info *info = dev_get_drvdata(dev); 122762306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 122862306a36Sopenharmony_ci 122962306a36Sopenharmony_ci return sysfs_emit(buf, "%u\n", ovl->mode); 123062306a36Sopenharmony_ci} 123162306a36Sopenharmony_ci 123262306a36Sopenharmony_cistatic ssize_t 123362306a36Sopenharmony_cioverlay_mode_store(struct device *dev, struct device_attribute *attr, 123462306a36Sopenharmony_ci const char *buf, size_t count) 123562306a36Sopenharmony_ci{ 123662306a36Sopenharmony_ci struct fb_info *info = dev_get_drvdata(dev); 123762306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 123862306a36Sopenharmony_ci unsigned int mode; 123962306a36Sopenharmony_ci char *endp; 124062306a36Sopenharmony_ci 124162306a36Sopenharmony_ci mode = simple_strtoul(buf, &endp, 10); 124262306a36Sopenharmony_ci if (isspace(*endp)) 124362306a36Sopenharmony_ci endp++; 124462306a36Sopenharmony_ci 124562306a36Sopenharmony_ci if (endp - buf != count) 124662306a36Sopenharmony_ci return -EINVAL; 124762306a36Sopenharmony_ci 124862306a36Sopenharmony_ci if (mode != LCDC_OVERLAY_BLEND && mode != LCDC_OVERLAY_ROP3) 124962306a36Sopenharmony_ci return -EINVAL; 125062306a36Sopenharmony_ci 125162306a36Sopenharmony_ci if (ovl->mode != mode) { 125262306a36Sopenharmony_ci ovl->mode = mode; 125362306a36Sopenharmony_ci 125462306a36Sopenharmony_ci if (ovl->enabled) 125562306a36Sopenharmony_ci sh_mobile_lcdc_overlay_setup(ovl); 125662306a36Sopenharmony_ci } 125762306a36Sopenharmony_ci 125862306a36Sopenharmony_ci return count; 125962306a36Sopenharmony_ci} 126062306a36Sopenharmony_ci 126162306a36Sopenharmony_cistatic ssize_t 126262306a36Sopenharmony_cioverlay_position_show(struct device *dev, struct device_attribute *attr, 126362306a36Sopenharmony_ci char *buf) 126462306a36Sopenharmony_ci{ 126562306a36Sopenharmony_ci struct fb_info *info = dev_get_drvdata(dev); 126662306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 126762306a36Sopenharmony_ci 126862306a36Sopenharmony_ci return sysfs_emit(buf, "%d,%d\n", ovl->pos_x, ovl->pos_y); 126962306a36Sopenharmony_ci} 127062306a36Sopenharmony_ci 127162306a36Sopenharmony_cistatic ssize_t 127262306a36Sopenharmony_cioverlay_position_store(struct device *dev, struct device_attribute *attr, 127362306a36Sopenharmony_ci const char *buf, size_t count) 127462306a36Sopenharmony_ci{ 127562306a36Sopenharmony_ci struct fb_info *info = dev_get_drvdata(dev); 127662306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 127762306a36Sopenharmony_ci char *endp; 127862306a36Sopenharmony_ci int pos_x; 127962306a36Sopenharmony_ci int pos_y; 128062306a36Sopenharmony_ci 128162306a36Sopenharmony_ci pos_x = simple_strtol(buf, &endp, 10); 128262306a36Sopenharmony_ci if (*endp != ',') 128362306a36Sopenharmony_ci return -EINVAL; 128462306a36Sopenharmony_ci 128562306a36Sopenharmony_ci pos_y = simple_strtol(endp + 1, &endp, 10); 128662306a36Sopenharmony_ci if (isspace(*endp)) 128762306a36Sopenharmony_ci endp++; 128862306a36Sopenharmony_ci 128962306a36Sopenharmony_ci if (endp - buf != count) 129062306a36Sopenharmony_ci return -EINVAL; 129162306a36Sopenharmony_ci 129262306a36Sopenharmony_ci if (ovl->pos_x != pos_x || ovl->pos_y != pos_y) { 129362306a36Sopenharmony_ci ovl->pos_x = pos_x; 129462306a36Sopenharmony_ci ovl->pos_y = pos_y; 129562306a36Sopenharmony_ci 129662306a36Sopenharmony_ci if (ovl->enabled) 129762306a36Sopenharmony_ci sh_mobile_lcdc_overlay_setup(ovl); 129862306a36Sopenharmony_ci } 129962306a36Sopenharmony_ci 130062306a36Sopenharmony_ci return count; 130162306a36Sopenharmony_ci} 130262306a36Sopenharmony_ci 130362306a36Sopenharmony_cistatic ssize_t 130462306a36Sopenharmony_cioverlay_rop3_show(struct device *dev, struct device_attribute *attr, char *buf) 130562306a36Sopenharmony_ci{ 130662306a36Sopenharmony_ci struct fb_info *info = dev_get_drvdata(dev); 130762306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 130862306a36Sopenharmony_ci 130962306a36Sopenharmony_ci return sysfs_emit(buf, "%u\n", ovl->rop3); 131062306a36Sopenharmony_ci} 131162306a36Sopenharmony_ci 131262306a36Sopenharmony_cistatic ssize_t 131362306a36Sopenharmony_cioverlay_rop3_store(struct device *dev, struct device_attribute *attr, 131462306a36Sopenharmony_ci const char *buf, size_t count) 131562306a36Sopenharmony_ci{ 131662306a36Sopenharmony_ci struct fb_info *info = dev_get_drvdata(dev); 131762306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 131862306a36Sopenharmony_ci unsigned int rop3; 131962306a36Sopenharmony_ci char *endp; 132062306a36Sopenharmony_ci 132162306a36Sopenharmony_ci rop3 = simple_strtoul(buf, &endp, 10); 132262306a36Sopenharmony_ci if (isspace(*endp)) 132362306a36Sopenharmony_ci endp++; 132462306a36Sopenharmony_ci 132562306a36Sopenharmony_ci if (endp - buf != count) 132662306a36Sopenharmony_ci return -EINVAL; 132762306a36Sopenharmony_ci 132862306a36Sopenharmony_ci if (rop3 > 255) 132962306a36Sopenharmony_ci return -EINVAL; 133062306a36Sopenharmony_ci 133162306a36Sopenharmony_ci if (ovl->rop3 != rop3) { 133262306a36Sopenharmony_ci ovl->rop3 = rop3; 133362306a36Sopenharmony_ci 133462306a36Sopenharmony_ci if (ovl->mode == LCDC_OVERLAY_ROP3 && ovl->enabled) 133562306a36Sopenharmony_ci sh_mobile_lcdc_overlay_setup(ovl); 133662306a36Sopenharmony_ci } 133762306a36Sopenharmony_ci 133862306a36Sopenharmony_ci return count; 133962306a36Sopenharmony_ci} 134062306a36Sopenharmony_ci 134162306a36Sopenharmony_cistatic const struct device_attribute overlay_sysfs_attrs[] = { 134262306a36Sopenharmony_ci __ATTR(ovl_alpha, S_IRUGO|S_IWUSR, 134362306a36Sopenharmony_ci overlay_alpha_show, overlay_alpha_store), 134462306a36Sopenharmony_ci __ATTR(ovl_mode, S_IRUGO|S_IWUSR, 134562306a36Sopenharmony_ci overlay_mode_show, overlay_mode_store), 134662306a36Sopenharmony_ci __ATTR(ovl_position, S_IRUGO|S_IWUSR, 134762306a36Sopenharmony_ci overlay_position_show, overlay_position_store), 134862306a36Sopenharmony_ci __ATTR(ovl_rop3, S_IRUGO|S_IWUSR, 134962306a36Sopenharmony_ci overlay_rop3_show, overlay_rop3_store), 135062306a36Sopenharmony_ci}; 135162306a36Sopenharmony_ci 135262306a36Sopenharmony_cistatic const struct fb_fix_screeninfo sh_mobile_lcdc_overlay_fix = { 135362306a36Sopenharmony_ci .id = "SH Mobile LCDC", 135462306a36Sopenharmony_ci .type = FB_TYPE_PACKED_PIXELS, 135562306a36Sopenharmony_ci .visual = FB_VISUAL_TRUECOLOR, 135662306a36Sopenharmony_ci .accel = FB_ACCEL_NONE, 135762306a36Sopenharmony_ci .xpanstep = 1, 135862306a36Sopenharmony_ci .ypanstep = 1, 135962306a36Sopenharmony_ci .ywrapstep = 0, 136062306a36Sopenharmony_ci .capabilities = FB_CAP_FOURCC, 136162306a36Sopenharmony_ci}; 136262306a36Sopenharmony_ci 136362306a36Sopenharmony_cistatic int sh_mobile_lcdc_overlay_pan(struct fb_var_screeninfo *var, 136462306a36Sopenharmony_ci struct fb_info *info) 136562306a36Sopenharmony_ci{ 136662306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 136762306a36Sopenharmony_ci unsigned long base_addr_y; 136862306a36Sopenharmony_ci unsigned long base_addr_c; 136962306a36Sopenharmony_ci unsigned long y_offset; 137062306a36Sopenharmony_ci unsigned long c_offset; 137162306a36Sopenharmony_ci 137262306a36Sopenharmony_ci if (!ovl->format->yuv) { 137362306a36Sopenharmony_ci y_offset = (var->yoffset * ovl->xres_virtual + var->xoffset) 137462306a36Sopenharmony_ci * ovl->format->bpp / 8; 137562306a36Sopenharmony_ci c_offset = 0; 137662306a36Sopenharmony_ci } else { 137762306a36Sopenharmony_ci unsigned int xsub = ovl->format->bpp < 24 ? 2 : 1; 137862306a36Sopenharmony_ci unsigned int ysub = ovl->format->bpp < 16 ? 2 : 1; 137962306a36Sopenharmony_ci 138062306a36Sopenharmony_ci y_offset = var->yoffset * ovl->xres_virtual + var->xoffset; 138162306a36Sopenharmony_ci c_offset = var->yoffset / ysub * ovl->xres_virtual * 2 / xsub 138262306a36Sopenharmony_ci + var->xoffset * 2 / xsub; 138362306a36Sopenharmony_ci } 138462306a36Sopenharmony_ci 138562306a36Sopenharmony_ci /* If the Y offset hasn't changed, the C offset hasn't either. There's 138662306a36Sopenharmony_ci * nothing to do in that case. 138762306a36Sopenharmony_ci */ 138862306a36Sopenharmony_ci if (y_offset == ovl->pan_y_offset) 138962306a36Sopenharmony_ci return 0; 139062306a36Sopenharmony_ci 139162306a36Sopenharmony_ci /* Set the source address for the next refresh */ 139262306a36Sopenharmony_ci base_addr_y = ovl->dma_handle + y_offset; 139362306a36Sopenharmony_ci base_addr_c = ovl->dma_handle + ovl->xres_virtual * ovl->yres_virtual 139462306a36Sopenharmony_ci + c_offset; 139562306a36Sopenharmony_ci 139662306a36Sopenharmony_ci ovl->base_addr_y = base_addr_y; 139762306a36Sopenharmony_ci ovl->base_addr_c = base_addr_c; 139862306a36Sopenharmony_ci ovl->pan_y_offset = y_offset; 139962306a36Sopenharmony_ci 140062306a36Sopenharmony_ci lcdc_write(ovl->channel->lcdc, LDBCR, LDBCR_UPC(ovl->index)); 140162306a36Sopenharmony_ci 140262306a36Sopenharmony_ci lcdc_write_overlay(ovl, LDBnBSAYR(ovl->index), ovl->base_addr_y); 140362306a36Sopenharmony_ci lcdc_write_overlay(ovl, LDBnBSACR(ovl->index), ovl->base_addr_c); 140462306a36Sopenharmony_ci 140562306a36Sopenharmony_ci lcdc_write(ovl->channel->lcdc, LDBCR, 140662306a36Sopenharmony_ci LDBCR_UPF(ovl->index) | LDBCR_UPD(ovl->index)); 140762306a36Sopenharmony_ci 140862306a36Sopenharmony_ci return 0; 140962306a36Sopenharmony_ci} 141062306a36Sopenharmony_ci 141162306a36Sopenharmony_cistatic int sh_mobile_lcdc_overlay_ioctl(struct fb_info *info, unsigned int cmd, 141262306a36Sopenharmony_ci unsigned long arg) 141362306a36Sopenharmony_ci{ 141462306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 141562306a36Sopenharmony_ci 141662306a36Sopenharmony_ci switch (cmd) { 141762306a36Sopenharmony_ci case FBIO_WAITFORVSYNC: 141862306a36Sopenharmony_ci return sh_mobile_lcdc_wait_for_vsync(ovl->channel); 141962306a36Sopenharmony_ci 142062306a36Sopenharmony_ci default: 142162306a36Sopenharmony_ci return -ENOIOCTLCMD; 142262306a36Sopenharmony_ci } 142362306a36Sopenharmony_ci} 142462306a36Sopenharmony_ci 142562306a36Sopenharmony_cistatic int sh_mobile_lcdc_overlay_check_var(struct fb_var_screeninfo *var, 142662306a36Sopenharmony_ci struct fb_info *info) 142762306a36Sopenharmony_ci{ 142862306a36Sopenharmony_ci return __sh_mobile_lcdc_check_var(var, info); 142962306a36Sopenharmony_ci} 143062306a36Sopenharmony_ci 143162306a36Sopenharmony_cistatic int sh_mobile_lcdc_overlay_set_par(struct fb_info *info) 143262306a36Sopenharmony_ci{ 143362306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 143462306a36Sopenharmony_ci 143562306a36Sopenharmony_ci ovl->format = 143662306a36Sopenharmony_ci sh_mobile_format_info(sh_mobile_format_fourcc(&info->var)); 143762306a36Sopenharmony_ci 143862306a36Sopenharmony_ci ovl->xres = info->var.xres; 143962306a36Sopenharmony_ci ovl->xres_virtual = info->var.xres_virtual; 144062306a36Sopenharmony_ci ovl->yres = info->var.yres; 144162306a36Sopenharmony_ci ovl->yres_virtual = info->var.yres_virtual; 144262306a36Sopenharmony_ci 144362306a36Sopenharmony_ci if (ovl->format->yuv) 144462306a36Sopenharmony_ci ovl->pitch = info->var.xres_virtual; 144562306a36Sopenharmony_ci else 144662306a36Sopenharmony_ci ovl->pitch = info->var.xres_virtual * ovl->format->bpp / 8; 144762306a36Sopenharmony_ci 144862306a36Sopenharmony_ci sh_mobile_lcdc_overlay_setup(ovl); 144962306a36Sopenharmony_ci 145062306a36Sopenharmony_ci info->fix.line_length = ovl->pitch; 145162306a36Sopenharmony_ci 145262306a36Sopenharmony_ci if (sh_mobile_format_is_fourcc(&info->var)) { 145362306a36Sopenharmony_ci info->fix.type = FB_TYPE_FOURCC; 145462306a36Sopenharmony_ci info->fix.visual = FB_VISUAL_FOURCC; 145562306a36Sopenharmony_ci } else { 145662306a36Sopenharmony_ci info->fix.type = FB_TYPE_PACKED_PIXELS; 145762306a36Sopenharmony_ci info->fix.visual = FB_VISUAL_TRUECOLOR; 145862306a36Sopenharmony_ci } 145962306a36Sopenharmony_ci 146062306a36Sopenharmony_ci return 0; 146162306a36Sopenharmony_ci} 146262306a36Sopenharmony_ci 146362306a36Sopenharmony_ci/* Overlay blanking. Disable the overlay when blanked. */ 146462306a36Sopenharmony_cistatic int sh_mobile_lcdc_overlay_blank(int blank, struct fb_info *info) 146562306a36Sopenharmony_ci{ 146662306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 146762306a36Sopenharmony_ci 146862306a36Sopenharmony_ci ovl->enabled = !blank; 146962306a36Sopenharmony_ci sh_mobile_lcdc_overlay_setup(ovl); 147062306a36Sopenharmony_ci 147162306a36Sopenharmony_ci /* Prevent the backlight from receiving a blanking event by returning 147262306a36Sopenharmony_ci * a non-zero value. 147362306a36Sopenharmony_ci */ 147462306a36Sopenharmony_ci return 1; 147562306a36Sopenharmony_ci} 147662306a36Sopenharmony_ci 147762306a36Sopenharmony_cistatic int 147862306a36Sopenharmony_cish_mobile_lcdc_overlay_mmap(struct fb_info *info, struct vm_area_struct *vma) 147962306a36Sopenharmony_ci{ 148062306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = info->par; 148162306a36Sopenharmony_ci 148262306a36Sopenharmony_ci if (info->fbdefio) 148362306a36Sopenharmony_ci return fb_deferred_io_mmap(info, vma); 148462306a36Sopenharmony_ci 148562306a36Sopenharmony_ci return dma_mmap_coherent(ovl->channel->lcdc->dev, vma, ovl->fb_mem, 148662306a36Sopenharmony_ci ovl->dma_handle, ovl->fb_size); 148762306a36Sopenharmony_ci} 148862306a36Sopenharmony_ci 148962306a36Sopenharmony_cistatic const struct fb_ops sh_mobile_lcdc_overlay_ops = { 149062306a36Sopenharmony_ci .owner = THIS_MODULE, 149162306a36Sopenharmony_ci .fb_read = fb_sys_read, 149262306a36Sopenharmony_ci .fb_write = fb_sys_write, 149362306a36Sopenharmony_ci .fb_fillrect = sys_fillrect, 149462306a36Sopenharmony_ci .fb_copyarea = sys_copyarea, 149562306a36Sopenharmony_ci .fb_imageblit = sys_imageblit, 149662306a36Sopenharmony_ci .fb_blank = sh_mobile_lcdc_overlay_blank, 149762306a36Sopenharmony_ci .fb_pan_display = sh_mobile_lcdc_overlay_pan, 149862306a36Sopenharmony_ci .fb_ioctl = sh_mobile_lcdc_overlay_ioctl, 149962306a36Sopenharmony_ci .fb_check_var = sh_mobile_lcdc_overlay_check_var, 150062306a36Sopenharmony_ci .fb_set_par = sh_mobile_lcdc_overlay_set_par, 150162306a36Sopenharmony_ci .fb_mmap = sh_mobile_lcdc_overlay_mmap, 150262306a36Sopenharmony_ci}; 150362306a36Sopenharmony_ci 150462306a36Sopenharmony_cistatic void 150562306a36Sopenharmony_cish_mobile_lcdc_overlay_fb_unregister(struct sh_mobile_lcdc_overlay *ovl) 150662306a36Sopenharmony_ci{ 150762306a36Sopenharmony_ci struct fb_info *info = ovl->info; 150862306a36Sopenharmony_ci 150962306a36Sopenharmony_ci if (info == NULL || info->dev == NULL) 151062306a36Sopenharmony_ci return; 151162306a36Sopenharmony_ci 151262306a36Sopenharmony_ci unregister_framebuffer(ovl->info); 151362306a36Sopenharmony_ci} 151462306a36Sopenharmony_ci 151562306a36Sopenharmony_cistatic int 151662306a36Sopenharmony_cish_mobile_lcdc_overlay_fb_register(struct sh_mobile_lcdc_overlay *ovl) 151762306a36Sopenharmony_ci{ 151862306a36Sopenharmony_ci struct sh_mobile_lcdc_priv *lcdc = ovl->channel->lcdc; 151962306a36Sopenharmony_ci struct fb_info *info = ovl->info; 152062306a36Sopenharmony_ci unsigned int i; 152162306a36Sopenharmony_ci int ret; 152262306a36Sopenharmony_ci 152362306a36Sopenharmony_ci if (info == NULL) 152462306a36Sopenharmony_ci return 0; 152562306a36Sopenharmony_ci 152662306a36Sopenharmony_ci ret = register_framebuffer(info); 152762306a36Sopenharmony_ci if (ret < 0) 152862306a36Sopenharmony_ci return ret; 152962306a36Sopenharmony_ci 153062306a36Sopenharmony_ci dev_info(lcdc->dev, "registered %s/overlay %u as %dx%d %dbpp.\n", 153162306a36Sopenharmony_ci dev_name(lcdc->dev), ovl->index, info->var.xres, 153262306a36Sopenharmony_ci info->var.yres, info->var.bits_per_pixel); 153362306a36Sopenharmony_ci 153462306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(overlay_sysfs_attrs); ++i) { 153562306a36Sopenharmony_ci ret = device_create_file(info->dev, &overlay_sysfs_attrs[i]); 153662306a36Sopenharmony_ci if (ret < 0) 153762306a36Sopenharmony_ci return ret; 153862306a36Sopenharmony_ci } 153962306a36Sopenharmony_ci 154062306a36Sopenharmony_ci return 0; 154162306a36Sopenharmony_ci} 154262306a36Sopenharmony_ci 154362306a36Sopenharmony_cistatic void 154462306a36Sopenharmony_cish_mobile_lcdc_overlay_fb_cleanup(struct sh_mobile_lcdc_overlay *ovl) 154562306a36Sopenharmony_ci{ 154662306a36Sopenharmony_ci struct fb_info *info = ovl->info; 154762306a36Sopenharmony_ci 154862306a36Sopenharmony_ci if (info == NULL || info->device == NULL) 154962306a36Sopenharmony_ci return; 155062306a36Sopenharmony_ci 155162306a36Sopenharmony_ci framebuffer_release(info); 155262306a36Sopenharmony_ci} 155362306a36Sopenharmony_ci 155462306a36Sopenharmony_cistatic int 155562306a36Sopenharmony_cish_mobile_lcdc_overlay_fb_init(struct sh_mobile_lcdc_overlay *ovl) 155662306a36Sopenharmony_ci{ 155762306a36Sopenharmony_ci struct sh_mobile_lcdc_priv *priv = ovl->channel->lcdc; 155862306a36Sopenharmony_ci struct fb_var_screeninfo *var; 155962306a36Sopenharmony_ci struct fb_info *info; 156062306a36Sopenharmony_ci 156162306a36Sopenharmony_ci /* Allocate and initialize the frame buffer device. */ 156262306a36Sopenharmony_ci info = framebuffer_alloc(0, priv->dev); 156362306a36Sopenharmony_ci if (!info) 156462306a36Sopenharmony_ci return -ENOMEM; 156562306a36Sopenharmony_ci 156662306a36Sopenharmony_ci ovl->info = info; 156762306a36Sopenharmony_ci 156862306a36Sopenharmony_ci info->fbops = &sh_mobile_lcdc_overlay_ops; 156962306a36Sopenharmony_ci info->device = priv->dev; 157062306a36Sopenharmony_ci info->screen_buffer = ovl->fb_mem; 157162306a36Sopenharmony_ci info->par = ovl; 157262306a36Sopenharmony_ci 157362306a36Sopenharmony_ci /* Initialize fixed screen information. Restrict pan to 2 lines steps 157462306a36Sopenharmony_ci * for NV12 and NV21. 157562306a36Sopenharmony_ci */ 157662306a36Sopenharmony_ci info->fix = sh_mobile_lcdc_overlay_fix; 157762306a36Sopenharmony_ci snprintf(info->fix.id, sizeof(info->fix.id), 157862306a36Sopenharmony_ci "SH Mobile LCDC Overlay %u", ovl->index); 157962306a36Sopenharmony_ci info->fix.smem_start = ovl->dma_handle; 158062306a36Sopenharmony_ci info->fix.smem_len = ovl->fb_size; 158162306a36Sopenharmony_ci info->fix.line_length = ovl->pitch; 158262306a36Sopenharmony_ci 158362306a36Sopenharmony_ci if (ovl->format->yuv) 158462306a36Sopenharmony_ci info->fix.visual = FB_VISUAL_FOURCC; 158562306a36Sopenharmony_ci else 158662306a36Sopenharmony_ci info->fix.visual = FB_VISUAL_TRUECOLOR; 158762306a36Sopenharmony_ci 158862306a36Sopenharmony_ci switch (ovl->format->fourcc) { 158962306a36Sopenharmony_ci case V4L2_PIX_FMT_NV12: 159062306a36Sopenharmony_ci case V4L2_PIX_FMT_NV21: 159162306a36Sopenharmony_ci info->fix.ypanstep = 2; 159262306a36Sopenharmony_ci fallthrough; 159362306a36Sopenharmony_ci case V4L2_PIX_FMT_NV16: 159462306a36Sopenharmony_ci case V4L2_PIX_FMT_NV61: 159562306a36Sopenharmony_ci info->fix.xpanstep = 2; 159662306a36Sopenharmony_ci } 159762306a36Sopenharmony_ci 159862306a36Sopenharmony_ci /* Initialize variable screen information. */ 159962306a36Sopenharmony_ci var = &info->var; 160062306a36Sopenharmony_ci memset(var, 0, sizeof(*var)); 160162306a36Sopenharmony_ci var->xres = ovl->xres; 160262306a36Sopenharmony_ci var->yres = ovl->yres; 160362306a36Sopenharmony_ci var->xres_virtual = ovl->xres_virtual; 160462306a36Sopenharmony_ci var->yres_virtual = ovl->yres_virtual; 160562306a36Sopenharmony_ci var->activate = FB_ACTIVATE_NOW; 160662306a36Sopenharmony_ci 160762306a36Sopenharmony_ci /* Use the legacy API by default for RGB formats, and the FOURCC API 160862306a36Sopenharmony_ci * for YUV formats. 160962306a36Sopenharmony_ci */ 161062306a36Sopenharmony_ci if (!ovl->format->yuv) 161162306a36Sopenharmony_ci var->bits_per_pixel = ovl->format->bpp; 161262306a36Sopenharmony_ci else 161362306a36Sopenharmony_ci var->grayscale = ovl->format->fourcc; 161462306a36Sopenharmony_ci 161562306a36Sopenharmony_ci return sh_mobile_lcdc_overlay_check_var(var, info); 161662306a36Sopenharmony_ci} 161762306a36Sopenharmony_ci 161862306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 161962306a36Sopenharmony_ci * Frame buffer operations - main frame buffer 162062306a36Sopenharmony_ci */ 162162306a36Sopenharmony_ci 162262306a36Sopenharmony_cistatic int sh_mobile_lcdc_setcolreg(u_int regno, 162362306a36Sopenharmony_ci u_int red, u_int green, u_int blue, 162462306a36Sopenharmony_ci u_int transp, struct fb_info *info) 162562306a36Sopenharmony_ci{ 162662306a36Sopenharmony_ci u32 *palette = info->pseudo_palette; 162762306a36Sopenharmony_ci 162862306a36Sopenharmony_ci if (regno >= PALETTE_NR) 162962306a36Sopenharmony_ci return -EINVAL; 163062306a36Sopenharmony_ci 163162306a36Sopenharmony_ci /* only FB_VISUAL_TRUECOLOR supported */ 163262306a36Sopenharmony_ci 163362306a36Sopenharmony_ci red >>= 16 - info->var.red.length; 163462306a36Sopenharmony_ci green >>= 16 - info->var.green.length; 163562306a36Sopenharmony_ci blue >>= 16 - info->var.blue.length; 163662306a36Sopenharmony_ci transp >>= 16 - info->var.transp.length; 163762306a36Sopenharmony_ci 163862306a36Sopenharmony_ci palette[regno] = (red << info->var.red.offset) | 163962306a36Sopenharmony_ci (green << info->var.green.offset) | 164062306a36Sopenharmony_ci (blue << info->var.blue.offset) | 164162306a36Sopenharmony_ci (transp << info->var.transp.offset); 164262306a36Sopenharmony_ci 164362306a36Sopenharmony_ci return 0; 164462306a36Sopenharmony_ci} 164562306a36Sopenharmony_ci 164662306a36Sopenharmony_cistatic const struct fb_fix_screeninfo sh_mobile_lcdc_fix = { 164762306a36Sopenharmony_ci .id = "SH Mobile LCDC", 164862306a36Sopenharmony_ci .type = FB_TYPE_PACKED_PIXELS, 164962306a36Sopenharmony_ci .visual = FB_VISUAL_TRUECOLOR, 165062306a36Sopenharmony_ci .accel = FB_ACCEL_NONE, 165162306a36Sopenharmony_ci .xpanstep = 1, 165262306a36Sopenharmony_ci .ypanstep = 1, 165362306a36Sopenharmony_ci .ywrapstep = 0, 165462306a36Sopenharmony_ci .capabilities = FB_CAP_FOURCC, 165562306a36Sopenharmony_ci}; 165662306a36Sopenharmony_ci 165762306a36Sopenharmony_cistatic void sh_mobile_lcdc_fillrect(struct fb_info *info, 165862306a36Sopenharmony_ci const struct fb_fillrect *rect) 165962306a36Sopenharmony_ci{ 166062306a36Sopenharmony_ci sys_fillrect(info, rect); 166162306a36Sopenharmony_ci sh_mobile_lcdc_deferred_io_touch(info); 166262306a36Sopenharmony_ci} 166362306a36Sopenharmony_ci 166462306a36Sopenharmony_cistatic void sh_mobile_lcdc_copyarea(struct fb_info *info, 166562306a36Sopenharmony_ci const struct fb_copyarea *area) 166662306a36Sopenharmony_ci{ 166762306a36Sopenharmony_ci sys_copyarea(info, area); 166862306a36Sopenharmony_ci sh_mobile_lcdc_deferred_io_touch(info); 166962306a36Sopenharmony_ci} 167062306a36Sopenharmony_ci 167162306a36Sopenharmony_cistatic void sh_mobile_lcdc_imageblit(struct fb_info *info, 167262306a36Sopenharmony_ci const struct fb_image *image) 167362306a36Sopenharmony_ci{ 167462306a36Sopenharmony_ci sys_imageblit(info, image); 167562306a36Sopenharmony_ci sh_mobile_lcdc_deferred_io_touch(info); 167662306a36Sopenharmony_ci} 167762306a36Sopenharmony_ci 167862306a36Sopenharmony_cistatic int sh_mobile_lcdc_pan(struct fb_var_screeninfo *var, 167962306a36Sopenharmony_ci struct fb_info *info) 168062306a36Sopenharmony_ci{ 168162306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = info->par; 168262306a36Sopenharmony_ci struct sh_mobile_lcdc_priv *priv = ch->lcdc; 168362306a36Sopenharmony_ci unsigned long ldrcntr; 168462306a36Sopenharmony_ci unsigned long base_addr_y, base_addr_c; 168562306a36Sopenharmony_ci unsigned long y_offset; 168662306a36Sopenharmony_ci unsigned long c_offset; 168762306a36Sopenharmony_ci 168862306a36Sopenharmony_ci if (!ch->format->yuv) { 168962306a36Sopenharmony_ci y_offset = (var->yoffset * ch->xres_virtual + var->xoffset) 169062306a36Sopenharmony_ci * ch->format->bpp / 8; 169162306a36Sopenharmony_ci c_offset = 0; 169262306a36Sopenharmony_ci } else { 169362306a36Sopenharmony_ci unsigned int xsub = ch->format->bpp < 24 ? 2 : 1; 169462306a36Sopenharmony_ci unsigned int ysub = ch->format->bpp < 16 ? 2 : 1; 169562306a36Sopenharmony_ci 169662306a36Sopenharmony_ci y_offset = var->yoffset * ch->xres_virtual + var->xoffset; 169762306a36Sopenharmony_ci c_offset = var->yoffset / ysub * ch->xres_virtual * 2 / xsub 169862306a36Sopenharmony_ci + var->xoffset * 2 / xsub; 169962306a36Sopenharmony_ci } 170062306a36Sopenharmony_ci 170162306a36Sopenharmony_ci /* If the Y offset hasn't changed, the C offset hasn't either. There's 170262306a36Sopenharmony_ci * nothing to do in that case. 170362306a36Sopenharmony_ci */ 170462306a36Sopenharmony_ci if (y_offset == ch->pan_y_offset) 170562306a36Sopenharmony_ci return 0; 170662306a36Sopenharmony_ci 170762306a36Sopenharmony_ci /* Set the source address for the next refresh */ 170862306a36Sopenharmony_ci base_addr_y = ch->dma_handle + y_offset; 170962306a36Sopenharmony_ci base_addr_c = ch->dma_handle + ch->xres_virtual * ch->yres_virtual 171062306a36Sopenharmony_ci + c_offset; 171162306a36Sopenharmony_ci 171262306a36Sopenharmony_ci ch->base_addr_y = base_addr_y; 171362306a36Sopenharmony_ci ch->base_addr_c = base_addr_c; 171462306a36Sopenharmony_ci ch->pan_y_offset = y_offset; 171562306a36Sopenharmony_ci 171662306a36Sopenharmony_ci lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y); 171762306a36Sopenharmony_ci if (ch->format->yuv) 171862306a36Sopenharmony_ci lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c); 171962306a36Sopenharmony_ci 172062306a36Sopenharmony_ci ldrcntr = lcdc_read(priv, _LDRCNTR); 172162306a36Sopenharmony_ci if (lcdc_chan_is_sublcd(ch)) 172262306a36Sopenharmony_ci lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS); 172362306a36Sopenharmony_ci else 172462306a36Sopenharmony_ci lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_MRS); 172562306a36Sopenharmony_ci 172662306a36Sopenharmony_ci 172762306a36Sopenharmony_ci sh_mobile_lcdc_deferred_io_touch(info); 172862306a36Sopenharmony_ci 172962306a36Sopenharmony_ci return 0; 173062306a36Sopenharmony_ci} 173162306a36Sopenharmony_ci 173262306a36Sopenharmony_cistatic int sh_mobile_lcdc_ioctl(struct fb_info *info, unsigned int cmd, 173362306a36Sopenharmony_ci unsigned long arg) 173462306a36Sopenharmony_ci{ 173562306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = info->par; 173662306a36Sopenharmony_ci int retval; 173762306a36Sopenharmony_ci 173862306a36Sopenharmony_ci switch (cmd) { 173962306a36Sopenharmony_ci case FBIO_WAITFORVSYNC: 174062306a36Sopenharmony_ci retval = sh_mobile_lcdc_wait_for_vsync(ch); 174162306a36Sopenharmony_ci break; 174262306a36Sopenharmony_ci 174362306a36Sopenharmony_ci default: 174462306a36Sopenharmony_ci retval = -ENOIOCTLCMD; 174562306a36Sopenharmony_ci break; 174662306a36Sopenharmony_ci } 174762306a36Sopenharmony_ci return retval; 174862306a36Sopenharmony_ci} 174962306a36Sopenharmony_ci 175062306a36Sopenharmony_cistatic void sh_mobile_fb_reconfig(struct fb_info *info) 175162306a36Sopenharmony_ci{ 175262306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = info->par; 175362306a36Sopenharmony_ci struct fb_var_screeninfo var; 175462306a36Sopenharmony_ci struct fb_videomode mode; 175562306a36Sopenharmony_ci 175662306a36Sopenharmony_ci if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par)) 175762306a36Sopenharmony_ci /* More framebuffer users are active */ 175862306a36Sopenharmony_ci return; 175962306a36Sopenharmony_ci 176062306a36Sopenharmony_ci fb_var_to_videomode(&mode, &info->var); 176162306a36Sopenharmony_ci 176262306a36Sopenharmony_ci if (fb_mode_is_equal(&ch->display.mode, &mode)) 176362306a36Sopenharmony_ci return; 176462306a36Sopenharmony_ci 176562306a36Sopenharmony_ci /* Display has been re-plugged, framebuffer is free now, reconfigure */ 176662306a36Sopenharmony_ci var = info->var; 176762306a36Sopenharmony_ci fb_videomode_to_var(&var, &ch->display.mode); 176862306a36Sopenharmony_ci var.width = ch->display.width; 176962306a36Sopenharmony_ci var.height = ch->display.height; 177062306a36Sopenharmony_ci var.activate = FB_ACTIVATE_NOW; 177162306a36Sopenharmony_ci 177262306a36Sopenharmony_ci if (fb_set_var(info, &var) < 0) 177362306a36Sopenharmony_ci /* Couldn't reconfigure, hopefully, can continue as before */ 177462306a36Sopenharmony_ci return; 177562306a36Sopenharmony_ci 177662306a36Sopenharmony_ci fbcon_update_vcs(info, true); 177762306a36Sopenharmony_ci} 177862306a36Sopenharmony_ci 177962306a36Sopenharmony_ci/* 178062306a36Sopenharmony_ci * Locking: both .fb_release() and .fb_open() are called with info->lock held if 178162306a36Sopenharmony_ci * user == 1, or with console sem held, if user == 0. 178262306a36Sopenharmony_ci */ 178362306a36Sopenharmony_cistatic int sh_mobile_lcdc_release(struct fb_info *info, int user) 178462306a36Sopenharmony_ci{ 178562306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = info->par; 178662306a36Sopenharmony_ci 178762306a36Sopenharmony_ci mutex_lock(&ch->open_lock); 178862306a36Sopenharmony_ci dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count); 178962306a36Sopenharmony_ci 179062306a36Sopenharmony_ci ch->use_count--; 179162306a36Sopenharmony_ci 179262306a36Sopenharmony_ci /* Nothing to reconfigure, when called from fbcon */ 179362306a36Sopenharmony_ci if (user) { 179462306a36Sopenharmony_ci console_lock(); 179562306a36Sopenharmony_ci sh_mobile_fb_reconfig(info); 179662306a36Sopenharmony_ci console_unlock(); 179762306a36Sopenharmony_ci } 179862306a36Sopenharmony_ci 179962306a36Sopenharmony_ci mutex_unlock(&ch->open_lock); 180062306a36Sopenharmony_ci 180162306a36Sopenharmony_ci return 0; 180262306a36Sopenharmony_ci} 180362306a36Sopenharmony_ci 180462306a36Sopenharmony_cistatic int sh_mobile_lcdc_open(struct fb_info *info, int user) 180562306a36Sopenharmony_ci{ 180662306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = info->par; 180762306a36Sopenharmony_ci 180862306a36Sopenharmony_ci mutex_lock(&ch->open_lock); 180962306a36Sopenharmony_ci ch->use_count++; 181062306a36Sopenharmony_ci 181162306a36Sopenharmony_ci dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count); 181262306a36Sopenharmony_ci mutex_unlock(&ch->open_lock); 181362306a36Sopenharmony_ci 181462306a36Sopenharmony_ci return 0; 181562306a36Sopenharmony_ci} 181662306a36Sopenharmony_ci 181762306a36Sopenharmony_cistatic int sh_mobile_lcdc_check_var(struct fb_var_screeninfo *var, 181862306a36Sopenharmony_ci struct fb_info *info) 181962306a36Sopenharmony_ci{ 182062306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = info->par; 182162306a36Sopenharmony_ci struct sh_mobile_lcdc_priv *p = ch->lcdc; 182262306a36Sopenharmony_ci unsigned int best_dist = (unsigned int)-1; 182362306a36Sopenharmony_ci unsigned int best_xres = 0; 182462306a36Sopenharmony_ci unsigned int best_yres = 0; 182562306a36Sopenharmony_ci unsigned int i; 182662306a36Sopenharmony_ci int ret; 182762306a36Sopenharmony_ci 182862306a36Sopenharmony_ci /* If board code provides us with a list of available modes, make sure 182962306a36Sopenharmony_ci * we use one of them. Find the mode closest to the requested one. The 183062306a36Sopenharmony_ci * distance between two modes is defined as the size of the 183162306a36Sopenharmony_ci * non-overlapping parts of the two rectangles. 183262306a36Sopenharmony_ci */ 183362306a36Sopenharmony_ci for (i = 0; i < ch->cfg->num_modes; ++i) { 183462306a36Sopenharmony_ci const struct fb_videomode *mode = &ch->cfg->lcd_modes[i]; 183562306a36Sopenharmony_ci unsigned int dist; 183662306a36Sopenharmony_ci 183762306a36Sopenharmony_ci /* We can only round up. */ 183862306a36Sopenharmony_ci if (var->xres > mode->xres || var->yres > mode->yres) 183962306a36Sopenharmony_ci continue; 184062306a36Sopenharmony_ci 184162306a36Sopenharmony_ci dist = var->xres * var->yres + mode->xres * mode->yres 184262306a36Sopenharmony_ci - 2 * min(var->xres, mode->xres) 184362306a36Sopenharmony_ci * min(var->yres, mode->yres); 184462306a36Sopenharmony_ci 184562306a36Sopenharmony_ci if (dist < best_dist) { 184662306a36Sopenharmony_ci best_xres = mode->xres; 184762306a36Sopenharmony_ci best_yres = mode->yres; 184862306a36Sopenharmony_ci best_dist = dist; 184962306a36Sopenharmony_ci } 185062306a36Sopenharmony_ci } 185162306a36Sopenharmony_ci 185262306a36Sopenharmony_ci /* If no available mode can be used, return an error. */ 185362306a36Sopenharmony_ci if (ch->cfg->num_modes != 0) { 185462306a36Sopenharmony_ci if (best_dist == (unsigned int)-1) 185562306a36Sopenharmony_ci return -EINVAL; 185662306a36Sopenharmony_ci 185762306a36Sopenharmony_ci var->xres = best_xres; 185862306a36Sopenharmony_ci var->yres = best_yres; 185962306a36Sopenharmony_ci } 186062306a36Sopenharmony_ci 186162306a36Sopenharmony_ci ret = __sh_mobile_lcdc_check_var(var, info); 186262306a36Sopenharmony_ci if (ret < 0) 186362306a36Sopenharmony_ci return ret; 186462306a36Sopenharmony_ci 186562306a36Sopenharmony_ci /* only accept the forced_fourcc for dual channel configurations */ 186662306a36Sopenharmony_ci if (p->forced_fourcc && 186762306a36Sopenharmony_ci p->forced_fourcc != sh_mobile_format_fourcc(var)) 186862306a36Sopenharmony_ci return -EINVAL; 186962306a36Sopenharmony_ci 187062306a36Sopenharmony_ci return 0; 187162306a36Sopenharmony_ci} 187262306a36Sopenharmony_ci 187362306a36Sopenharmony_cistatic int sh_mobile_lcdc_set_par(struct fb_info *info) 187462306a36Sopenharmony_ci{ 187562306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = info->par; 187662306a36Sopenharmony_ci int ret; 187762306a36Sopenharmony_ci 187862306a36Sopenharmony_ci sh_mobile_lcdc_stop(ch->lcdc); 187962306a36Sopenharmony_ci 188062306a36Sopenharmony_ci ch->format = sh_mobile_format_info(sh_mobile_format_fourcc(&info->var)); 188162306a36Sopenharmony_ci ch->colorspace = info->var.colorspace; 188262306a36Sopenharmony_ci 188362306a36Sopenharmony_ci ch->xres = info->var.xres; 188462306a36Sopenharmony_ci ch->xres_virtual = info->var.xres_virtual; 188562306a36Sopenharmony_ci ch->yres = info->var.yres; 188662306a36Sopenharmony_ci ch->yres_virtual = info->var.yres_virtual; 188762306a36Sopenharmony_ci 188862306a36Sopenharmony_ci if (ch->format->yuv) 188962306a36Sopenharmony_ci ch->pitch = info->var.xres_virtual; 189062306a36Sopenharmony_ci else 189162306a36Sopenharmony_ci ch->pitch = info->var.xres_virtual * ch->format->bpp / 8; 189262306a36Sopenharmony_ci 189362306a36Sopenharmony_ci ret = sh_mobile_lcdc_start(ch->lcdc); 189462306a36Sopenharmony_ci if (ret < 0) 189562306a36Sopenharmony_ci dev_err(info->dev, "%s: unable to restart LCDC\n", __func__); 189662306a36Sopenharmony_ci 189762306a36Sopenharmony_ci info->fix.line_length = ch->pitch; 189862306a36Sopenharmony_ci 189962306a36Sopenharmony_ci if (sh_mobile_format_is_fourcc(&info->var)) { 190062306a36Sopenharmony_ci info->fix.type = FB_TYPE_FOURCC; 190162306a36Sopenharmony_ci info->fix.visual = FB_VISUAL_FOURCC; 190262306a36Sopenharmony_ci } else { 190362306a36Sopenharmony_ci info->fix.type = FB_TYPE_PACKED_PIXELS; 190462306a36Sopenharmony_ci info->fix.visual = FB_VISUAL_TRUECOLOR; 190562306a36Sopenharmony_ci } 190662306a36Sopenharmony_ci 190762306a36Sopenharmony_ci return ret; 190862306a36Sopenharmony_ci} 190962306a36Sopenharmony_ci 191062306a36Sopenharmony_ci/* 191162306a36Sopenharmony_ci * Screen blanking. Behavior is as follows: 191262306a36Sopenharmony_ci * FB_BLANK_UNBLANK: screen unblanked, clocks enabled 191362306a36Sopenharmony_ci * FB_BLANK_NORMAL: screen blanked, clocks enabled 191462306a36Sopenharmony_ci * FB_BLANK_VSYNC, 191562306a36Sopenharmony_ci * FB_BLANK_HSYNC, 191662306a36Sopenharmony_ci * FB_BLANK_POWEROFF: screen blanked, clocks disabled 191762306a36Sopenharmony_ci */ 191862306a36Sopenharmony_cistatic int sh_mobile_lcdc_blank(int blank, struct fb_info *info) 191962306a36Sopenharmony_ci{ 192062306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = info->par; 192162306a36Sopenharmony_ci struct sh_mobile_lcdc_priv *p = ch->lcdc; 192262306a36Sopenharmony_ci 192362306a36Sopenharmony_ci /* blank the screen? */ 192462306a36Sopenharmony_ci if (blank > FB_BLANK_UNBLANK && ch->blank_status == FB_BLANK_UNBLANK) { 192562306a36Sopenharmony_ci struct fb_fillrect rect = { 192662306a36Sopenharmony_ci .width = ch->xres, 192762306a36Sopenharmony_ci .height = ch->yres, 192862306a36Sopenharmony_ci }; 192962306a36Sopenharmony_ci sh_mobile_lcdc_fillrect(info, &rect); 193062306a36Sopenharmony_ci } 193162306a36Sopenharmony_ci /* turn clocks on? */ 193262306a36Sopenharmony_ci if (blank <= FB_BLANK_NORMAL && ch->blank_status > FB_BLANK_NORMAL) { 193362306a36Sopenharmony_ci sh_mobile_lcdc_clk_on(p); 193462306a36Sopenharmony_ci } 193562306a36Sopenharmony_ci /* turn clocks off? */ 193662306a36Sopenharmony_ci if (blank > FB_BLANK_NORMAL && ch->blank_status <= FB_BLANK_NORMAL) { 193762306a36Sopenharmony_ci /* make sure the screen is updated with the black fill before 193862306a36Sopenharmony_ci * switching the clocks off. one vsync is not enough since 193962306a36Sopenharmony_ci * blanking may occur in the middle of a refresh. deferred io 194062306a36Sopenharmony_ci * mode will reenable the clocks and update the screen in time, 194162306a36Sopenharmony_ci * so it does not need this. */ 194262306a36Sopenharmony_ci if (!info->fbdefio) { 194362306a36Sopenharmony_ci sh_mobile_lcdc_wait_for_vsync(ch); 194462306a36Sopenharmony_ci sh_mobile_lcdc_wait_for_vsync(ch); 194562306a36Sopenharmony_ci } 194662306a36Sopenharmony_ci sh_mobile_lcdc_clk_off(p); 194762306a36Sopenharmony_ci } 194862306a36Sopenharmony_ci 194962306a36Sopenharmony_ci ch->blank_status = blank; 195062306a36Sopenharmony_ci return 0; 195162306a36Sopenharmony_ci} 195262306a36Sopenharmony_ci 195362306a36Sopenharmony_cistatic int 195462306a36Sopenharmony_cish_mobile_lcdc_mmap(struct fb_info *info, struct vm_area_struct *vma) 195562306a36Sopenharmony_ci{ 195662306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = info->par; 195762306a36Sopenharmony_ci 195862306a36Sopenharmony_ci if (info->fbdefio) 195962306a36Sopenharmony_ci return fb_deferred_io_mmap(info, vma); 196062306a36Sopenharmony_ci 196162306a36Sopenharmony_ci return dma_mmap_coherent(ch->lcdc->dev, vma, ch->fb_mem, 196262306a36Sopenharmony_ci ch->dma_handle, ch->fb_size); 196362306a36Sopenharmony_ci} 196462306a36Sopenharmony_ci 196562306a36Sopenharmony_cistatic const struct fb_ops sh_mobile_lcdc_ops = { 196662306a36Sopenharmony_ci .owner = THIS_MODULE, 196762306a36Sopenharmony_ci .fb_setcolreg = sh_mobile_lcdc_setcolreg, 196862306a36Sopenharmony_ci .fb_read = fb_sys_read, 196962306a36Sopenharmony_ci .fb_write = fb_sys_write, 197062306a36Sopenharmony_ci .fb_fillrect = sh_mobile_lcdc_fillrect, 197162306a36Sopenharmony_ci .fb_copyarea = sh_mobile_lcdc_copyarea, 197262306a36Sopenharmony_ci .fb_imageblit = sh_mobile_lcdc_imageblit, 197362306a36Sopenharmony_ci .fb_blank = sh_mobile_lcdc_blank, 197462306a36Sopenharmony_ci .fb_pan_display = sh_mobile_lcdc_pan, 197562306a36Sopenharmony_ci .fb_ioctl = sh_mobile_lcdc_ioctl, 197662306a36Sopenharmony_ci .fb_open = sh_mobile_lcdc_open, 197762306a36Sopenharmony_ci .fb_release = sh_mobile_lcdc_release, 197862306a36Sopenharmony_ci .fb_check_var = sh_mobile_lcdc_check_var, 197962306a36Sopenharmony_ci .fb_set_par = sh_mobile_lcdc_set_par, 198062306a36Sopenharmony_ci .fb_mmap = sh_mobile_lcdc_mmap, 198162306a36Sopenharmony_ci}; 198262306a36Sopenharmony_ci 198362306a36Sopenharmony_cistatic void 198462306a36Sopenharmony_cish_mobile_lcdc_channel_fb_unregister(struct sh_mobile_lcdc_chan *ch) 198562306a36Sopenharmony_ci{ 198662306a36Sopenharmony_ci if (ch->info && ch->info->dev) 198762306a36Sopenharmony_ci unregister_framebuffer(ch->info); 198862306a36Sopenharmony_ci} 198962306a36Sopenharmony_ci 199062306a36Sopenharmony_cistatic int 199162306a36Sopenharmony_cish_mobile_lcdc_channel_fb_register(struct sh_mobile_lcdc_chan *ch) 199262306a36Sopenharmony_ci{ 199362306a36Sopenharmony_ci struct fb_info *info = ch->info; 199462306a36Sopenharmony_ci int ret; 199562306a36Sopenharmony_ci 199662306a36Sopenharmony_ci if (info->fbdefio) { 199762306a36Sopenharmony_ci ch->sglist = vmalloc(sizeof(struct scatterlist) * 199862306a36Sopenharmony_ci ch->fb_size >> PAGE_SHIFT); 199962306a36Sopenharmony_ci if (!ch->sglist) 200062306a36Sopenharmony_ci return -ENOMEM; 200162306a36Sopenharmony_ci } 200262306a36Sopenharmony_ci 200362306a36Sopenharmony_ci info->bl_dev = ch->bl; 200462306a36Sopenharmony_ci 200562306a36Sopenharmony_ci ret = register_framebuffer(info); 200662306a36Sopenharmony_ci if (ret < 0) 200762306a36Sopenharmony_ci return ret; 200862306a36Sopenharmony_ci 200962306a36Sopenharmony_ci dev_info(ch->lcdc->dev, "registered %s/%s as %dx%d %dbpp.\n", 201062306a36Sopenharmony_ci dev_name(ch->lcdc->dev), (ch->cfg->chan == LCDC_CHAN_MAINLCD) ? 201162306a36Sopenharmony_ci "mainlcd" : "sublcd", info->var.xres, info->var.yres, 201262306a36Sopenharmony_ci info->var.bits_per_pixel); 201362306a36Sopenharmony_ci 201462306a36Sopenharmony_ci /* deferred io mode: disable clock to save power */ 201562306a36Sopenharmony_ci if (info->fbdefio || info->state == FBINFO_STATE_SUSPENDED) 201662306a36Sopenharmony_ci sh_mobile_lcdc_clk_off(ch->lcdc); 201762306a36Sopenharmony_ci 201862306a36Sopenharmony_ci return ret; 201962306a36Sopenharmony_ci} 202062306a36Sopenharmony_ci 202162306a36Sopenharmony_cistatic void 202262306a36Sopenharmony_cish_mobile_lcdc_channel_fb_cleanup(struct sh_mobile_lcdc_chan *ch) 202362306a36Sopenharmony_ci{ 202462306a36Sopenharmony_ci struct fb_info *info = ch->info; 202562306a36Sopenharmony_ci 202662306a36Sopenharmony_ci if (!info || !info->device) 202762306a36Sopenharmony_ci return; 202862306a36Sopenharmony_ci 202962306a36Sopenharmony_ci vfree(ch->sglist); 203062306a36Sopenharmony_ci 203162306a36Sopenharmony_ci fb_dealloc_cmap(&info->cmap); 203262306a36Sopenharmony_ci framebuffer_release(info); 203362306a36Sopenharmony_ci} 203462306a36Sopenharmony_ci 203562306a36Sopenharmony_cistatic int 203662306a36Sopenharmony_cish_mobile_lcdc_channel_fb_init(struct sh_mobile_lcdc_chan *ch, 203762306a36Sopenharmony_ci const struct fb_videomode *modes, 203862306a36Sopenharmony_ci unsigned int num_modes) 203962306a36Sopenharmony_ci{ 204062306a36Sopenharmony_ci struct sh_mobile_lcdc_priv *priv = ch->lcdc; 204162306a36Sopenharmony_ci struct fb_var_screeninfo *var; 204262306a36Sopenharmony_ci struct fb_info *info; 204362306a36Sopenharmony_ci int ret; 204462306a36Sopenharmony_ci 204562306a36Sopenharmony_ci /* Allocate and initialize the frame buffer device. Create the modes 204662306a36Sopenharmony_ci * list and allocate the color map. 204762306a36Sopenharmony_ci */ 204862306a36Sopenharmony_ci info = framebuffer_alloc(0, priv->dev); 204962306a36Sopenharmony_ci if (!info) 205062306a36Sopenharmony_ci return -ENOMEM; 205162306a36Sopenharmony_ci 205262306a36Sopenharmony_ci ch->info = info; 205362306a36Sopenharmony_ci 205462306a36Sopenharmony_ci info->fbops = &sh_mobile_lcdc_ops; 205562306a36Sopenharmony_ci info->device = priv->dev; 205662306a36Sopenharmony_ci info->screen_buffer = ch->fb_mem; 205762306a36Sopenharmony_ci info->pseudo_palette = &ch->pseudo_palette; 205862306a36Sopenharmony_ci info->par = ch; 205962306a36Sopenharmony_ci 206062306a36Sopenharmony_ci fb_videomode_to_modelist(modes, num_modes, &info->modelist); 206162306a36Sopenharmony_ci 206262306a36Sopenharmony_ci ret = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0); 206362306a36Sopenharmony_ci if (ret < 0) { 206462306a36Sopenharmony_ci dev_err(priv->dev, "unable to allocate cmap\n"); 206562306a36Sopenharmony_ci return ret; 206662306a36Sopenharmony_ci } 206762306a36Sopenharmony_ci 206862306a36Sopenharmony_ci /* Initialize fixed screen information. Restrict pan to 2 lines steps 206962306a36Sopenharmony_ci * for NV12 and NV21. 207062306a36Sopenharmony_ci */ 207162306a36Sopenharmony_ci info->fix = sh_mobile_lcdc_fix; 207262306a36Sopenharmony_ci info->fix.smem_start = ch->dma_handle; 207362306a36Sopenharmony_ci info->fix.smem_len = ch->fb_size; 207462306a36Sopenharmony_ci info->fix.line_length = ch->pitch; 207562306a36Sopenharmony_ci 207662306a36Sopenharmony_ci if (ch->format->yuv) 207762306a36Sopenharmony_ci info->fix.visual = FB_VISUAL_FOURCC; 207862306a36Sopenharmony_ci else 207962306a36Sopenharmony_ci info->fix.visual = FB_VISUAL_TRUECOLOR; 208062306a36Sopenharmony_ci 208162306a36Sopenharmony_ci switch (ch->format->fourcc) { 208262306a36Sopenharmony_ci case V4L2_PIX_FMT_NV12: 208362306a36Sopenharmony_ci case V4L2_PIX_FMT_NV21: 208462306a36Sopenharmony_ci info->fix.ypanstep = 2; 208562306a36Sopenharmony_ci fallthrough; 208662306a36Sopenharmony_ci case V4L2_PIX_FMT_NV16: 208762306a36Sopenharmony_ci case V4L2_PIX_FMT_NV61: 208862306a36Sopenharmony_ci info->fix.xpanstep = 2; 208962306a36Sopenharmony_ci } 209062306a36Sopenharmony_ci 209162306a36Sopenharmony_ci /* Initialize variable screen information using the first mode as 209262306a36Sopenharmony_ci * default. 209362306a36Sopenharmony_ci */ 209462306a36Sopenharmony_ci var = &info->var; 209562306a36Sopenharmony_ci fb_videomode_to_var(var, modes); 209662306a36Sopenharmony_ci var->width = ch->display.width; 209762306a36Sopenharmony_ci var->height = ch->display.height; 209862306a36Sopenharmony_ci var->xres_virtual = ch->xres_virtual; 209962306a36Sopenharmony_ci var->yres_virtual = ch->yres_virtual; 210062306a36Sopenharmony_ci var->activate = FB_ACTIVATE_NOW; 210162306a36Sopenharmony_ci 210262306a36Sopenharmony_ci /* Use the legacy API by default for RGB formats, and the FOURCC API 210362306a36Sopenharmony_ci * for YUV formats. 210462306a36Sopenharmony_ci */ 210562306a36Sopenharmony_ci if (!ch->format->yuv) 210662306a36Sopenharmony_ci var->bits_per_pixel = ch->format->bpp; 210762306a36Sopenharmony_ci else 210862306a36Sopenharmony_ci var->grayscale = ch->format->fourcc; 210962306a36Sopenharmony_ci 211062306a36Sopenharmony_ci ret = sh_mobile_lcdc_check_var(var, info); 211162306a36Sopenharmony_ci if (ret) 211262306a36Sopenharmony_ci return ret; 211362306a36Sopenharmony_ci 211462306a36Sopenharmony_ci return 0; 211562306a36Sopenharmony_ci} 211662306a36Sopenharmony_ci 211762306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 211862306a36Sopenharmony_ci * Backlight 211962306a36Sopenharmony_ci */ 212062306a36Sopenharmony_ci 212162306a36Sopenharmony_cistatic int sh_mobile_lcdc_update_bl(struct backlight_device *bdev) 212262306a36Sopenharmony_ci{ 212362306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev); 212462306a36Sopenharmony_ci int brightness = bdev->props.brightness; 212562306a36Sopenharmony_ci 212662306a36Sopenharmony_ci if (bdev->props.power != FB_BLANK_UNBLANK || 212762306a36Sopenharmony_ci bdev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) 212862306a36Sopenharmony_ci brightness = 0; 212962306a36Sopenharmony_ci 213062306a36Sopenharmony_ci ch->bl_brightness = brightness; 213162306a36Sopenharmony_ci return ch->cfg->bl_info.set_brightness(brightness); 213262306a36Sopenharmony_ci} 213362306a36Sopenharmony_ci 213462306a36Sopenharmony_cistatic int sh_mobile_lcdc_get_brightness(struct backlight_device *bdev) 213562306a36Sopenharmony_ci{ 213662306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev); 213762306a36Sopenharmony_ci 213862306a36Sopenharmony_ci return ch->bl_brightness; 213962306a36Sopenharmony_ci} 214062306a36Sopenharmony_ci 214162306a36Sopenharmony_cistatic int sh_mobile_lcdc_check_fb(struct backlight_device *bdev, 214262306a36Sopenharmony_ci struct fb_info *info) 214362306a36Sopenharmony_ci{ 214462306a36Sopenharmony_ci return (info->bl_dev == bdev); 214562306a36Sopenharmony_ci} 214662306a36Sopenharmony_ci 214762306a36Sopenharmony_cistatic const struct backlight_ops sh_mobile_lcdc_bl_ops = { 214862306a36Sopenharmony_ci .options = BL_CORE_SUSPENDRESUME, 214962306a36Sopenharmony_ci .update_status = sh_mobile_lcdc_update_bl, 215062306a36Sopenharmony_ci .get_brightness = sh_mobile_lcdc_get_brightness, 215162306a36Sopenharmony_ci .check_fb = sh_mobile_lcdc_check_fb, 215262306a36Sopenharmony_ci}; 215362306a36Sopenharmony_ci 215462306a36Sopenharmony_cistatic struct backlight_device *sh_mobile_lcdc_bl_probe(struct device *parent, 215562306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch) 215662306a36Sopenharmony_ci{ 215762306a36Sopenharmony_ci struct backlight_device *bl; 215862306a36Sopenharmony_ci 215962306a36Sopenharmony_ci bl = backlight_device_register(ch->cfg->bl_info.name, parent, ch, 216062306a36Sopenharmony_ci &sh_mobile_lcdc_bl_ops, NULL); 216162306a36Sopenharmony_ci if (IS_ERR(bl)) { 216262306a36Sopenharmony_ci dev_err(parent, "unable to register backlight device: %ld\n", 216362306a36Sopenharmony_ci PTR_ERR(bl)); 216462306a36Sopenharmony_ci return NULL; 216562306a36Sopenharmony_ci } 216662306a36Sopenharmony_ci 216762306a36Sopenharmony_ci bl->props.max_brightness = ch->cfg->bl_info.max_brightness; 216862306a36Sopenharmony_ci bl->props.brightness = bl->props.max_brightness; 216962306a36Sopenharmony_ci backlight_update_status(bl); 217062306a36Sopenharmony_ci 217162306a36Sopenharmony_ci return bl; 217262306a36Sopenharmony_ci} 217362306a36Sopenharmony_ci 217462306a36Sopenharmony_cistatic void sh_mobile_lcdc_bl_remove(struct backlight_device *bdev) 217562306a36Sopenharmony_ci{ 217662306a36Sopenharmony_ci backlight_device_unregister(bdev); 217762306a36Sopenharmony_ci} 217862306a36Sopenharmony_ci 217962306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 218062306a36Sopenharmony_ci * Power management 218162306a36Sopenharmony_ci */ 218262306a36Sopenharmony_ci 218362306a36Sopenharmony_cistatic int sh_mobile_lcdc_suspend(struct device *dev) 218462306a36Sopenharmony_ci{ 218562306a36Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 218662306a36Sopenharmony_ci 218762306a36Sopenharmony_ci sh_mobile_lcdc_stop(platform_get_drvdata(pdev)); 218862306a36Sopenharmony_ci return 0; 218962306a36Sopenharmony_ci} 219062306a36Sopenharmony_ci 219162306a36Sopenharmony_cistatic int sh_mobile_lcdc_resume(struct device *dev) 219262306a36Sopenharmony_ci{ 219362306a36Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 219462306a36Sopenharmony_ci 219562306a36Sopenharmony_ci return sh_mobile_lcdc_start(platform_get_drvdata(pdev)); 219662306a36Sopenharmony_ci} 219762306a36Sopenharmony_ci 219862306a36Sopenharmony_cistatic int sh_mobile_lcdc_runtime_suspend(struct device *dev) 219962306a36Sopenharmony_ci{ 220062306a36Sopenharmony_ci struct sh_mobile_lcdc_priv *priv = dev_get_drvdata(dev); 220162306a36Sopenharmony_ci 220262306a36Sopenharmony_ci /* turn off LCDC hardware */ 220362306a36Sopenharmony_ci lcdc_write(priv, _LDCNT1R, 0); 220462306a36Sopenharmony_ci 220562306a36Sopenharmony_ci return 0; 220662306a36Sopenharmony_ci} 220762306a36Sopenharmony_ci 220862306a36Sopenharmony_cistatic int sh_mobile_lcdc_runtime_resume(struct device *dev) 220962306a36Sopenharmony_ci{ 221062306a36Sopenharmony_ci struct sh_mobile_lcdc_priv *priv = dev_get_drvdata(dev); 221162306a36Sopenharmony_ci 221262306a36Sopenharmony_ci __sh_mobile_lcdc_start(priv); 221362306a36Sopenharmony_ci 221462306a36Sopenharmony_ci return 0; 221562306a36Sopenharmony_ci} 221662306a36Sopenharmony_ci 221762306a36Sopenharmony_cistatic const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { 221862306a36Sopenharmony_ci .suspend = sh_mobile_lcdc_suspend, 221962306a36Sopenharmony_ci .resume = sh_mobile_lcdc_resume, 222062306a36Sopenharmony_ci .runtime_suspend = sh_mobile_lcdc_runtime_suspend, 222162306a36Sopenharmony_ci .runtime_resume = sh_mobile_lcdc_runtime_resume, 222262306a36Sopenharmony_ci}; 222362306a36Sopenharmony_ci 222462306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 222562306a36Sopenharmony_ci * Framebuffer notifier 222662306a36Sopenharmony_ci */ 222762306a36Sopenharmony_ci 222862306a36Sopenharmony_ci/* ----------------------------------------------------------------------------- 222962306a36Sopenharmony_ci * Probe/remove and driver init/exit 223062306a36Sopenharmony_ci */ 223162306a36Sopenharmony_ci 223262306a36Sopenharmony_cistatic const struct fb_videomode default_720p = { 223362306a36Sopenharmony_ci .name = "HDMI 720p", 223462306a36Sopenharmony_ci .xres = 1280, 223562306a36Sopenharmony_ci .yres = 720, 223662306a36Sopenharmony_ci 223762306a36Sopenharmony_ci .left_margin = 220, 223862306a36Sopenharmony_ci .right_margin = 110, 223962306a36Sopenharmony_ci .hsync_len = 40, 224062306a36Sopenharmony_ci 224162306a36Sopenharmony_ci .upper_margin = 20, 224262306a36Sopenharmony_ci .lower_margin = 5, 224362306a36Sopenharmony_ci .vsync_len = 5, 224462306a36Sopenharmony_ci 224562306a36Sopenharmony_ci .pixclock = 13468, 224662306a36Sopenharmony_ci .refresh = 60, 224762306a36Sopenharmony_ci .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT, 224862306a36Sopenharmony_ci}; 224962306a36Sopenharmony_ci 225062306a36Sopenharmony_cistatic void sh_mobile_lcdc_remove(struct platform_device *pdev) 225162306a36Sopenharmony_ci{ 225262306a36Sopenharmony_ci struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev); 225362306a36Sopenharmony_ci unsigned int i; 225462306a36Sopenharmony_ci 225562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(priv->overlays); i++) 225662306a36Sopenharmony_ci sh_mobile_lcdc_overlay_fb_unregister(&priv->overlays[i]); 225762306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(priv->ch); i++) 225862306a36Sopenharmony_ci sh_mobile_lcdc_channel_fb_unregister(&priv->ch[i]); 225962306a36Sopenharmony_ci 226062306a36Sopenharmony_ci sh_mobile_lcdc_stop(priv); 226162306a36Sopenharmony_ci 226262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(priv->overlays); i++) { 226362306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i]; 226462306a36Sopenharmony_ci 226562306a36Sopenharmony_ci sh_mobile_lcdc_overlay_fb_cleanup(ovl); 226662306a36Sopenharmony_ci 226762306a36Sopenharmony_ci if (ovl->fb_mem) 226862306a36Sopenharmony_ci dma_free_coherent(&pdev->dev, ovl->fb_size, 226962306a36Sopenharmony_ci ovl->fb_mem, ovl->dma_handle); 227062306a36Sopenharmony_ci } 227162306a36Sopenharmony_ci 227262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(priv->ch); i++) { 227362306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = &priv->ch[i]; 227462306a36Sopenharmony_ci 227562306a36Sopenharmony_ci if (ch->tx_dev) { 227662306a36Sopenharmony_ci ch->tx_dev->lcdc = NULL; 227762306a36Sopenharmony_ci module_put(ch->cfg->tx_dev->dev.driver->owner); 227862306a36Sopenharmony_ci } 227962306a36Sopenharmony_ci 228062306a36Sopenharmony_ci sh_mobile_lcdc_channel_fb_cleanup(ch); 228162306a36Sopenharmony_ci 228262306a36Sopenharmony_ci if (ch->fb_mem) 228362306a36Sopenharmony_ci dma_free_coherent(&pdev->dev, ch->fb_size, 228462306a36Sopenharmony_ci ch->fb_mem, ch->dma_handle); 228562306a36Sopenharmony_ci } 228662306a36Sopenharmony_ci 228762306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(priv->ch); i++) { 228862306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = &priv->ch[i]; 228962306a36Sopenharmony_ci 229062306a36Sopenharmony_ci if (ch->bl) 229162306a36Sopenharmony_ci sh_mobile_lcdc_bl_remove(ch->bl); 229262306a36Sopenharmony_ci mutex_destroy(&ch->open_lock); 229362306a36Sopenharmony_ci } 229462306a36Sopenharmony_ci 229562306a36Sopenharmony_ci if (priv->dot_clk) { 229662306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 229762306a36Sopenharmony_ci clk_put(priv->dot_clk); 229862306a36Sopenharmony_ci } 229962306a36Sopenharmony_ci 230062306a36Sopenharmony_ci if (priv->base) 230162306a36Sopenharmony_ci iounmap(priv->base); 230262306a36Sopenharmony_ci 230362306a36Sopenharmony_ci if (priv->irq) 230462306a36Sopenharmony_ci free_irq(priv->irq, priv); 230562306a36Sopenharmony_ci kfree(priv); 230662306a36Sopenharmony_ci} 230762306a36Sopenharmony_ci 230862306a36Sopenharmony_cistatic int sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch) 230962306a36Sopenharmony_ci{ 231062306a36Sopenharmony_ci int interface_type = ch->cfg->interface_type; 231162306a36Sopenharmony_ci 231262306a36Sopenharmony_ci switch (interface_type) { 231362306a36Sopenharmony_ci case RGB8: 231462306a36Sopenharmony_ci case RGB9: 231562306a36Sopenharmony_ci case RGB12A: 231662306a36Sopenharmony_ci case RGB12B: 231762306a36Sopenharmony_ci case RGB16: 231862306a36Sopenharmony_ci case RGB18: 231962306a36Sopenharmony_ci case RGB24: 232062306a36Sopenharmony_ci case SYS8A: 232162306a36Sopenharmony_ci case SYS8B: 232262306a36Sopenharmony_ci case SYS8C: 232362306a36Sopenharmony_ci case SYS8D: 232462306a36Sopenharmony_ci case SYS9: 232562306a36Sopenharmony_ci case SYS12: 232662306a36Sopenharmony_ci case SYS16A: 232762306a36Sopenharmony_ci case SYS16B: 232862306a36Sopenharmony_ci case SYS16C: 232962306a36Sopenharmony_ci case SYS18: 233062306a36Sopenharmony_ci case SYS24: 233162306a36Sopenharmony_ci break; 233262306a36Sopenharmony_ci default: 233362306a36Sopenharmony_ci return -EINVAL; 233462306a36Sopenharmony_ci } 233562306a36Sopenharmony_ci 233662306a36Sopenharmony_ci /* SUBLCD only supports SYS interface */ 233762306a36Sopenharmony_ci if (lcdc_chan_is_sublcd(ch)) { 233862306a36Sopenharmony_ci if (!(interface_type & LDMT1R_IFM)) 233962306a36Sopenharmony_ci return -EINVAL; 234062306a36Sopenharmony_ci 234162306a36Sopenharmony_ci interface_type &= ~LDMT1R_IFM; 234262306a36Sopenharmony_ci } 234362306a36Sopenharmony_ci 234462306a36Sopenharmony_ci ch->ldmt1r_value = interface_type; 234562306a36Sopenharmony_ci return 0; 234662306a36Sopenharmony_ci} 234762306a36Sopenharmony_ci 234862306a36Sopenharmony_cistatic int 234962306a36Sopenharmony_cish_mobile_lcdc_overlay_init(struct sh_mobile_lcdc_overlay *ovl) 235062306a36Sopenharmony_ci{ 235162306a36Sopenharmony_ci const struct sh_mobile_lcdc_format_info *format; 235262306a36Sopenharmony_ci struct device *dev = ovl->channel->lcdc->dev; 235362306a36Sopenharmony_ci int ret; 235462306a36Sopenharmony_ci 235562306a36Sopenharmony_ci if (ovl->cfg->fourcc == 0) 235662306a36Sopenharmony_ci return 0; 235762306a36Sopenharmony_ci 235862306a36Sopenharmony_ci /* Validate the format. */ 235962306a36Sopenharmony_ci format = sh_mobile_format_info(ovl->cfg->fourcc); 236062306a36Sopenharmony_ci if (format == NULL) { 236162306a36Sopenharmony_ci dev_err(dev, "Invalid FOURCC %08x\n", ovl->cfg->fourcc); 236262306a36Sopenharmony_ci return -EINVAL; 236362306a36Sopenharmony_ci } 236462306a36Sopenharmony_ci 236562306a36Sopenharmony_ci ovl->enabled = false; 236662306a36Sopenharmony_ci ovl->mode = LCDC_OVERLAY_BLEND; 236762306a36Sopenharmony_ci ovl->alpha = 255; 236862306a36Sopenharmony_ci ovl->rop3 = 0; 236962306a36Sopenharmony_ci ovl->pos_x = 0; 237062306a36Sopenharmony_ci ovl->pos_y = 0; 237162306a36Sopenharmony_ci 237262306a36Sopenharmony_ci /* The default Y virtual resolution is twice the panel size to allow for 237362306a36Sopenharmony_ci * double-buffering. 237462306a36Sopenharmony_ci */ 237562306a36Sopenharmony_ci ovl->format = format; 237662306a36Sopenharmony_ci ovl->xres = ovl->cfg->max_xres; 237762306a36Sopenharmony_ci ovl->xres_virtual = ovl->xres; 237862306a36Sopenharmony_ci ovl->yres = ovl->cfg->max_yres; 237962306a36Sopenharmony_ci ovl->yres_virtual = ovl->yres * 2; 238062306a36Sopenharmony_ci 238162306a36Sopenharmony_ci if (!format->yuv) 238262306a36Sopenharmony_ci ovl->pitch = ovl->xres_virtual * format->bpp / 8; 238362306a36Sopenharmony_ci else 238462306a36Sopenharmony_ci ovl->pitch = ovl->xres_virtual; 238562306a36Sopenharmony_ci 238662306a36Sopenharmony_ci /* Allocate frame buffer memory. */ 238762306a36Sopenharmony_ci ovl->fb_size = ovl->cfg->max_xres * ovl->cfg->max_yres 238862306a36Sopenharmony_ci * format->bpp / 8 * 2; 238962306a36Sopenharmony_ci ovl->fb_mem = dma_alloc_coherent(dev, ovl->fb_size, &ovl->dma_handle, 239062306a36Sopenharmony_ci GFP_KERNEL); 239162306a36Sopenharmony_ci if (!ovl->fb_mem) { 239262306a36Sopenharmony_ci dev_err(dev, "unable to allocate buffer\n"); 239362306a36Sopenharmony_ci return -ENOMEM; 239462306a36Sopenharmony_ci } 239562306a36Sopenharmony_ci 239662306a36Sopenharmony_ci ret = sh_mobile_lcdc_overlay_fb_init(ovl); 239762306a36Sopenharmony_ci if (ret < 0) 239862306a36Sopenharmony_ci return ret; 239962306a36Sopenharmony_ci 240062306a36Sopenharmony_ci return 0; 240162306a36Sopenharmony_ci} 240262306a36Sopenharmony_ci 240362306a36Sopenharmony_cistatic int 240462306a36Sopenharmony_cish_mobile_lcdc_channel_init(struct sh_mobile_lcdc_chan *ch) 240562306a36Sopenharmony_ci{ 240662306a36Sopenharmony_ci const struct sh_mobile_lcdc_format_info *format; 240762306a36Sopenharmony_ci const struct sh_mobile_lcdc_chan_cfg *cfg = ch->cfg; 240862306a36Sopenharmony_ci struct device *dev = ch->lcdc->dev; 240962306a36Sopenharmony_ci const struct fb_videomode *max_mode; 241062306a36Sopenharmony_ci const struct fb_videomode *mode; 241162306a36Sopenharmony_ci unsigned int num_modes; 241262306a36Sopenharmony_ci unsigned int max_size; 241362306a36Sopenharmony_ci unsigned int i; 241462306a36Sopenharmony_ci 241562306a36Sopenharmony_ci /* Validate the format. */ 241662306a36Sopenharmony_ci format = sh_mobile_format_info(cfg->fourcc); 241762306a36Sopenharmony_ci if (format == NULL) { 241862306a36Sopenharmony_ci dev_err(dev, "Invalid FOURCC %08x.\n", cfg->fourcc); 241962306a36Sopenharmony_ci return -EINVAL; 242062306a36Sopenharmony_ci } 242162306a36Sopenharmony_ci 242262306a36Sopenharmony_ci /* Iterate through the modes to validate them and find the highest 242362306a36Sopenharmony_ci * resolution. 242462306a36Sopenharmony_ci */ 242562306a36Sopenharmony_ci max_mode = NULL; 242662306a36Sopenharmony_ci max_size = 0; 242762306a36Sopenharmony_ci 242862306a36Sopenharmony_ci for (i = 0, mode = cfg->lcd_modes; i < cfg->num_modes; i++, mode++) { 242962306a36Sopenharmony_ci unsigned int size = mode->yres * mode->xres; 243062306a36Sopenharmony_ci 243162306a36Sopenharmony_ci /* NV12/NV21 buffers must have even number of lines */ 243262306a36Sopenharmony_ci if ((cfg->fourcc == V4L2_PIX_FMT_NV12 || 243362306a36Sopenharmony_ci cfg->fourcc == V4L2_PIX_FMT_NV21) && (mode->yres & 0x1)) { 243462306a36Sopenharmony_ci dev_err(dev, "yres must be multiple of 2 for " 243562306a36Sopenharmony_ci "YCbCr420 mode.\n"); 243662306a36Sopenharmony_ci return -EINVAL; 243762306a36Sopenharmony_ci } 243862306a36Sopenharmony_ci 243962306a36Sopenharmony_ci if (size > max_size) { 244062306a36Sopenharmony_ci max_mode = mode; 244162306a36Sopenharmony_ci max_size = size; 244262306a36Sopenharmony_ci } 244362306a36Sopenharmony_ci } 244462306a36Sopenharmony_ci 244562306a36Sopenharmony_ci if (!max_size) 244662306a36Sopenharmony_ci max_size = MAX_XRES * MAX_YRES; 244762306a36Sopenharmony_ci else 244862306a36Sopenharmony_ci dev_dbg(dev, "Found largest videomode %ux%u\n", 244962306a36Sopenharmony_ci max_mode->xres, max_mode->yres); 245062306a36Sopenharmony_ci 245162306a36Sopenharmony_ci if (cfg->lcd_modes == NULL) { 245262306a36Sopenharmony_ci mode = &default_720p; 245362306a36Sopenharmony_ci num_modes = 1; 245462306a36Sopenharmony_ci } else { 245562306a36Sopenharmony_ci mode = cfg->lcd_modes; 245662306a36Sopenharmony_ci num_modes = cfg->num_modes; 245762306a36Sopenharmony_ci } 245862306a36Sopenharmony_ci 245962306a36Sopenharmony_ci /* Use the first mode as default. The default Y virtual resolution is 246062306a36Sopenharmony_ci * twice the panel size to allow for double-buffering. 246162306a36Sopenharmony_ci */ 246262306a36Sopenharmony_ci ch->format = format; 246362306a36Sopenharmony_ci ch->xres = mode->xres; 246462306a36Sopenharmony_ci ch->xres_virtual = mode->xres; 246562306a36Sopenharmony_ci ch->yres = mode->yres; 246662306a36Sopenharmony_ci ch->yres_virtual = mode->yres * 2; 246762306a36Sopenharmony_ci 246862306a36Sopenharmony_ci if (!format->yuv) { 246962306a36Sopenharmony_ci ch->colorspace = V4L2_COLORSPACE_SRGB; 247062306a36Sopenharmony_ci ch->pitch = ch->xres_virtual * format->bpp / 8; 247162306a36Sopenharmony_ci } else { 247262306a36Sopenharmony_ci ch->colorspace = V4L2_COLORSPACE_REC709; 247362306a36Sopenharmony_ci ch->pitch = ch->xres_virtual; 247462306a36Sopenharmony_ci } 247562306a36Sopenharmony_ci 247662306a36Sopenharmony_ci ch->display.width = cfg->panel_cfg.width; 247762306a36Sopenharmony_ci ch->display.height = cfg->panel_cfg.height; 247862306a36Sopenharmony_ci ch->display.mode = *mode; 247962306a36Sopenharmony_ci 248062306a36Sopenharmony_ci /* Allocate frame buffer memory. */ 248162306a36Sopenharmony_ci ch->fb_size = max_size * format->bpp / 8 * 2; 248262306a36Sopenharmony_ci ch->fb_mem = dma_alloc_coherent(dev, ch->fb_size, &ch->dma_handle, 248362306a36Sopenharmony_ci GFP_KERNEL); 248462306a36Sopenharmony_ci if (ch->fb_mem == NULL) { 248562306a36Sopenharmony_ci dev_err(dev, "unable to allocate buffer\n"); 248662306a36Sopenharmony_ci return -ENOMEM; 248762306a36Sopenharmony_ci } 248862306a36Sopenharmony_ci 248962306a36Sopenharmony_ci /* Initialize the transmitter device if present. */ 249062306a36Sopenharmony_ci if (cfg->tx_dev) { 249162306a36Sopenharmony_ci if (!cfg->tx_dev->dev.driver || 249262306a36Sopenharmony_ci !try_module_get(cfg->tx_dev->dev.driver->owner)) { 249362306a36Sopenharmony_ci dev_warn(dev, "unable to get transmitter device\n"); 249462306a36Sopenharmony_ci return -EINVAL; 249562306a36Sopenharmony_ci } 249662306a36Sopenharmony_ci ch->tx_dev = platform_get_drvdata(cfg->tx_dev); 249762306a36Sopenharmony_ci ch->tx_dev->lcdc = ch; 249862306a36Sopenharmony_ci ch->tx_dev->def_mode = *mode; 249962306a36Sopenharmony_ci } 250062306a36Sopenharmony_ci 250162306a36Sopenharmony_ci return sh_mobile_lcdc_channel_fb_init(ch, mode, num_modes); 250262306a36Sopenharmony_ci} 250362306a36Sopenharmony_ci 250462306a36Sopenharmony_cistatic int sh_mobile_lcdc_probe(struct platform_device *pdev) 250562306a36Sopenharmony_ci{ 250662306a36Sopenharmony_ci struct sh_mobile_lcdc_info *pdata = pdev->dev.platform_data; 250762306a36Sopenharmony_ci struct sh_mobile_lcdc_priv *priv; 250862306a36Sopenharmony_ci struct resource *res; 250962306a36Sopenharmony_ci int num_channels; 251062306a36Sopenharmony_ci int error; 251162306a36Sopenharmony_ci int irq, i; 251262306a36Sopenharmony_ci 251362306a36Sopenharmony_ci if (!pdata) { 251462306a36Sopenharmony_ci dev_err(&pdev->dev, "no platform data defined\n"); 251562306a36Sopenharmony_ci return -EINVAL; 251662306a36Sopenharmony_ci } 251762306a36Sopenharmony_ci 251862306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 251962306a36Sopenharmony_ci irq = platform_get_irq(pdev, 0); 252062306a36Sopenharmony_ci if (!res || irq < 0) { 252162306a36Sopenharmony_ci dev_err(&pdev->dev, "cannot get platform resources\n"); 252262306a36Sopenharmony_ci return -ENOENT; 252362306a36Sopenharmony_ci } 252462306a36Sopenharmony_ci 252562306a36Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 252662306a36Sopenharmony_ci if (!priv) 252762306a36Sopenharmony_ci return -ENOMEM; 252862306a36Sopenharmony_ci 252962306a36Sopenharmony_ci priv->dev = &pdev->dev; 253062306a36Sopenharmony_ci 253162306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(priv->ch); i++) 253262306a36Sopenharmony_ci mutex_init(&priv->ch[i].open_lock); 253362306a36Sopenharmony_ci platform_set_drvdata(pdev, priv); 253462306a36Sopenharmony_ci 253562306a36Sopenharmony_ci error = request_irq(irq, sh_mobile_lcdc_irq, 0, 253662306a36Sopenharmony_ci dev_name(&pdev->dev), priv); 253762306a36Sopenharmony_ci if (error) { 253862306a36Sopenharmony_ci dev_err(&pdev->dev, "unable to request irq\n"); 253962306a36Sopenharmony_ci goto err1; 254062306a36Sopenharmony_ci } 254162306a36Sopenharmony_ci 254262306a36Sopenharmony_ci priv->irq = irq; 254362306a36Sopenharmony_ci atomic_set(&priv->hw_usecnt, -1); 254462306a36Sopenharmony_ci 254562306a36Sopenharmony_ci for (i = 0, num_channels = 0; i < ARRAY_SIZE(pdata->ch); i++) { 254662306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = priv->ch + num_channels; 254762306a36Sopenharmony_ci 254862306a36Sopenharmony_ci ch->lcdc = priv; 254962306a36Sopenharmony_ci ch->cfg = &pdata->ch[i]; 255062306a36Sopenharmony_ci 255162306a36Sopenharmony_ci error = sh_mobile_lcdc_check_interface(ch); 255262306a36Sopenharmony_ci if (error) { 255362306a36Sopenharmony_ci dev_err(&pdev->dev, "unsupported interface type\n"); 255462306a36Sopenharmony_ci goto err1; 255562306a36Sopenharmony_ci } 255662306a36Sopenharmony_ci init_waitqueue_head(&ch->frame_end_wait); 255762306a36Sopenharmony_ci init_completion(&ch->vsync_completion); 255862306a36Sopenharmony_ci 255962306a36Sopenharmony_ci /* probe the backlight is there is one defined */ 256062306a36Sopenharmony_ci if (ch->cfg->bl_info.max_brightness) 256162306a36Sopenharmony_ci ch->bl = sh_mobile_lcdc_bl_probe(&pdev->dev, ch); 256262306a36Sopenharmony_ci 256362306a36Sopenharmony_ci switch (pdata->ch[i].chan) { 256462306a36Sopenharmony_ci case LCDC_CHAN_MAINLCD: 256562306a36Sopenharmony_ci ch->enabled = LDCNT2R_ME; 256662306a36Sopenharmony_ci ch->reg_offs = lcdc_offs_mainlcd; 256762306a36Sopenharmony_ci num_channels++; 256862306a36Sopenharmony_ci break; 256962306a36Sopenharmony_ci case LCDC_CHAN_SUBLCD: 257062306a36Sopenharmony_ci ch->enabled = LDCNT2R_SE; 257162306a36Sopenharmony_ci ch->reg_offs = lcdc_offs_sublcd; 257262306a36Sopenharmony_ci num_channels++; 257362306a36Sopenharmony_ci break; 257462306a36Sopenharmony_ci } 257562306a36Sopenharmony_ci } 257662306a36Sopenharmony_ci 257762306a36Sopenharmony_ci if (!num_channels) { 257862306a36Sopenharmony_ci dev_err(&pdev->dev, "no channels defined\n"); 257962306a36Sopenharmony_ci error = -EINVAL; 258062306a36Sopenharmony_ci goto err1; 258162306a36Sopenharmony_ci } 258262306a36Sopenharmony_ci 258362306a36Sopenharmony_ci /* for dual channel LCDC (MAIN + SUB) force shared format setting */ 258462306a36Sopenharmony_ci if (num_channels == 2) 258562306a36Sopenharmony_ci priv->forced_fourcc = pdata->ch[0].fourcc; 258662306a36Sopenharmony_ci 258762306a36Sopenharmony_ci priv->base = ioremap(res->start, resource_size(res)); 258862306a36Sopenharmony_ci if (!priv->base) { 258962306a36Sopenharmony_ci error = -ENOMEM; 259062306a36Sopenharmony_ci goto err1; 259162306a36Sopenharmony_ci } 259262306a36Sopenharmony_ci 259362306a36Sopenharmony_ci error = sh_mobile_lcdc_setup_clocks(priv, pdata->clock_source); 259462306a36Sopenharmony_ci if (error) { 259562306a36Sopenharmony_ci dev_err(&pdev->dev, "unable to setup clocks\n"); 259662306a36Sopenharmony_ci goto err1; 259762306a36Sopenharmony_ci } 259862306a36Sopenharmony_ci 259962306a36Sopenharmony_ci /* Enable runtime PM. */ 260062306a36Sopenharmony_ci pm_runtime_enable(&pdev->dev); 260162306a36Sopenharmony_ci 260262306a36Sopenharmony_ci for (i = 0; i < num_channels; i++) { 260362306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = &priv->ch[i]; 260462306a36Sopenharmony_ci 260562306a36Sopenharmony_ci error = sh_mobile_lcdc_channel_init(ch); 260662306a36Sopenharmony_ci if (error) 260762306a36Sopenharmony_ci goto err1; 260862306a36Sopenharmony_ci } 260962306a36Sopenharmony_ci 261062306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(pdata->overlays); i++) { 261162306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i]; 261262306a36Sopenharmony_ci 261362306a36Sopenharmony_ci ovl->cfg = &pdata->overlays[i]; 261462306a36Sopenharmony_ci ovl->channel = &priv->ch[0]; 261562306a36Sopenharmony_ci 261662306a36Sopenharmony_ci error = sh_mobile_lcdc_overlay_init(ovl); 261762306a36Sopenharmony_ci if (error) 261862306a36Sopenharmony_ci goto err1; 261962306a36Sopenharmony_ci } 262062306a36Sopenharmony_ci 262162306a36Sopenharmony_ci error = sh_mobile_lcdc_start(priv); 262262306a36Sopenharmony_ci if (error) { 262362306a36Sopenharmony_ci dev_err(&pdev->dev, "unable to start hardware\n"); 262462306a36Sopenharmony_ci goto err1; 262562306a36Sopenharmony_ci } 262662306a36Sopenharmony_ci 262762306a36Sopenharmony_ci for (i = 0; i < num_channels; i++) { 262862306a36Sopenharmony_ci struct sh_mobile_lcdc_chan *ch = priv->ch + i; 262962306a36Sopenharmony_ci 263062306a36Sopenharmony_ci error = sh_mobile_lcdc_channel_fb_register(ch); 263162306a36Sopenharmony_ci if (error) 263262306a36Sopenharmony_ci goto err1; 263362306a36Sopenharmony_ci } 263462306a36Sopenharmony_ci 263562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(pdata->overlays); i++) { 263662306a36Sopenharmony_ci struct sh_mobile_lcdc_overlay *ovl = &priv->overlays[i]; 263762306a36Sopenharmony_ci 263862306a36Sopenharmony_ci error = sh_mobile_lcdc_overlay_fb_register(ovl); 263962306a36Sopenharmony_ci if (error) 264062306a36Sopenharmony_ci goto err1; 264162306a36Sopenharmony_ci } 264262306a36Sopenharmony_ci 264362306a36Sopenharmony_ci return 0; 264462306a36Sopenharmony_cierr1: 264562306a36Sopenharmony_ci sh_mobile_lcdc_remove(pdev); 264662306a36Sopenharmony_ci 264762306a36Sopenharmony_ci return error; 264862306a36Sopenharmony_ci} 264962306a36Sopenharmony_ci 265062306a36Sopenharmony_cistatic struct platform_driver sh_mobile_lcdc_driver = { 265162306a36Sopenharmony_ci .driver = { 265262306a36Sopenharmony_ci .name = "sh_mobile_lcdc_fb", 265362306a36Sopenharmony_ci .pm = &sh_mobile_lcdc_dev_pm_ops, 265462306a36Sopenharmony_ci }, 265562306a36Sopenharmony_ci .probe = sh_mobile_lcdc_probe, 265662306a36Sopenharmony_ci .remove_new = sh_mobile_lcdc_remove, 265762306a36Sopenharmony_ci}; 265862306a36Sopenharmony_ci 265962306a36Sopenharmony_cimodule_platform_driver(sh_mobile_lcdc_driver); 266062306a36Sopenharmony_ci 266162306a36Sopenharmony_ciMODULE_DESCRIPTION("SuperH Mobile LCDC Framebuffer driver"); 266262306a36Sopenharmony_ciMODULE_AUTHOR("Magnus Damm <damm@opensource.se>"); 266362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2664