forked from Minki/linux
0a6824bc10
Multiple pinctrl states are defined for 8, 16 and 24 data pin groups in PPI peripheral. The driver should select correct group before set up further PPI parameters. Signed-off-by: Sonic Zhang <sonic.zhang@analog.com> Acked-by: Scott Jiang <scott.jiang.linux@gmail.com> Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>
365 lines
8.8 KiB
C
365 lines
8.8 KiB
C
/*
|
|
* ppi.c Analog Devices Parallel Peripheral Interface driver
|
|
*
|
|
* Copyright (c) 2011 Analog Devices Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <asm/bfin_ppi.h>
|
|
#include <asm/blackfin.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/dma.h>
|
|
#include <asm/portmux.h>
|
|
|
|
#include <media/blackfin/ppi.h>
|
|
|
|
static int ppi_attach_irq(struct ppi_if *ppi, irq_handler_t handler);
|
|
static void ppi_detach_irq(struct ppi_if *ppi);
|
|
static int ppi_start(struct ppi_if *ppi);
|
|
static int ppi_stop(struct ppi_if *ppi);
|
|
static int ppi_set_params(struct ppi_if *ppi, struct ppi_params *params);
|
|
static void ppi_update_addr(struct ppi_if *ppi, unsigned long addr);
|
|
|
|
static const struct ppi_ops ppi_ops = {
|
|
.attach_irq = ppi_attach_irq,
|
|
.detach_irq = ppi_detach_irq,
|
|
.start = ppi_start,
|
|
.stop = ppi_stop,
|
|
.set_params = ppi_set_params,
|
|
.update_addr = ppi_update_addr,
|
|
};
|
|
|
|
static irqreturn_t ppi_irq_err(int irq, void *dev_id)
|
|
{
|
|
struct ppi_if *ppi = dev_id;
|
|
const struct ppi_info *info = ppi->info;
|
|
|
|
switch (info->type) {
|
|
case PPI_TYPE_PPI:
|
|
{
|
|
struct bfin_ppi_regs *reg = info->base;
|
|
unsigned short status;
|
|
|
|
/* register on bf561 is cleared when read
|
|
* others are W1C
|
|
*/
|
|
status = bfin_read16(®->status);
|
|
if (status & 0x3000)
|
|
ppi->err = true;
|
|
bfin_write16(®->status, 0xff00);
|
|
break;
|
|
}
|
|
case PPI_TYPE_EPPI:
|
|
{
|
|
struct bfin_eppi_regs *reg = info->base;
|
|
unsigned short status;
|
|
|
|
status = bfin_read16(®->status);
|
|
if (status & 0x2)
|
|
ppi->err = true;
|
|
bfin_write16(®->status, 0xffff);
|
|
break;
|
|
}
|
|
case PPI_TYPE_EPPI3:
|
|
{
|
|
struct bfin_eppi3_regs *reg = info->base;
|
|
unsigned long stat;
|
|
|
|
stat = bfin_read32(®->stat);
|
|
if (stat & 0x2)
|
|
ppi->err = true;
|
|
bfin_write32(®->stat, 0xc0ff);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int ppi_attach_irq(struct ppi_if *ppi, irq_handler_t handler)
|
|
{
|
|
const struct ppi_info *info = ppi->info;
|
|
int ret;
|
|
|
|
ret = request_dma(info->dma_ch, "PPI_DMA");
|
|
|
|
if (ret) {
|
|
pr_err("Unable to allocate DMA channel for PPI\n");
|
|
return ret;
|
|
}
|
|
set_dma_callback(info->dma_ch, handler, ppi);
|
|
|
|
if (ppi->err_int) {
|
|
ret = request_irq(info->irq_err, ppi_irq_err, 0, "PPI ERROR", ppi);
|
|
if (ret) {
|
|
pr_err("Unable to allocate IRQ for PPI\n");
|
|
free_dma(info->dma_ch);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void ppi_detach_irq(struct ppi_if *ppi)
|
|
{
|
|
const struct ppi_info *info = ppi->info;
|
|
|
|
if (ppi->err_int)
|
|
free_irq(info->irq_err, ppi);
|
|
free_dma(info->dma_ch);
|
|
}
|
|
|
|
static int ppi_start(struct ppi_if *ppi)
|
|
{
|
|
const struct ppi_info *info = ppi->info;
|
|
|
|
/* enable DMA */
|
|
enable_dma(info->dma_ch);
|
|
|
|
/* enable PPI */
|
|
ppi->ppi_control |= PORT_EN;
|
|
switch (info->type) {
|
|
case PPI_TYPE_PPI:
|
|
{
|
|
struct bfin_ppi_regs *reg = info->base;
|
|
bfin_write16(®->control, ppi->ppi_control);
|
|
break;
|
|
}
|
|
case PPI_TYPE_EPPI:
|
|
{
|
|
struct bfin_eppi_regs *reg = info->base;
|
|
bfin_write32(®->control, ppi->ppi_control);
|
|
break;
|
|
}
|
|
case PPI_TYPE_EPPI3:
|
|
{
|
|
struct bfin_eppi3_regs *reg = info->base;
|
|
bfin_write32(®->ctl, ppi->ppi_control);
|
|
break;
|
|
}
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
SSYNC();
|
|
return 0;
|
|
}
|
|
|
|
static int ppi_stop(struct ppi_if *ppi)
|
|
{
|
|
const struct ppi_info *info = ppi->info;
|
|
|
|
/* disable PPI */
|
|
ppi->ppi_control &= ~PORT_EN;
|
|
switch (info->type) {
|
|
case PPI_TYPE_PPI:
|
|
{
|
|
struct bfin_ppi_regs *reg = info->base;
|
|
bfin_write16(®->control, ppi->ppi_control);
|
|
break;
|
|
}
|
|
case PPI_TYPE_EPPI:
|
|
{
|
|
struct bfin_eppi_regs *reg = info->base;
|
|
bfin_write32(®->control, ppi->ppi_control);
|
|
break;
|
|
}
|
|
case PPI_TYPE_EPPI3:
|
|
{
|
|
struct bfin_eppi3_regs *reg = info->base;
|
|
bfin_write32(®->ctl, ppi->ppi_control);
|
|
break;
|
|
}
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* disable DMA */
|
|
clear_dma_irqstat(info->dma_ch);
|
|
disable_dma(info->dma_ch);
|
|
|
|
SSYNC();
|
|
return 0;
|
|
}
|
|
|
|
static int ppi_set_params(struct ppi_if *ppi, struct ppi_params *params)
|
|
{
|
|
const struct ppi_info *info = ppi->info;
|
|
int dma32 = 0;
|
|
int dma_config, bytes_per_line;
|
|
int hcount, hdelay, samples_per_line;
|
|
|
|
#ifdef CONFIG_PINCTRL
|
|
static const char * const pin_state[] = {"8bit", "16bit", "24bit"};
|
|
struct pinctrl *pctrl;
|
|
struct pinctrl_state *pstate;
|
|
|
|
if (params->dlen > 24 || params->dlen <= 0)
|
|
return -EINVAL;
|
|
pctrl = devm_pinctrl_get(ppi->dev);
|
|
pstate = pinctrl_lookup_state(pctrl,
|
|
pin_state[(params->dlen + 7) / 8 - 1]);
|
|
if (pinctrl_select_state(pctrl, pstate))
|
|
return -EINVAL;
|
|
#endif
|
|
|
|
bytes_per_line = params->width * params->bpp / 8;
|
|
/* convert parameters unit from pixels to samples */
|
|
hcount = params->width * params->bpp / params->dlen;
|
|
hdelay = params->hdelay * params->bpp / params->dlen;
|
|
samples_per_line = params->line * params->bpp / params->dlen;
|
|
if (params->int_mask == 0xFFFFFFFF)
|
|
ppi->err_int = false;
|
|
else
|
|
ppi->err_int = true;
|
|
|
|
dma_config = (DMA_FLOW_STOP | RESTART | DMA2D | DI_EN_Y);
|
|
ppi->ppi_control = params->ppi_control & ~PORT_EN;
|
|
if (!(ppi->ppi_control & PORT_DIR))
|
|
dma_config |= WNR;
|
|
switch (info->type) {
|
|
case PPI_TYPE_PPI:
|
|
{
|
|
struct bfin_ppi_regs *reg = info->base;
|
|
|
|
if (params->ppi_control & DMA32)
|
|
dma32 = 1;
|
|
|
|
bfin_write16(®->control, ppi->ppi_control);
|
|
bfin_write16(®->count, samples_per_line - 1);
|
|
bfin_write16(®->frame, params->frame);
|
|
break;
|
|
}
|
|
case PPI_TYPE_EPPI:
|
|
{
|
|
struct bfin_eppi_regs *reg = info->base;
|
|
|
|
if ((params->ppi_control & PACK_EN)
|
|
|| (params->ppi_control & 0x38000) > DLEN_16)
|
|
dma32 = 1;
|
|
|
|
bfin_write32(®->control, ppi->ppi_control);
|
|
bfin_write16(®->line, samples_per_line);
|
|
bfin_write16(®->frame, params->frame);
|
|
bfin_write16(®->hdelay, hdelay);
|
|
bfin_write16(®->vdelay, params->vdelay);
|
|
bfin_write16(®->hcount, hcount);
|
|
bfin_write16(®->vcount, params->height);
|
|
break;
|
|
}
|
|
case PPI_TYPE_EPPI3:
|
|
{
|
|
struct bfin_eppi3_regs *reg = info->base;
|
|
|
|
if ((params->ppi_control & PACK_EN)
|
|
|| (params->ppi_control & 0x70000) > DLEN_16)
|
|
dma32 = 1;
|
|
|
|
bfin_write32(®->ctl, ppi->ppi_control);
|
|
bfin_write32(®->line, samples_per_line);
|
|
bfin_write32(®->frame, params->frame);
|
|
bfin_write32(®->hdly, hdelay);
|
|
bfin_write32(®->vdly, params->vdelay);
|
|
bfin_write32(®->hcnt, hcount);
|
|
bfin_write32(®->vcnt, params->height);
|
|
if (params->int_mask)
|
|
bfin_write32(®->imsk, params->int_mask & 0xFF);
|
|
if (ppi->ppi_control & PORT_DIR) {
|
|
u32 hsync_width, vsync_width, vsync_period;
|
|
|
|
hsync_width = params->hsync
|
|
* params->bpp / params->dlen;
|
|
vsync_width = params->vsync * samples_per_line;
|
|
vsync_period = samples_per_line * params->frame;
|
|
bfin_write32(®->fs1_wlhb, hsync_width);
|
|
bfin_write32(®->fs1_paspl, samples_per_line);
|
|
bfin_write32(®->fs2_wlvb, vsync_width);
|
|
bfin_write32(®->fs2_palpf, vsync_period);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dma32) {
|
|
dma_config |= WDSIZE_32 | PSIZE_32;
|
|
set_dma_x_count(info->dma_ch, bytes_per_line >> 2);
|
|
set_dma_x_modify(info->dma_ch, 4);
|
|
set_dma_y_modify(info->dma_ch, 4);
|
|
} else {
|
|
dma_config |= WDSIZE_16 | PSIZE_16;
|
|
set_dma_x_count(info->dma_ch, bytes_per_line >> 1);
|
|
set_dma_x_modify(info->dma_ch, 2);
|
|
set_dma_y_modify(info->dma_ch, 2);
|
|
}
|
|
set_dma_y_count(info->dma_ch, params->height);
|
|
set_dma_config(info->dma_ch, dma_config);
|
|
|
|
SSYNC();
|
|
return 0;
|
|
}
|
|
|
|
static void ppi_update_addr(struct ppi_if *ppi, unsigned long addr)
|
|
{
|
|
set_dma_start_addr(ppi->info->dma_ch, addr);
|
|
}
|
|
|
|
struct ppi_if *ppi_create_instance(struct platform_device *pdev,
|
|
const struct ppi_info *info)
|
|
{
|
|
struct ppi_if *ppi;
|
|
|
|
if (!info || !info->pin_req)
|
|
return NULL;
|
|
|
|
#ifndef CONFIG_PINCTRL
|
|
if (peripheral_request_list(info->pin_req, KBUILD_MODNAME)) {
|
|
dev_err(&pdev->dev, "request peripheral failed\n");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
ppi = kzalloc(sizeof(*ppi), GFP_KERNEL);
|
|
if (!ppi) {
|
|
peripheral_free_list(info->pin_req);
|
|
dev_err(&pdev->dev, "unable to allocate memory for ppi handle\n");
|
|
return NULL;
|
|
}
|
|
ppi->ops = &ppi_ops;
|
|
ppi->info = info;
|
|
ppi->dev = &pdev->dev;
|
|
|
|
pr_info("ppi probe success\n");
|
|
return ppi;
|
|
}
|
|
EXPORT_SYMBOL(ppi_create_instance);
|
|
|
|
void ppi_delete_instance(struct ppi_if *ppi)
|
|
{
|
|
peripheral_free_list(ppi->info->pin_req);
|
|
kfree(ppi);
|
|
}
|
|
EXPORT_SYMBOL(ppi_delete_instance);
|
|
|
|
MODULE_DESCRIPTION("Analog Devices PPI driver");
|
|
MODULE_AUTHOR("Scott Jiang <Scott.Jiang.Linux@gmail.com>");
|
|
MODULE_LICENSE("GPL v2");
|