Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,49 @@
# SPDX-License-Identifier: GPL-2.0-only
config FB_OMAP
tristate "OMAP frame buffer support"
depends on FB
depends on ARCH_OMAP1 || (ARM && COMPILE_TEST)
select FB_IOMEM_HELPERS
help
Frame buffer driver for OMAP based boards.
config FB_OMAP_LCDC_EXTERNAL
bool "External LCD controller support"
depends on FB_OMAP
help
Say Y here, if you want to have support for boards with an
external LCD controller connected to the SoSSI/RFBI interface.
config FB_OMAP_LCDC_HWA742
bool "Epson HWA742 LCD controller support"
depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL
help
Say Y here if you want to have support for the external
Epson HWA742 LCD controller.
config FB_OMAP_MANUAL_UPDATE
bool "Default to manual update mode"
depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL
help
Say Y here, if your user-space applications are capable of
notifying the frame buffer driver when a change has occurred in
the frame buffer content and thus a reload of the image data to
the external frame buffer is required. If unsure, say N.
config FB_OMAP_LCD_MIPID
bool "MIPI DBI-C/DCS compatible LCD support"
depends on FB_OMAP && SPI_MASTER
help
Say Y here if you want to have support for LCDs compatible with
the Mobile Industry Processor Interface DBI-C/DCS
specification. (Supported LCDs: Philips LPH8923, Sharp LS041Y3)
config FB_OMAP_DMA_TUNE
bool "Set DMA SDRAM access priority high"
depends on FB_OMAP
help
On systems in which video memory is in system memory
(SDRAM) this will speed up graphics DMA operations.
If you have such a system and want to use rotation
answer yes. Answer no if you have a dedicated video
memory, or don't use any of the accelerated features.
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the OMAP1 framebuffer device driver
#
obj-$(CONFIG_FB_OMAP) += omapfb.o
ifdef CONFIG_FB_OMAP
# must be built-in
obj-y += lcd_dma.o
endif
objs-yy := omapfb_main.o lcdc.o
objs-y$(CONFIG_FB_OMAP_LCDC_EXTERNAL) += sossi.o
objs-y$(CONFIG_FB_OMAP_LCDC_HWA742) += hwa742.o
lcds-y$(CONFIG_MACH_AMS_DELTA) += lcd_ams_delta.o
lcds-y$(CONFIG_MACH_OMAP_PALMTE) += lcd_palmte.o
lcds-y$(CONFIG_FB_OMAP_LCD_MIPID) += lcd_mipid.o
omapfb-objs := $(objs-yy)
obj-$(CONFIG_FB_OMAP) += $(lcds-yy)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Based on drivers/video/omap/lcd_inn1510.c
*
* LCD panel support for the Amstrad E3 (Delta) videophone.
*
* Copyright (C) 2006 Jonathan McDowell <noodles@earth.li>
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/lcd.h>
#include <linux/soc/ti/omap1-io.h>
#include "omapfb.h"
#define AMS_DELTA_DEFAULT_CONTRAST 112
#define AMS_DELTA_MAX_CONTRAST 0x00FF
#define AMS_DELTA_LCD_POWER 0x0100
/* LCD class device section */
static int ams_delta_lcd;
static struct gpio_desc *gpiod_vblen;
static struct gpio_desc *gpiod_ndisp;
static int ams_delta_lcd_set_power(struct lcd_device *dev, int power)
{
if (power == LCD_POWER_ON) {
if (!(ams_delta_lcd & AMS_DELTA_LCD_POWER)) {
omap_writeb(ams_delta_lcd & AMS_DELTA_MAX_CONTRAST,
OMAP_PWL_ENABLE);
omap_writeb(1, OMAP_PWL_CLK_ENABLE);
ams_delta_lcd |= AMS_DELTA_LCD_POWER;
}
} else {
if (ams_delta_lcd & AMS_DELTA_LCD_POWER) {
omap_writeb(0, OMAP_PWL_ENABLE);
omap_writeb(0, OMAP_PWL_CLK_ENABLE);
ams_delta_lcd &= ~AMS_DELTA_LCD_POWER;
}
}
return 0;
}
static int ams_delta_lcd_set_contrast(struct lcd_device *dev, int value)
{
if ((value >= 0) && (value <= AMS_DELTA_MAX_CONTRAST)) {
omap_writeb(value, OMAP_PWL_ENABLE);
ams_delta_lcd &= ~AMS_DELTA_MAX_CONTRAST;
ams_delta_lcd |= value;
}
return 0;
}
#ifdef CONFIG_LCD_CLASS_DEVICE
static int ams_delta_lcd_get_power(struct lcd_device *dev)
{
if (ams_delta_lcd & AMS_DELTA_LCD_POWER)
return LCD_POWER_ON;
else
return LCD_POWER_OFF;
}
static int ams_delta_lcd_get_contrast(struct lcd_device *dev)
{
if (!(ams_delta_lcd & AMS_DELTA_LCD_POWER))
return 0;
return ams_delta_lcd & AMS_DELTA_MAX_CONTRAST;
}
static const struct lcd_ops ams_delta_lcd_ops = {
.get_power = ams_delta_lcd_get_power,
.set_power = ams_delta_lcd_set_power,
.get_contrast = ams_delta_lcd_get_contrast,
.set_contrast = ams_delta_lcd_set_contrast,
};
#endif
/* omapfb panel section */
static int ams_delta_panel_enable(struct lcd_panel *panel)
{
gpiod_set_value(gpiod_ndisp, 1);
gpiod_set_value(gpiod_vblen, 1);
return 0;
}
static void ams_delta_panel_disable(struct lcd_panel *panel)
{
gpiod_set_value(gpiod_vblen, 0);
gpiod_set_value(gpiod_ndisp, 0);
}
static struct lcd_panel ams_delta_panel = {
.name = "ams-delta",
.config = 0,
.bpp = 12,
.data_lines = 16,
.x_res = 480,
.y_res = 320,
.pixel_clock = 4687,
.hsw = 3,
.hfp = 1,
.hbp = 1,
.vsw = 1,
.vfp = 0,
.vbp = 0,
.pcd = 0,
.acb = 37,
.enable = ams_delta_panel_enable,
.disable = ams_delta_panel_disable,
};
/* platform driver section */
static int ams_delta_panel_probe(struct platform_device *pdev)
{
struct lcd_device *lcd_device = NULL;
gpiod_vblen = devm_gpiod_get(&pdev->dev, "vblen", GPIOD_OUT_LOW);
if (IS_ERR(gpiod_vblen))
return dev_err_probe(&pdev->dev, PTR_ERR(gpiod_vblen),
"VBLEN GPIO request failed\n");
gpiod_ndisp = devm_gpiod_get(&pdev->dev, "ndisp", GPIOD_OUT_LOW);
if (IS_ERR(gpiod_ndisp))
return dev_err_probe(&pdev->dev, PTR_ERR(gpiod_ndisp),
"NDISP GPIO request failed\n");
#ifdef CONFIG_LCD_CLASS_DEVICE
lcd_device = lcd_device_register("omapfb", &pdev->dev, NULL,
&ams_delta_lcd_ops);
if (IS_ERR(lcd_device)) {
int ret = PTR_ERR(lcd_device);
dev_err(&pdev->dev, "failed to register device\n");
return ret;
}
platform_set_drvdata(pdev, lcd_device);
lcd_device->props.max_contrast = AMS_DELTA_MAX_CONTRAST;
#endif
ams_delta_lcd_set_contrast(lcd_device, AMS_DELTA_DEFAULT_CONTRAST);
ams_delta_lcd_set_power(lcd_device, LCD_POWER_ON);
omapfb_register_panel(&ams_delta_panel);
return 0;
}
static struct platform_driver ams_delta_panel_driver = {
.probe = ams_delta_panel_probe,
.driver = {
.name = "lcd_ams_delta",
},
};
module_platform_driver(ams_delta_panel_driver);
MODULE_AUTHOR("Jonathan McDowell <noodles@earth.li>");
MODULE_DESCRIPTION("LCD panel support for the Amstrad E3 (Delta) videophone");
MODULE_LICENSE("GPL");
@@ -0,0 +1,445 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/arch/arm/mach-omap1/lcd_dma.c
*
* Extracted from arch/arm/plat-omap/dma.c
* Copyright (C) 2003 - 2008 Nokia Corporation
* Author: Juha Yrjölä <juha.yrjola@nokia.com>
* DMA channel linking for 1610 by Samuel Ortiz <samuel.ortiz@nokia.com>
* Graphics DMA and LCD DMA graphics tranformations
* by Imre Deak <imre.deak@nokia.com>
* OMAP2/3 support Copyright (C) 2004-2007 Texas Instruments, Inc.
* Merged to support both OMAP1 and OMAP2 by Tony Lindgren <tony@atomide.com>
* Some functions based on earlier dma-omap.c Copyright (C) 2001 RidgeRun, Inc.
*
* Copyright (C) 2009 Texas Instruments
* Added OMAP4 support - Santosh Shilimkar <santosh.shilimkar@ti.com>
*
* Support functions for the OMAP internal DMA channels.
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/omap-dma.h>
#include <linux/soc/ti/omap1-soc.h>
#include <linux/soc/ti/omap1-io.h>
#include "lcdc.h"
#include "lcd_dma.h"
int omap_lcd_dma_running(void)
{
/*
* On OMAP1510, internal LCD controller will start the transfer
* when it gets enabled, so assume DMA running if LCD enabled.
*/
if (cpu_is_omap15xx())
if (omap_readw(OMAP_LCDC_CONTROL) & OMAP_LCDC_CTRL_LCD_EN)
return 1;
/* Check if LCD DMA is running */
if (cpu_is_omap16xx())
if (omap_readw(OMAP1610_DMA_LCD_CCR) & OMAP_DMA_CCR_EN)
return 1;
return 0;
}
static struct lcd_dma_info {
spinlock_t lock;
int reserved;
void (*callback)(u16 status, void *data);
void *cb_data;
int active;
unsigned long addr;
int rotate, data_type, xres, yres;
int vxres;
int mirror;
int xscale, yscale;
int ext_ctrl;
int src_port;
int single_transfer;
} lcd_dma;
void omap_set_lcd_dma_b1(unsigned long addr, u16 fb_xres, u16 fb_yres,
int data_type)
{
lcd_dma.addr = addr;
lcd_dma.data_type = data_type;
lcd_dma.xres = fb_xres;
lcd_dma.yres = fb_yres;
}
EXPORT_SYMBOL(omap_set_lcd_dma_b1);
void omap_set_lcd_dma_ext_controller(int external)
{
lcd_dma.ext_ctrl = external;
}
EXPORT_SYMBOL(omap_set_lcd_dma_ext_controller);
void omap_set_lcd_dma_single_transfer(int single)
{
lcd_dma.single_transfer = single;
}
EXPORT_SYMBOL(omap_set_lcd_dma_single_transfer);
void omap_set_lcd_dma_b1_rotation(int rotate)
{
if (cpu_is_omap15xx()) {
printk(KERN_ERR "DMA rotation is not supported in 1510 mode\n");
BUG();
return;
}
lcd_dma.rotate = rotate;
}
EXPORT_SYMBOL(omap_set_lcd_dma_b1_rotation);
void omap_set_lcd_dma_b1_mirror(int mirror)
{
if (cpu_is_omap15xx()) {
printk(KERN_ERR "DMA mirror is not supported in 1510 mode\n");
BUG();
}
lcd_dma.mirror = mirror;
}
EXPORT_SYMBOL(omap_set_lcd_dma_b1_mirror);
void omap_set_lcd_dma_b1_vxres(unsigned long vxres)
{
if (cpu_is_omap15xx()) {
pr_err("DMA virtual resolution is not supported in 1510 mode\n");
BUG();
}
lcd_dma.vxres = vxres;
}
EXPORT_SYMBOL(omap_set_lcd_dma_b1_vxres);
void omap_set_lcd_dma_b1_scale(unsigned int xscale, unsigned int yscale)
{
if (cpu_is_omap15xx()) {
printk(KERN_ERR "DMA scale is not supported in 1510 mode\n");
BUG();
}
lcd_dma.xscale = xscale;
lcd_dma.yscale = yscale;
}
EXPORT_SYMBOL(omap_set_lcd_dma_b1_scale);
static void set_b1_regs(void)
{
unsigned long top, bottom;
int es;
u16 w;
unsigned long en, fn;
long ei, fi;
unsigned long vxres;
unsigned int xscale, yscale;
switch (lcd_dma.data_type) {
case OMAP_DMA_DATA_TYPE_S8:
es = 1;
break;
case OMAP_DMA_DATA_TYPE_S16:
es = 2;
break;
case OMAP_DMA_DATA_TYPE_S32:
es = 4;
break;
default:
BUG();
return;
}
vxres = lcd_dma.vxres ? lcd_dma.vxres : lcd_dma.xres;
xscale = lcd_dma.xscale ? lcd_dma.xscale : 1;
yscale = lcd_dma.yscale ? lcd_dma.yscale : 1;
BUG_ON(vxres < lcd_dma.xres);
#define PIXADDR(x, y) (lcd_dma.addr + \
((y) * vxres * yscale + (x) * xscale) * es)
#define PIXSTEP(sx, sy, dx, dy) (PIXADDR(dx, dy) - PIXADDR(sx, sy) - es + 1)
switch (lcd_dma.rotate) {
case 0:
if (!lcd_dma.mirror) {
top = PIXADDR(0, 0);
bottom = PIXADDR(lcd_dma.xres - 1, lcd_dma.yres - 1);
/* 1510 DMA requires the bottom address to be 2 more
* than the actual last memory access location. */
if (cpu_is_omap15xx() &&
lcd_dma.data_type == OMAP_DMA_DATA_TYPE_S32)
bottom += 2;
ei = PIXSTEP(0, 0, 1, 0);
fi = PIXSTEP(lcd_dma.xres - 1, 0, 0, 1);
} else {
top = PIXADDR(lcd_dma.xres - 1, 0);
bottom = PIXADDR(0, lcd_dma.yres - 1);
ei = PIXSTEP(1, 0, 0, 0);
fi = PIXSTEP(0, 0, lcd_dma.xres - 1, 1);
}
en = lcd_dma.xres;
fn = lcd_dma.yres;
break;
case 90:
if (!lcd_dma.mirror) {
top = PIXADDR(0, lcd_dma.yres - 1);
bottom = PIXADDR(lcd_dma.xres - 1, 0);
ei = PIXSTEP(0, 1, 0, 0);
fi = PIXSTEP(0, 0, 1, lcd_dma.yres - 1);
} else {
top = PIXADDR(lcd_dma.xres - 1, lcd_dma.yres - 1);
bottom = PIXADDR(0, 0);
ei = PIXSTEP(0, 1, 0, 0);
fi = PIXSTEP(1, 0, 0, lcd_dma.yres - 1);
}
en = lcd_dma.yres;
fn = lcd_dma.xres;
break;
case 180:
if (!lcd_dma.mirror) {
top = PIXADDR(lcd_dma.xres - 1, lcd_dma.yres - 1);
bottom = PIXADDR(0, 0);
ei = PIXSTEP(1, 0, 0, 0);
fi = PIXSTEP(0, 1, lcd_dma.xres - 1, 0);
} else {
top = PIXADDR(0, lcd_dma.yres - 1);
bottom = PIXADDR(lcd_dma.xres - 1, 0);
ei = PIXSTEP(0, 0, 1, 0);
fi = PIXSTEP(lcd_dma.xres - 1, 1, 0, 0);
}
en = lcd_dma.xres;
fn = lcd_dma.yres;
break;
case 270:
if (!lcd_dma.mirror) {
top = PIXADDR(lcd_dma.xres - 1, 0);
bottom = PIXADDR(0, lcd_dma.yres - 1);
ei = PIXSTEP(0, 0, 0, 1);
fi = PIXSTEP(1, lcd_dma.yres - 1, 0, 0);
} else {
top = PIXADDR(0, 0);
bottom = PIXADDR(lcd_dma.xres - 1, lcd_dma.yres - 1);
ei = PIXSTEP(0, 0, 0, 1);
fi = PIXSTEP(0, lcd_dma.yres - 1, 1, 0);
}
en = lcd_dma.yres;
fn = lcd_dma.xres;
break;
default:
BUG();
return; /* Suppress warning about uninitialized vars */
}
if (cpu_is_omap15xx()) {
omap_writew(top >> 16, OMAP1510_DMA_LCD_TOP_F1_U);
omap_writew(top, OMAP1510_DMA_LCD_TOP_F1_L);
omap_writew(bottom >> 16, OMAP1510_DMA_LCD_BOT_F1_U);
omap_writew(bottom, OMAP1510_DMA_LCD_BOT_F1_L);
return;
}
/* 1610 regs */
omap_writew(top >> 16, OMAP1610_DMA_LCD_TOP_B1_U);
omap_writew(top, OMAP1610_DMA_LCD_TOP_B1_L);
omap_writew(bottom >> 16, OMAP1610_DMA_LCD_BOT_B1_U);
omap_writew(bottom, OMAP1610_DMA_LCD_BOT_B1_L);
omap_writew(en, OMAP1610_DMA_LCD_SRC_EN_B1);
omap_writew(fn, OMAP1610_DMA_LCD_SRC_FN_B1);
w = omap_readw(OMAP1610_DMA_LCD_CSDP);
w &= ~0x03;
w |= lcd_dma.data_type;
omap_writew(w, OMAP1610_DMA_LCD_CSDP);
w = omap_readw(OMAP1610_DMA_LCD_CTRL);
/* Always set the source port as SDRAM for now*/
w &= ~(0x03 << 6);
if (lcd_dma.callback != NULL)
w |= 1 << 1; /* Block interrupt enable */
else
w &= ~(1 << 1);
omap_writew(w, OMAP1610_DMA_LCD_CTRL);
if (!(lcd_dma.rotate || lcd_dma.mirror ||
lcd_dma.vxres || lcd_dma.xscale || lcd_dma.yscale))
return;
w = omap_readw(OMAP1610_DMA_LCD_CCR);
/* Set the double-indexed addressing mode */
w |= (0x03 << 12);
omap_writew(w, OMAP1610_DMA_LCD_CCR);
omap_writew(ei, OMAP1610_DMA_LCD_SRC_EI_B1);
omap_writew(fi >> 16, OMAP1610_DMA_LCD_SRC_FI_B1_U);
omap_writew(fi, OMAP1610_DMA_LCD_SRC_FI_B1_L);
}
static irqreturn_t lcd_dma_irq_handler(int irq, void *dev_id)
{
u16 w;
w = omap_readw(OMAP1610_DMA_LCD_CTRL);
if (unlikely(!(w & (1 << 3)))) {
printk(KERN_WARNING "Spurious LCD DMA IRQ\n");
return IRQ_NONE;
}
/* Ack the IRQ */
w |= (1 << 3);
omap_writew(w, OMAP1610_DMA_LCD_CTRL);
lcd_dma.active = 0;
if (lcd_dma.callback != NULL)
lcd_dma.callback(w, lcd_dma.cb_data);
return IRQ_HANDLED;
}
int omap_request_lcd_dma(void (*callback)(u16 status, void *data),
void *data)
{
spin_lock_irq(&lcd_dma.lock);
if (lcd_dma.reserved) {
spin_unlock_irq(&lcd_dma.lock);
printk(KERN_ERR "LCD DMA channel already reserved\n");
BUG();
return -EBUSY;
}
lcd_dma.reserved = 1;
spin_unlock_irq(&lcd_dma.lock);
lcd_dma.callback = callback;
lcd_dma.cb_data = data;
lcd_dma.active = 0;
lcd_dma.single_transfer = 0;
lcd_dma.rotate = 0;
lcd_dma.vxres = 0;
lcd_dma.mirror = 0;
lcd_dma.xscale = 0;
lcd_dma.yscale = 0;
lcd_dma.ext_ctrl = 0;
lcd_dma.src_port = 0;
return 0;
}
EXPORT_SYMBOL(omap_request_lcd_dma);
void omap_free_lcd_dma(void)
{
spin_lock(&lcd_dma.lock);
if (!lcd_dma.reserved) {
spin_unlock(&lcd_dma.lock);
printk(KERN_ERR "LCD DMA is not reserved\n");
BUG();
return;
}
if (!cpu_is_omap15xx())
omap_writew(omap_readw(OMAP1610_DMA_LCD_CCR) & ~1,
OMAP1610_DMA_LCD_CCR);
lcd_dma.reserved = 0;
spin_unlock(&lcd_dma.lock);
}
EXPORT_SYMBOL(omap_free_lcd_dma);
void omap_enable_lcd_dma(void)
{
u16 w;
/*
* Set the Enable bit only if an external controller is
* connected. Otherwise the OMAP internal controller will
* start the transfer when it gets enabled.
*/
if (cpu_is_omap15xx() || !lcd_dma.ext_ctrl)
return;
w = omap_readw(OMAP1610_DMA_LCD_CTRL);
w |= 1 << 8;
omap_writew(w, OMAP1610_DMA_LCD_CTRL);
lcd_dma.active = 1;
w = omap_readw(OMAP1610_DMA_LCD_CCR);
w |= 1 << 7;
omap_writew(w, OMAP1610_DMA_LCD_CCR);
}
EXPORT_SYMBOL(omap_enable_lcd_dma);
void omap_setup_lcd_dma(void)
{
BUG_ON(lcd_dma.active);
if (!cpu_is_omap15xx()) {
/* Set some reasonable defaults */
omap_writew(0x5440, OMAP1610_DMA_LCD_CCR);
omap_writew(0x9102, OMAP1610_DMA_LCD_CSDP);
omap_writew(0x0004, OMAP1610_DMA_LCD_LCH_CTRL);
}
set_b1_regs();
if (!cpu_is_omap15xx()) {
u16 w;
w = omap_readw(OMAP1610_DMA_LCD_CCR);
/*
* If DMA was already active set the end_prog bit to have
* the programmed register set loaded into the active
* register set.
*/
w |= 1 << 11; /* End_prog */
if (!lcd_dma.single_transfer)
w |= (3 << 8); /* Auto_init, repeat */
omap_writew(w, OMAP1610_DMA_LCD_CCR);
}
}
EXPORT_SYMBOL(omap_setup_lcd_dma);
void omap_stop_lcd_dma(void)
{
u16 w;
lcd_dma.active = 0;
if (cpu_is_omap15xx() || !lcd_dma.ext_ctrl)
return;
w = omap_readw(OMAP1610_DMA_LCD_CCR);
w &= ~(1 << 7);
omap_writew(w, OMAP1610_DMA_LCD_CCR);
w = omap_readw(OMAP1610_DMA_LCD_CTRL);
w &= ~(1 << 8);
omap_writew(w, OMAP1610_DMA_LCD_CTRL);
}
EXPORT_SYMBOL(omap_stop_lcd_dma);
static int __init omap_init_lcd_dma(void)
{
int r;
if (!cpu_class_is_omap1())
return -ENODEV;
if (cpu_is_omap16xx()) {
u16 w;
/* this would prevent OMAP sleep */
w = omap_readw(OMAP1610_DMA_LCD_CTRL);
w &= ~(1 << 8);
omap_writew(w, OMAP1610_DMA_LCD_CTRL);
}
spin_lock_init(&lcd_dma.lock);
r = request_threaded_irq(INT_DMA_LCD, NULL, lcd_dma_irq_handler,
IRQF_ONESHOT, "LCD DMA", NULL);
if (r != 0)
pr_err("unable to request IRQ for LCD DMA (error %d)\n", r);
return r;
}
arch_initcall(omap_init_lcd_dma);
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* arch/arm/mach-omap1/include/mach/lcd_dma.h
*
* Extracted from arch/arm/plat-omap/include/plat/dma.h
* Copyright (C) 2003 Nokia Corporation
* Author: Juha Yrjölä <juha.yrjola@nokia.com>
*/
#ifndef __MACH_OMAP1_LCD_DMA_H__
#define __MACH_OMAP1_LCD_DMA_H__
/* Hardware registers for LCD DMA */
#define OMAP1510_DMA_LCD_BASE (0xfffedb00)
#define OMAP1510_DMA_LCD_CTRL (OMAP1510_DMA_LCD_BASE + 0x00)
#define OMAP1510_DMA_LCD_TOP_F1_L (OMAP1510_DMA_LCD_BASE + 0x02)
#define OMAP1510_DMA_LCD_TOP_F1_U (OMAP1510_DMA_LCD_BASE + 0x04)
#define OMAP1510_DMA_LCD_BOT_F1_L (OMAP1510_DMA_LCD_BASE + 0x06)
#define OMAP1510_DMA_LCD_BOT_F1_U (OMAP1510_DMA_LCD_BASE + 0x08)
#define OMAP1610_DMA_LCD_BASE (0xfffee300)
#define OMAP1610_DMA_LCD_CSDP (OMAP1610_DMA_LCD_BASE + 0xc0)
#define OMAP1610_DMA_LCD_CCR (OMAP1610_DMA_LCD_BASE + 0xc2)
#define OMAP1610_DMA_LCD_CTRL (OMAP1610_DMA_LCD_BASE + 0xc4)
#define OMAP1610_DMA_LCD_TOP_B1_L (OMAP1610_DMA_LCD_BASE + 0xc8)
#define OMAP1610_DMA_LCD_TOP_B1_U (OMAP1610_DMA_LCD_BASE + 0xca)
#define OMAP1610_DMA_LCD_BOT_B1_L (OMAP1610_DMA_LCD_BASE + 0xcc)
#define OMAP1610_DMA_LCD_BOT_B1_U (OMAP1610_DMA_LCD_BASE + 0xce)
#define OMAP1610_DMA_LCD_TOP_B2_L (OMAP1610_DMA_LCD_BASE + 0xd0)
#define OMAP1610_DMA_LCD_TOP_B2_U (OMAP1610_DMA_LCD_BASE + 0xd2)
#define OMAP1610_DMA_LCD_BOT_B2_L (OMAP1610_DMA_LCD_BASE + 0xd4)
#define OMAP1610_DMA_LCD_BOT_B2_U (OMAP1610_DMA_LCD_BASE + 0xd6)
#define OMAP1610_DMA_LCD_SRC_EI_B1 (OMAP1610_DMA_LCD_BASE + 0xd8)
#define OMAP1610_DMA_LCD_SRC_FI_B1_L (OMAP1610_DMA_LCD_BASE + 0xda)
#define OMAP1610_DMA_LCD_SRC_EN_B1 (OMAP1610_DMA_LCD_BASE + 0xe0)
#define OMAP1610_DMA_LCD_SRC_FN_B1 (OMAP1610_DMA_LCD_BASE + 0xe4)
#define OMAP1610_DMA_LCD_LCH_CTRL (OMAP1610_DMA_LCD_BASE + 0xea)
#define OMAP1610_DMA_LCD_SRC_FI_B1_U (OMAP1610_DMA_LCD_BASE + 0xf4)
/* LCD DMA block numbers */
enum {
OMAP_LCD_DMA_B1_TOP,
OMAP_LCD_DMA_B1_BOTTOM,
OMAP_LCD_DMA_B2_TOP,
OMAP_LCD_DMA_B2_BOTTOM
};
/* LCD DMA functions */
extern int omap_request_lcd_dma(void (*callback)(u16 status, void *data),
void *data);
extern void omap_free_lcd_dma(void);
extern void omap_setup_lcd_dma(void);
extern void omap_enable_lcd_dma(void);
extern void omap_stop_lcd_dma(void);
extern void omap_set_lcd_dma_ext_controller(int external);
extern void omap_set_lcd_dma_single_transfer(int single);
extern void omap_set_lcd_dma_b1(unsigned long addr, u16 fb_xres, u16 fb_yres,
int data_type);
extern void omap_set_lcd_dma_b1_rotation(int rotate);
extern void omap_set_lcd_dma_b1_vxres(unsigned long vxres);
extern void omap_set_lcd_dma_b1_mirror(int mirror);
extern void omap_set_lcd_dma_b1_scale(unsigned int xscale, unsigned int yscale);
#endif /* __MACH_OMAP1_LCD_DMA_H__ */
@@ -0,0 +1,606 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* LCD driver for MIPI DBI-C / DCS compatible LCDs
*
* Copyright (C) 2006 Nokia Corporation
* Author: Imre Deak <imre.deak@nokia.com>
*/
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/spi/spi.h>
#include <linux/module.h>
#include <linux/platform_data/lcd-mipid.h>
#include "omapfb.h"
#define MIPID_MODULE_NAME "lcd_mipid"
#define MIPID_CMD_READ_DISP_ID 0x04
#define MIPID_CMD_READ_RED 0x06
#define MIPID_CMD_READ_GREEN 0x07
#define MIPID_CMD_READ_BLUE 0x08
#define MIPID_CMD_READ_DISP_STATUS 0x09
#define MIPID_CMD_RDDSDR 0x0F
#define MIPID_CMD_SLEEP_IN 0x10
#define MIPID_CMD_SLEEP_OUT 0x11
#define MIPID_CMD_DISP_OFF 0x28
#define MIPID_CMD_DISP_ON 0x29
#define MIPID_ESD_CHECK_PERIOD msecs_to_jiffies(5000)
#define to_mipid_device(p) container_of(p, struct mipid_device, \
panel)
struct mipid_device {
int enabled;
int revision;
unsigned int saved_bklight_level;
unsigned long hw_guard_end; /* next value of jiffies
when we can issue the
next sleep in/out command */
unsigned long hw_guard_wait; /* max guard time in jiffies */
struct gpio_desc *reset;
struct omapfb_device *fbdev;
struct spi_device *spi;
struct mutex mutex;
struct lcd_panel panel;
struct delayed_work esd_work;
void (*esd_check)(struct mipid_device *m);
};
static void mipid_transfer(struct mipid_device *md, int cmd, const u8 *wbuf,
int wlen, u8 *rbuf, int rlen)
{
struct spi_message m;
struct spi_transfer *x, xfer[4];
u16 w;
int r;
BUG_ON(md->spi == NULL);
spi_message_init(&m);
memset(xfer, 0, sizeof(xfer));
x = &xfer[0];
cmd &= 0xff;
x->tx_buf = &cmd;
x->bits_per_word = 9;
x->len = 2;
spi_message_add_tail(x, &m);
if (wlen) {
x++;
x->tx_buf = wbuf;
x->len = wlen;
x->bits_per_word = 9;
spi_message_add_tail(x, &m);
}
if (rlen) {
x++;
x->rx_buf = &w;
x->len = 1;
spi_message_add_tail(x, &m);
if (rlen > 1) {
/* Arrange for the extra clock before the first
* data bit.
*/
x->bits_per_word = 9;
x->len = 2;
x++;
x->rx_buf = &rbuf[1];
x->len = rlen - 1;
spi_message_add_tail(x, &m);
}
}
r = spi_sync(md->spi, &m);
if (r < 0)
dev_dbg(&md->spi->dev, "spi_sync %d\n", r);
if (rlen)
rbuf[0] = w & 0xff;
}
static inline void mipid_cmd(struct mipid_device *md, int cmd)
{
mipid_transfer(md, cmd, NULL, 0, NULL, 0);
}
static inline void mipid_write(struct mipid_device *md,
int reg, const u8 *buf, int len)
{
mipid_transfer(md, reg, buf, len, NULL, 0);
}
static inline void mipid_read(struct mipid_device *md,
int reg, u8 *buf, int len)
{
mipid_transfer(md, reg, NULL, 0, buf, len);
}
static void set_data_lines(struct mipid_device *md, int data_lines)
{
u16 par;
switch (data_lines) {
case 16:
par = 0x150;
break;
case 18:
par = 0x160;
break;
case 24:
par = 0x170;
break;
}
mipid_write(md, 0x3a, (u8 *)&par, 2);
}
static void send_init_string(struct mipid_device *md)
{
u16 initpar[] = { 0x0102, 0x0100, 0x0100 };
mipid_write(md, 0xc2, (u8 *)initpar, sizeof(initpar));
set_data_lines(md, md->panel.data_lines);
}
static void hw_guard_start(struct mipid_device *md, int guard_msec)
{
md->hw_guard_wait = msecs_to_jiffies(guard_msec);
md->hw_guard_end = jiffies + md->hw_guard_wait;
}
static void hw_guard_wait(struct mipid_device *md)
{
unsigned long wait = md->hw_guard_end - jiffies;
if ((long)wait > 0 && time_before_eq(wait, md->hw_guard_wait)) {
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(wait);
}
}
static void set_sleep_mode(struct mipid_device *md, int on)
{
int cmd, sleep_time = 50;
if (on)
cmd = MIPID_CMD_SLEEP_IN;
else
cmd = MIPID_CMD_SLEEP_OUT;
hw_guard_wait(md);
mipid_cmd(md, cmd);
hw_guard_start(md, 120);
/*
* When we enable the panel, it seems we _have_ to sleep
* 120 ms before sending the init string. When disabling the
* panel we'll sleep for the duration of 2 frames, so that the
* controller can still provide the PCLK,HS,VS signals.
*/
if (!on)
sleep_time = 120;
msleep(sleep_time);
}
static void set_display_state(struct mipid_device *md, int enabled)
{
int cmd = enabled ? MIPID_CMD_DISP_ON : MIPID_CMD_DISP_OFF;
mipid_cmd(md, cmd);
}
static int mipid_set_bklight_level(struct lcd_panel *panel, unsigned int level)
{
struct mipid_device *md = to_mipid_device(panel);
struct mipid_platform_data *pd = md->spi->dev.platform_data;
if (pd->get_bklight_max == NULL || pd->set_bklight_level == NULL)
return -ENODEV;
if (level > pd->get_bklight_max(pd))
return -EINVAL;
if (!md->enabled) {
md->saved_bklight_level = level;
return 0;
}
pd->set_bklight_level(pd, level);
return 0;
}
static unsigned int mipid_get_bklight_level(struct lcd_panel *panel)
{
struct mipid_device *md = to_mipid_device(panel);
struct mipid_platform_data *pd = md->spi->dev.platform_data;
if (pd->get_bklight_level == NULL)
return -ENODEV;
return pd->get_bklight_level(pd);
}
static unsigned int mipid_get_bklight_max(struct lcd_panel *panel)
{
struct mipid_device *md = to_mipid_device(panel);
struct mipid_platform_data *pd = md->spi->dev.platform_data;
if (pd->get_bklight_max == NULL)
return -ENODEV;
return pd->get_bklight_max(pd);
}
static unsigned long mipid_get_caps(struct lcd_panel *panel)
{
return OMAPFB_CAPS_SET_BACKLIGHT;
}
static u16 read_first_pixel(struct mipid_device *md)
{
u16 pixel;
u8 red, green, blue;
mutex_lock(&md->mutex);
mipid_read(md, MIPID_CMD_READ_RED, &red, 1);
mipid_read(md, MIPID_CMD_READ_GREEN, &green, 1);
mipid_read(md, MIPID_CMD_READ_BLUE, &blue, 1);
mutex_unlock(&md->mutex);
switch (md->panel.data_lines) {
case 16:
pixel = ((red >> 1) << 11) | (green << 5) | (blue >> 1);
break;
case 24:
/* 24 bit -> 16 bit */
pixel = ((red >> 3) << 11) | ((green >> 2) << 5) |
(blue >> 3);
break;
default:
pixel = 0;
BUG();
}
return pixel;
}
static int mipid_run_test(struct lcd_panel *panel, int test_num)
{
struct mipid_device *md = to_mipid_device(panel);
static const u16 test_values[4] = {
0x0000, 0xffff, 0xaaaa, 0x5555,
};
int i;
if (test_num != MIPID_TEST_RGB_LINES)
return MIPID_TEST_INVALID;
for (i = 0; i < ARRAY_SIZE(test_values); i++) {
int delay;
unsigned long tmo;
omapfb_write_first_pixel(md->fbdev, test_values[i]);
tmo = jiffies + msecs_to_jiffies(100);
delay = 25;
while (1) {
u16 pixel;
msleep(delay);
pixel = read_first_pixel(md);
if (pixel == test_values[i])
break;
if (time_after(jiffies, tmo)) {
dev_err(&md->spi->dev,
"MIPI LCD RGB I/F test failed: "
"expecting %04x, got %04x\n",
test_values[i], pixel);
return MIPID_TEST_FAILED;
}
delay = 10;
}
}
return 0;
}
static void ls041y3_esd_recover(struct mipid_device *md)
{
dev_err(&md->spi->dev, "performing LCD ESD recovery\n");
set_sleep_mode(md, 1);
set_sleep_mode(md, 0);
}
static void ls041y3_esd_check_mode1(struct mipid_device *md)
{
u8 state1, state2;
mipid_read(md, MIPID_CMD_RDDSDR, &state1, 1);
set_sleep_mode(md, 0);
mipid_read(md, MIPID_CMD_RDDSDR, &state2, 1);
dev_dbg(&md->spi->dev, "ESD mode 1 state1 %02x state2 %02x\n",
state1, state2);
/* Each sleep out command will trigger a self diagnostic and flip
* Bit6 if the test passes.
*/
if (!((state1 ^ state2) & (1 << 6)))
ls041y3_esd_recover(md);
}
static void ls041y3_esd_check_mode2(struct mipid_device *md)
{
int i;
u8 rbuf[2];
static const struct {
int cmd;
int wlen;
u16 wbuf[3];
} *rd, rd_ctrl[7] = {
{ 0xb0, 4, { 0x0101, 0x01fe, } },
{ 0xb1, 4, { 0x01de, 0x0121, } },
{ 0xc2, 4, { 0x0100, 0x0100, } },
{ 0xbd, 2, { 0x0100, } },
{ 0xc2, 4, { 0x01fc, 0x0103, } },
{ 0xb4, 0, },
{ 0x00, 0, },
};
rd = rd_ctrl;
for (i = 0; i < 3; i++, rd++)
mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen);
udelay(10);
mipid_read(md, rd->cmd, rbuf, 2);
rd++;
for (i = 0; i < 3; i++, rd++) {
udelay(10);
mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen);
}
dev_dbg(&md->spi->dev, "ESD mode 2 state %02x\n", rbuf[1]);
if (rbuf[1] == 0x00)
ls041y3_esd_recover(md);
}
static void ls041y3_esd_check(struct mipid_device *md)
{
ls041y3_esd_check_mode1(md);
if (md->revision >= 0x88)
ls041y3_esd_check_mode2(md);
}
static void mipid_esd_start_check(struct mipid_device *md)
{
if (md->esd_check != NULL)
schedule_delayed_work(&md->esd_work,
MIPID_ESD_CHECK_PERIOD);
}
static void mipid_esd_stop_check(struct mipid_device *md)
{
if (md->esd_check != NULL)
cancel_delayed_work_sync(&md->esd_work);
}
static void mipid_esd_work(struct work_struct *work)
{
struct mipid_device *md = container_of(work, struct mipid_device,
esd_work.work);
mutex_lock(&md->mutex);
md->esd_check(md);
mutex_unlock(&md->mutex);
mipid_esd_start_check(md);
}
static int mipid_enable(struct lcd_panel *panel)
{
struct mipid_device *md = to_mipid_device(panel);
mutex_lock(&md->mutex);
if (md->enabled) {
mutex_unlock(&md->mutex);
return 0;
}
set_sleep_mode(md, 0);
md->enabled = 1;
send_init_string(md);
set_display_state(md, 1);
mipid_set_bklight_level(panel, md->saved_bklight_level);
mipid_esd_start_check(md);
mutex_unlock(&md->mutex);
return 0;
}
static void mipid_disable(struct lcd_panel *panel)
{
struct mipid_device *md = to_mipid_device(panel);
/*
* A final ESD work might be called before returning,
* so do this without holding the lock.
*/
mipid_esd_stop_check(md);
mutex_lock(&md->mutex);
if (!md->enabled) {
mutex_unlock(&md->mutex);
return;
}
md->saved_bklight_level = mipid_get_bklight_level(panel);
mipid_set_bklight_level(panel, 0);
set_display_state(md, 0);
set_sleep_mode(md, 1);
md->enabled = 0;
mutex_unlock(&md->mutex);
}
static int panel_enabled(struct mipid_device *md)
{
u32 disp_status;
int enabled;
mipid_read(md, MIPID_CMD_READ_DISP_STATUS, (u8 *)&disp_status, 4);
disp_status = __be32_to_cpu(disp_status);
enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10));
dev_dbg(&md->spi->dev,
"LCD panel %senabled by bootloader (status 0x%04x)\n",
enabled ? "" : "not ", disp_status);
return enabled;
}
static int mipid_init(struct lcd_panel *panel,
struct omapfb_device *fbdev)
{
struct mipid_device *md = to_mipid_device(panel);
md->fbdev = fbdev;
INIT_DELAYED_WORK(&md->esd_work, mipid_esd_work);
mutex_init(&md->mutex);
md->enabled = panel_enabled(md);
if (md->enabled)
mipid_esd_start_check(md);
else
md->saved_bklight_level = mipid_get_bklight_level(panel);
return 0;
}
static void mipid_cleanup(struct lcd_panel *panel)
{
struct mipid_device *md = to_mipid_device(panel);
if (md->enabled)
mipid_esd_stop_check(md);
}
static const struct lcd_panel mipid_panel = {
.config = OMAP_LCDC_PANEL_TFT,
.bpp = 16,
.x_res = 800,
.y_res = 480,
.pixel_clock = 21940,
.hsw = 50,
.hfp = 20,
.hbp = 15,
.vsw = 2,
.vfp = 1,
.vbp = 3,
.init = mipid_init,
.cleanup = mipid_cleanup,
.enable = mipid_enable,
.disable = mipid_disable,
.get_caps = mipid_get_caps,
.set_bklight_level = mipid_set_bklight_level,
.get_bklight_level = mipid_get_bklight_level,
.get_bklight_max = mipid_get_bklight_max,
.run_test = mipid_run_test,
};
static int mipid_detect(struct mipid_device *md)
{
struct mipid_platform_data *pdata;
u8 display_id[3];
pdata = md->spi->dev.platform_data;
if (pdata == NULL) {
dev_err(&md->spi->dev, "missing platform data\n");
return -ENOENT;
}
mipid_read(md, MIPID_CMD_READ_DISP_ID, display_id, 3);
dev_dbg(&md->spi->dev, "MIPI display ID: %02x%02x%02x\n",
display_id[0], display_id[1], display_id[2]);
switch (display_id[0]) {
case 0x45:
md->panel.name = "lph8923";
break;
case 0x83:
md->panel.name = "ls041y3";
md->esd_check = ls041y3_esd_check;
break;
default:
md->panel.name = "unknown";
dev_err(&md->spi->dev, "invalid display ID\n");
return -ENODEV;
}
md->revision = display_id[1];
md->panel.data_lines = pdata->data_lines;
pr_info("omapfb: %s rev %02x LCD detected, %d data lines\n",
md->panel.name, md->revision, md->panel.data_lines);
return 0;
}
static int mipid_spi_probe(struct spi_device *spi)
{
struct mipid_device *md;
int r;
md = kzalloc_obj(*md);
if (md == NULL) {
dev_err(&spi->dev, "out of memory\n");
return -ENOMEM;
}
/* This will de-assert RESET if active */
md->reset = gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(md->reset))
return dev_err_probe(&spi->dev, PTR_ERR(md->reset),
"no reset GPIO line\n");
spi->mode = SPI_MODE_0;
md->spi = spi;
dev_set_drvdata(&spi->dev, md);
md->panel = mipid_panel;
r = mipid_detect(md);
if (r < 0)
goto free_md;
omapfb_register_panel(&md->panel);
return 0;
free_md:
kfree(md);
return r;
}
static void mipid_spi_remove(struct spi_device *spi)
{
struct mipid_device *md = dev_get_drvdata(&spi->dev);
/* Asserts RESET */
gpiod_set_value(md->reset, 1);
mipid_disable(&md->panel);
kfree(md);
}
static struct spi_driver mipid_spi_driver = {
.driver = {
.name = MIPID_MODULE_NAME,
},
.probe = mipid_spi_probe,
.remove = mipid_spi_remove,
};
module_spi_driver(mipid_spi_driver);
MODULE_DESCRIPTION("MIPI display driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* LCD panel support for the Palm Tungsten E
*
* Original version : Romain Goyet <r.goyet@gmail.com>
* Current version : Laurent Gonzalez <palmte.linux@free.fr>
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include "omapfb.h"
static struct lcd_panel palmte_panel = {
.name = "palmte",
.config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC |
OMAP_LCDC_INV_HSYNC | OMAP_LCDC_HSVS_RISING_EDGE |
OMAP_LCDC_HSVS_OPPOSITE,
.data_lines = 16,
.bpp = 8,
.pixel_clock = 12000,
.x_res = 320,
.y_res = 320,
.hsw = 4,
.hfp = 8,
.hbp = 28,
.vsw = 1,
.vfp = 8,
.vbp = 7,
.pcd = 0,
};
static int palmte_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&palmte_panel);
return 0;
}
static struct platform_driver palmte_panel_driver = {
.probe = palmte_panel_probe,
.driver = {
.name = "lcd_palmte",
},
};
module_platform_driver(palmte_panel_driver);
MODULE_AUTHOR("Romain Goyet <r.goyet@gmail.com>, Laurent Gonzalez <palmte.linux@free.fr>");
MODULE_DESCRIPTION("LCD panel support for the Palm Tungsten E");
MODULE_LICENSE("GPL");
+782
View File
@@ -0,0 +1,782 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* OMAP1 internal LCD controller
*
* Copyright (C) 2004 Nokia Corporation
* Author: Imre Deak <imre.deak@nokia.com>
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/err.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/vmalloc.h>
#include <linux/clk.h>
#include <linux/gfp.h>
#include <linux/soc/ti/omap1-io.h>
#include <linux/soc/ti/omap1-soc.h>
#include <linux/omap-dma.h>
#include <asm/mach-types.h>
#include "omapfb.h"
#include "lcdc.h"
#include "lcd_dma.h"
#define MODULE_NAME "lcdc"
#define MAX_PALETTE_SIZE PAGE_SIZE
enum lcdc_load_mode {
OMAP_LCDC_LOAD_PALETTE,
OMAP_LCDC_LOAD_FRAME,
OMAP_LCDC_LOAD_PALETTE_AND_FRAME
};
static struct omap_lcd_controller {
enum omapfb_update_mode update_mode;
int ext_mode;
unsigned long frame_offset;
int screen_width;
int xres;
int yres;
enum omapfb_color_format color_mode;
int bpp;
void *palette_virt;
dma_addr_t palette_phys;
int palette_code;
int palette_size;
unsigned int irq_mask;
struct completion last_frame_complete;
struct completion palette_load_complete;
struct clk *lcd_ck;
struct omapfb_device *fbdev;
void (*dma_callback)(void *data);
void *dma_callback_data;
dma_addr_t vram_phys;
void *vram_virt;
unsigned long vram_size;
} lcdc;
static inline void enable_irqs(int mask)
{
lcdc.irq_mask |= mask;
}
static inline void disable_irqs(int mask)
{
lcdc.irq_mask &= ~mask;
}
static void set_load_mode(enum lcdc_load_mode mode)
{
u32 l;
l = omap_readl(OMAP_LCDC_CONTROL);
l &= ~(3 << 20);
switch (mode) {
case OMAP_LCDC_LOAD_PALETTE:
l |= 1 << 20;
break;
case OMAP_LCDC_LOAD_FRAME:
l |= 2 << 20;
break;
case OMAP_LCDC_LOAD_PALETTE_AND_FRAME:
break;
default:
BUG();
}
omap_writel(l, OMAP_LCDC_CONTROL);
}
static void enable_controller(void)
{
u32 l;
l = omap_readl(OMAP_LCDC_CONTROL);
l |= OMAP_LCDC_CTRL_LCD_EN;
l &= ~OMAP_LCDC_IRQ_MASK;
l |= lcdc.irq_mask | OMAP_LCDC_IRQ_DONE; /* enabled IRQs */
omap_writel(l, OMAP_LCDC_CONTROL);
}
static void disable_controller_async(void)
{
u32 l;
u32 mask;
l = omap_readl(OMAP_LCDC_CONTROL);
mask = OMAP_LCDC_CTRL_LCD_EN | OMAP_LCDC_IRQ_MASK;
/*
* Preserve the DONE mask, since we still want to get the
* final DONE irq. It will be disabled in the IRQ handler.
*/
mask &= ~OMAP_LCDC_IRQ_DONE;
l &= ~mask;
omap_writel(l, OMAP_LCDC_CONTROL);
}
static void disable_controller(void)
{
init_completion(&lcdc.last_frame_complete);
disable_controller_async();
if (!wait_for_completion_timeout(&lcdc.last_frame_complete,
msecs_to_jiffies(500)))
dev_err(lcdc.fbdev->dev, "timeout waiting for FRAME DONE\n");
}
static void reset_controller(u32 status)
{
static unsigned long reset_count;
static unsigned long last_jiffies;
disable_controller_async();
reset_count++;
if (reset_count == 1 || time_after(jiffies, last_jiffies + HZ)) {
dev_err(lcdc.fbdev->dev,
"resetting (status %#010x,reset count %lu)\n",
status, reset_count);
last_jiffies = jiffies;
}
if (reset_count < 100) {
enable_controller();
} else {
reset_count = 0;
dev_err(lcdc.fbdev->dev,
"too many reset attempts, giving up.\n");
}
}
/*
* Configure the LCD DMA according to the current mode specified by parameters
* in lcdc.fbdev and fbdev->var.
*/
static void setup_lcd_dma(void)
{
static const int dma_elem_type[] = {
0,
OMAP_DMA_DATA_TYPE_S8,
OMAP_DMA_DATA_TYPE_S16,
0,
OMAP_DMA_DATA_TYPE_S32,
};
struct omapfb_plane_struct *plane = lcdc.fbdev->fb_info[0]->par;
struct fb_var_screeninfo *var = &lcdc.fbdev->fb_info[0]->var;
unsigned long src;
int esize, xelem, yelem;
src = lcdc.vram_phys + lcdc.frame_offset;
switch (var->rotate) {
case 0:
if (plane->info.mirror || (src & 3) ||
lcdc.color_mode == OMAPFB_COLOR_YUV420 ||
(lcdc.xres & 1))
esize = 2;
else
esize = 4;
xelem = lcdc.xres * lcdc.bpp / 8 / esize;
yelem = lcdc.yres;
break;
case 90:
case 180:
case 270:
if (cpu_is_omap15xx()) {
BUG();
}
esize = 2;
xelem = lcdc.yres * lcdc.bpp / 16;
yelem = lcdc.xres;
break;
default:
BUG();
return;
}
#ifdef VERBOSE
dev_dbg(lcdc.fbdev->dev,
"setup_dma: src %#010lx esize %d xelem %d yelem %d\n",
src, esize, xelem, yelem);
#endif
omap_set_lcd_dma_b1(src, xelem, yelem, dma_elem_type[esize]);
if (!cpu_is_omap15xx()) {
int bpp = lcdc.bpp;
/*
* YUV support is only for external mode when we have the
* YUV window embedded in a 16bpp frame buffer.
*/
if (lcdc.color_mode == OMAPFB_COLOR_YUV420)
bpp = 16;
/* Set virtual xres elem size */
omap_set_lcd_dma_b1_vxres(
lcdc.screen_width * bpp / 8 / esize);
/* Setup transformations */
omap_set_lcd_dma_b1_rotation(var->rotate);
omap_set_lcd_dma_b1_mirror(plane->info.mirror);
}
omap_setup_lcd_dma();
}
static irqreturn_t lcdc_irq_handler(int irq, void *dev_id)
{
u32 status;
status = omap_readl(OMAP_LCDC_STATUS);
if (status & (OMAP_LCDC_STAT_FUF | OMAP_LCDC_STAT_SYNC_LOST))
reset_controller(status);
else {
if (status & OMAP_LCDC_STAT_DONE) {
u32 l;
/*
* Disable IRQ_DONE. The status bit will be cleared
* only when the controller is reenabled and we don't
* want to get more interrupts.
*/
l = omap_readl(OMAP_LCDC_CONTROL);
l &= ~OMAP_LCDC_IRQ_DONE;
omap_writel(l, OMAP_LCDC_CONTROL);
complete(&lcdc.last_frame_complete);
}
if (status & OMAP_LCDC_STAT_LOADED_PALETTE) {
disable_controller_async();
complete(&lcdc.palette_load_complete);
}
}
/*
* Clear these interrupt status bits.
* Sync_lost, FUF bits were cleared by disabling the LCD controller
* LOADED_PALETTE can be cleared this way only in palette only
* load mode. In other load modes it's cleared by disabling the
* controller.
*/
status &= ~(OMAP_LCDC_STAT_VSYNC |
OMAP_LCDC_STAT_LOADED_PALETTE |
OMAP_LCDC_STAT_ABC |
OMAP_LCDC_STAT_LINE_INT);
omap_writel(status, OMAP_LCDC_STATUS);
return IRQ_HANDLED;
}
/*
* Change to a new video mode. We defer this to a later time to avoid any
* flicker and not to mess up the current LCD DMA context. For this we disable
* the LCD controller, which will generate a DONE irq after the last frame has
* been transferred. Then it'll be safe to reconfigure both the LCD controller
* as well as the LCD DMA.
*/
static int omap_lcdc_setup_plane(int plane, int channel_out,
unsigned long offset, int screen_width,
int pos_x, int pos_y, int width, int height,
int color_mode)
{
struct fb_var_screeninfo *var = &lcdc.fbdev->fb_info[0]->var;
struct lcd_panel *panel = lcdc.fbdev->panel;
int rot_x, rot_y;
if (var->rotate == 0) {
rot_x = panel->x_res;
rot_y = panel->y_res;
} else {
rot_x = panel->y_res;
rot_y = panel->x_res;
}
if (plane != 0 || channel_out != 0 || pos_x != 0 || pos_y != 0 ||
width > rot_x || height > rot_y) {
#ifdef VERBOSE
dev_dbg(lcdc.fbdev->dev,
"invalid plane params plane %d pos_x %d pos_y %d "
"w %d h %d\n", plane, pos_x, pos_y, width, height);
#endif
return -EINVAL;
}
lcdc.frame_offset = offset;
lcdc.xres = width;
lcdc.yres = height;
lcdc.screen_width = screen_width;
lcdc.color_mode = color_mode;
switch (color_mode) {
case OMAPFB_COLOR_CLUT_8BPP:
lcdc.bpp = 8;
lcdc.palette_code = 0x3000;
lcdc.palette_size = 512;
break;
case OMAPFB_COLOR_RGB565:
lcdc.bpp = 16;
lcdc.palette_code = 0x4000;
lcdc.palette_size = 32;
break;
case OMAPFB_COLOR_RGB444:
lcdc.bpp = 16;
lcdc.palette_code = 0x4000;
lcdc.palette_size = 32;
break;
case OMAPFB_COLOR_YUV420:
if (lcdc.ext_mode) {
lcdc.bpp = 12;
break;
}
fallthrough;
case OMAPFB_COLOR_YUV422:
if (lcdc.ext_mode) {
lcdc.bpp = 16;
break;
}
fallthrough;
default:
/* FIXME: other BPPs.
* bpp1: code 0, size 256
* bpp2: code 0x1000 size 256
* bpp4: code 0x2000 size 256
* bpp12: code 0x4000 size 32
*/
dev_dbg(lcdc.fbdev->dev, "invalid color mode %d\n", color_mode);
BUG();
return -1;
}
if (lcdc.ext_mode) {
setup_lcd_dma();
return 0;
}
if (lcdc.update_mode == OMAPFB_AUTO_UPDATE) {
disable_controller();
omap_stop_lcd_dma();
setup_lcd_dma();
enable_controller();
}
return 0;
}
static int omap_lcdc_enable_plane(int plane, int enable)
{
dev_dbg(lcdc.fbdev->dev,
"plane %d enable %d update_mode %d ext_mode %d\n",
plane, enable, lcdc.update_mode, lcdc.ext_mode);
if (plane != OMAPFB_PLANE_GFX)
return -EINVAL;
return 0;
}
/*
* Configure the LCD DMA for a palette load operation and do the palette
* downloading synchronously. We don't use the frame+palette load mode of
* the controller, since the palette can always be downloaded separately.
*/
static void load_palette(void)
{
u16 *palette;
palette = (u16 *)lcdc.palette_virt;
*(u16 *)palette &= 0x0fff;
*(u16 *)palette |= lcdc.palette_code;
omap_set_lcd_dma_b1(lcdc.palette_phys,
lcdc.palette_size / 4 + 1, 1, OMAP_DMA_DATA_TYPE_S32);
omap_set_lcd_dma_single_transfer(1);
omap_setup_lcd_dma();
init_completion(&lcdc.palette_load_complete);
enable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE);
set_load_mode(OMAP_LCDC_LOAD_PALETTE);
enable_controller();
if (!wait_for_completion_timeout(&lcdc.palette_load_complete,
msecs_to_jiffies(500)))
dev_err(lcdc.fbdev->dev, "timeout waiting for FRAME DONE\n");
/* The controller gets disabled in the irq handler */
disable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE);
omap_stop_lcd_dma();
omap_set_lcd_dma_single_transfer(lcdc.ext_mode);
}
/* Used only in internal controller mode */
static int omap_lcdc_setcolreg(u_int regno, u16 red, u16 green, u16 blue,
u16 transp, int update_hw_pal)
{
u16 *palette;
if (lcdc.color_mode != OMAPFB_COLOR_CLUT_8BPP || regno > 255)
return -EINVAL;
palette = (u16 *)lcdc.palette_virt;
palette[regno] &= ~0x0fff;
palette[regno] |= ((red >> 12) << 8) | ((green >> 12) << 4 ) |
(blue >> 12);
if (update_hw_pal) {
disable_controller();
omap_stop_lcd_dma();
load_palette();
setup_lcd_dma();
set_load_mode(OMAP_LCDC_LOAD_FRAME);
enable_controller();
}
return 0;
}
static void calc_ck_div(int is_tft, int pck, int *pck_div)
{
unsigned long lck;
pck = max(1, pck);
lck = clk_get_rate(lcdc.lcd_ck);
*pck_div = (lck + pck - 1) / pck;
if (is_tft)
*pck_div = max(2, *pck_div);
else
*pck_div = max(3, *pck_div);
if (*pck_div > 255) {
/* FIXME: try to adjust logic clock divider as well */
*pck_div = 255;
dev_warn(lcdc.fbdev->dev, "pixclock %d kHz too low.\n",
pck / 1000);
}
}
static inline void setup_regs(void)
{
u32 l;
struct lcd_panel *panel = lcdc.fbdev->panel;
int is_tft = panel->config & OMAP_LCDC_PANEL_TFT;
unsigned long lck;
int pcd;
l = omap_readl(OMAP_LCDC_CONTROL);
l &= ~OMAP_LCDC_CTRL_LCD_TFT;
l |= is_tft ? OMAP_LCDC_CTRL_LCD_TFT : 0;
#ifdef CONFIG_MACH_OMAP_PALMTE
/* FIXME:if (machine_is_omap_palmte()) { */
/* PalmTE uses alternate TFT setting in 8BPP mode */
l |= (is_tft && panel->bpp == 8) ? 0x810000 : 0;
/* } */
#endif
omap_writel(l, OMAP_LCDC_CONTROL);
l = omap_readl(OMAP_LCDC_TIMING2);
l &= ~(((1 << 6) - 1) << 20);
l |= (panel->config & OMAP_LCDC_SIGNAL_MASK) << 20;
omap_writel(l, OMAP_LCDC_TIMING2);
l = panel->x_res - 1;
l |= (panel->hsw - 1) << 10;
l |= (panel->hfp - 1) << 16;
l |= (panel->hbp - 1) << 24;
omap_writel(l, OMAP_LCDC_TIMING0);
l = panel->y_res - 1;
l |= (panel->vsw - 1) << 10;
l |= panel->vfp << 16;
l |= panel->vbp << 24;
omap_writel(l, OMAP_LCDC_TIMING1);
l = omap_readl(OMAP_LCDC_TIMING2);
l &= ~0xff;
lck = clk_get_rate(lcdc.lcd_ck);
if (!panel->pcd)
calc_ck_div(is_tft, panel->pixel_clock * 1000, &pcd);
else {
dev_warn(lcdc.fbdev->dev,
"Pixel clock divider value is obsolete.\n"
"Try to set pixel_clock to %lu and pcd to 0 "
"in drivers/video/omap/lcd_%s.c and submit a patch.\n",
lck / panel->pcd / 1000, panel->name);
pcd = panel->pcd;
}
l |= pcd & 0xff;
l |= panel->acb << 8;
omap_writel(l, OMAP_LCDC_TIMING2);
/* update panel info with the exact clock */
panel->pixel_clock = lck / pcd / 1000;
}
/*
* Configure the LCD controller, download the color palette and start a looped
* DMA transfer of the frame image data. Called only in internal
* controller mode.
*/
static int omap_lcdc_set_update_mode(enum omapfb_update_mode mode)
{
int r = 0;
if (mode != lcdc.update_mode) {
switch (mode) {
case OMAPFB_AUTO_UPDATE:
setup_regs();
load_palette();
/* Setup and start LCD DMA */
setup_lcd_dma();
set_load_mode(OMAP_LCDC_LOAD_FRAME);
enable_irqs(OMAP_LCDC_IRQ_DONE);
/* This will start the actual DMA transfer */
enable_controller();
lcdc.update_mode = mode;
break;
case OMAPFB_UPDATE_DISABLED:
disable_controller();
omap_stop_lcd_dma();
lcdc.update_mode = mode;
break;
default:
r = -EINVAL;
}
}
return r;
}
static enum omapfb_update_mode omap_lcdc_get_update_mode(void)
{
return lcdc.update_mode;
}
/* PM code called only in internal controller mode */
static void omap_lcdc_suspend(void)
{
omap_lcdc_set_update_mode(OMAPFB_UPDATE_DISABLED);
}
static void omap_lcdc_resume(void)
{
omap_lcdc_set_update_mode(OMAPFB_AUTO_UPDATE);
}
static void omap_lcdc_get_caps(int plane, struct omapfb_caps *caps)
{
return;
}
int omap_lcdc_set_dma_callback(void (*callback)(void *data), void *data)
{
BUG_ON(callback == NULL);
if (lcdc.dma_callback)
return -EBUSY;
else {
lcdc.dma_callback = callback;
lcdc.dma_callback_data = data;
}
return 0;
}
EXPORT_SYMBOL(omap_lcdc_set_dma_callback);
void omap_lcdc_free_dma_callback(void)
{
lcdc.dma_callback = NULL;
}
EXPORT_SYMBOL(omap_lcdc_free_dma_callback);
static void lcdc_dma_handler(u16 status, void *data)
{
if (lcdc.dma_callback)
lcdc.dma_callback(lcdc.dma_callback_data);
}
static int alloc_palette_ram(void)
{
lcdc.palette_virt = dma_alloc_wc(lcdc.fbdev->dev, MAX_PALETTE_SIZE,
&lcdc.palette_phys, GFP_KERNEL);
if (lcdc.palette_virt == NULL) {
dev_err(lcdc.fbdev->dev, "failed to alloc palette memory\n");
return -ENOMEM;
}
memset(lcdc.palette_virt, 0, MAX_PALETTE_SIZE);
return 0;
}
static void free_palette_ram(void)
{
dma_free_wc(lcdc.fbdev->dev, MAX_PALETTE_SIZE, lcdc.palette_virt,
lcdc.palette_phys);
}
static int alloc_fbmem(struct omapfb_mem_region *region)
{
int bpp;
int frame_size;
struct lcd_panel *panel = lcdc.fbdev->panel;
bpp = panel->bpp;
if (bpp == 12)
bpp = 16;
frame_size = PAGE_ALIGN(panel->x_res * bpp / 8 * panel->y_res);
if (region->size > frame_size)
frame_size = region->size;
lcdc.vram_size = frame_size;
lcdc.vram_virt = dma_alloc_wc(lcdc.fbdev->dev, lcdc.vram_size,
&lcdc.vram_phys, GFP_KERNEL);
if (lcdc.vram_virt == NULL) {
dev_err(lcdc.fbdev->dev, "unable to allocate FB DMA memory\n");
return -ENOMEM;
}
region->size = frame_size;
region->paddr = lcdc.vram_phys;
region->vaddr = lcdc.vram_virt;
region->alloc = 1;
memset(lcdc.vram_virt, 0, lcdc.vram_size);
return 0;
}
static void free_fbmem(void)
{
dma_free_wc(lcdc.fbdev->dev, lcdc.vram_size, lcdc.vram_virt,
lcdc.vram_phys);
}
static int setup_fbmem(struct omapfb_mem_desc *req_md)
{
if (!req_md->region_cnt) {
dev_err(lcdc.fbdev->dev, "no memory regions defined\n");
return -EINVAL;
}
if (req_md->region_cnt > 1) {
dev_err(lcdc.fbdev->dev, "only one plane is supported\n");
req_md->region_cnt = 1;
}
return alloc_fbmem(&req_md->region[0]);
}
static int omap_lcdc_init(struct omapfb_device *fbdev, int ext_mode,
struct omapfb_mem_desc *req_vram)
{
int r;
u32 l;
int rate;
struct clk *tc_ck;
lcdc.irq_mask = 0;
lcdc.fbdev = fbdev;
lcdc.ext_mode = ext_mode;
l = 0;
omap_writel(l, OMAP_LCDC_CONTROL);
/* FIXME:
* According to errata some platforms have a clock rate limitiation
*/
lcdc.lcd_ck = clk_get(fbdev->dev, "lcd_ck");
if (IS_ERR(lcdc.lcd_ck)) {
dev_err(fbdev->dev, "unable to access LCD clock\n");
r = PTR_ERR(lcdc.lcd_ck);
goto fail0;
}
tc_ck = clk_get(fbdev->dev, "tc_ck");
if (IS_ERR(tc_ck)) {
dev_err(fbdev->dev, "unable to access TC clock\n");
r = PTR_ERR(tc_ck);
goto fail1;
}
rate = clk_get_rate(tc_ck);
clk_put(tc_ck);
if (machine_is_ams_delta())
rate /= 4;
r = clk_set_rate(lcdc.lcd_ck, rate);
if (r) {
dev_err(fbdev->dev, "failed to adjust LCD rate\n");
goto fail1;
}
clk_prepare_enable(lcdc.lcd_ck);
r = request_irq(fbdev->int_irq, lcdc_irq_handler, 0, MODULE_NAME, fbdev);
if (r) {
dev_err(fbdev->dev, "unable to get IRQ\n");
goto fail2;
}
r = omap_request_lcd_dma(lcdc_dma_handler, NULL);
if (r) {
dev_err(fbdev->dev, "unable to get LCD DMA\n");
goto fail3;
}
omap_set_lcd_dma_single_transfer(ext_mode);
omap_set_lcd_dma_ext_controller(ext_mode);
if (!ext_mode)
if ((r = alloc_palette_ram()) < 0)
goto fail4;
if ((r = setup_fbmem(req_vram)) < 0)
goto fail5;
pr_info("omapfb: LCDC initialized\n");
return 0;
fail5:
if (!ext_mode)
free_palette_ram();
fail4:
omap_free_lcd_dma();
fail3:
free_irq(fbdev->int_irq, lcdc.fbdev);
fail2:
clk_disable_unprepare(lcdc.lcd_ck);
fail1:
clk_put(lcdc.lcd_ck);
fail0:
return r;
}
static void omap_lcdc_cleanup(void)
{
if (!lcdc.ext_mode)
free_palette_ram();
free_fbmem();
omap_free_lcd_dma();
free_irq(lcdc.fbdev->int_irq, lcdc.fbdev);
clk_disable_unprepare(lcdc.lcd_ck);
clk_put(lcdc.lcd_ck);
}
const struct lcd_ctrl omap1_int_ctrl = {
.name = "internal",
.init = omap_lcdc_init,
.cleanup = omap_lcdc_cleanup,
.get_caps = omap_lcdc_get_caps,
.set_update_mode = omap_lcdc_set_update_mode,
.get_update_mode = omap_lcdc_get_update_mode,
.update_window = NULL,
.suspend = omap_lcdc_suspend,
.resume = omap_lcdc_resume,
.setup_plane = omap_lcdc_setup_plane,
.enable_plane = omap_lcdc_enable_plane,
.setcolreg = omap_lcdc_setcolreg,
};
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef LCDC_H
#define LCDC_H
/*
* Copyright (C) 2004 Nokia Corporation
* Author: Imre Deak <imre.deak@nokia.com>
*/
#define OMAP_LCDC_BASE 0xfffec000
#define OMAP_LCDC_SIZE 256
#define OMAP_LCDC_IRQ INT_LCD_CTRL
#define OMAP_LCDC_CONTROL (OMAP_LCDC_BASE + 0x00)
#define OMAP_LCDC_TIMING0 (OMAP_LCDC_BASE + 0x04)
#define OMAP_LCDC_TIMING1 (OMAP_LCDC_BASE + 0x08)
#define OMAP_LCDC_TIMING2 (OMAP_LCDC_BASE + 0x0c)
#define OMAP_LCDC_STATUS (OMAP_LCDC_BASE + 0x10)
#define OMAP_LCDC_SUBPANEL (OMAP_LCDC_BASE + 0x14)
#define OMAP_LCDC_LINE_INT (OMAP_LCDC_BASE + 0x18)
#define OMAP_LCDC_DISPLAY_STATUS (OMAP_LCDC_BASE + 0x1c)
#define OMAP_LCDC_STAT_DONE (1 << 0)
#define OMAP_LCDC_STAT_VSYNC (1 << 1)
#define OMAP_LCDC_STAT_SYNC_LOST (1 << 2)
#define OMAP_LCDC_STAT_ABC (1 << 3)
#define OMAP_LCDC_STAT_LINE_INT (1 << 4)
#define OMAP_LCDC_STAT_FUF (1 << 5)
#define OMAP_LCDC_STAT_LOADED_PALETTE (1 << 6)
#define OMAP_LCDC_CTRL_LCD_EN (1 << 0)
#define OMAP_LCDC_CTRL_LCD_TFT (1 << 7)
#define OMAP_LCDC_CTRL_LINE_IRQ_CLR_SEL (1 << 10)
#define OMAP_LCDC_IRQ_VSYNC (1 << 2)
#define OMAP_LCDC_IRQ_DONE (1 << 3)
#define OMAP_LCDC_IRQ_LOADED_PALETTE (1 << 4)
#define OMAP_LCDC_IRQ_LINE_NIRQ (1 << 5)
#define OMAP_LCDC_IRQ_LINE (1 << 6)
#define OMAP_LCDC_IRQ_MASK (((1 << 5) - 1) << 2)
int omap_lcdc_set_dma_callback(void (*callback)(void *data), void *data);
void omap_lcdc_free_dma_callback(void);
extern const struct lcd_ctrl omap1_int_ctrl;
#endif
@@ -0,0 +1,230 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* File: drivers/video/omap/omapfb.h
*
* Framebuffer driver for TI OMAP boards
*
* Copyright (C) 2004 Nokia Corporation
* Author: Imre Deak <imre.deak@nokia.com>
*/
#ifndef __OMAPFB_H
#define __OMAPFB_H
#include <linux/fb.h>
#include <linux/mutex.h>
#include <linux/omapfb.h>
#define OMAPFB_EVENT_READY 1
#define OMAPFB_EVENT_DISABLED 2
#define OMAP_LCDC_INV_VSYNC 0x0001
#define OMAP_LCDC_INV_HSYNC 0x0002
#define OMAP_LCDC_INV_PIX_CLOCK 0x0004
#define OMAP_LCDC_INV_OUTPUT_EN 0x0008
#define OMAP_LCDC_HSVS_RISING_EDGE 0x0010
#define OMAP_LCDC_HSVS_OPPOSITE 0x0020
#define OMAP_LCDC_SIGNAL_MASK 0x003f
#define OMAP_LCDC_PANEL_TFT 0x0100
#define OMAPFB_PLANE_XRES_MIN 8
#define OMAPFB_PLANE_YRES_MIN 8
struct omapfb_device;
#define OMAPFB_PLANE_NUM 1
struct omapfb_mem_region {
u32 paddr;
void __iomem *vaddr;
unsigned long size;
u8 type; /* OMAPFB_PLANE_MEM_* */
enum omapfb_color_format format;/* OMAPFB_COLOR_* */
unsigned format_used:1; /* Must be set when format is set.
* Needed b/c of the badly chosen 0
* base for OMAPFB_COLOR_* values
*/
unsigned alloc:1; /* allocated by the driver */
unsigned map:1; /* kernel mapped by the driver */
};
struct omapfb_mem_desc {
int region_cnt;
struct omapfb_mem_region region[OMAPFB_PLANE_NUM];
};
struct lcd_panel {
const char *name;
int config; /* TFT/STN, signal inversion */
int bpp; /* Pixel format in fb mem */
int data_lines; /* Lines on LCD HW interface */
int x_res, y_res;
int pixel_clock; /* In kHz */
int hsw; /* Horizontal synchronization
pulse width */
int hfp; /* Horizontal front porch */
int hbp; /* Horizontal back porch */
int vsw; /* Vertical synchronization
pulse width */
int vfp; /* Vertical front porch */
int vbp; /* Vertical back porch */
int acb; /* ac-bias pin frequency */
int pcd; /* pixel clock divider.
Obsolete use pixel_clock instead */
int (*init) (struct lcd_panel *panel,
struct omapfb_device *fbdev);
void (*cleanup) (struct lcd_panel *panel);
int (*enable) (struct lcd_panel *panel);
void (*disable) (struct lcd_panel *panel);
unsigned long (*get_caps) (struct lcd_panel *panel);
int (*set_bklight_level)(struct lcd_panel *panel,
unsigned int level);
unsigned int (*get_bklight_level)(struct lcd_panel *panel);
unsigned int (*get_bklight_max) (struct lcd_panel *panel);
int (*run_test) (struct lcd_panel *panel, int test_num);
};
struct extif_timings {
int cs_on_time;
int cs_off_time;
int we_on_time;
int we_off_time;
int re_on_time;
int re_off_time;
int we_cycle_time;
int re_cycle_time;
int cs_pulse_width;
int access_time;
int clk_div;
u32 tim[5]; /* set by extif->convert_timings */
int converted;
};
struct lcd_ctrl_extif {
int (*init) (struct omapfb_device *fbdev);
void (*cleanup) (void);
void (*get_clk_info) (u32 *clk_period, u32 *max_clk_div);
unsigned long (*get_max_tx_rate)(void);
int (*convert_timings) (struct extif_timings *timings);
void (*set_timings) (const struct extif_timings *timings);
void (*set_bits_per_cycle)(int bpc);
void (*write_command) (const void *buf, unsigned int len);
void (*read_data) (void *buf, unsigned int len);
void (*write_data) (const void *buf, unsigned int len);
void (*transfer_area) (int width, int height,
void (callback)(void *data), void *data);
int (*setup_tearsync) (unsigned pin_cnt,
unsigned hs_pulse_time, unsigned vs_pulse_time,
int hs_pol_inv, int vs_pol_inv, int div);
int (*enable_tearsync) (int enable, unsigned line);
unsigned long max_transmit_size;
};
struct omapfb_notifier_block {
struct notifier_block nb;
void *data;
int plane_idx;
};
typedef int (*omapfb_notifier_callback_t)(struct notifier_block *,
unsigned long event,
void *fbi);
struct lcd_ctrl {
const char *name;
void *data;
int (*init) (struct omapfb_device *fbdev,
int ext_mode,
struct omapfb_mem_desc *req_md);
void (*cleanup) (void);
void (*bind_client) (struct omapfb_notifier_block *nb);
void (*get_caps) (int plane, struct omapfb_caps *caps);
int (*set_update_mode)(enum omapfb_update_mode mode);
enum omapfb_update_mode (*get_update_mode)(void);
int (*setup_plane) (int plane, int channel_out,
unsigned long offset,
int screen_width,
int pos_x, int pos_y, int width,
int height, int color_mode);
int (*set_rotate) (int angle);
int (*setup_mem) (int plane, size_t size,
int mem_type, unsigned long *paddr);
int (*mmap) (struct fb_info *info,
struct vm_area_struct *vma);
int (*set_scale) (int plane,
int orig_width, int orig_height,
int out_width, int out_height);
int (*enable_plane) (int plane, int enable);
int (*update_window) (struct fb_info *fbi,
struct omapfb_update_window *win,
void (*callback)(void *),
void *callback_data);
void (*sync) (void);
void (*suspend) (void);
void (*resume) (void);
int (*run_test) (int test_num);
int (*setcolreg) (u_int regno, u16 red, u16 green,
u16 blue, u16 transp,
int update_hw_mem);
int (*set_color_key) (struct omapfb_color_key *ck);
int (*get_color_key) (struct omapfb_color_key *ck);
};
enum omapfb_state {
OMAPFB_DISABLED = 0,
OMAPFB_SUSPENDED = 99,
OMAPFB_ACTIVE = 100
};
struct omapfb_plane_struct {
int idx;
struct omapfb_plane_info info;
enum omapfb_color_format color_mode;
struct omapfb_device *fbdev;
};
struct omapfb_device {
int state;
int ext_lcdc; /* Using external
LCD controller */
struct mutex rqueue_mutex;
int palette_size;
u32 pseudo_palette[17];
struct lcd_panel *panel; /* LCD panel */
const struct lcd_ctrl *ctrl; /* LCD controller */
const struct lcd_ctrl *int_ctrl; /* internal LCD ctrl */
int ext_irq;
int int_irq;
struct lcd_ctrl_extif *ext_if; /* LCD ctrl external
interface */
struct device *dev;
struct fb_var_screeninfo new_var; /* for mode changes */
struct omapfb_mem_desc mem_desc;
struct fb_info *fb_info[OMAPFB_PLANE_NUM];
struct platform_device *dssdev; /* dummy dev for clocks */
};
extern struct lcd_ctrl omap1_lcd_ctrl;
extern void omapfb_register_panel(struct lcd_panel *panel);
extern void omapfb_write_first_pixel(struct omapfb_device *fbdev, u16 pixval);
extern void omapfb_notify_clients(struct omapfb_device *fbdev,
unsigned long event);
extern int omapfb_register_client(struct omapfb_notifier_block *nb,
omapfb_notifier_callback_t callback,
void *callback_data);
extern int omapfb_unregister_client(struct omapfb_notifier_block *nb);
#endif /* __OMAPFB_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,683 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* OMAP1 Special OptimiSed Screen Interface support
*
* Copyright (C) 2004-2005 Nokia Corporation
* Author: Juha Yrjölä <juha.yrjola@nokia.com>
*/
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/clk.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/omap-dma.h>
#include <linux/soc/ti/omap1-io.h>
#include "omapfb.h"
#include "lcd_dma.h"
#include "lcdc.h"
#define MODULE_NAME "omapfb-sossi"
#define OMAP_SOSSI_BASE 0xfffbac00
#define SOSSI_ID_REG 0x00
#define SOSSI_INIT1_REG 0x04
#define SOSSI_INIT2_REG 0x08
#define SOSSI_INIT3_REG 0x0c
#define SOSSI_FIFO_REG 0x10
#define SOSSI_REOTABLE_REG 0x14
#define SOSSI_TEARING_REG 0x18
#define SOSSI_INIT1B_REG 0x1c
#define SOSSI_FIFOB_REG 0x20
#define DMA_GSCR 0xfffedc04
#define DMA_LCD_CCR 0xfffee3c2
#define DMA_LCD_CTRL 0xfffee3c4
#define DMA_LCD_LCH_CTRL 0xfffee3ea
#define CONF_SOSSI_RESET_R (1 << 23)
#define RD_ACCESS 0
#define WR_ACCESS 1
#define SOSSI_MAX_XMIT_BYTES (512 * 1024)
static struct {
void __iomem *base;
struct clk *fck;
unsigned long fck_hz;
spinlock_t lock;
int bus_pick_count;
int bus_pick_width;
int tearsync_mode;
int tearsync_line;
void (*lcdc_callback)(void *data);
void *lcdc_callback_data;
int vsync_dma_pending;
/* timing for read and write access */
int clk_div;
u8 clk_tw0[2];
u8 clk_tw1[2];
/*
* if last_access is the same as current we don't have to change
* the timings
*/
int last_access;
struct omapfb_device *fbdev;
} sossi;
static inline u32 sossi_read_reg(int reg)
{
return readl(sossi.base + reg);
}
static inline u16 sossi_read_reg16(int reg)
{
return readw(sossi.base + reg);
}
static inline u8 sossi_read_reg8(int reg)
{
return readb(sossi.base + reg);
}
static inline void sossi_write_reg(int reg, u32 value)
{
writel(value, sossi.base + reg);
}
static inline void sossi_write_reg16(int reg, u16 value)
{
writew(value, sossi.base + reg);
}
static inline void sossi_write_reg8(int reg, u8 value)
{
writeb(value, sossi.base + reg);
}
static void sossi_set_bits(int reg, u32 bits)
{
sossi_write_reg(reg, sossi_read_reg(reg) | bits);
}
static void sossi_clear_bits(int reg, u32 bits)
{
sossi_write_reg(reg, sossi_read_reg(reg) & ~bits);
}
#define HZ_TO_PS(x) (1000000000 / (x / 1000))
static u32 ps_to_sossi_ticks(u32 ps, int div)
{
u32 clk_period = HZ_TO_PS(sossi.fck_hz) * div;
return (clk_period + ps - 1) / clk_period;
}
static int calc_rd_timings(struct extif_timings *t)
{
u32 tw0, tw1;
int reon, reoff, recyc, actim;
int div = t->clk_div;
/*
* Make sure that after conversion it still holds that:
* reoff > reon, recyc >= reoff, actim > reon
*/
reon = ps_to_sossi_ticks(t->re_on_time, div);
/* reon will be exactly one sossi tick */
if (reon > 1)
return -1;
reoff = ps_to_sossi_ticks(t->re_off_time, div);
if (reoff <= reon)
reoff = reon + 1;
tw0 = reoff - reon;
if (tw0 > 0x10)
return -1;
recyc = ps_to_sossi_ticks(t->re_cycle_time, div);
if (recyc <= reoff)
recyc = reoff + 1;
tw1 = recyc - tw0;
/* values less then 3 result in the SOSSI block resetting itself */
if (tw1 < 3)
tw1 = 3;
if (tw1 > 0x40)
return -1;
actim = ps_to_sossi_ticks(t->access_time, div);
if (actim < reoff)
actim++;
/*
* access time (data hold time) will be exactly one sossi
* tick
*/
if (actim - reoff > 1)
return -1;
t->tim[0] = tw0 - 1;
t->tim[1] = tw1 - 1;
return 0;
}
static int calc_wr_timings(struct extif_timings *t)
{
u32 tw0, tw1;
int weon, weoff, wecyc;
int div = t->clk_div;
/*
* Make sure that after conversion it still holds that:
* weoff > weon, wecyc >= weoff
*/
weon = ps_to_sossi_ticks(t->we_on_time, div);
/* weon will be exactly one sossi tick */
if (weon > 1)
return -1;
weoff = ps_to_sossi_ticks(t->we_off_time, div);
if (weoff <= weon)
weoff = weon + 1;
tw0 = weoff - weon;
if (tw0 > 0x10)
return -1;
wecyc = ps_to_sossi_ticks(t->we_cycle_time, div);
if (wecyc <= weoff)
wecyc = weoff + 1;
tw1 = wecyc - tw0;
/* values less then 3 result in the SOSSI block resetting itself */
if (tw1 < 3)
tw1 = 3;
if (tw1 > 0x40)
return -1;
t->tim[2] = tw0 - 1;
t->tim[3] = tw1 - 1;
return 0;
}
static void _set_timing(int div, int tw0, int tw1)
{
u32 l;
#ifdef VERBOSE
dev_dbg(sossi.fbdev->dev, "Using TW0 = %d, TW1 = %d, div = %d\n",
tw0 + 1, tw1 + 1, div);
#endif
clk_set_rate(sossi.fck, sossi.fck_hz / div);
clk_enable(sossi.fck);
l = sossi_read_reg(SOSSI_INIT1_REG);
l &= ~((0x0f << 20) | (0x3f << 24));
l |= (tw0 << 20) | (tw1 << 24);
sossi_write_reg(SOSSI_INIT1_REG, l);
clk_disable(sossi.fck);
}
static void _set_bits_per_cycle(int bus_pick_count, int bus_pick_width)
{
u32 l;
l = sossi_read_reg(SOSSI_INIT3_REG);
l &= ~0x3ff;
l |= ((bus_pick_count - 1) << 5) | ((bus_pick_width - 1) & 0x1f);
sossi_write_reg(SOSSI_INIT3_REG, l);
}
static void _set_tearsync_mode(int mode, unsigned line)
{
u32 l;
l = sossi_read_reg(SOSSI_TEARING_REG);
l &= ~(((1 << 11) - 1) << 15);
l |= line << 15;
l &= ~(0x3 << 26);
l |= mode << 26;
sossi_write_reg(SOSSI_TEARING_REG, l);
if (mode)
sossi_set_bits(SOSSI_INIT2_REG, 1 << 6); /* TE logic */
else
sossi_clear_bits(SOSSI_INIT2_REG, 1 << 6);
}
static inline void set_timing(int access)
{
if (access != sossi.last_access) {
sossi.last_access = access;
_set_timing(sossi.clk_div,
sossi.clk_tw0[access], sossi.clk_tw1[access]);
}
}
static void sossi_start_transfer(void)
{
/* WE */
sossi_clear_bits(SOSSI_INIT2_REG, 1 << 4);
/* CS active low */
sossi_clear_bits(SOSSI_INIT1_REG, 1 << 30);
}
static void sossi_stop_transfer(void)
{
/* WE */
sossi_set_bits(SOSSI_INIT2_REG, 1 << 4);
/* CS active low */
sossi_set_bits(SOSSI_INIT1_REG, 1 << 30);
}
static void wait_end_of_write(void)
{
/* Before reading we must check if some writings are going on */
while (!(sossi_read_reg(SOSSI_INIT2_REG) & (1 << 3)));
}
static void send_data(const void *data, unsigned int len)
{
while (len >= 4) {
sossi_write_reg(SOSSI_FIFO_REG, *(const u32 *) data);
len -= 4;
data += 4;
}
while (len >= 2) {
sossi_write_reg16(SOSSI_FIFO_REG, *(const u16 *) data);
len -= 2;
data += 2;
}
while (len) {
sossi_write_reg8(SOSSI_FIFO_REG, *(const u8 *) data);
len--;
data++;
}
}
static void set_cycles(unsigned int len)
{
unsigned long nr_cycles = len / (sossi.bus_pick_width / 8);
BUG_ON((nr_cycles - 1) & ~0x3ffff);
sossi_clear_bits(SOSSI_INIT1_REG, 0x3ffff);
sossi_set_bits(SOSSI_INIT1_REG, (nr_cycles - 1) & 0x3ffff);
}
static int sossi_convert_timings(struct extif_timings *t)
{
int r = 0;
int div = t->clk_div;
t->converted = 0;
if (div <= 0 || div > 8)
return -1;
/* no CS on SOSSI, so ignore cson, csoff, cs_pulsewidth */
if ((r = calc_rd_timings(t)) < 0)
return r;
if ((r = calc_wr_timings(t)) < 0)
return r;
t->tim[4] = div;
t->converted = 1;
return 0;
}
static void sossi_set_timings(const struct extif_timings *t)
{
BUG_ON(!t->converted);
sossi.clk_tw0[RD_ACCESS] = t->tim[0];
sossi.clk_tw1[RD_ACCESS] = t->tim[1];
sossi.clk_tw0[WR_ACCESS] = t->tim[2];
sossi.clk_tw1[WR_ACCESS] = t->tim[3];
sossi.clk_div = t->tim[4];
}
static void sossi_get_clk_info(u32 *clk_period, u32 *max_clk_div)
{
*clk_period = HZ_TO_PS(sossi.fck_hz);
*max_clk_div = 8;
}
static void sossi_set_bits_per_cycle(int bpc)
{
int bus_pick_count, bus_pick_width;
/*
* We set explicitly the bus_pick_count as well, although
* with remapping/reordering disabled it will be calculated by HW
* as (32 / bus_pick_width).
*/
switch (bpc) {
case 8:
bus_pick_count = 4;
bus_pick_width = 8;
break;
case 16:
bus_pick_count = 2;
bus_pick_width = 16;
break;
default:
BUG();
return;
}
sossi.bus_pick_width = bus_pick_width;
sossi.bus_pick_count = bus_pick_count;
}
static int sossi_setup_tearsync(unsigned pin_cnt,
unsigned hs_pulse_time, unsigned vs_pulse_time,
int hs_pol_inv, int vs_pol_inv, int div)
{
int hs, vs;
u32 l;
if (pin_cnt != 1 || div < 1 || div > 8)
return -EINVAL;
hs = ps_to_sossi_ticks(hs_pulse_time, div);
vs = ps_to_sossi_ticks(vs_pulse_time, div);
if (vs < 8 || vs <= hs || vs >= (1 << 12))
return -EDOM;
vs /= 8;
vs--;
if (hs > 8)
hs = 8;
if (hs)
hs--;
dev_dbg(sossi.fbdev->dev,
"setup_tearsync: hs %d vs %d hs_inv %d vs_inv %d\n",
hs, vs, hs_pol_inv, vs_pol_inv);
clk_enable(sossi.fck);
l = sossi_read_reg(SOSSI_TEARING_REG);
l &= ~((1 << 15) - 1);
l |= vs << 3;
l |= hs;
if (hs_pol_inv)
l |= 1 << 29;
else
l &= ~(1 << 29);
if (vs_pol_inv)
l |= 1 << 28;
else
l &= ~(1 << 28);
sossi_write_reg(SOSSI_TEARING_REG, l);
clk_disable(sossi.fck);
return 0;
}
static int sossi_enable_tearsync(int enable, unsigned line)
{
int mode;
dev_dbg(sossi.fbdev->dev, "tearsync %d line %d\n", enable, line);
if (line >= 1 << 11)
return -EINVAL;
if (enable) {
if (line)
mode = 2; /* HS or VS */
else
mode = 3; /* VS only */
} else
mode = 0;
sossi.tearsync_line = line;
sossi.tearsync_mode = mode;
return 0;
}
static void sossi_write_command(const void *data, unsigned int len)
{
clk_enable(sossi.fck);
set_timing(WR_ACCESS);
_set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
/* CMD#/DATA */
sossi_clear_bits(SOSSI_INIT1_REG, 1 << 18);
set_cycles(len);
sossi_start_transfer();
send_data(data, len);
sossi_stop_transfer();
wait_end_of_write();
clk_disable(sossi.fck);
}
static void sossi_write_data(const void *data, unsigned int len)
{
clk_enable(sossi.fck);
set_timing(WR_ACCESS);
_set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
/* CMD#/DATA */
sossi_set_bits(SOSSI_INIT1_REG, 1 << 18);
set_cycles(len);
sossi_start_transfer();
send_data(data, len);
sossi_stop_transfer();
wait_end_of_write();
clk_disable(sossi.fck);
}
static void sossi_transfer_area(int width, int height,
void (callback)(void *data), void *data)
{
BUG_ON(callback == NULL);
sossi.lcdc_callback = callback;
sossi.lcdc_callback_data = data;
clk_enable(sossi.fck);
set_timing(WR_ACCESS);
_set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
_set_tearsync_mode(sossi.tearsync_mode, sossi.tearsync_line);
/* CMD#/DATA */
sossi_set_bits(SOSSI_INIT1_REG, 1 << 18);
set_cycles(width * height * sossi.bus_pick_width / 8);
sossi_start_transfer();
if (sossi.tearsync_mode) {
/*
* Wait for the sync signal and start the transfer only
* then. We can't seem to be able to use HW sync DMA for
* this since LCD DMA shows huge latencies, as if it
* would ignore some of the DMA requests from SoSSI.
*/
unsigned long flags;
spin_lock_irqsave(&sossi.lock, flags);
sossi.vsync_dma_pending++;
spin_unlock_irqrestore(&sossi.lock, flags);
} else
/* Just start the transfer right away. */
omap_enable_lcd_dma();
}
static void sossi_dma_callback(void *data)
{
omap_stop_lcd_dma();
sossi_stop_transfer();
clk_disable(sossi.fck);
sossi.lcdc_callback(sossi.lcdc_callback_data);
}
static void sossi_read_data(void *data, unsigned int len)
{
clk_enable(sossi.fck);
set_timing(RD_ACCESS);
_set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
/* CMD#/DATA */
sossi_set_bits(SOSSI_INIT1_REG, 1 << 18);
set_cycles(len);
sossi_start_transfer();
while (len >= 4) {
*(u32 *) data = sossi_read_reg(SOSSI_FIFO_REG);
len -= 4;
data += 4;
}
while (len >= 2) {
*(u16 *) data = sossi_read_reg16(SOSSI_FIFO_REG);
len -= 2;
data += 2;
}
while (len) {
*(u8 *) data = sossi_read_reg8(SOSSI_FIFO_REG);
len--;
data++;
}
sossi_stop_transfer();
clk_disable(sossi.fck);
}
static irqreturn_t sossi_match_irq(int irq, void *data)
{
unsigned long flags;
spin_lock_irqsave(&sossi.lock, flags);
if (sossi.vsync_dma_pending) {
sossi.vsync_dma_pending--;
omap_enable_lcd_dma();
}
spin_unlock_irqrestore(&sossi.lock, flags);
return IRQ_HANDLED;
}
static int sossi_init(struct omapfb_device *fbdev)
{
u32 l, k;
struct clk *fck;
struct clk *dpll1out_ck;
int r;
sossi.base = ioremap(OMAP_SOSSI_BASE, SZ_1K);
if (!sossi.base) {
dev_err(fbdev->dev, "can't ioremap SoSSI\n");
return -ENOMEM;
}
sossi.fbdev = fbdev;
spin_lock_init(&sossi.lock);
dpll1out_ck = clk_get(fbdev->dev, "ck_dpll1out");
if (IS_ERR(dpll1out_ck)) {
dev_err(fbdev->dev, "can't get DPLL1OUT clock\n");
return PTR_ERR(dpll1out_ck);
}
/*
* We need the parent clock rate, which we might divide further
* depending on the timing requirements of the controller. See
* _set_timings.
*/
sossi.fck_hz = clk_get_rate(dpll1out_ck);
clk_put(dpll1out_ck);
fck = clk_get(fbdev->dev, "ck_sossi");
if (IS_ERR(fck)) {
dev_err(fbdev->dev, "can't get SoSSI functional clock\n");
return PTR_ERR(fck);
}
sossi.fck = fck;
/* Reset and enable the SoSSI module */
l = omap_readl(MOD_CONF_CTRL_1);
l |= CONF_SOSSI_RESET_R;
omap_writel(l, MOD_CONF_CTRL_1);
l &= ~CONF_SOSSI_RESET_R;
omap_writel(l, MOD_CONF_CTRL_1);
clk_prepare_enable(sossi.fck);
l = omap_readl(ARM_IDLECT2);
l &= ~(1 << 8); /* DMACK_REQ */
omap_writel(l, ARM_IDLECT2);
l = sossi_read_reg(SOSSI_INIT2_REG);
/* Enable and reset the SoSSI block */
l |= (1 << 0) | (1 << 1);
sossi_write_reg(SOSSI_INIT2_REG, l);
/* Take SoSSI out of reset */
l &= ~(1 << 1);
sossi_write_reg(SOSSI_INIT2_REG, l);
sossi_write_reg(SOSSI_ID_REG, 0);
l = sossi_read_reg(SOSSI_ID_REG);
k = sossi_read_reg(SOSSI_ID_REG);
if (l != 0x55555555 || k != 0xaaaaaaaa) {
dev_err(fbdev->dev,
"invalid SoSSI sync pattern: %08x, %08x\n", l, k);
r = -ENODEV;
goto err;
}
if ((r = omap_lcdc_set_dma_callback(sossi_dma_callback, NULL)) < 0) {
dev_err(fbdev->dev, "can't get LCDC IRQ\n");
r = -ENODEV;
goto err;
}
l = sossi_read_reg(SOSSI_ID_REG); /* Component code */
l = sossi_read_reg(SOSSI_ID_REG);
dev_info(fbdev->dev, "SoSSI version %d.%d initialized\n",
l >> 16, l & 0xffff);
l = sossi_read_reg(SOSSI_INIT1_REG);
l |= (1 << 19); /* DMA_MODE */
l &= ~(1 << 31); /* REORDERING */
sossi_write_reg(SOSSI_INIT1_REG, l);
if ((r = request_irq(fbdev->ext_irq, sossi_match_irq,
IRQ_TYPE_EDGE_FALLING,
"sossi_match", sossi.fbdev->dev)) < 0) {
dev_err(sossi.fbdev->dev, "can't get SoSSI match IRQ\n");
goto err;
}
clk_disable(sossi.fck);
return 0;
err:
clk_disable_unprepare(sossi.fck);
clk_put(sossi.fck);
return r;
}
static void sossi_cleanup(void)
{
omap_lcdc_free_dma_callback();
clk_unprepare(sossi.fck);
clk_put(sossi.fck);
iounmap(sossi.base);
}
struct lcd_ctrl_extif omap1_ext_if = {
.init = sossi_init,
.cleanup = sossi_cleanup,
.get_clk_info = sossi_get_clk_info,
.convert_timings = sossi_convert_timings,
.set_timings = sossi_set_timings,
.set_bits_per_cycle = sossi_set_bits_per_cycle,
.setup_tearsync = sossi_setup_tearsync,
.enable_tearsync = sossi_enable_tearsync,
.write_command = sossi_write_command,
.read_data = sossi_read_data,
.write_data = sossi_write_data,
.transfer_area = sossi_transfer_area,
.max_transmit_size = SOSSI_MAX_XMIT_BYTES,
};