1
0
mirror of git://projects.qi-hardware.com/openwrt-xburst.git synced 2025-04-21 12:27:27 +03:00

[ubicom32]: move new files out from platform support patch

git-svn-id: svn://svn.openwrt.org/openwrt/trunk@19815 3c298f89-4303-0410-b956-a3cf2f4a3e73
This commit is contained in:
kaloz
2010-02-22 13:54:47 +00:00
parent fdcca1ea95
commit 1a29ef8e97
279 changed files with 56440 additions and 57274 deletions

View File

@@ -0,0 +1,105 @@
/*
* drivers/net/ubi32-eth.c
* Ubicom32 hardware random number generator driver.
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from (with many thanks):
* arch/m68knommu
* arch/blackfin
* arch/parisc
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/hw_random.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/ip5000.h>
#define MODULE_NAME "ubicom32_rng"
static int ubicom32_rng_data_present(struct hwrng *rng, int wait)
{
int data, i;
for (i = 0; i < 20; i++) {
data = *(int *)(TIMER_BASE + TIMER_TRN);
if (data || !wait)
break;
udelay(10);
}
return data;
}
static int ubicom32_rng_data_read(struct hwrng *rng, u32 *data)
{
*data = *(int *)(TIMER_BASE + TIMER_TRN);
return 4;
}
static int ubicom32_rng_init(struct hwrng *rng)
{
printk(KERN_INFO "ubicom32 rng init\n");
*(int *)(TIMER_BASE + TIMER_TRN_CFG) = TIMER_TRN_CFG_ENABLE_OSC;
return 0;
}
static void ubicom32_rng_cleanup(struct hwrng *rng)
{
printk(KERN_INFO "ubicom32 rng cleanup\n");
*(int *)(TIMER_BASE + TIMER_TRN_CFG) = 0;
}
static struct hwrng ubicom32_rng = {
.name = MODULE_NAME,
.init = ubicom32_rng_init,
.cleanup = ubicom32_rng_cleanup,
.data_present = ubicom32_rng_data_present,
.data_read = ubicom32_rng_data_read,
.priv = 0,
};
static int __init mod_init(void)
{
int err;
printk(KERN_INFO "ubicom32 rng started\n");
err = hwrng_register(&ubicom32_rng);
if (err) {
printk(KERN_ERR "ubicom32 rng register failed (%d)\n",
err);
}
return err;
}
static void __exit mod_exit(void)
{
printk(KERN_INFO "ubicom32 rng stopped\n");
hwrng_unregister(&ubicom32_rng);
}
module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ubicom, Inc.");
MODULE_DESCRIPTION("H/W rng driver for ubicom32 processor");
MODULE_VERSION("1:1.0.a");

View File

@@ -0,0 +1,773 @@
/*
* drivers/mmc/host/ubicom32sd.c
* Ubicom32 Secure Digital Host Controller Interface driver
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/scatterlist.h>
#include <linux/leds.h>
#include <linux/gpio.h>
#include <linux/mmc/host.h>
#include <asm/ubicom32sd.h>
#define DRIVER_NAME "ubicom32sd"
#define sd_printk(...)
//#define sd_printk printk
#define SDTIO_VP_VERSION 3
#define SDTIO_MAX_SG_BLOCKS 16
enum sdtio_commands {
SDTIO_COMMAND_NOP,
SDTIO_COMMAND_SETUP,
SDTIO_COMMAND_SETUP_SDIO,
SDTIO_COMMAND_EXECUTE,
SDTIO_COMMAND_RESET,
};
#define SDTIO_COMMAND_SHIFT 24
#define SDTIO_COMMAND_FLAG_STOP_RSP_CRC (1 << 10)
#define SDTIO_COMMAND_FLAG_STOP_RSP_136 (1 << 9)
#define SDTIO_COMMAND_FLAG_STOP_RSP (1 << 8)
#define SDTIO_COMMAND_FLAG_STOP_CMD (1 << 7)
#define SDTIO_COMMAND_FLAG_DATA_STREAM (1 << 6)
#define SDTIO_COMMAND_FLAG_DATA_RD (1 << 5)
#define SDTIO_COMMAND_FLAG_DATA_WR (1 << 4)
#define SDTIO_COMMAND_FLAG_CMD_RSP_CRC (1 << 3)
#define SDTIO_COMMAND_FLAG_CMD_RSP_136 (1 << 2)
#define SDTIO_COMMAND_FLAG_CMD_RSP (1 << 1)
#define SDTIO_COMMAND_FLAG_CMD (1 << 0)
/*
* SDTIO_COMMAND_SETUP_SDIO
*/
#define SDTIO_COMMAND_FLAG_SDIO_INT_EN (1 << 0)
/*
* SDTIO_COMMAND_SETUP
* clock speed in arg
*/
#define SDTIO_COMMAND_FLAG_4BIT (1 << 3)
#define SDTIO_COMMAND_FLAG_1BIT (1 << 2)
#define SDTIO_COMMAND_FLAG_SET_CLOCK (1 << 1)
#define SDTIO_COMMAND_FLAG_SET_WIDTH (1 << 0)
#define SDTIO_COMMAND_FLAG_CMD_RSP_MASK (SDTIO_COMMAND_FLAG_CMD_RSP | SDTIO_COMMAND_FLAG_CMD_RSP_136)
#define SDTIO_COMMAND_FLAG_STOP_RSP_MASK (SDTIO_COMMAND_FLAG_STOP_RSP | SDTIO_COMMAND_FLAG_STOP_RSP_136)
#define SDTIO_COMMAND_FLAG_RSP_MASK (SDTIO_COMMAND_FLAG_CMD_RSP_MASK | SDTIO_COMMAND_FLAG_STOP_RSP_MASK)
struct sdtio_vp_sg {
volatile void *addr;
volatile u32_t len;
};
#define SDTIO_VP_INT_STATUS_DONE (1 << 31)
#define SDTIO_VP_INT_STATUS_SDIO_INT (1 << 10)
#define SDTIO_VP_INT_STATUS_DATA_CRC_ERR (1 << 9)
#define SDTIO_VP_INT_STATUS_DATA_PROG_ERR (1 << 8)
#define SDTIO_VP_INT_STATUS_DATA_TIMEOUT (1 << 7)
#define SDTIO_VP_INT_STATUS_STOP_RSP_CRC (1 << 6)
#define SDTIO_VP_INT_STATUS_STOP_RSP_TIMEOUT (1 << 5)
#define SDTIO_VP_INT_STATUS_CMD_RSP_CRC (1 << 4)
#define SDTIO_VP_INT_STATUS_CMD_RSP_TIMEOUT (1 << 3)
#define SDTIO_VP_INT_STATUS_CMD_TIMEOUT (1 << 2)
#define SDTIO_VP_INT_STATUS_CARD1_INSERT (1 << 1)
#define SDTIO_VP_INT_STATUS_CARD0_INSERT (1 << 0)
struct sdtio_vp_regs {
u32_t version;
u32_t f_max;
u32_t f_min;
volatile u32_t int_status;
volatile u32_t command;
volatile u32_t arg;
volatile u32_t cmd_opcode;
volatile u32_t cmd_arg;
volatile u32_t cmd_rsp0;
volatile u32_t cmd_rsp1;
volatile u32_t cmd_rsp2;
volatile u32_t cmd_rsp3;
volatile u32_t stop_opcode;
volatile u32_t stop_arg;
volatile u32_t stop_rsp0;
volatile u32_t stop_rsp1;
volatile u32_t stop_rsp2;
volatile u32_t stop_rsp3;
volatile u32_t data_timeout_ns;
volatile u16_t data_blksz;
volatile u16_t data_blkct;
volatile u32_t data_bytes_transferred;
volatile u32_t sg_len;
struct sdtio_vp_sg sg[SDTIO_MAX_SG_BLOCKS];
};
struct ubicom32sd_data {
const struct ubicom32sd_platform_data *pdata;
struct mmc_host *mmc;
/*
* Lock used to protect the data structure
spinlock_t lock;
*/
int int_en;
int int_pend;
/*
* Receive and transmit interrupts used for communicating
* with hardware
*/
int irq_tx;
int irq_rx;
/*
* Current outstanding mmc request
*/
struct mmc_request *mrq;
/*
* Hardware registers
*/
struct sdtio_vp_regs *regs;
};
/*****************************************************************************\
* *
* Suspend/resume *
* *
\*****************************************************************************/
#if 0//def CONFIG_PM
int ubicom32sd_suspend_host(struct ubicom32sd_host *host, pm_message_t state)
{
int ret;
ret = mmc_suspend_host(host->mmc, state);
if (ret)
return ret;
free_irq(host->irq, host);
return 0;
}
EXPORT_SYMBOL_GPL(ubicom32sd_suspend_host);
int ubicom32sd_resume_host(struct ubicom32sd_host *host)
{
int ret;
if (host->flags & UBICOM32SD_USE_DMA) {
if (host->ops->enable_dma)
host->ops->enable_dma(host);
}
ret = request_irq(host->irq, ubicom32sd_irq, IRQF_SHARED,
mmc_hostname(host->mmc), host);
if (ret)
return ret;
ubicom32sd_init(host);
mmiowb();
ret = mmc_resume_host(host->mmc);
if (ret)
return ret;
return 0;
}
EXPORT_SYMBOL_GPL(ubicom32sd_resume_host);
#endif /* CONFIG_PM */
/*
* ubicom32sd_send_command_sync
*/
static void ubicom32sd_send_command_sync(struct ubicom32sd_data *ud, u32_t command, u32_t arg)
{
ud->regs->command = command;
ud->regs->arg = arg;
ubicom32_set_interrupt(ud->irq_tx);
while (ud->regs->command) {
ndelay(100);
}
}
/*
* ubicom32sd_send_command
*/
static void ubicom32sd_send_command(struct ubicom32sd_data *ud, u32_t command, u32_t arg)
{
ud->regs->command = command;
ud->regs->arg = arg;
ubicom32_set_interrupt(ud->irq_tx);
}
/*
* ubicom32sd_reset
*/
static void ubicom32sd_reset(struct ubicom32sd_data *ud)
{
ubicom32sd_send_command_sync(ud, SDTIO_COMMAND_RESET << SDTIO_COMMAND_SHIFT, 0);
ud->regs->int_status = 0;
}
/*
* ubicom32sd_mmc_request
*/
static void ubicom32sd_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct ubicom32sd_data *ud = (struct ubicom32sd_data *)mmc_priv(mmc);
u32_t command = SDTIO_COMMAND_EXECUTE << SDTIO_COMMAND_SHIFT;
int ret = 0;
WARN(ud->mrq != NULL, "ud->mrq still set to %p\n", ud->mrq);
//pr_debug("send cmd %08x arg %08x flags %08x\n", cmd->opcode, cmd->arg, cmd->flags);
if (mrq->cmd) {
struct mmc_command *cmd = mrq->cmd;
sd_printk("%s:\t\t\tsetup cmd %02d arg %08x flags %08x\n", mmc_hostname(mmc), cmd->opcode, cmd->arg, cmd->flags);
ud->regs->cmd_opcode = cmd->opcode;
ud->regs->cmd_arg = cmd->arg;
command |= SDTIO_COMMAND_FLAG_CMD;
if (cmd->flags & MMC_RSP_PRESENT) {
command |= SDTIO_COMMAND_FLAG_CMD_RSP;
}
if (cmd->flags & MMC_RSP_136) {
command |= SDTIO_COMMAND_FLAG_CMD_RSP_136;
}
if (cmd->flags & MMC_RSP_CRC) {
command |= SDTIO_COMMAND_FLAG_CMD_RSP_CRC;
}
}
if (mrq->data) {
struct mmc_data *data = mrq->data;
struct scatterlist *sg = data->sg;
int i;
printk("%s:\t\t\tsetup data blksz %d num %d sglen=%d fl=%08x Tns=%u\n", mmc_hostname(mmc), data->blksz, data->blocks, data->sg_len, data->flags, data->timeout_ns);
sd_printk("%s:\t\t\tsetup data blksz %d num %d sglen=%d fl=%08x Tns=%u\n",
mmc_hostname(mmc), data->blksz, data->blocks, data->sg_len,
data->flags, data->timeout_ns);
if (data->sg_len > SDTIO_MAX_SG_BLOCKS) {
ret = -EINVAL;
data->error = -EINVAL;
goto fail;
}
ud->regs->data_timeout_ns = data->timeout_ns;
ud->regs->data_blksz = data->blksz;
ud->regs->data_blkct = data->blocks;
ud->regs->sg_len = data->sg_len;
/*
* Load all of our sg list into the driver sg buffer
*/
for (i = 0; i < data->sg_len; i++) {
sd_printk("%s: sg %d = %p %d\n", mmc_hostname(mmc), i, sg_virt(sg), sg->length);
ud->regs->sg[i].addr = sg_virt(sg);
ud->regs->sg[i].len = sg->length;
if (((u32_t)ud->regs->sg[i].addr & 0x03) || (sg->length & 0x03)) {
sd_printk("%s: Need aligned buffers\n", mmc_hostname(mmc));
ret = -EINVAL;
data->error = -EINVAL;
goto fail;
}
sg++;
}
if (data->flags & MMC_DATA_READ) {
command |= SDTIO_COMMAND_FLAG_DATA_RD;
} else if (data->flags & MMC_DATA_WRITE) {
command |= SDTIO_COMMAND_FLAG_DATA_WR;
} else if (data->flags & MMC_DATA_STREAM) {
command |= SDTIO_COMMAND_FLAG_DATA_STREAM;
}
}
if (mrq->stop) {
struct mmc_command *stop = mrq->stop;
sd_printk("%s: \t\t\tsetup stop %02d arg %08x flags %08x\n", mmc_hostname(mmc), stop->opcode, stop->arg, stop->flags);
ud->regs->stop_opcode = stop->opcode;
ud->regs->stop_arg = stop->arg;
command |= SDTIO_COMMAND_FLAG_STOP_CMD;
if (stop->flags & MMC_RSP_PRESENT) {
command |= SDTIO_COMMAND_FLAG_STOP_RSP;
}
if (stop->flags & MMC_RSP_136) {
command |= SDTIO_COMMAND_FLAG_STOP_RSP_136;
}
if (stop->flags & MMC_RSP_CRC) {
command |= SDTIO_COMMAND_FLAG_STOP_RSP_CRC;
}
}
ud->mrq = mrq;
sd_printk("%s: Sending command %08x\n", mmc_hostname(mmc), command);
ubicom32sd_send_command(ud, command, 0);
return;
fail:
sd_printk("%s: mmcreq ret = %d\n", mmc_hostname(mmc), ret);
mrq->cmd->error = ret;
mmc_request_done(mmc, mrq);
}
/*
* ubicom32sd_mmc_set_ios
*/
static void ubicom32sd_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct ubicom32sd_data *ud = (struct ubicom32sd_data *)mmc_priv(mmc);
u32_t command = SDTIO_COMMAND_SETUP << SDTIO_COMMAND_SHIFT;
u32_t arg = 0;
sd_printk("%s: ios call bw:%u pm:%u clk:%u\n", mmc_hostname(mmc), 1 << ios->bus_width, ios->power_mode, ios->clock);
switch (ios->bus_width) {
case MMC_BUS_WIDTH_1:
command |= SDTIO_COMMAND_FLAG_SET_WIDTH | SDTIO_COMMAND_FLAG_1BIT;
break;
case MMC_BUS_WIDTH_4:
command |= SDTIO_COMMAND_FLAG_SET_WIDTH | SDTIO_COMMAND_FLAG_4BIT;
break;
}
if (ios->clock) {
arg = ios->clock;
command |= SDTIO_COMMAND_FLAG_SET_CLOCK;
}
switch (ios->power_mode) {
/*
* Turn off the SD bus (power + clock)
*/
case MMC_POWER_OFF:
gpio_set_value(ud->pdata->cards[0].pin_pwr, !ud->pdata->cards[0].pwr_polarity);
command |= SDTIO_COMMAND_FLAG_SET_CLOCK;
break;
/*
* Turn on the power to the SD bus
*/
case MMC_POWER_ON:
gpio_set_value(ud->pdata->cards[0].pin_pwr, ud->pdata->cards[0].pwr_polarity);
break;
/*
* Turn on the clock to the SD bus
*/
case MMC_POWER_UP:
/*
* Done above
*/
break;
}
ubicom32sd_send_command_sync(ud, command, arg);
/*
* Let the power settle down
*/
udelay(500);
}
/*
* ubicom32sd_mmc_get_cd
*/
static int ubicom32sd_mmc_get_cd(struct mmc_host *mmc)
{
struct ubicom32sd_data *ud = (struct ubicom32sd_data *)mmc_priv(mmc);
sd_printk("%s: get cd %u %u\n", mmc_hostname(mmc), ud->pdata->cards[0].pin_cd, gpio_get_value(ud->pdata->cards[0].pin_cd));
return gpio_get_value(ud->pdata->cards[0].pin_cd) ?
ud->pdata->cards[0].cd_polarity :
!ud->pdata->cards[0].cd_polarity;
}
/*
* ubicom32sd_mmc_get_ro
*/
static int ubicom32sd_mmc_get_ro(struct mmc_host *mmc)
{
struct ubicom32sd_data *ud = (struct ubicom32sd_data *)mmc_priv(mmc);
sd_printk("%s: get ro %u %u\n", mmc_hostname(mmc), ud->pdata->cards[0].pin_wp, gpio_get_value(ud->pdata->cards[0].pin_wp));
return gpio_get_value(ud->pdata->cards[0].pin_wp) ?
ud->pdata->cards[0].wp_polarity :
!ud->pdata->cards[0].wp_polarity;
}
/*
* ubicom32sd_mmc_enable_sdio_irq
*/
static void ubicom32sd_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
{
struct ubicom32sd_data *ud = (struct ubicom32sd_data *)mmc_priv(mmc);
ud->int_en = enable;
if (enable && ud->int_pend) {
ud->int_pend = 0;
mmc_signal_sdio_irq(mmc);
}
}
/*
* ubicom32sd_interrupt
*/
static irqreturn_t ubicom32sd_interrupt(int irq, void *dev)
{
struct mmc_host *mmc = (struct mmc_host *)dev;
struct mmc_request *mrq;
struct ubicom32sd_data *ud;
u32_t int_status;
if (!mmc) {
return IRQ_HANDLED;
}
ud = (struct ubicom32sd_data *)mmc_priv(mmc);
if (!ud) {
return IRQ_HANDLED;
}
int_status = ud->regs->int_status;
ud->regs->int_status &= ~int_status;
if (int_status & SDTIO_VP_INT_STATUS_SDIO_INT) {
if (ud->int_en) {
ud->int_pend = 0;
mmc_signal_sdio_irq(mmc);
} else {
ud->int_pend++;
}
}
if (!(int_status & SDTIO_VP_INT_STATUS_DONE)) {
return IRQ_HANDLED;
}
mrq = ud->mrq;
if (!mrq) {
sd_printk("%s: Spurious interrupt", mmc_hostname(mmc));
return IRQ_HANDLED;
}
ud->mrq = NULL;
/*
* SDTIO_VP_INT_DONE
*/
if (mrq->cmd->flags & MMC_RSP_PRESENT) {
struct mmc_command *cmd = mrq->cmd;
cmd->error = 0;
if ((cmd->flags & MMC_RSP_CRC) && (int_status & SDTIO_VP_INT_STATUS_CMD_RSP_CRC)) {
cmd->error = -EILSEQ;
} else if (int_status & SDTIO_VP_INT_STATUS_CMD_RSP_TIMEOUT) {
cmd->error = -ETIMEDOUT;
goto done;
} else if (cmd->flags & MMC_RSP_136) {
cmd->resp[0] = ud->regs->cmd_rsp0;
cmd->resp[1] = ud->regs->cmd_rsp1;
cmd->resp[2] = ud->regs->cmd_rsp2;
cmd->resp[3] = ud->regs->cmd_rsp3;
} else {
cmd->resp[0] = ud->regs->cmd_rsp0;
}
sd_printk("%s:\t\t\tResponse %08x %08x %08x %08x err=%d\n", mmc_hostname(mmc), cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3], cmd->error);
}
if (mrq->data) {
struct mmc_data *data = mrq->data;
if (int_status & SDTIO_VP_INT_STATUS_DATA_TIMEOUT) {
data->error = -ETIMEDOUT;
sd_printk("%s:\t\t\tData Timeout\n", mmc_hostname(mmc));
goto done;
} else if (int_status & SDTIO_VP_INT_STATUS_DATA_CRC_ERR) {
data->error = -EILSEQ;
sd_printk("%s:\t\t\tData CRC\n", mmc_hostname(mmc));
goto done;
} else if (int_status & SDTIO_VP_INT_STATUS_DATA_PROG_ERR) {
data->error = -EILSEQ;
sd_printk("%s:\t\t\tData Program Error\n", mmc_hostname(mmc));
goto done;
} else {
data->error = 0;
data->bytes_xfered = ud->regs->data_bytes_transferred;
}
}
if (mrq->stop && (mrq->stop->flags & MMC_RSP_PRESENT)) {
struct mmc_command *stop = mrq->stop;
stop->error = 0;
if ((stop->flags & MMC_RSP_CRC) && (int_status & SDTIO_VP_INT_STATUS_STOP_RSP_CRC)) {
stop->error = -EILSEQ;
} else if (int_status & SDTIO_VP_INT_STATUS_STOP_RSP_TIMEOUT) {
stop->error = -ETIMEDOUT;
goto done;
} else if (stop->flags & MMC_RSP_136) {
stop->resp[0] = ud->regs->stop_rsp0;
stop->resp[1] = ud->regs->stop_rsp1;
stop->resp[2] = ud->regs->stop_rsp2;
stop->resp[3] = ud->regs->stop_rsp3;
} else {
stop->resp[0] = ud->regs->stop_rsp0;
}
sd_printk("%s:\t\t\tStop Response %08x %08x %08x %08x err=%d\n", mmc_hostname(mmc), stop->resp[0], stop->resp[1], stop->resp[2], stop->resp[3], stop->error);
}
done:
mmc_request_done(mmc, mrq);
return IRQ_HANDLED;
}
static struct mmc_host_ops ubicom32sd_ops = {
.request = ubicom32sd_mmc_request,
.set_ios = ubicom32sd_mmc_set_ios,
.get_ro = ubicom32sd_mmc_get_ro,
.get_cd = ubicom32sd_mmc_get_cd,
.enable_sdio_irq = ubicom32sd_mmc_enable_sdio_irq,
};
/*
* ubicom32sd_probe
*/
static int __devinit ubicom32sd_probe(struct platform_device *pdev)
{
struct ubicom32sd_platform_data *pdata = (struct ubicom32sd_platform_data *)pdev->dev.platform_data;
struct mmc_host *mmc;
struct ubicom32sd_data *ud;
struct resource *res_regs;
struct resource *res_irq_tx;
struct resource *res_irq_rx;
int ret;
/*
* Get our resources, regs is the hardware driver base address
* and the tx and rx irqs are used to communicate with the
* hardware driver.
*/
res_regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
res_irq_tx = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
res_irq_rx = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
if (!res_regs || !res_irq_tx || !res_irq_rx) {
ret = -EINVAL;
goto fail;
}
/*
* Reserve any gpios we need
*/
ret = gpio_request(pdata->cards[0].pin_wp, "sd-wp");
if (ret) {
goto fail;
}
gpio_direction_input(pdata->cards[0].pin_wp);
ret = gpio_request(pdata->cards[0].pin_cd, "sd-cd");
if (ret) {
goto fail_cd;
}
gpio_direction_input(pdata->cards[0].pin_cd);
/*
* HACK: for the dual port controller on port F, we don't support the second port right now
*/
if (pdata->ncards > 1) {
ret = gpio_request(pdata->cards[1].pin_pwr, "sd-pwr");
gpio_direction_output(pdata->cards[1].pin_pwr, !pdata->cards[1].pwr_polarity);
gpio_direction_output(pdata->cards[1].pin_pwr, pdata->cards[1].pwr_polarity);
}
ret = gpio_request(pdata->cards[0].pin_pwr, "sd-pwr");
if (ret) {
goto fail_pwr;
}
gpio_direction_output(pdata->cards[0].pin_pwr, !pdata->cards[0].pwr_polarity);
/*
* Allocate the MMC driver, it includes memory for our data.
*/
mmc = mmc_alloc_host(sizeof(struct ubicom32sd_data), &pdev->dev);
if (!mmc) {
ret = -ENOMEM;
goto fail_mmc;
}
ud = (struct ubicom32sd_data *)mmc_priv(mmc);
ud->mmc = mmc;
ud->pdata = pdata;
ud->regs = (struct sdtio_vp_regs *)res_regs->start;
ud->irq_tx = res_irq_tx->start;
ud->irq_rx = res_irq_rx->start;
platform_set_drvdata(pdev, mmc);
ret = request_irq(ud->irq_rx, ubicom32sd_interrupt, IRQF_DISABLED, mmc_hostname(mmc), mmc);
if (ret) {
goto fail_mmc;
}
/*
* Fill in the mmc structure
*/
mmc->ops = &ubicom32sd_ops;
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_NEEDS_POLL | MMC_CAP_SDIO_IRQ |
MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
mmc->f_min = ud->regs->f_min;
mmc->f_max = ud->regs->f_max;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
/*
* Setup some restrictions on transfers
*
* We allow up to SDTIO_MAX_SG_BLOCKS of data to DMA into, there are
* not really any "max_seg_size", "max_req_size", or "max_blk_count"
* restrictions (must be less than U32_MAX though), pick
* something large?!...
*
* The hardware can do up to 4095 bytes per block, since the spec
* only requires 2048, we'll set it to that and not worry about
* potential weird blk lengths.
*/
mmc->max_hw_segs = SDTIO_MAX_SG_BLOCKS;
mmc->max_phys_segs = SDTIO_MAX_SG_BLOCKS;
mmc->max_seg_size = 1024 * 1024;
mmc->max_req_size = 1024 * 1024;
mmc->max_blk_count = 1024;
mmc->max_blk_size = 2048;
ubicom32sd_reset(ud);
/*
* enable interrupts
*/
ud->int_en = 0;
ubicom32sd_send_command_sync(ud, SDTIO_COMMAND_SETUP_SDIO << SDTIO_COMMAND_SHIFT | SDTIO_COMMAND_FLAG_SDIO_INT_EN, 0);
mmc_add_host(mmc);
printk(KERN_INFO "%s at %p, irq %d/%d\n", mmc_hostname(mmc),
ud->regs, ud->irq_tx, ud->irq_rx);
return 0;
fail_mmc:
gpio_free(pdata->cards[0].pin_pwr);
fail_pwr:
gpio_free(pdata->cards[0].pin_cd);
fail_cd:
gpio_free(pdata->cards[0].pin_wp);
fail:
return ret;
}
/*
* ubicom32sd_remove
*/
static int __devexit ubicom32sd_remove(struct platform_device *pdev)
{
struct mmc_host *mmc = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
if (mmc) {
struct ubicom32sd_data *ud = (struct ubicom32sd_data *)mmc_priv(mmc);
gpio_free(ud->pdata->cards[0].pin_pwr);
gpio_free(ud->pdata->cards[0].pin_cd);
gpio_free(ud->pdata->cards[0].pin_wp);
mmc_remove_host(mmc);
mmc_free_host(mmc);
}
/*
* Note that our data is allocated as part of the mmc structure
* so we don't need to free it.
*/
return 0;
}
static struct platform_driver ubicom32sd_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.probe = ubicom32sd_probe,
.remove = __devexit_p(ubicom32sd_remove),
#if 0
.suspend = ubicom32sd_suspend,
.resume = ubicom32sd_resume,
#endif
};
/*
* ubicom32sd_init
*/
static int __init ubicom32sd_init(void)
{
return platform_driver_register(&ubicom32sd_driver);
}
module_init(ubicom32sd_init);
/*
* ubicom32sd_exit
*/
static void __exit ubicom32sd_exit(void)
{
platform_driver_unregister(&ubicom32sd_driver);
}
module_exit(ubicom32sd_exit);
MODULE_AUTHOR("Patrick Tjin");
MODULE_DESCRIPTION("Ubicom32 Secure Digital Host Controller Interface driver");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,760 @@
/*
* drivers/net/ubi32-eth.c
* Ubicom32 ethernet TIO interface driver.
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from (with many thanks):
* arch/m68knommu
* arch/blackfin
* arch/parisc
*/
/*
* ubi32_eth.c
* Ethernet driver for Ip5k/Ip7K
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/in.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/mii.h>
#include <linux/if_vlan.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/skbuff.h>
#include <asm/checksum.h>
#include <asm/ip5000.h>
#include <asm/devtree.h>
#include <asm/system.h>
#define UBICOM32_USE_NAPI /* define this to use NAPI instead of tasklet */
//#define UBICOM32_USE_POLLING /* define this to use polling instead of interrupt */
#include "ubi32-eth.h"
/*
* TODO:
* mac address from flash
* multicast filter
* ethtool support
* sysfs support
* skb->nrfrag support
* ioctl
* monitor phy status
*/
extern int ubi32_ocm_skbuf_max, ubi32_ocm_skbuf, ubi32_ddr_skbuf;
static const char *eth_if_name[UBI32_ETH_NUM_OF_DEVICES] =
{"eth_lan", "eth_wan"};
static struct net_device *ubi32_eth_devices[UBI32_ETH_NUM_OF_DEVICES] =
{NULL, NULL};
static u8_t mac_addr[UBI32_ETH_NUM_OF_DEVICES][ETH_ALEN] = {
{0x00, 0x03, 0x64, 'l', 'a', 'n'},
{0x00, 0x03, 0x64, 'w', 'a', 'n'}};
#if (defined(CONFIG_ZONE_DMA) && defined(CONFIG_UBICOM32_OCM_FOR_SKB))
static inline struct sk_buff *ubi32_alloc_skb_ocm(struct net_device *dev, unsigned int length)
{
return __dev_alloc_skb(length, GFP_ATOMIC | __GFP_NOWARN | __GFP_NORETRY | GFP_DMA);
}
#endif
static inline struct sk_buff *ubi32_alloc_skb(struct net_device *dev, unsigned int length)
{
return __dev_alloc_skb(length, GFP_ATOMIC | __GFP_NOWARN);
}
static void ubi32_eth_vp_rxtx_enable(struct net_device *dev)
{
struct ubi32_eth_private *priv = netdev_priv(dev);
priv->regs->command = UBI32_ETH_VP_CMD_RX_ENABLE | UBI32_ETH_VP_CMD_TX_ENABLE;
priv->regs->int_mask = (UBI32_ETH_VP_INT_RX | UBI32_ETH_VP_INT_TX);
ubicom32_set_interrupt(priv->vp_int_bit);
}
static void ubi32_eth_vp_rxtx_stop(struct net_device *dev)
{
struct ubi32_eth_private *priv = netdev_priv(dev);
priv->regs->command = 0;
priv->regs->int_mask = 0;
ubicom32_set_interrupt(priv->vp_int_bit);
/* Wait for graceful shutdown */
while (priv->regs->status & (UBI32_ETH_VP_STATUS_RX_STATE | UBI32_ETH_VP_STATUS_TX_STATE));
}
/*
* ubi32_eth_tx_done()
*/
static int ubi32_eth_tx_done(struct net_device *dev)
{
struct ubi32_eth_private *priv;
struct sk_buff *skb;
volatile void *pdata;
struct ubi32_eth_dma_desc *desc;
u32_t count = 0;
priv = netdev_priv(dev);
priv->regs->int_status &= ~UBI32_ETH_VP_INT_TX;
while (priv->tx_tail != priv->regs->tx_out) {
pdata = priv->regs->tx_dma_ring[priv->tx_tail];
BUG_ON(pdata == NULL);
skb = container_of((void *)pdata, struct sk_buff, cb);
desc = (struct ubi32_eth_dma_desc *)pdata;
if (unlikely(!(desc->status & UBI32_ETH_VP_TX_OK))) {
dev->stats.tx_errors++;
} else {
dev->stats.tx_packets++;
dev->stats.tx_bytes += skb->len;
}
dev_kfree_skb_any(skb);
priv->regs->tx_dma_ring[priv->tx_tail] = NULL;
priv->tx_tail = (priv->tx_tail + 1) & TX_DMA_RING_MASK;
count++;
}
if (unlikely(priv->regs->status & UBI32_ETH_VP_STATUS_TX_Q_FULL)) {
spin_lock(&priv->lock);
if (priv->regs->status & UBI32_ETH_VP_STATUS_TX_Q_FULL) {
priv->regs->status &= ~UBI32_ETH_VP_STATUS_TX_Q_FULL;
netif_wake_queue(dev);
}
spin_unlock(&priv->lock);
}
return count;
}
/*
* ubi32_eth_receive()
* To avoid locking overhead, this is called only
* by tasklet when not using NAPI, or
* by NAPI poll when using NAPI.
* return number of frames processed
*/
static int ubi32_eth_receive(struct net_device *dev, int quota)
{
struct ubi32_eth_private *priv = netdev_priv(dev);
unsigned short rx_in = priv->regs->rx_in;
struct sk_buff *skb;
struct ubi32_eth_dma_desc *desc = NULL;
volatile void *pdata;
int extra_reserve_adj;
int extra_alloc = UBI32_ETH_RESERVE_SPACE + UBI32_ETH_TRASHED_MEMORY;
int replenish_cnt, count = 0;
int replenish_max = RX_DMA_MAX_QUEUE_SIZE;
#if (defined(CONFIG_ZONE_DMA) && defined(CONFIG_UBICOM32_OCM_FOR_SKB))
if (likely(dev == ubi32_eth_devices[0]))
replenish_max = min(ubi32_ocm_skbuf_max, RX_DMA_MAX_QUEUE_SIZE);;
#endif
if (unlikely(rx_in == priv->regs->rx_out))
priv->vp_stats.rx_q_full_cnt++;
priv->regs->int_status &= ~UBI32_ETH_VP_INT_RX;
while (priv->rx_tail != priv->regs->rx_out) {
if (unlikely(count == quota)) {
/* There is still frame pending to be processed */
priv->vp_stats.rx_throttle++;
break;
}
pdata = priv->regs->rx_dma_ring[priv->rx_tail];
BUG_ON(pdata == NULL);
desc = (struct ubi32_eth_dma_desc *)pdata;
skb = container_of((void *)pdata, struct sk_buff, cb);
count++;
priv->regs->rx_dma_ring[priv->rx_tail] = NULL;
priv->rx_tail = ((priv->rx_tail + 1) & RX_DMA_RING_MASK);
/*
* Check only RX_OK bit here.
* The rest of status word is used as timestamp
*/
if (unlikely(!(desc->status & UBI32_ETH_VP_RX_OK))) {
dev->stats.rx_errors++;
dev_kfree_skb_any(skb);
continue;
}
skb_put(skb, desc->data_len);
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
skb->ip_summed = CHECKSUM_NONE;
dev->stats.rx_bytes += skb->len;
dev->stats.rx_packets++;
#ifndef UBICOM32_USE_NAPI
netif_rx(skb);
#else
netif_receive_skb(skb);
#endif
}
/* fill in more descripor for VP*/
replenish_cnt = replenish_max -
((RX_DMA_RING_SIZE + rx_in - priv->rx_tail) & RX_DMA_RING_MASK);
if (replenish_cnt > 0) {
#if (defined(CONFIG_ZONE_DMA) && defined(CONFIG_UBICOM32_OCM_FOR_SKB))
/*
* black magic for perforamnce:
* Try to allocate skb from OCM only for first Ethernet I/F.
* Also limit number of RX buffers to 21 due to limited OCM.
*/
if (likely(dev == ubi32_eth_devices[0])) {
do {
skb = ubi32_alloc_skb_ocm(dev, RX_BUF_SIZE + extra_alloc);
if (!skb) {
break;
}
/* set up dma descriptor */
ubi32_ocm_skbuf++;
desc = (struct ubi32_eth_dma_desc *)skb->cb;
extra_reserve_adj =
((u32)skb->data + UBI32_ETH_RESERVE_SPACE + ETH_HLEN) &
(CACHE_LINE_SIZE - 1);
skb_reserve(skb, UBI32_ETH_RESERVE_SPACE - extra_reserve_adj);
desc->data_pointer = skb->data;
desc->buffer_len = RX_BUF_SIZE + UBI32_ETH_TRASHED_MEMORY;
desc->data_len = 0;
desc->status = 0;
priv->regs->rx_dma_ring[rx_in] = desc;
rx_in = (rx_in + 1) & RX_DMA_RING_MASK;
} while (--replenish_cnt > 0);
}
#endif
while (replenish_cnt-- > 0) {
skb = ubi32_alloc_skb(dev, RX_BUF_SIZE + extra_alloc);
if (!skb) {
priv->vp_stats.rx_alloc_err++;
break;
}
/* set up dma descriptor */
ubi32_ddr_skbuf++;
desc = (struct ubi32_eth_dma_desc *)skb->cb;
extra_reserve_adj =
((u32)skb->data + UBI32_ETH_RESERVE_SPACE + ETH_HLEN) &
(CACHE_LINE_SIZE - 1);
skb_reserve(skb, UBI32_ETH_RESERVE_SPACE - extra_reserve_adj);
desc->data_pointer = skb->data;
desc->buffer_len = RX_BUF_SIZE + UBI32_ETH_TRASHED_MEMORY;
desc->data_len = 0;
desc->status = 0;
priv->regs->rx_dma_ring[rx_in] = desc;
rx_in = (rx_in + 1) & RX_DMA_RING_MASK;
}
wmb();
priv->regs->rx_in = rx_in;
ubicom32_set_interrupt(priv->vp_int_bit);
}
if (likely(count > 0)) {
dev->last_rx = jiffies;
}
return count;
}
#ifdef UBICOM32_USE_NAPI
static int ubi32_eth_napi_poll(struct napi_struct *napi, int budget)
{
struct ubi32_eth_private *priv = container_of(napi, struct ubi32_eth_private, napi);
struct net_device *dev = priv->dev;
u32_t count;
if (priv->tx_tail != priv->regs->tx_out) {
ubi32_eth_tx_done(dev);
}
count = ubi32_eth_receive(dev, budget);
if (count < budget) {
napi_complete(napi);
priv->regs->int_mask |= (UBI32_ETH_VP_INT_RX | UBI32_ETH_VP_INT_TX);
if ((priv->rx_tail != priv->regs->rx_out) || (priv->tx_tail != priv->regs->tx_out)) {
if (napi_reschedule(napi)) {
priv->regs->int_mask = 0;
}
}
}
return count;
}
#else
static void ubi32_eth_do_tasklet(unsigned long arg)
{
struct net_device *dev = (struct net_device *)arg;
struct ubi32_eth_private *priv = netdev_priv(dev);
if (priv->tx_tail != priv->regs->tx_out) {
ubi32_eth_tx_done(dev);
}
/* always call receive to process new RX frame as well as replenish RX buffers */
ubi32_eth_receive(dev, UBI32_RX_BOUND);
priv->regs->int_mask |= (UBI32_ETH_VP_INT_RX | UBI32_ETH_VP_INT_TX);
if ((priv->rx_tail != priv->regs->rx_out) || (priv->tx_tail != priv->regs->tx_out)) {
priv->regs->int_mask = 0;
tasklet_schedule(&priv->tsk);
}
}
#endif
#if defined(UBICOM32_USE_POLLING)
static struct timer_list eth_poll_timer;
static void ubi32_eth_poll(unsigned long arg)
{
struct net_device *dev;
struct ubi32_eth_private *priv;
int i;
for (i = 0; i < UBI32_ETH_NUM_OF_DEVICES; i++) {
dev = ubi32_eth_devices[i];
if (dev && (dev->flags & IFF_UP)) {
priv = netdev_priv(dev);
#ifdef UBICOM32_USE_NAPI
napi_schedule(&priv->napi);
#else
tasklet_schedule(&priv->tsk);
#endif
}
}
eth_poll_timer.expires = jiffies + 2;
add_timer(&eth_poll_timer);
}
#else
static irqreturn_t ubi32_eth_interrupt(int irq, void *dev_id)
{
struct ubi32_eth_private *priv;
struct net_device *dev = (struct net_device *)dev_id;
BUG_ON(irq != dev->irq);
priv = netdev_priv(dev);
if (unlikely(!(priv->regs->int_status & priv->regs->int_mask))) {
return IRQ_NONE;
}
/*
* Disable port interrupt
*/
#ifdef UBICOM32_USE_NAPI
if (napi_schedule_prep(&priv->napi)) {
priv->regs->int_mask = 0;
__napi_schedule(&priv->napi);
}
#else
priv->regs->int_mask = 0;
tasklet_schedule(&priv->tsk);
#endif
return IRQ_HANDLED;
}
#endif
/*
* ubi32_eth_open
*/
static int ubi32_eth_open(struct net_device *dev)
{
struct ubi32_eth_private *priv = netdev_priv(dev);
int err;
printk(KERN_INFO "eth open %s\n",dev->name);
#ifndef UBICOM32_USE_POLLING
/* request_region() */
err = request_irq(dev->irq, ubi32_eth_interrupt, IRQF_DISABLED, dev->name, dev);
if (err) {
printk(KERN_WARNING "fail to request_irq %d\n",err);
return -ENODEV;
}
#endif
#ifdef UBICOM32_USE_NAPI
napi_enable(&priv->napi);
#else
tasklet_init(&priv->tsk, ubi32_eth_do_tasklet, (unsigned long)dev);
#endif
/* call receive to supply RX buffers */
ubi32_eth_receive(dev, RX_DMA_MAX_QUEUE_SIZE);
/* check phy status and call netif_carrier_on */
ubi32_eth_vp_rxtx_enable(dev);
netif_start_queue(dev);
return 0;
}
static int ubi32_eth_close(struct net_device *dev)
{
struct ubi32_eth_private *priv = netdev_priv(dev);
volatile void *pdata;
struct sk_buff *skb;
#ifndef UBICOM32_USE_POLLING
free_irq(dev->irq, dev);
#endif
netif_stop_queue(dev); /* can't transmit any more */
#ifdef UBICOM32_USE_NAPI
napi_disable(&priv->napi);
#else
tasklet_kill(&priv->tsk);
#endif
ubi32_eth_vp_rxtx_stop(dev);
/*
* RX clean up
*/
while (priv->rx_tail != priv->regs->rx_in) {
pdata = priv->regs->rx_dma_ring[priv->rx_tail];
skb = container_of((void *)pdata, struct sk_buff, cb);
priv->regs->rx_dma_ring[priv->rx_tail] = NULL;
dev_kfree_skb_any(skb);
priv->rx_tail = ((priv->rx_tail + 1) & RX_DMA_RING_MASK);
}
priv->regs->rx_in = 0;
priv->regs->rx_out = priv->regs->rx_in;
priv->rx_tail = priv->regs->rx_in;
/*
* TX clean up
*/
BUG_ON(priv->regs->tx_out != priv->regs->tx_in);
ubi32_eth_tx_done(dev);
BUG_ON(priv->tx_tail != priv->regs->tx_in);
priv->regs->tx_in = 0;
priv->regs->tx_out = priv->regs->tx_in;
priv->tx_tail = priv->regs->tx_in;
return 0;
}
/*
* ubi32_eth_set_config
*/
static int ubi32_eth_set_config(struct net_device *dev, struct ifmap *map)
{
/* if must to down to config it */
printk(KERN_INFO "set_config %x\n", dev->flags);
if (dev->flags & IFF_UP)
return -EBUSY;
/* I/O and IRQ can not be changed */
if (map->base_addr != dev->base_addr) {
printk(KERN_WARNING "%s: Can't change I/O address\n", dev->name);
return -EOPNOTSUPP;
}
#ifndef UBICOM32_USE_POLLING
if (map->irq != dev->irq) {
printk(KERN_WARNING "%s: Can't change IRQ\n", dev->name);
return -EOPNOTSUPP;
}
#endif
/* ignore other fields */
return 0;
}
static int ubi32_eth_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct ubi32_eth_private *priv = netdev_priv(dev);
struct ubi32_eth_dma_desc *desc = NULL;
unsigned short space, tx_in;
tx_in = priv->regs->tx_in;
dev->trans_start = jiffies; /* save the timestamp */
space = TX_DMA_RING_MASK - ((TX_DMA_RING_SIZE + tx_in - priv->tx_tail) & TX_DMA_RING_MASK);
if (unlikely(space == 0)) {
if (!(priv->regs->status & UBI32_ETH_VP_STATUS_TX_Q_FULL)) {
spin_lock(&priv->lock);
if (!(priv->regs->status & UBI32_ETH_VP_STATUS_TX_Q_FULL)) {
priv->regs->status |= UBI32_ETH_VP_STATUS_TX_Q_FULL;
priv->vp_stats.tx_q_full_cnt++;
netif_stop_queue(dev);
}
spin_unlock(&priv->lock);
}
/* give both HW and this driver an extra trigger */
priv->regs->int_mask |= UBI32_ETH_VP_INT_TX;
#ifndef UBICOM32_USE_POLLING
ubicom32_set_interrupt(dev->irq);
#endif
ubicom32_set_interrupt(priv->vp_int_bit);
return NETDEV_TX_BUSY;
}
/*still have room */
desc = (struct ubi32_eth_dma_desc *)skb->cb;
desc->data_pointer = skb->data;
desc->data_len = skb->len;
priv->regs->tx_dma_ring[tx_in] = desc;
tx_in = ((tx_in + 1) & TX_DMA_RING_MASK);
wmb();
priv->regs->tx_in = tx_in;
/* kick the HRT */
ubicom32_set_interrupt(priv->vp_int_bit);
return NETDEV_TX_OK;
}
/*
* Deal with a transmit timeout.
*/
static void ubi32_eth_tx_timeout (struct net_device *dev)
{
struct ubi32_eth_private *priv = netdev_priv(dev);
dev->stats.tx_errors++;
priv->regs->int_mask |= UBI32_ETH_VP_INT_TX;
#ifndef UBICOM32_USE_POLLING
ubicom32_set_interrupt(dev->irq);
#endif
ubicom32_set_interrupt(priv->vp_int_bit);
}
static int ubi32_eth_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
struct ubi32_eth_private *priv = netdev_priv(dev);
struct mii_ioctl_data *data = if_mii(rq);
printk(KERN_INFO "ioctl %s, %d\n", dev->name, cmd);
switch (cmd) {
case SIOCGMIIPHY:
data->phy_id = 0;
break;
case SIOCGMIIREG:
if ((data->reg_num & 0x1F) == MII_BMCR) {
/* Make up MII control register value from what we know */
data->val_out = 0x0000
| ((priv->regs->status & UBI32_ETH_VP_STATUS_DUPLEX)
? BMCR_FULLDPLX : 0)
| ((priv->regs->status & UBI32_ETH_VP_STATUS_SPEED100)
? BMCR_SPEED100 : 0)
| ((priv->regs->status & UBI32_ETH_VP_STATUS_SPEED1000)
? BMCR_SPEED1000 : 0);
} else if ((data->reg_num & 0x1F) == MII_BMSR) {
/* Make up MII status register value from what we know */
data->val_out =
(BMSR_100FULL|BMSR_100HALF|BMSR_10FULL|BMSR_10HALF)
| ((priv->regs->status & UBI32_ETH_VP_STATUS_LINK)
? BMSR_LSTATUS : 0);
} else {
return -EIO;
}
break;
case SIOCSMIIREG:
return -EOPNOTSUPP;
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
/*
* Return statistics to the caller
*/
static struct net_device_stats *ubi32_eth_get_stats(struct net_device *dev)
{
return &dev->stats;
}
static int ubi32_eth_change_mtu(struct net_device *dev, int new_mtu)
{
struct ubi32_eth_private *priv = netdev_priv(dev);
unsigned long flags;
if ((new_mtu < 68) || (new_mtu > 1500))
return -EINVAL;
spin_lock_irqsave(&priv->lock, flags);
dev->mtu = new_mtu;
spin_unlock_irqrestore(&priv->lock, flags);
printk(KERN_INFO "set mtu to %d", new_mtu);
return 0;
}
/*
* ubi32_eth_cleanup: unload the module
*/
void ubi32_eth_cleanup(void)
{
struct ubi32_eth_private *priv;
struct net_device *dev;
int i;
for (i = 0; i < UBI32_ETH_NUM_OF_DEVICES; i++) {
dev = ubi32_eth_devices[i];
if (dev) {
priv = netdev_priv(dev);
kfree(priv->regs->tx_dma_ring);
unregister_netdev(dev);
free_netdev(dev);
ubi32_eth_devices[i] = NULL;
}
}
}
int ubi32_eth_init_module(void)
{
struct ethtionode *eth_node;
struct net_device *dev;
struct ubi32_eth_private *priv;
int i, err;
/*
* Device allocation.
*/
err = 0;
for (i = 0; i < UBI32_ETH_NUM_OF_DEVICES; i++) {
/*
* See if the eth_vp is in the device tree.
*/
eth_node = (struct ethtionode *)devtree_find_node(eth_if_name[i]);
if (!eth_node) {
printk(KERN_INFO "%s does not exist\n", eth_if_name[i]);
continue;
}
eth_node->tx_dma_ring = (struct ubi32_eth_dma_desc **)kmalloc(
sizeof(struct ubi32_eth_dma_desc *) *
(TX_DMA_RING_SIZE + RX_DMA_RING_SIZE),
GFP_ATOMIC | __GFP_NOWARN | __GFP_NORETRY | GFP_DMA);
if (eth_node->tx_dma_ring == NULL) {
eth_node->tx_dma_ring = (struct ubi32_eth_dma_desc **)kmalloc(
sizeof(struct ubi32_eth_dma_desc *) *
(TX_DMA_RING_SIZE + RX_DMA_RING_SIZE), GFP_KERNEL);
printk(KERN_INFO "fail to allocate from OCM\n");
}
if (!eth_node->tx_dma_ring) {
err = -ENOMEM;
break;
}
eth_node->rx_dma_ring = eth_node->tx_dma_ring + TX_DMA_RING_SIZE;
eth_node->tx_sz = TX_DMA_RING_SIZE - 1;
eth_node->rx_sz = RX_DMA_RING_SIZE - 1;
dev = alloc_etherdev(sizeof(struct ubi32_eth_private));
if (!dev) {
kfree(eth_node->tx_dma_ring);
err = -ENOMEM;
break;
}
priv = netdev_priv(dev);
priv->dev = dev;
/*
* This just fill in some default Ubicom MAC address
*/
memcpy(dev->dev_addr, mac_addr[i], ETH_ALEN);
memset(dev->broadcast, 0xff, ETH_ALEN);
priv->regs = eth_node;
priv->regs->command = 0;
priv->regs->int_mask = 0;
priv->regs->int_status = 0;
priv->regs->tx_out = 0;
priv->regs->rx_out = 0;
priv->regs->tx_in = 0;
priv->regs->rx_in = 0;
priv->rx_tail = 0;
priv->tx_tail = 0;
priv->vp_int_bit = eth_node->dn.sendirq;
dev->irq = eth_node->dn.recvirq;
spin_lock_init(&priv->lock);
dev->open = ubi32_eth_open;
dev->stop = ubi32_eth_close;
dev->hard_start_xmit = ubi32_eth_start_xmit;
dev->tx_timeout = ubi32_eth_tx_timeout;
dev->watchdog_timeo = UBI32_ETH_VP_TX_TIMEOUT;
dev->set_config = ubi32_eth_set_config;
dev->do_ioctl = ubi32_eth_ioctl;
dev->get_stats = ubi32_eth_get_stats;
dev->change_mtu = ubi32_eth_change_mtu;
#ifdef UBICOM32_USE_NAPI
netif_napi_add(dev, &priv->napi, ubi32_eth_napi_poll, UBI32_ETH_NAPI_WEIGHT);
#endif
err = register_netdev(dev);
if (err) {
printk(KERN_WARNING "Failed to register netdev %s\n", eth_if_name[i]);
//release_region();
free_netdev(dev);
kfree(eth_node->tx_dma_ring);
break;
}
ubi32_eth_devices[i] = dev;
printk(KERN_INFO "%s vp_base:0x%p, tio_int:%d irq:%d feature:0x%lx\n",
dev->name, priv->regs, eth_node->dn.sendirq, dev->irq, dev->features);
}
if (err) {
ubi32_eth_cleanup();
return err;
}
if (!ubi32_eth_devices[0] && !ubi32_eth_devices[1]) {
return -ENODEV;
}
#if defined(UBICOM32_USE_POLLING)
init_timer(&eth_poll_timer);
eth_poll_timer.function = ubi32_eth_poll;
eth_poll_timer.data = (unsigned long)0;
eth_poll_timer.expires = jiffies + 2;
add_timer(&eth_poll_timer);
#endif
return 0;
}
module_init(ubi32_eth_init_module);
module_exit(ubi32_eth_cleanup);
MODULE_AUTHOR("Kan Yan, Greg Ren");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,132 @@
/*
* drivers/net/ubi32-eth.h
* Ubicom32 ethernet TIO interface driver definitions.
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from (with many thanks):
* arch/m68knommu
* arch/blackfin
* arch/parisc
*/
#ifndef _UBI32_ETH_H
#define _UBI32_ETH_H
#include <asm/devtree.h>
#define UBI32_ETH_NUM_OF_DEVICES 2
/*
* Number of bytes trashed beyond the packet data.
*/
#define UBI32_ETH_TRASHED_MEMORY (CACHE_LINE_SIZE + ETH_HLEN - 1)
/*
* Linux already reserves NET_SKB_PAD bytes of headroom in each sk_buff.
* We want to be able to reserve at least one cache line to align Ethernet
* and IP header to cache line.
* Note that the TIO expects a CACHE_LINE_SIZE - ETH_HLEN aligned Ethernet
* header, while satisfies NET_IP_ALIGN (= 2) automatically.
* (NET_SKB_PAD is 16, NET_IP_ALIGN is 2, CACHE_LINE_SIZE is 32).
* You can add more space by making UBI32_ETH_RESERVE_EXTRA != 0.
*/
#define UBI32_ETH_RESERVE_EXTRA (1 * CACHE_LINE_SIZE)
#define UBI32_ETH_RESERVE_SPACE (UBI32_ETH_RESERVE_EXTRA + CACHE_LINE_SIZE)
struct ubi32_eth_dma_desc {
volatile void *data_pointer; /* pointer to the buffer */
volatile u16 buffer_len; /* the buffer size */
volatile u16 data_len; /* actual frame length */
volatile u32 status; /* bit0: status to be update by VP; bit[31:1] time stamp */
};
#define TX_DMA_RING_SIZE (1<<8)
#define TX_DMA_RING_MASK (TX_DMA_RING_SIZE - 1)
#define RX_DMA_RING_SIZE (1<<8)
#define RX_DMA_RING_MASK (RX_DMA_RING_SIZE - 1)
#define RX_DMA_MAX_QUEUE_SIZE (RX_DMA_RING_SIZE - 1) /* no more than (RX_DMA_RING_SIZE - 1) */
#define RX_MAX_PKT_SIZE (ETH_DATA_LEN + ETH_HLEN + VLAN_HLEN)
#define RX_MIN_PKT_SIZE ETH_ZLEN
#define RX_BUF_SIZE (RX_MAX_PKT_SIZE + VLAN_HLEN) /* allow double VLAN tag */
#define UBI32_ETH_VP_TX_TIMEOUT (10*HZ)
struct ubi32_eth_vp_stats {
u32 rx_alloc_err;
u32 tx_q_full_cnt;
u32 rx_q_full_cnt;
u32 rx_throttle;
};
struct ubi32_eth_private {
struct net_device *dev;
struct ubi32_eth_vp_stats vp_stats;
spinlock_t lock;
#ifdef UBICOM32_USE_NAPI
struct napi_struct napi;
#else
struct tasklet_struct tsk;
#endif
struct ethtionode *regs;
u16 rx_tail;
u16 tx_tail;
u32 vp_int_bit;
};
struct ethtionode {
struct devtree_node dn;
volatile u16 command;
volatile u16 status;
volatile u16 int_mask; /* interrupt mask */
volatile u16 int_status; /* interrupt mask */
volatile u16 tx_in; /* owned by driver */
volatile u16 tx_out; /* owned by vp */
volatile u16 rx_in; /* owned by driver */
volatile u16 rx_out; /* owned by vp */
u16 tx_sz; /* owned by driver */
u16 rx_sz; /* owned by driver */
struct ubi32_eth_dma_desc **tx_dma_ring;
struct ubi32_eth_dma_desc **rx_dma_ring;
};
#define UBI32_ETH_VP_STATUS_LINK (1<<0)
#define UBI32_ETH_VP_STATUS_SPEED100 (0x1<<1)
#define UBI32_ETH_VP_STATUS_SPEED1000 (0x1<<2)
#define UBI32_ETH_VP_STATUS_DUPLEX (0x1<<3)
#define UBI32_ETH_VP_STATUS_FLOW_CTRL (0x1<<4)
#define UBI32_ETH_VP_STATUS_RX_STATE (0x1<<5)
#define UBI32_ETH_VP_STATUS_TX_STATE (0x1<<6)
#define UBI32_ETH_VP_STATUS_TX_Q_FULL (1<<8)
#define UBI32_ETH_VP_INT_RX (1<<0)
#define UBI32_ETH_VP_INT_TX (1<<1)
#define UBI32_ETH_VP_CMD_RX_ENABLE (1<<0)
#define UBI32_ETH_VP_CMD_TX_ENABLE (1<<1)
#define UBI32_ETH_VP_RX_OK (1<<0)
#define UBI32_ETH_VP_TX_OK (1<<1)
#define UBI32_TX_BOUND TX_DMA_RING_SIZE
#define UBI32_RX_BOUND 64
#define UBI32_ETH_NAPI_WEIGHT 64 /* for GigE */
#endif

View File

@@ -0,0 +1,928 @@
/*
* drivers/serial/ubi32_mailbox.c
* Ubicom32 On-Chip Mailbox Driver
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from (with many thanks):
* arch/m68knommu
* arch/blackfin
* arch/parisc
*/
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/sysrq.h>
#include <linux/platform_device.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <asm/ip5000.h>
#define SERIAL_UBICOM_BAUDRATE 115200
#define SERIAL_UBICOM_DATA_BIT 8 /* Fixed parameter - do not change */
#define SERIAL_UBICOM_PAR_BIT 0 /* Fixed parameter - do not change */
#define SERIAL_UBICOM_STOP_BIT 1 /* Fixed parameter - do not change */
/* UART name and device definitions */
#define UBI32_MAILBOX_NAME "ttyUM" // XXX
#define UBI32_MAILBOX_MAJOR 207 // XXX
#define UBI32_MAILBOX_MINOR 64
#define PORT_UBI32_MAILBOX 1235
#define NR_PORTS 1
#define get_sclk() 0
struct ubi32_mailbox_port {
struct uart_port port;
/*
* NOTE (rkeller):
* the uart port is wrapped in another structure in case we need to hold more state than
* what we can hold in the uart_port.
* Not sure if we need this, I took over the concept from the blackfin driver.
*/
} ubi32_mailbox_ports[NR_PORTS];
struct ubi32_mailbox_resource {
int uart_base_addr;
int uart_irq;
} ubi32_mailbox_resource[NR_PORTS] = {
/*
* uart_base_addr has to be non-NULL because it is put in the uart_port membase.
* If membase if null the kernel skips the configuration and our port_type never gets set.
*/
{ISD_MAILBOX_BASE, ISD_MAILBOX_INT}
};
static volatile struct ubicom32_isd_mailbox {
volatile u32_t in;
volatile u32_t out;
volatile u32_t status;
} *ubi32_mailbox = (struct ubicom32_isd_mailbox *)ISD_MAILBOX_BASE;
static void ubi32_mailbox_tx_chars(struct ubi32_mailbox_port *uart);
static void ubi32_mailbox_mctrl_check(struct ubi32_mailbox_port *uart);
#define TRUE 1
#define FALSE 0
static int mailbox_console_flg = TRUE;
static int num_timeouts = 0;
/*
* dummy functions and defined to be able to compile the Blackfin code
*/
#define UART_GET_LSR(port) (1)
#define UART_PUT_LSR(port, bits)
#define UART_CLEAR_LSR(port) (1)
#define TEMT 1
#define TFI 1
#define BI 1
#define PE 1
#define OE 1
#define FE 1
#define THRE 1
#define DR 1
#define UART_GET_LCR(port) (1)
#define UART_PUT_LCR(port, bits)
#define SB 1
#define STB 1
#define PEN 1
#define EPS 1
#define STP 1
#define WLS(n) 0
#define UART_GET_IER(port) (1)
#define UART_SET_IER(port, bits)
#define UART_CLEAR_IER(port, bits)
#define ETBEI 0
#define ERBFI 0
#define UART_GET_CHAR(port) ubi32_mailbox_get_char()
#define UART_PUT_CHAR(port, ch) ubi32_mailbox_put_char(ch)
#define SSYNC()
#define UART_GET_DLL(port) 0
#define UART_PUT_DLL(port, ch)
#define UART_GET_DLH(port) 0
#define UART_PUT_DLH(port, ch)
#define UART_GET_GCTL(port) (0)
#define UART_PUT_GCTL(port, ch)
#define UCEN 1
/*
* ubi32_mailbox_get_char_avail()
*/
static int ubi32_mailbox_get_char_avail(void)
{
return !(ubi32_mailbox->status & ISD_MAILBOX_STATUS_IN_EMPTY);
}
/*
* ubi32_mailbox_get_char()
*/
static u32_t ubi32_mailbox_get_char(void)
{
if (mailbox_console_flg == TRUE) {
/*
* Mailbox console is connected.
*/
while (ubi32_mailbox->status & ISD_MAILBOX_STATUS_IN_EMPTY);
return ubi32_mailbox->in & 0xff;
}
/*
* Mailbox console was not connected.
*/
if (ubi32_mailbox->status & ISD_MAILBOX_STATUS_IN_EMPTY) {
return 0xff;
}
/*
* Mailbox console is connecting.
*/
mailbox_console_flg = TRUE;
num_timeouts = 0;
return ubi32_mailbox->in & 0xff;
}
#define MAILBOX_MAX_ATTEMPTS 1000000
#define MAILBOX_MAX_TIMEOUTS 5
/*
* ubi32_mailbox_put_char()
*/
static void ubi32_mailbox_put_char(u32_t v)
{
/*
* Wait to be able to output.
*/
u32_t num_attempts = 0;
if(mailbox_console_flg == TRUE) {
while(num_attempts++ < MAILBOX_MAX_ATTEMPTS) {
if(ubi32_mailbox->status & ISD_MAILBOX_STATUS_OUT_EMPTY) {
break;
}
}
/*
* If timed out more than 5 times on send, mailbox console is disconnected now.
*/
if (num_attempts > MAILBOX_MAX_ATTEMPTS) {
if (num_timeouts++ > MAILBOX_MAX_TIMEOUTS) {
mailbox_console_flg = FALSE;
}
}
}
asm volatile(
"pipe_flush 0 \n\t"
"pipe_flush 0 \n\t"
"pipe_flush 0 \n\t"
"pipe_flush 0 \n\t"
"pipe_flush 0 \n\t"
"pipe_flush 0 \n\t"
"pipe_flush 0 \n\t"
);
ubi32_mailbox->out = v & 0xff;
}
static void ubi32_mailbox_hw_init(struct ubi32_mailbox_port *uart)
{
// NOTE: It does not do any good to do these here because we are running on the linux hardware thread,
// and these have to be called on the ldsr thread.
// ubicom32_clear_interrupt(ISD_MAILBOX_INT);
// ubicom32_enable_interrupt(ISD_MAILBOX_INT);
}
/*
* interrupts are disabled on entry
*/
static void ubi32_mailbox_stop_tx(struct uart_port *port)
{
// struct ubi32_mailbox_port *uart = (struct ubi32_mailbox_port *)port;
// struct circ_buf *xmit = &uart->port.info->xmit;
while (!(UART_GET_LSR(uart) & TEMT))
cpu_relax();
/* Clear TFI bit */
UART_PUT_LSR(uart, TFI);
UART_CLEAR_IER(uart, ETBEI);
}
/*
* port is locked and interrupts are disabled
*/
static void ubi32_mailbox_start_tx(struct uart_port *port)
{
struct ubi32_mailbox_port *uart = (struct ubi32_mailbox_port *)port;
UART_SET_IER(uart, ETBEI);
ubi32_mailbox_tx_chars(uart);
}
/*
* Interrupts are enabled
*/
static void ubi32_mailbox_stop_rx(struct uart_port *port)
{
// struct ubi32_mailbox_port *uart = (struct ubi32_mailbox_port *)port;
UART_CLEAR_IER(uart, ERBFI);
}
/*
* Set the modem control timer to fire immediately.
*/
static void ubi32_mailbox_enable_ms(struct uart_port *port)
{
}
static void ubi32_mailbox_rx_chars(struct ubi32_mailbox_port *uart)
{
struct uart_info *info = uart->port.info;
struct tty_struct *tty = info->port.tty;
unsigned int status, ch, flg;
status = 0; // XXX? UART_GET_LSR(uart);
UART_CLEAR_LSR(uart);
ch = UART_GET_CHAR(uart);
if(ch == 0xff)
return;
uart->port.icount.rx++;
if (status & BI) {
uart->port.icount.brk++;
if (uart_handle_break(&uart->port))
goto ignore_char;
status &= ~(PE | FE);
}
if (status & PE)
uart->port.icount.parity++;
if (status & OE)
uart->port.icount.overrun++;
if (status & FE)
uart->port.icount.frame++;
status &= uart->port.read_status_mask;
if (status & BI)
flg = TTY_BREAK;
else if (status & PE)
flg = TTY_PARITY;
else if (status & FE)
flg = TTY_FRAME;
else
flg = TTY_NORMAL;
if (uart_handle_sysrq_char(&uart->port, ch))
goto ignore_char;
uart_insert_char(&uart->port, status, OE, ch, flg);
ignore_char:
tty_flip_buffer_push(tty);
}
static void ubi32_mailbox_tx_chars(struct ubi32_mailbox_port *uart)
{
struct circ_buf *xmit = &uart->port.info->xmit;
if (uart->port.x_char) {
UART_PUT_CHAR(uart, uart->port.x_char);
uart->port.icount.tx++;
uart->port.x_char = 0;
}
/*
* Check the modem control lines before
* transmitting anything.
*/
ubi32_mailbox_mctrl_check(uart);
if (uart_circ_empty(xmit) || uart_tx_stopped(&uart->port)) {
ubi32_mailbox_stop_tx(&uart->port);
return;
}
while ((UART_GET_LSR(uart) & THRE) && xmit->tail != xmit->head) {
UART_PUT_CHAR(uart, xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
uart->port.icount.tx++;
SSYNC();
}
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(&uart->port);
if (uart_circ_empty(xmit))
ubi32_mailbox_stop_tx(&uart->port);
}
static irqreturn_t ubi32_mailbox_isr(int irq, void *dev_id)
{
struct ubi32_mailbox_port *uart = dev_id;
spin_lock(&uart->port.lock);
//XXX?while (UART_GET_LSR(uart) & DR)
/*
* RX process
*/
while (ubi32_mailbox_get_char_avail()) {
ubi32_mailbox_rx_chars(uart);
}
#if 0
/*
* TX process
*/
if (this_uart.tx_in == this_uart.tx_out) {
UBICOM32_IO_PORT(SERIAL_UBICOM_PORT)->int_mask &= ~IO_PORTX_INT_SERDES_TXBE;
} else if (UBICOM32_IO_PORT(SERIAL_UBICOM_PORT)->int_status & IO_PORTX_INT_SERDES_TXBE) {
uart_ubicom32_send(this_uart.tx_buf[this_uart.tx_out & (SERIAL_UBICOM_BUF_SIZE - 1)]);
this_uart.tx_out++;
UBICOM32_IO_PORT(SERIAL_UBICOM_PORT)->int_mask |= IO_PORTX_INT_SERDES_TXBE;
}
#endif
spin_unlock(&uart->port.lock);
return IRQ_HANDLED;
}
#if 0
static irqreturn_t ubi32_mailbox_tx_int(int irq, void *dev_id)
{
struct ubi32_mailbox_port *uart = dev_id;
spin_lock(&uart->port.lock);
if (UART_GET_LSR(uart) & THRE)
ubi32_mailbox_tx_chars(uart);
spin_unlock(&uart->port.lock);
return IRQ_HANDLED;
}
#endif
/*
* Return TIOCSER_TEMT when transmitter is not busy.
*/
static unsigned int ubi32_mailbox_tx_empty(struct uart_port *port)
{
// struct ubi32_mailbox_port *uart = (struct ubi32_mailbox_port *)port;
unsigned short lsr;
lsr = UART_GET_LSR(uart);
if (lsr & TEMT)
return TIOCSER_TEMT;
else
return 0;
}
static unsigned int ubi32_mailbox_get_mctrl(struct uart_port *port)
{
return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
}
static void ubi32_mailbox_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
}
/*
* Handle any change of modem status signal since we were last called.
*/
static void ubi32_mailbox_mctrl_check(struct ubi32_mailbox_port *uart)
{
}
/*
* Interrupts are always disabled.
*/
static void ubi32_mailbox_break_ctl(struct uart_port *port, int break_state)
{
// struct ubi32_mailbox_port *uart = (struct ubi32_mailbox_port *)port;
u16 lcr = UART_GET_LCR(uart);
if (break_state)
lcr |= SB;
else
lcr &= ~SB;
UART_PUT_LCR(uart, lcr);
SSYNC();
}
static int ubi32_mailbox_startup(struct uart_port *port)
{
struct ubi32_mailbox_port *uart = (struct ubi32_mailbox_port *)port;
if (request_irq(uart->port.irq, ubi32_mailbox_isr, IRQF_DISABLED,
"UBI32_MAILBOX", uart)) {
printk(KERN_NOTICE "Unable to attach Ubicom32 SERDES interrupt\n");
return -EBUSY;
}
UART_SET_IER(uart, ERBFI);
return 0;
}
static void ubi32_mailbox_shutdown(struct uart_port *port)
{
struct ubi32_mailbox_port *uart = (struct ubi32_mailbox_port *)port;
free_irq(uart->port.irq, uart);
}
static void
ubi32_mailbox_set_termios(struct uart_port *port, struct ktermios *termios,
struct ktermios *old)
{
struct ubi32_mailbox_port *uart = (struct ubi32_mailbox_port *)port;
unsigned long flags;
unsigned int baud, quot;
unsigned short val, ier, lsr, lcr = 0;
switch (termios->c_cflag & CSIZE) {
case CS8:
lcr = WLS(8);
break;
case CS7:
lcr = WLS(7);
break;
case CS6:
lcr = WLS(6);
break;
case CS5:
lcr = WLS(5);
break;
default:
printk(KERN_ERR "%s: word lengh not supported\n",
__FUNCTION__);
}
if (termios->c_cflag & CSTOPB)
lcr |= STB;
if (termios->c_cflag & PARENB)
lcr |= PEN;
if (!(termios->c_cflag & PARODD))
lcr |= EPS;
if (termios->c_cflag & CMSPAR)
lcr |= STP;
port->read_status_mask = OE;
if (termios->c_iflag & INPCK)
port->read_status_mask |= (FE | PE);
if (termios->c_iflag & (BRKINT | PARMRK))
port->read_status_mask |= BI;
/*
* Characters to ignore
*/
port->ignore_status_mask = 0;
if (termios->c_iflag & IGNPAR)
port->ignore_status_mask |= FE | PE;
if (termios->c_iflag & IGNBRK) {
port->ignore_status_mask |= BI;
/*
* If we're ignoring parity and break indicators,
* ignore overruns too (for real raw support).
*/
if (termios->c_iflag & IGNPAR)
port->ignore_status_mask |= OE;
}
baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
quot = uart_get_divisor(port, baud);
spin_lock_irqsave(&uart->port.lock, flags);
do {
lsr = UART_GET_LSR(uart);
} while (!(lsr & TEMT));
/* Disable UART */
ier = UART_GET_IER(uart);
UART_CLEAR_IER(uart, 0xF);
UART_PUT_DLL(uart, quot & 0xFF);
SSYNC();
UART_PUT_DLH(uart, (quot >> 8) & 0xFF);
SSYNC();
UART_PUT_LCR(uart, lcr);
/* Enable UART */
UART_SET_IER(uart, ier);
val = UART_GET_GCTL(uart);
val |= UCEN;
UART_PUT_GCTL(uart, val);
spin_unlock_irqrestore(&uart->port.lock, flags);
}
static const char *ubi32_mailbox_type(struct uart_port *port)
{
struct ubi32_mailbox_port *uart = (struct ubi32_mailbox_port *)port;
return uart->port.type == PORT_UBI32_MAILBOX ? "UBI32_MAILBOX" : NULL;
}
/*
* Release the memory region(s) being used by 'port'.
*/
static void ubi32_mailbox_release_port(struct uart_port *port)
{
}
/*
* Request the memory region(s) being used by 'port'.
*/
static int ubi32_mailbox_request_port(struct uart_port *port)
{
return 0;
}
/*
* Configure/autoconfigure the port.
*/
static void ubi32_mailbox_config_port(struct uart_port *port, int flags)
{
struct ubi32_mailbox_port *uart = (struct ubi32_mailbox_port *)port;
if (flags & UART_CONFIG_TYPE && ubi32_mailbox_request_port(&uart->port) == 0)
uart->port.type = PORT_UBI32_MAILBOX;
}
/*
* Verify the new serial_struct (for TIOCSSERIAL).
* The only change we allow are to the flags and type, and
* even then only between PORT_UBI32_MAILBOX and PORT_UNKNOWN
*/
static int
ubi32_mailbox_verify_port(struct uart_port *port, struct serial_struct *ser)
{
return 0;
}
static struct uart_ops ubi32_mailbox_pops = {
.tx_empty = ubi32_mailbox_tx_empty,
.set_mctrl = ubi32_mailbox_set_mctrl,
.get_mctrl = ubi32_mailbox_get_mctrl,
.stop_tx = ubi32_mailbox_stop_tx,
.start_tx = ubi32_mailbox_start_tx,
.stop_rx = ubi32_mailbox_stop_rx,
.enable_ms = ubi32_mailbox_enable_ms,
.break_ctl = ubi32_mailbox_break_ctl,
.startup = ubi32_mailbox_startup,
.shutdown = ubi32_mailbox_shutdown,
.set_termios = ubi32_mailbox_set_termios,
.type = ubi32_mailbox_type,
.release_port = ubi32_mailbox_release_port,
.request_port = ubi32_mailbox_request_port,
.config_port = ubi32_mailbox_config_port,
.verify_port = ubi32_mailbox_verify_port,
};
static void __init ubi32_mailbox_init_ports(void)
{
static int first = 1;
int i;
if (!first)
return;
first = 0;
for (i = 0; i < NR_PORTS; i++) {
ubi32_mailbox_ports[i].port.uartclk = get_sclk();
ubi32_mailbox_ports[i].port.ops = &ubi32_mailbox_pops;
ubi32_mailbox_ports[i].port.line = i;
ubi32_mailbox_ports[i].port.iotype = UPIO_MEM;
ubi32_mailbox_ports[i].port.membase =
(void __iomem *)ubi32_mailbox_resource[i].uart_base_addr;
ubi32_mailbox_ports[i].port.mapbase =
ubi32_mailbox_resource[i].uart_base_addr;
ubi32_mailbox_ports[i].port.irq =
ubi32_mailbox_resource[i].uart_irq;
ubi32_mailbox_ports[i].port.flags = UPF_BOOT_AUTOCONF;
spin_lock_init(&ubi32_mailbox_ports[i].port.lock);
ubi32_mailbox_hw_init(&ubi32_mailbox_ports[i]);
}
}
#ifdef CONFIG_SERIAL_UBI32_MAILBOX_CONSOLE
/*
* If the port was already initialised (eg, by a boot loader),
* try to determine the current setup.
*/
static void __init
ubi32_mailbox_console_get_options(struct ubi32_mailbox_port *uart, int *baud,
int *parity, int *bits)
{
unsigned short status;
status = UART_GET_IER(uart) & (ERBFI | ETBEI);
if (status == (ERBFI | ETBEI)) {
/* ok, the port was enabled */
unsigned short lcr;
unsigned short dlh, dll;
lcr = UART_GET_LCR(uart);
*parity = 'n';
if (lcr & PEN) {
if (lcr & EPS)
*parity = 'e';
else
*parity = 'o';
}
switch (lcr & 0x03) {
case 0: *bits = 5; break;
case 1: *bits = 6; break;
case 2: *bits = 7; break;
case 3: *bits = 8; break;
}
dll = UART_GET_DLL(uart);
dlh = UART_GET_DLH(uart);
*baud = get_sclk() / (16*(dll | dlh << 8));
}
pr_debug("%s:baud = %d, parity = %c, bits= %d\n", __FUNCTION__, *baud, *parity, *bits);
}
#endif
#if defined(CONFIG_SERIAL_UBI32_MAILBOX_CONSOLE) || defined(CONFIG_EARLY_PRINTK)
static struct uart_driver ubi32_mailbox_reg;
static int __init
ubi32_mailbox_console_setup(struct console *co, char *options)
{
struct ubi32_mailbox_port *uart;
# ifdef CONFIG_SERIAL_UBI32_MAILBOX_CONSOLE
int baud = SERIAL_UBICOM_BAUDRATE;
int bits = 8;
int parity = 'n';
int flow = 'n';
# endif
/*
* Check whether an invalid uart number has been specified, and
* if so, search for the first available port that does have
* console support.
*/
if (co->index == -1 || co->index >= NR_PORTS)
co->index = 0;
uart = &ubi32_mailbox_ports[co->index];
# ifdef CONFIG_SERIAL_UBI32_MAILBOX_CONSOLE
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
else
ubi32_mailbox_console_get_options(uart, &baud, &parity, &bits);
//JB return uart_set_options(&uart->port, co, baud, parity, bits, flow);
return 0;
# else
return 0;
# endif
}
#endif /* defined (CONFIG_SERIAL_UBI32_MAILBOX_CONSOLE) ||
defined (CONFIG_EARLY_PRINTK) */
#ifdef CONFIG_SERIAL_UBI32_MAILBOX_CONSOLE
static void ubi32_mailbox_console_putchar(struct uart_port *port, int ch)
{
// struct ubi32_mailbox_port *uart = (struct ubi32_mailbox_port *)port;
while (!(UART_GET_LSR(uart) & THRE))
barrier();
UART_PUT_CHAR(uart, ch);
SSYNC();
}
/*
* Interrupts are disabled on entering
*/
static void
ubi32_mailbox_console_write(struct console *co, const char *s, unsigned int count)
{
struct ubi32_mailbox_port *uart = &ubi32_mailbox_ports[co->index];
unsigned long flags = 0;
spin_lock_irqsave(&uart->port.lock, flags);
uart_console_write(&uart->port, s, count, ubi32_mailbox_console_putchar);
spin_unlock_irqrestore(&uart->port.lock, flags);
}
static struct console ubi32_mailbox_console = {
.name = UBI32_MAILBOX_NAME,
.write = ubi32_mailbox_console_write,
.device = uart_console_device,
.setup = ubi32_mailbox_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &ubi32_mailbox_reg,
};
static int __init ubi32_mailbox_console_init(void)
{
ubi32_mailbox_init_ports();
register_console(&ubi32_mailbox_console);
return 0;
}
console_initcall(ubi32_mailbox_console_init);
#define UBI32_MAILBOX_CONSOLE &ubi32_mailbox_console
#else
#define UBI32_MAILBOX_CONSOLE NULL
#endif /* CONFIG_SERIAL_UBI32_MAILBOX_CONSOLE */
#ifdef CONFIG_EARLY_PRINTK
static __init void ubi32_mailbox_early_putc(struct uart_port *port, int ch)
{
UART_PUT_CHAR(uart, ch);
}
static __init void ubi32_mailbox_early_write(struct console *con, const char *s,
unsigned int n)
{
struct ubi32_mailbox_port *uart = &ubi32_mailbox_ports[con->index];
unsigned int i;
for (i = 0; i < n; i++, s++) {
if (*s == '\n')
ubi32_mailbox_early_putc(&uart->port, '\r');
ubi32_mailbox_early_putc(&uart->port, *s);
}
}
static struct __init console ubi32_mailbox_early_console = {
.name = "early_UM",
.write = ubi32_mailbox_early_write,
.device = uart_console_device,
.flags = CON_PRINTBUFFER,
.setup = ubi32_mailbox_console_setup,
.index = -1,
.data = &ubi32_mailbox_reg,
};
/*
* XXX Unused in our driver. Need to find out what the termios initialization is good/needed for.
*/
struct console __init *ubi32_mailbox_early_init(unsigned int port,
unsigned int cflag)
{
struct ubi32_mailbox_port *uart;
struct ktermios t;
if (port == -1 || port >= NR_PORTS)
port = 0;
ubi32_mailbox_init_ports();
ubi32_mailbox_early_console.index = port;
uart = &ubi32_mailbox_ports[port];
t.c_cflag = cflag;
t.c_iflag = 0;
t.c_oflag = 0;
t.c_lflag = ICANON;
t.c_line = port;
ubi32_mailbox_set_termios(&uart->port, &t, &t);
return &ubi32_mailbox_early_console;
}
#endif /* CONFIG_SERIAL_UBI32_MAILBOX_CONSOLE */
static struct uart_driver ubi32_mailbox_reg = {
.owner = THIS_MODULE,
.driver_name = "ubi32_mailbox",
.dev_name = UBI32_MAILBOX_NAME,
.major = UBI32_MAILBOX_MAJOR,
.minor = UBI32_MAILBOX_MINOR,
.nr = NR_PORTS,
.cons = UBI32_MAILBOX_CONSOLE,
};
static int ubi32_mailbox_suspend(struct platform_device *dev, pm_message_t state)
{
struct ubi32_mailbox_port *uart = platform_get_drvdata(dev);
if (uart)
uart_suspend_port(&ubi32_mailbox_reg, &uart->port);
return 0;
}
static int ubi32_mailbox_resume(struct platform_device *dev)
{
struct ubi32_mailbox_port *uart = platform_get_drvdata(dev);
if (uart)
uart_resume_port(&ubi32_mailbox_reg, &uart->port);
return 0;
}
static int ubi32_mailbox_probe(struct platform_device *dev)
{
struct resource *res = dev->resource;
int i;
for (i = 0; i < dev->num_resources; i++, res++)
if (res->flags & IORESOURCE_MEM)
break;
if (i < dev->num_resources) {
for (i = 0; i < NR_PORTS; i++, res++) {
if (ubi32_mailbox_ports[i].port.mapbase != res->start)
continue;
ubi32_mailbox_ports[i].port.dev = &dev->dev;
uart_add_one_port(&ubi32_mailbox_reg, &ubi32_mailbox_ports[i].port);
platform_set_drvdata(dev, &ubi32_mailbox_ports[i]);
}
}
return 0;
}
static int ubi32_mailbox_remove(struct platform_device *pdev)
{
struct ubi32_mailbox_port *uart = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
if (uart)
uart_remove_one_port(&ubi32_mailbox_reg, &uart->port);
return 0;
}
static struct platform_driver ubi32_mailbox_driver = {
.probe = ubi32_mailbox_probe,
.remove = ubi32_mailbox_remove,
.suspend = ubi32_mailbox_suspend,
.resume = ubi32_mailbox_resume,
.driver = {
.name = "ubi32-mbox",
.owner = THIS_MODULE,
},
};
static int __init ubi32_mailbox_init(void)
{
int ret;
pr_info("Serial: Ubicom32 mailbox serial driver.\n");
mailbox_console_flg = TRUE;
num_timeouts = 0;
ubi32_mailbox_init_ports();
ret = uart_register_driver(&ubi32_mailbox_reg);
if (ret == 0) {
ret = platform_driver_register(&ubi32_mailbox_driver);
if (ret) {
pr_debug("uart register failed\n");
uart_unregister_driver(&ubi32_mailbox_reg);
}
}
/*
* XXX HACK: currently probe does not get called, but the port needs to be added to work.
*/
uart_add_one_port(&ubi32_mailbox_reg, &ubi32_mailbox_ports[0].port);
return ret;
}
static void __exit ubi32_mailbox_exit(void)
{
platform_driver_unregister(&ubi32_mailbox_driver);
uart_unregister_driver(&ubi32_mailbox_reg);
}
module_init(ubi32_mailbox_init);
module_exit(ubi32_mailbox_exit);
MODULE_ALIAS_CHARDEV_MAJOR(UBI32_MAILBOX_MAJOR);
MODULE_ALIAS("platform:ubi32_mailbox");

View File

@@ -0,0 +1,817 @@
/*
* drivers/serial/ubi32_serdes.c
* Ubicom32 On-Chip Serial Driver
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from (with many thanks):
* arch/m68knommu
* arch/blackfin
* arch/parisc
*/
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/sysrq.h>
#include <linux/platform_device.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <asm/ip5000.h>
#include <asm/ubicom32suart.h>
#define SERIAL_UBICOM_PIN_RXD (1 << 0)
#define SERIAL_UBICOM_PIN_TXD (1 << 6)
#define SERIAL_UBICOM_CTL0 0x8b300000
#define SERIAL_UBICOM_CTL1 0x00000009
#define SERIAL_UBICOM_DATA_BIT 8 /* Fixed parameter - do not change */
#define SERIAL_UBICOM_PAR_BIT 0 /* Fixed parameter - do not change */
#define SERIAL_UBICOM_STOP_BIT 1 /* Fixed parameter - do not change */
/* UART name and device definitions */
#define UBI32_SERDES_NAME "ttyUS" // XXX
#define UBI32_SERDES_MAJOR 206 // XXX
#define UBI32_SERDES_MINOR 64 // XXX
#define PORT_UBI32_SERDES 1234
#define NR_PORTS 1
struct uart_port ubi32_serdes_ports[NR_PORTS];
struct ubi32_serdes_resource {
void *uart_base_addr;
int uart_irq;
int uart_clock;
} ubi32_serdes_resource[NR_PORTS] = {
/*
* Get params from kernel command line (required for early printk)
* or from platform resources.
*/
{0, 0, 0}
};
/*
* Can get overridden by 'serdes=' kernel command line.
*/
static int ubi32_serdes_default_baud_rate = 115200;
#define IO_PORT(port) ((struct ubicom32_io_port *)port->membase)
#define IO_PORT_INT_STATUS(port) (IO_PORT(port)->int_status)
#define IO_PORT_INT_MASK(port) (IO_PORT(port)->int_mask)
#define IO_PORT_INT_CLR(port) (IO_PORT(port)->int_clr)
/*
* ubi32_serdes_get_char()
*/
static u8_t ubi32_serdes_get_char(struct ubicom32_io_port *io_port)
{
/*
* Read from hardware (forced 32-bit atomic read).
*/
u32_t data = 0;
if ( io_port ) {
io_port->int_clr = IO_PORTX_INT_SERDES_RXBF;
asm volatile (
"move.4 %0, %1 \n\t"
: "=r" (data)
: "m" (*(u32_t *)&(io_port->rx_fifo))
);
}
return (u8_t)(data & 0x000000ff);
}
/*
* ubi32_serdes_put_char()
*/
static void ubi32_serdes_put_char(struct ubicom32_io_port *io_port, u8_t c)
{
u32_t data = 0x0000fe00 | (c << 1);
if ( io_port ) {
/*
* Fixed data format:
* [LSB]1 start bit - 8 data bits - no parity - 1 stop bit[MSB]
*/
io_port->int_clr = IO_PORTX_INT_SERDES_TXBE;
io_port->ctl2 = data;
io_port->int_set = IO_PORTX_INT_SERDES_TXBUF_VALID;
}
}
static void ubi32_serdes_hw_init(struct uart_port *port, int baud)
{
struct ubicom32_io_port *io_port = IO_PORT(port);
if ( io_port ) {
/*
* Put port functions 1-4 into reset state.
* Function 0 (GPIO) does not need or have a reset bit.
*
* Select SERDES function for restart below.
*/
io_port->function =
IO_FUNC_FUNCTION_RESET(1) | IO_FUNC_FUNCTION_RESET(2) |
IO_FUNC_FUNCTION_RESET(3) | IO_FUNC_FUNCTION_RESET(4) |
IO_PORTX_FUNC_SERDES;
/*
* Configure SERDES baudrate
*/
if ( baud == 0 ) {
baud = ubi32_serdes_default_baud_rate;
}
io_port->ctl0 =
SERIAL_UBICOM_CTL0 |
((port->uartclk / (16 * baud)) - 1);
io_port->ctl1 =
SERIAL_UBICOM_CTL1;
/*
* don't interrupt until startup and start_tx
*/
io_port->int_mask = 0;
/*
* Set TXD pin output, RXD input and prevent GPIO
* override on the TXD & RXD pins
*/
io_port->gpio_ctl &= ~SERIAL_UBICOM_PIN_RXD;
io_port->gpio_ctl |= SERIAL_UBICOM_PIN_TXD;
io_port->gpio_mask &= ~(SERIAL_UBICOM_PIN_RXD | SERIAL_UBICOM_PIN_TXD);
/*
* Restart (un-reset) the port's SERDES function.
*/
io_port->function &= ~(IO_FUNC_FUNCTION_RESET(IO_PORTX_FUNC_SERDES));
}
}
#define ULITE_STATUS_RXVALID IO_PORTX_INT_SERDES_RXBF
#define ULITE_STATUS_OVERRUN 0
#define ULITE_STATUS_FRAME 0
#define ULITE_STATUS_PARITY 0
#define ULITE_STATUS_TXEMPTY IO_PORTX_INT_SERDES_TXBE
#define ULITE_STATUS_TXFULL 0
static int ubi32_serdes_receive(struct uart_port *port, int stat)
{
struct tty_struct *tty = port->info->port.tty;
unsigned char ch = 0;
char flag = TTY_NORMAL;
if ((stat & (ULITE_STATUS_RXVALID | ULITE_STATUS_OVERRUN
| ULITE_STATUS_FRAME)) == 0)
return 0;
/* stats */
if (stat & ULITE_STATUS_RXVALID) {
port->icount.rx++;
ch = ubi32_serdes_get_char((struct ubicom32_io_port *)port->membase);
if (stat & ULITE_STATUS_PARITY)
port->icount.parity++;
}
if (stat & ULITE_STATUS_OVERRUN)
port->icount.overrun++;
if (stat & ULITE_STATUS_FRAME)
port->icount.frame++;
/* drop byte with parity error if IGNPAR specificed */
if (stat & port->ignore_status_mask & ULITE_STATUS_PARITY)
stat &= ~ULITE_STATUS_RXVALID;
stat &= port->read_status_mask;
if (stat & ULITE_STATUS_PARITY)
flag = TTY_PARITY;
stat &= ~port->ignore_status_mask;
if (stat & ULITE_STATUS_RXVALID)
tty_insert_flip_char(tty, ch, flag);
if (stat & ULITE_STATUS_FRAME)
tty_insert_flip_char(tty, 0, TTY_FRAME);
if (stat & ULITE_STATUS_OVERRUN)
tty_insert_flip_char(tty, 0, TTY_OVERRUN);
return 1;
}
/*
* interrupts are disabled on entry
*/
static void ubi32_serdes_stop_tx(struct uart_port *port)
{
IO_PORT_INT_MASK(port) = IO_PORT_INT_MASK(port) & ~IO_PORTX_INT_SERDES_TXBE;
}
static int ubi32_serdes_transmit(struct uart_port *port, int stat)
{
struct circ_buf *xmit = &port->info->xmit;
if (!(stat & IO_PORTX_INT_SERDES_TXBE))
return 0;
if (port->x_char) {
ubi32_serdes_put_char(IO_PORT(port), port->x_char);
port->x_char = 0;
port->icount.tx++;
return 1;
}
if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
ubi32_serdes_stop_tx(port);
return 0;
}
ubi32_serdes_put_char(IO_PORT(port), xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE-1);
port->icount.tx++;
/* wake up */
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(port);
if (uart_circ_empty(xmit))
ubi32_serdes_stop_tx(port);
return 1;
}
/*
* port is locked and interrupts are disabled
*/
static void ubi32_serdes_start_tx(struct uart_port *port)
{
IO_PORT_INT_MASK(port) = IO_PORT_INT_MASK(port) | IO_PORTX_INT_SERDES_TXBE;
ubi32_serdes_transmit(port, IO_PORT_INT_STATUS(port));
}
/*
* Interrupts are enabled
*/
static void ubi32_serdes_stop_rx(struct uart_port *port)
{
/* don't forward any more data (like !CREAD) */
port->ignore_status_mask = IO_PORTX_INT_SERDES_RXBF;
}
/*
* Set the modem control timer to fire immediately.
*/
static void ubi32_serdes_enable_ms(struct uart_port *port)
{
/* N/A */
}
static irqreturn_t ubi32_serdes_isr(int irq, void *dev_id)
{
struct uart_port *port = dev_id;
int busy;
spin_lock(&port->lock);
do {
int stat = IO_PORT_INT_STATUS(port);
busy = ubi32_serdes_receive(port, stat);
busy |= ubi32_serdes_transmit(port, stat);
} while (busy);
tty_flip_buffer_push(port->info->port.tty);
spin_unlock(&port->lock);
return IRQ_HANDLED;
}
/*
* Return TIOCSER_TEMT when transmitter is not busy.
*/
static unsigned int ubi32_serdes_tx_empty(struct uart_port *port)
{
unsigned long flags;
unsigned int ret;
spin_lock_irqsave(&port->lock, flags);
ret = IO_PORT_INT_STATUS(port);
spin_unlock_irqrestore(&port->lock, flags);
return ret & ULITE_STATUS_TXEMPTY ? TIOCSER_TEMT : 0;
}
static unsigned int ubi32_serdes_get_mctrl(struct uart_port *port)
{
return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
}
static void ubi32_serdes_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
/* N/A */
}
/*
* Interrupts are always disabled.
*/
static void ubi32_serdes_break_ctl(struct uart_port *port, int break_state)
{
/* N/A */
}
static int ubi32_serdes_startup(struct uart_port *port)
{
if (request_irq(port->irq, ubi32_serdes_isr, IRQF_DISABLED,
"UBI32_SERDES", port)) {
printk(KERN_NOTICE "Unable to attach port interrupt\n");
return -EBUSY;
}
IO_PORT_INT_CLR(port) = IO_PORTX_INT_SERDES_RXBF;
IO_PORT_INT_MASK(port) = IO_PORTX_INT_SERDES_RXBF;
return 0;
}
static void ubi32_serdes_shutdown(struct uart_port *port)
{
struct ubi32_serdes_port *uart = (struct ubi32_serdes_port *)port;
IO_PORT_INT_MASK(port) = 0;
free_irq(port->irq, uart);
}
static void
ubi32_serdes_set_termios(struct uart_port *port, struct ktermios *termios,
struct ktermios *old)
{
unsigned long flags;
unsigned int baud;
spin_lock_irqsave(&port->lock, flags);
port->read_status_mask = ULITE_STATUS_RXVALID | ULITE_STATUS_OVERRUN
| ULITE_STATUS_TXFULL;
if (termios->c_iflag & INPCK)
port->read_status_mask |=
ULITE_STATUS_PARITY | ULITE_STATUS_FRAME;
port->ignore_status_mask = 0;
if (termios->c_iflag & IGNPAR)
port->ignore_status_mask |= ULITE_STATUS_PARITY
| ULITE_STATUS_FRAME | ULITE_STATUS_OVERRUN;
/* ignore all characters if CREAD is not set */
if ((termios->c_cflag & CREAD) == 0)
port->ignore_status_mask |=
ULITE_STATUS_RXVALID | ULITE_STATUS_PARITY
| ULITE_STATUS_FRAME | ULITE_STATUS_OVERRUN;
/* update timeout */
baud = uart_get_baud_rate(port, termios, old, 0, 460800);
uart_update_timeout(port, termios->c_cflag, baud);
IO_PORT(port)->ctl0 = SERIAL_UBICOM_CTL0 |
((port->uartclk / (16 * baud)) - 1);
spin_unlock_irqrestore(&port->lock, flags);
}
static const char *ubi32_serdes_type(struct uart_port *port)
{
return port->type == PORT_UBI32_SERDES ? "UBI32_SERDES" : NULL;
}
/*
* Release the memory region(s) being used by 'port'.
*/
static void ubi32_serdes_release_port(struct uart_port *port)
{
}
/*
* Request the memory region(s) being used by 'port'.
*/
static int ubi32_serdes_request_port(struct uart_port *port)
{
return 0;
}
/*
* Configure/autoconfigure the port.
*/
static void ubi32_serdes_config_port(struct uart_port *port, int flags)
{
if (flags & UART_CONFIG_TYPE &&
ubi32_serdes_request_port(port) == 0)
port->type = PORT_UBI32_SERDES;
}
/*
* Verify the new serial_struct (for TIOCSSERIAL).
* The only change we allow are to the flags and type, and
* even then only between PORT_UBI32_SERDES and PORT_UNKNOWN
*/
static int
ubi32_serdes_verify_port(struct uart_port *port, struct serial_struct *ser)
{
return 0;
}
static struct uart_ops ubi32_serdes_pops = {
.tx_empty = ubi32_serdes_tx_empty,
.set_mctrl = ubi32_serdes_set_mctrl,
.get_mctrl = ubi32_serdes_get_mctrl,
.stop_tx = ubi32_serdes_stop_tx,
.start_tx = ubi32_serdes_start_tx,
.stop_rx = ubi32_serdes_stop_rx,
.enable_ms = ubi32_serdes_enable_ms,
.break_ctl = ubi32_serdes_break_ctl,
.startup = ubi32_serdes_startup,
.shutdown = ubi32_serdes_shutdown,
.set_termios = ubi32_serdes_set_termios,
.type = ubi32_serdes_type,
.release_port = ubi32_serdes_release_port,
.request_port = ubi32_serdes_request_port,
.config_port = ubi32_serdes_config_port,
.verify_port = ubi32_serdes_verify_port,
};
static void __init ubi32_serdes_init_ports(void)
{
int i;
for (i = 0; i < NR_PORTS; i++) {
ubi32_serdes_ports[i].uartclk = ubi32_serdes_resource[i].uart_clock;
ubi32_serdes_ports[i].ops = &ubi32_serdes_pops;
ubi32_serdes_ports[i].line = i;
ubi32_serdes_ports[i].iotype = UPIO_MEM;
ubi32_serdes_ports[i].membase =
(void __iomem *)ubi32_serdes_resource[i].uart_base_addr;
ubi32_serdes_ports[i].mapbase =
(resource_size_t)ubi32_serdes_resource[i].uart_base_addr;
ubi32_serdes_ports[i].irq =
ubi32_serdes_resource[i].uart_irq;
ubi32_serdes_ports[i].flags = UPF_BOOT_AUTOCONF;
ubi32_serdes_hw_init(&ubi32_serdes_ports[i], 0);
}
}
#ifdef CONFIG_SERIAL_UBI32_SERDES_CONSOLE
/*
* If the port was already initialised (eg, by a boot loader),
* try to determine the current setup.
*/
static void __init
ubi32_serdes_console_get_options(struct uart_port *port, int *baud)
{
u32 round_to = 1200;
u32 real_baud;
/*
* We might get called before platform init and with no
* kernel command line options, so port might be NULL.
*/
*baud = ubi32_serdes_default_baud_rate;;
if ( IO_PORT(port) == 0 )
return;
real_baud = port->uartclk
/ (16 * ((IO_PORT(port)->ctl0 & ~SERIAL_UBICOM_CTL0) + 1));
*baud = ((real_baud + round_to - 1) / round_to) * round_to;
pr_debug("%s:baud = %d, real_baud = %d\n", __FUNCTION__, *baud, real_baud);
}
#endif
#if defined(CONFIG_SERIAL_UBI32_SERDES_CONSOLE) || defined(CONFIG_EARLY_PRINTK)
static struct uart_driver ubi32_serdes_reg;
static int __init
ubi32_serdes_console_setup(struct console *co, char *options)
{
struct uart_port *port;
#ifdef CONFIG_SERIAL_UBI32_SERDES_CONSOLE
int baud = ubi32_serdes_default_baud_rate;
int bits = 8;
int parity = 'n';
int flow = 'n';
#endif
/*
* Check whether an invalid uart number has been specified, and
* if so, search for the first available port that does have
* console support.
*/
if (co->index == -1 || co->index >= NR_PORTS)
co->index = 0;
port = &ubi32_serdes_ports[co->index];
#ifdef CONFIG_SERIAL_UBI32_SERDES_CONSOLE
if (options) {
uart_parse_options(options, &baud, &parity, &bits, &flow);
ubi32_serdes_hw_init(port, baud);
}
else
ubi32_serdes_console_get_options(port, &baud);
return uart_set_options(port, co, baud, parity, bits, flow);
#else
return 0;
#endif
}
#endif /* defined (CONFIG_SERIAL_UBI32_SERDES_CONSOLE) ||
defined (CONFIG_EARLY_PRINTK) */
#ifdef CONFIG_SERIAL_UBI32_SERDES_CONSOLE
static void
ubi32_serdes_console_putchar(struct uart_port *port, int ch)
{
if ( IO_PORT(port) ) {
while (!(IO_PORT_INT_STATUS(port) & IO_PORTX_INT_SERDES_TXBE))
barrier();
ubi32_serdes_put_char(IO_PORT(port), ch);
}
}
/*
* Interrupts are disabled on entering
*/
static void
ubi32_serdes_console_write(struct console *co, const char *s, unsigned int count)
{
struct uart_port *port = &ubi32_serdes_ports[co->index];
unsigned long flags = 0;
spin_lock_irqsave(&port->lock, flags);
uart_console_write(port, s, count, ubi32_serdes_console_putchar);
spin_unlock_irqrestore(&port->lock, flags);
}
static struct console ubi32_serdes_console = {
.name = UBI32_SERDES_NAME,
.write = ubi32_serdes_console_write,
.device = uart_console_device,
.setup = ubi32_serdes_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &ubi32_serdes_reg,
};
static int __init ubi32_serdes_console_init(void)
{
ubi32_serdes_init_ports();
register_console(&ubi32_serdes_console);
return 0;
}
console_initcall(ubi32_serdes_console_init);
#define UBI32_SERDES_CONSOLE &ubi32_serdes_console
#else
#define UBI32_SERDES_CONSOLE NULL
#endif /* CONFIG_SERIAL_UBI32_SERDES_CONSOLE */
#ifdef CONFIG_EARLY_PRINTK
static __init void ubi32_serdes_early_putc(struct uart_port *port, int ch)
{
unsigned timeout = 0xffff;
while ((!(IO_PORT_INT_STATUS(port) & IO_PORTX_INT_SERDES_TXBE)) && --timeout)
cpu_relax();
ubi32_serdes_put_char(IO_PORT(port), ch);
}
static __init void ubi32_serdes_early_write(struct console *con, const char *s,
unsigned int n)
{
struct uart_port *port = &ubi32_serdes_ports[con->index];
unsigned int i;
for (i = 0; i < n; i++, s++) {
if (*s == '\n')
ubi32_serdes_early_putc(port, '\r');
ubi32_serdes_early_putc(port, *s);
}
}
static struct __init console ubi32_serdes_early_console = {
.name = "early_US",
.write = ubi32_serdes_early_write,
.device = uart_console_device,
.flags = CON_PRINTBUFFER,
.setup = ubi32_serdes_console_setup,
.index = -1,
.data = &ubi32_serdes_reg,
};
/*
* XXX Unused in our driver. Need to find out what the termios initialization is good/needed for.
*/
struct console __init *ubi32_serdes_early_init(unsigned int port_index,
unsigned int cflag)
{
struct uart_port *uart;
struct ktermios t;
if (port_index == -1 || port_index >= NR_PORTS)
port_index = 0;
ubi32_serdes_init_ports();
ubi32_serdes_early_console.index = port_index;
uart = &ubi32_serdes_ports[port_index];
t.c_cflag = cflag;
t.c_iflag = 0;
t.c_oflag = 0;
t.c_lflag = ICANON;
t.c_line = port_index;
ubi32_serdes_set_termios(uart, &t, &t);
return &ubi32_serdes_early_console;
}
#endif /* CONFIG_SERIAL_UBI32_SERDES_CONSOLE */
static struct uart_driver ubi32_serdes_reg = {
.owner = THIS_MODULE,
.driver_name = "ubi32_serdes",
.dev_name = UBI32_SERDES_NAME,
.major = UBI32_SERDES_MAJOR,
.minor = UBI32_SERDES_MINOR,
.nr = NR_PORTS,
.cons = UBI32_SERDES_CONSOLE,
};
static int ubi32_serdes_suspend(struct platform_device *dev, pm_message_t state)
{
struct uart_port *port = platform_get_drvdata(dev);
if (port)
uart_suspend_port(&ubi32_serdes_reg, port);
return 0;
}
static int ubi32_serdes_resume(struct platform_device *dev)
{
struct uart_port *port = platform_get_drvdata(dev);
if (port)
uart_resume_port(&ubi32_serdes_reg, port);
return 0;
}
static int ubi32_serdes_probe(struct platform_device *dev)
{
struct resource *res = dev->resource;
int i;
for (i = 0; i < dev->num_resources; i++, res++) {
if (res->flags & IORESOURCE_MEM) {
ubi32_serdes_resource[0].uart_base_addr = (void *) res->start;
}
else if (res->flags & IORESOURCE_IRQ) {
ubi32_serdes_resource[0].uart_irq = res->start;
}
else if (res->flags & UBICOM32_SUART_IORESOURCE_CLOCK) {
ubi32_serdes_resource[0].uart_clock = res->start;
}
}
ubi32_serdes_init_ports();
return 0;
}
static int ubi32_serdes_remove(struct platform_device *pdev)
{
struct uart_port *port = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
if (port)
uart_remove_one_port(&ubi32_serdes_reg, port);
return 0;
}
static struct platform_driver ubi32_serdes_driver = {
.remove = ubi32_serdes_remove,
.suspend = ubi32_serdes_suspend,
.resume = ubi32_serdes_resume,
.driver = {
.name = "ubicom32suart",
.owner = THIS_MODULE,
},
};
#ifndef MODULE
/*
* Called at boot time.
*
* You can specify IO base, IRQ, and clock for the serdes serial port
* using kernel command line "serdes=0xiobase,irq,clock". Values
* specified will be overwritten by platform device data, if present.
*/
static int __init ubi32_serdes_setup(char *str)
{
#define N_PARMS (4+1)
int ints[N_PARMS];
int i;
str = get_options(str, ARRAY_SIZE(ints), ints);
for (i = 0; i < N_PARMS; i++) {
if (i < ints[0]) {
if (i == 0) {
ubi32_serdes_resource[0].uart_base_addr = (void *) ints[i+1];
}
else if (i == 1) {
ubi32_serdes_resource[0].uart_irq = ints[i+1];
}
else if (i == 2) {
ubi32_serdes_resource[0].uart_clock = ints[i+1];
}
else if (i == 3) {
ubi32_serdes_default_baud_rate = ints[i+1];
}
}
}
return 1;
}
__setup("serdes=", ubi32_serdes_setup);
#endif
static int __init ubi32_serdes_init(void)
{
int ret;
pr_info("Serial: Ubicom32 serdes uart serial driver\n");
ret = platform_driver_probe(&ubi32_serdes_driver, ubi32_serdes_probe);
if (ret != 0) {
printk(KERN_INFO "serdes platform_driver_probe() failed: %d\n", ret);
return ret;
}
ubi32_serdes_init_ports();
ret = uart_register_driver(&ubi32_serdes_reg);
if ( ret == 0 ) {
ret = uart_add_one_port(&ubi32_serdes_reg, &ubi32_serdes_ports[0]);
if ( ret != 0 ) {
uart_unregister_driver(&ubi32_serdes_reg);
}
}
return ret;
}
static void __exit ubi32_serdes_exit(void)
{
platform_driver_unregister(&ubi32_serdes_driver);
uart_unregister_driver(&ubi32_serdes_reg);
}
module_init(ubi32_serdes_init);
module_exit(ubi32_serdes_exit);
MODULE_AUTHOR("Rainer Keller <rkeller@ubicom.com>");
MODULE_DESCRIPTION("Ubicom generic serial port driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_CHARDEV_MAJOR(UBI32_SERDES_MAJOR);
MODULE_ALIAS("platform:ubi32_serdes");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,267 @@
/*
* drivers/spi_spi_ubicom32_gpio.c
* Ubicom32 GPIO based SPI driver
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from (with many thanks):
* arch/m68knommu
* arch/blackfin
* arch/parisc
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>
#include <linux/gpio.h>
#include <asm/ubicom32-spi-gpio.h>
#define DRIVER_NAME "ubicom32-spi-gpio"
struct ubicom32_spi_gpio {
struct spi_bitbang bitbang;
struct ubicom32_spi_gpio_platform_data *pdata;
struct platform_device *dev;
};
/*
* The following 4 functions are used by EXPAND_BITBANG_TXRX to bitbang the data out.
*/
static inline void setsck(struct spi_device *dev, int on)
{
struct ubicom32_spi_gpio *usg = (struct ubicom32_spi_gpio *)spi_master_get_devdata(dev->master);
gpio_set_value(usg->pdata->pin_clk, on ? 1 : 0);
}
static inline void setmosi(struct spi_device *dev, int on)
{
struct ubicom32_spi_gpio *usg = (struct ubicom32_spi_gpio *)spi_master_get_devdata(dev->master);
gpio_set_value(usg->pdata->pin_mosi, on ? 1 : 0);
}
static inline u32 getmiso(struct spi_device *dev)
{
struct ubicom32_spi_gpio *usg = (struct ubicom32_spi_gpio *)spi_master_get_devdata(dev->master);
return gpio_get_value(usg->pdata->pin_miso) ? 1 : 0;
}
#define spidelay(x) ndelay(x)
#define EXPAND_BITBANG_TXRX
#include <linux/spi/spi_bitbang.h>
/*
* ubicom32_spi_gpio_txrx_mode0
*/
static u32 ubicom32_spi_gpio_txrx_mode0(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits)
{
return bitbang_txrx_be_cpha0(spi, nsecs, 0, word, bits);
}
/*
* ubicom32_spi_gpio_txrx_mode1
*/
static u32 ubicom32_spi_gpio_txrx_mode1(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits)
{
return bitbang_txrx_be_cpha1(spi, nsecs, 0, word, bits);
}
/*
* ubicom32_spi_gpio_txrx_mode2
*/
static u32 ubicom32_spi_gpio_txrx_mode2(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits)
{
return bitbang_txrx_be_cpha0(spi, nsecs, 1, word, bits);
}
/*
* ubicom32_spi_gpio_txrx_mode3
*/
static u32 ubicom32_spi_gpio_txrx_mode3(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits)
{
return bitbang_txrx_be_cpha1(spi, nsecs, 1, word, bits);
}
/*
* ubicom32_spi_gpio_chipselect
*/
static void ubicom32_spi_gpio_chipselect(struct spi_device *dev, int value)
{
struct ubicom32_spi_gpio_controller_data *cd = (struct ubicom32_spi_gpio_controller_data *)dev->controller_data;
unsigned int cs_polarity = dev->mode & SPI_CS_HIGH ? 1 : 0;
if (value == BITBANG_CS_ACTIVE) {
gpio_set_value(cd->pin_cs, cs_polarity);
return;
}
gpio_set_value(cd->pin_cs, !cs_polarity);
}
/*
* ubicom32_spi_gpio_probe
*/
static int ubicom32_spi_gpio_probe(struct platform_device *dev)
{
struct ubicom32_spi_gpio_platform_data *pdata;
struct spi_master *master;
struct ubicom32_spi_gpio *usg;
int ret;
master = spi_alloc_master(&dev->dev, sizeof(struct ubicom32_spi_gpio));
if (master == NULL) {
dev_err(&dev->dev, "failed to allocate spi master\n");
ret = -ENOMEM;
goto err;
}
usg = (struct ubicom32_spi_gpio *)spi_master_get_devdata(master);
platform_set_drvdata(dev, usg);
/*
* Copy in the platform data
*/
pdata = dev->dev.platform_data;
usg->pdata = dev->dev.platform_data;
/*
* Request the GPIO lines
*/
ret = gpio_request(pdata->pin_mosi, "spi-mosi");
if (ret) {
dev_err(&dev->dev, "Failed to allocate spi-mosi GPIO\n");
goto err;
}
ret = gpio_request(pdata->pin_miso, "spi-miso");
if (ret) {
dev_err(&dev->dev, "Failed to allocate spi-miso GPIO\n");
goto err_nomiso;
}
ret = gpio_request(pdata->pin_clk, "spi-clk");
if (ret) {
dev_err(&dev->dev, "Failed to allocate spi-clk GPIO\n");
goto err_noclk;
}
/*
* Setup spi-bitbang adaptor
*/
usg->bitbang.flags |= SPI_CS_HIGH;
usg->bitbang.master = spi_master_get(master);
usg->bitbang.master->bus_num = pdata->bus_num;
usg->bitbang.master->num_chipselect = pdata->num_chipselect;
usg->bitbang.chipselect = ubicom32_spi_gpio_chipselect;
usg->bitbang.txrx_word[SPI_MODE_0] = ubicom32_spi_gpio_txrx_mode0;
usg->bitbang.txrx_word[SPI_MODE_1] = ubicom32_spi_gpio_txrx_mode1;
usg->bitbang.txrx_word[SPI_MODE_2] = ubicom32_spi_gpio_txrx_mode2;
usg->bitbang.txrx_word[SPI_MODE_3] = ubicom32_spi_gpio_txrx_mode3;
/*
* Setup the GPIO pins
*/
gpio_direction_output(pdata->pin_clk, pdata->clk_default);
gpio_direction_output(pdata->pin_mosi, 0);
gpio_direction_input(pdata->pin_miso);
/*
* Ready to go
*/
ret = spi_bitbang_start(&usg->bitbang);
if (ret) {
goto err_no_bitbang;
}
return 0;
err_no_bitbang:
spi_master_put(usg->bitbang.master);
gpio_free(pdata->pin_clk);
err_noclk:
gpio_free(pdata->pin_miso);
err_nomiso:
gpio_free(pdata->pin_mosi);
err:
return ret;
}
/*
* ubicom32_spi_gpio_remove
*/
static int ubicom32_spi_gpio_remove(struct platform_device *dev)
{
struct ubicom32_spi_gpio *sp = platform_get_drvdata(dev);
spi_bitbang_stop(&sp->bitbang);
spi_master_put(sp->bitbang.master);
return 0;
}
/*
* Work with hotplug and coldplug
*/
MODULE_ALIAS("platform:ubicom32_spi_gpio");
static struct platform_driver ubicom32_spi_gpio_drv = {
.probe = ubicom32_spi_gpio_probe,
.remove = ubicom32_spi_gpio_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
};
/*
* ubicom32_spi_gpio_init
*/
static int __init ubicom32_spi_gpio_init(void)
{
return platform_driver_register(&ubicom32_spi_gpio_drv);
}
/*
* ubicom32_spi_gpio_exit
*/
static void __exit ubicom32_spi_gpio_exit(void)
{
platform_driver_unregister(&ubicom32_spi_gpio_drv);
}
module_init(ubicom32_spi_gpio_init);
module_exit(ubicom32_spi_gpio_exit);
MODULE_DESCRIPTION("Ubicom32 SPI-GPIO Driver");
MODULE_AUTHOR("Pat Tjin, <@ubicom.com>");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,288 @@
/*
* drivers/uio/uio_ubicom32ring.c
*
* Userspace I/O platform driver for Ubicom32 ring buffers
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* Based on uio_ubicom32ring.c by Magnus Damm
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*/
#include <linux/platform_device.h>
#include <linux/uio_driver.h>
#include <linux/spinlock.h>
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/stringify.h>
#include <asm/ip5000.h>
#include <asm/ubicom32ring.h>
#define DRIVER_NAME "uio_ubicom32ring"
struct uio_ubicom32ring_data {
struct uio_info *uioinfo;
struct uio_ubicom32ring_regs *regs;
/*
* IRQ used to kick the ring buffer
*/
int irq_tx;
int irq_rx;
spinlock_t lock;
unsigned long flags;
char name[0];
};
static irqreturn_t uio_ubicom32ring_handler(int irq, struct uio_info *dev_info)
{
struct uio_ubicom32ring_data *priv = dev_info->priv;
/* Just disable the interrupt in the interrupt controller, and
* remember the state so we can allow user space to enable it later.
*/
if (!test_and_set_bit(0, &priv->flags))
disable_irq_nosync(irq);
return IRQ_HANDLED;
}
static int uio_ubicom32ring_irqcontrol(struct uio_info *dev_info, s32 irq_on)
{
struct uio_ubicom32ring_data *priv = dev_info->priv;
unsigned long flags;
/* Allow user space to enable and disable the interrupt
* in the interrupt controller, but keep track of the
* state to prevent per-irq depth damage.
*
* Serialize this operation to support multiple tasks.
*/
spin_lock_irqsave(&priv->lock, flags);
if (irq_on & 2) {
/*
* Kick the ring buffer (if we can)
*/
if (priv->irq_tx != 0xFF) {
ubicom32_set_interrupt(priv->irq_tx);
}
}
if (priv->irq_rx != 0xFF) {
if (irq_on & 1) {
if (test_and_clear_bit(0, &priv->flags))
enable_irq(dev_info->irq);
} else {
if (!test_and_set_bit(0, &priv->flags))
disable_irq(dev_info->irq);
}
}
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
static int uio_ubicom32ring_probe(struct platform_device *pdev)
{
struct uio_info *uioinfo;
struct uio_mem *uiomem;
struct uio_ubicom32ring_data *priv;
struct uio_ubicom32ring_regs *regs;
struct resource *mem_resource;
struct resource *irqtx_resource;
struct resource *irqrx_resource;
int ret = -EINVAL;
int i;
uioinfo = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
if (!uioinfo) {
dev_err(&pdev->dev, "unable to kmalloc\n");
return -ENOMEM;
}
/*
* Allocate private data with some string space after
*/
i = sizeof(DRIVER_NAME) + 1;
i += pdev->dev.platform_data ? strlen(pdev->dev.platform_data) : 0;
priv = kzalloc(sizeof(struct uio_ubicom32ring_data) + i, GFP_KERNEL);
if (!priv) {
dev_err(&pdev->dev, "unable to kmalloc\n");
kfree(uioinfo);
return -ENOMEM;
}
strcpy(priv->name, DRIVER_NAME ":");
if (pdev->dev.platform_data) {
strcat(priv->name, pdev->dev.platform_data);
}
uioinfo->priv = priv;
uioinfo->name = priv->name;
uioinfo->version = "0.1";
priv->uioinfo = uioinfo;
spin_lock_init(&priv->lock);
priv->flags = 0; /* interrupt is enabled to begin with */
/*
* Get our resources, the IRQ_TX and IRQ_RX are optional.
*/
priv->irq_tx = 0xFF;
irqtx_resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (irqtx_resource) {
priv->irq_tx = irqtx_resource->start;
}
uioinfo->irq = -1;
priv->irq_rx = 0xFF;
irqrx_resource = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
if (irqrx_resource) {
priv->irq_rx = irqrx_resource->start;
uioinfo->irq = priv->irq_rx;
uioinfo->handler = uio_ubicom32ring_handler;
}
mem_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_resource || !mem_resource->start) {
dev_err(&pdev->dev, "No valid memory resource found\n");
ret = -ENODEV;
goto fail;
}
regs = (struct uio_ubicom32ring_regs *)mem_resource->start;
priv->regs = regs;
if (regs->version != UIO_UBICOM32RING_REG_VERSION) {
dev_err(&pdev->dev, "version %d not supported\n", regs->version);
ret = -ENODEV;
goto fail;
}
/*
* First range is the shared register space, if we have any
*/
uiomem = &uioinfo->mem[0];
if (regs->regs_size) {
uiomem->memtype = UIO_MEM_PHYS;
uiomem->addr = (u32_t)regs->regs;
uiomem->size = regs->regs_size;
++uiomem;
dev_info(&pdev->dev, "regs:%p (%u) / rings: %d found\n", regs->regs, regs->regs_size, regs->num_rings);
} else {
dev_info(&pdev->dev, "rings: %d found\n", regs->num_rings);
}
/*
* The rest of the range correspond to the rings
*/
for (i = 0; i < regs->num_rings; i++) {
dev_info(&pdev->dev, "\t%d: entries:%d ring:%p\n",
i, regs->rings[i]->entries, &(regs->rings[i]->ring));
if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) {
dev_warn(&pdev->dev, "device has more than "
__stringify(MAX_UIO_MAPS)
" I/O memory resources.\n");
break;
}
uiomem->memtype = UIO_MEM_PHYS;
uiomem->addr = (u32_t)&(regs->rings[i]->head);
uiomem->size = (regs->rings[i]->entries * sizeof(u32_t)) +
sizeof(struct uio_ubicom32ring_desc);
++uiomem;
}
while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) {
uiomem->size = 0;
++uiomem;
}
/* This driver requires no hardware specific kernel code to handle
* interrupts. Instead, the interrupt handler simply disables the
* interrupt in the interrupt controller. User space is responsible
* for performing hardware specific acknowledge and re-enabling of
* the interrupt in the interrupt controller.
*
* Interrupt sharing is not supported.
*/
uioinfo->irq_flags = IRQF_DISABLED;
uioinfo->irqcontrol = uio_ubicom32ring_irqcontrol;
ret = uio_register_device(&pdev->dev, priv->uioinfo);
if (ret) {
dev_err(&pdev->dev, "unable to register uio device\n");
goto fail;
}
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "'%s' using irq: rx %d tx %d, regs %p\n",
priv->name, priv->irq_rx, priv->irq_tx, priv->regs);
return 0;
fail:
kfree(uioinfo);
kfree(priv);
return ret;
}
static int uio_ubicom32ring_remove(struct platform_device *pdev)
{
struct uio_ubicom32ring_data *priv = platform_get_drvdata(pdev);
uio_unregister_device(priv->uioinfo);
kfree(priv->uioinfo);
kfree(priv);
return 0;
}
static struct platform_driver uio_ubicom32ring = {
.probe = uio_ubicom32ring_probe,
.remove = uio_ubicom32ring_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
};
static int __init uio_ubicom32ring_init(void)
{
return platform_driver_register(&uio_ubicom32ring);
}
static void __exit uio_ubicom32ring_exit(void)
{
platform_driver_unregister(&uio_ubicom32ring);
}
module_init(uio_ubicom32ring_init);
module_exit(uio_ubicom32ring_exit);
MODULE_AUTHOR("Patrick Tjin");
MODULE_DESCRIPTION("Userspace I/O driver for Ubicom32 ring buffers");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRIVER_NAME);

View File

@@ -0,0 +1,156 @@
/*
* drivers/usb/musb/ubi32_usb.c
* Ubicom32 usb controller driver.
*
* (C) Copyright 2009, Ubicom, Inc.
* Copyright (C) 2005-2006 by Texas Instruments
*
* Derived from the Texas Instruments Inventra Controller Driver for Linux.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from (with many thanks):
* arch/m68knommu
* arch/blackfin
* arch/parisc
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <asm/io.h>
#include <asm/ip5000.h>
#include "musb_core.h"
void musb_platform_enable(struct musb *musb)
{
}
void musb_platform_disable(struct musb *musb)
{
}
int musb_platform_set_mode(struct musb *musb, u8 musb_mode) {
return 0;
}
static void ip5k_usb_hcd_vbus_power(struct musb *musb, int is_on, int sleeping)
{
}
static void ip5k_usb_hcd_set_vbus(struct musb *musb, int is_on)
{
u8 devctl;
/* HDRC controls CPEN, but beware current surges during device
* connect. They can trigger transient overcurrent conditions
* that must be ignored.
*/
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
if (is_on) {
musb->is_active = 1;
musb->xceiv.default_a = 1;
musb->xceiv.state = OTG_STATE_A_WAIT_VRISE;
devctl |= MUSB_DEVCTL_SESSION;
MUSB_HST_MODE(musb);
} else {
musb->is_active = 0;
/* NOTE: we're skipping A_WAIT_VFALL -> A_IDLE and
* jumping right to B_IDLE...
*/
musb->xceiv.default_a = 0;
musb->xceiv.state = OTG_STATE_B_IDLE;
devctl &= ~MUSB_DEVCTL_SESSION;
MUSB_DEV_MODE(musb);
}
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
DBG(1, "VBUS %s, devctl %02x "
/* otg %3x conf %08x prcm %08x */ "\n",
otg_state_string(musb),
musb_readb(musb->mregs, MUSB_DEVCTL));
}
static int ip5k_usb_hcd_set_power(struct otg_transceiver *x, unsigned mA)
{
return 0;
}
static int musb_platform_resume(struct musb *musb);
int __init musb_platform_init(struct musb *musb)
{
#ifdef CONFIG_UBICOM32_V4
u32_t chip_id;
asm volatile (
"move.4 %0, CHIP_ID \n\t"
: "=r" (chip_id)
);
if (chip_id == 0x30001) {
*((u32_t *)(GENERAL_CFG_BASE + GEN_USB_PHY_TEST)) &= ~(1 << 30);
udelay(1);
*((u32_t *)(GENERAL_CFG_BASE + GEN_USB_PHY_TEST)) &= ~(1 << 31);
} else {
*((u32_t *)(GENERAL_CFG_BASE + GEN_USB_PHY_TEST)) &= ~(1 << 17);
udelay(1);
*((u32_t *)(GENERAL_CFG_BASE + GEN_USB_PHY_TEST)) &= ~(1 << 14);
}
#endif
*((u32_t *)(GENERAL_CFG_BASE + GEN_USB_PHY_CFG)) |= ((1 << 14) | (1 <<15));
/* The i-clk is AUTO gated. Hence there is no need
* to disable it until the driver is shutdown */
clk_enable(musb->clock);
musb_platform_resume(musb);
ip5k_usb_hcd_vbus_power(musb, musb->board_mode == MUSB_HOST, 1);
if (is_host_enabled(musb))
musb->board_set_vbus = ip5k_usb_hcd_set_vbus;
if (is_peripheral_enabled(musb))
musb->xceiv.set_power = ip5k_usb_hcd_set_power;
return 0;
}
int musb_platform_suspend(struct musb *musb)
{
return 0;
}
int musb_platform_resume(struct musb *musb)
{
return 0;
}
int musb_platform_exit(struct musb *musb)
{
ip5k_usb_hcd_vbus_power(musb, 0 /*off*/, 1);
musb_platform_suspend(musb);
return 0;
}

View File

@@ -0,0 +1,399 @@
/*
* drivers/video/backlight/ubicom32bl.c
* Backlight driver for the Ubicom32 platform
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from (with many thanks):
* arch/m68knommu
* arch/blackfin
* arch/parisc
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/backlight.h>
#include <linux/fb.h>
#include <asm/ubicom32bl.h>
#include <asm/ip5000.h>
#define DRIVER_NAME "ubicom32bl"
#define UBICOM32BL_MAX_BRIGHTNESS 255
struct ubicom32bl_data {
/*
* Pointer to the platform data structure. Keep this around since we need values
* from it to set the backlight intensity.
*/
const struct ubicom32bl_platform_data *pdata;
/*
* Backlight device, we have to save this for use when we remove ourselves.
*/
struct backlight_device *bldev;
/*
* Current intensity, used for get_intensity.
*/
int cur_intensity;
/*
* Init function for PWM
*/
int (*init_fn)(struct ubicom32bl_data *);
/*
* Set intensity function depending on the backlight type
*/
int (*set_intensity_fn)(struct ubicom32bl_data *, int);
};
/*
* ubicom32bl_set_intensity_gpio
*/
static int ubicom32bl_set_intensity_gpio(struct ubicom32bl_data *ud, int intensity)
{
ud->cur_intensity = intensity ? 255 : 0;
if (intensity) {
// set gpio
return 0;
}
// clear gpio
return 0;
}
/*
* ubicom32bl_set_intensity_hw
*/
static int ubicom32bl_set_intensity_hw(struct ubicom32bl_data *ud, int intensity)
{
u16_t period = ud->pdata->pwm_period;
u16_t duty;
/*
* Calculate the new duty cycle
*/
duty = (period * intensity) / (UBICOM32BL_MAX_BRIGHTNESS + 1);
/*
* Set the new duty cycle
*/
switch (ud->pdata->pwm_channel) {
case 0:
/*
* Channel 0 is in the lower half of PORT C ctl0 and ctl1
*/
UBICOM32_IO_PORT(RC)->ctl1 = (ud->pdata->pwm_period << 16) | duty;
break;
case 1:
/*
* Channel 1 is in the upper half of PORT C ctl0 and ctl2
*/
UBICOM32_IO_PORT(RC)->ctl2 = (ud->pdata->pwm_period << 16) | duty;
break;
case 2:
/*
* Channel 2 is in PORT H ctl0 and ctl1
*/
UBICOM32_IO_PORT(RH)->ctl1 = (ud->pdata->pwm_period << 16) | duty;
break;
}
ud->cur_intensity = intensity;
return 0;
}
/*
* ubicom32bl_set_intensity
*/
static int ubicom32bl_set_intensity(struct backlight_device *bd)
{
struct ubicom32bl_data *ud = (struct ubicom32bl_data *)bl_get_data(bd);
int intensity = bd->props.brightness;
/*
* If we're blanked the the intensity doesn't matter.
*/
if ((bd->props.power != FB_BLANK_UNBLANK) || (bd->props.fb_blank != FB_BLANK_UNBLANK)) {
intensity = 0;
}
/*
* Check for inverted backlight.
*/
if (ud->pdata->invert) {
intensity = UBICOM32BL_MAX_BRIGHTNESS - intensity;
}
if (ud->set_intensity_fn) {
return ud->set_intensity_fn(ud, intensity);
}
return -ENXIO;
}
/*
* ubicom32bl_get_intensity
* Return the current intensity of the backlight.
*/
static int ubicom32bl_get_intensity(struct backlight_device *bd)
{
struct ubicom32bl_data *ud = (struct ubicom32bl_data *)bl_get_data(bd);
return ud->cur_intensity;
}
/*
* ubicom32bl_init_hw_pwm
* Set the appropriate PWM registers
*/
static int ubicom32bl_init_hw_pwm(struct ubicom32bl_data *ud)
{
/*
* bit 13: enable
*/
u16_t pwm_cfg = (1 << 13) | (ud->pdata->pwm_prescale << 8) ;
switch (ud->pdata->pwm_channel) {
case 0:
/*
* Channel 0 is in the lower half of PORT C ctl0 and ctl1 (PA5)
*/
UBICOM32_IO_PORT(RC)->ctl0 &= ~0xFFFF;
UBICOM32_IO_PORT(RC)->ctl0 |= pwm_cfg;
UBICOM32_IO_PORT(RC)->ctl1 = ud->pdata->pwm_period << 16;
/*
* If the port function is not set, set it to GPIO/PWM
*/
if (!UBICOM32_IO_PORT(RA)->function) {
UBICOM32_IO_PORT(RA)->function = 3;
}
break;
case 1:
/*
* Channel 1 is in the upper half of PORT C ctl0 and ctl2 (PE4)
*/
UBICOM32_IO_PORT(RC)->ctl0 &= ~0xFFFF0000;
UBICOM32_IO_PORT(RC)->ctl0 |= (pwm_cfg << 16);
UBICOM32_IO_PORT(RC)->ctl2 = ud->pdata->pwm_period << 16;
/*
* If the port function is not set, set it to GPIO/ExtIOInt
*/
if (!UBICOM32_IO_PORT(RE)->function) {
UBICOM32_IO_PORT(RE)->function = 3;
}
break;
case 2:
/*
* Channel 2 is in PORT H ctl0 and ctl1 (PD0)
*/
UBICOM32_IO_PORT(RH)->ctl0 &= ~0xFFFF0000;
UBICOM32_IO_PORT(RH)->ctl0 = pwm_cfg;
UBICOM32_IO_PORT(RH)->ctl1 = ud->pdata->pwm_period << 16;
/*
* If the port function is not set, set it to GPIO
*/
if (!UBICOM32_IO_PORT(RD)->function) {
UBICOM32_IO_PORT(RD)->function = 3;
}
break;
}
return 0;
}
/*
* ubicom32bl_init_gpio
* Allocate the appropriate GPIO
*/
static int ubicom32bl_init_gpio(struct ubicom32bl_data *ud)
{
return 0;
}
static struct backlight_ops ubicom32bl_ops = {
.get_brightness = ubicom32bl_get_intensity,
.update_status = ubicom32bl_set_intensity,
};
/*
* ubicom32bl_probe
*/
static int ubicom32bl_probe(struct platform_device *pdev)
{
const struct ubicom32bl_platform_data *pdata = pdev->dev.platform_data;
struct ubicom32bl_data *ud;
struct backlight_device *bldev;
int retval;
/*
* Check to see if we have any platform data, if we don't then the backlight is not
* configured on this device.
*/
if (!pdata) {
return -ENODEV;
}
/*
* Allocate our private data
*/
ud = kzalloc(sizeof(struct ubicom32bl_data), GFP_KERNEL);
if (!ud) {
return -ENOMEM;
}
ud->pdata = pdata;
/*
* Check to see that the platform data is valid for this driver
*/
switch (pdata->type) {
case UBICOM32BL_TYPE_PWM:
{
/*
* Make sure we have a PWM peripheral
*/
u32_t chipid;
asm volatile (
"move.4 %0, CHIP_ID \n\t"
: "=r" (chipid)
);
if (chipid != 0x00030001) {
retval = -ENODEV;
goto fail;
}
if (pdata->pwm_channel > 3) {
retval = -ENODEV;
goto fail;
}
if (pdata->pwm_prescale > 16) {
retval = -EINVAL;
goto fail;
}
ud->init_fn = ubicom32bl_init_hw_pwm;
ud->set_intensity_fn = ubicom32bl_set_intensity_hw;
break;
}
case UBICOM32BL_TYPE_PWM_HRT:
// For now, PWM HRT devices are treated as binary lights.
case UBICOM32BL_TYPE_BINARY:
ud->init_fn = ubicom32bl_init_gpio;
ud->set_intensity_fn = ubicom32bl_set_intensity_gpio;
break;
}
/*
* Register our backlight device
*/
bldev = backlight_device_register(DRIVER_NAME, &pdev->dev, ud, &ubicom32bl_ops);
if (IS_ERR(bldev)) {
retval = PTR_ERR(bldev);
goto fail;
}
ud->bldev = bldev;
ud->cur_intensity = pdata->default_intensity;
platform_set_drvdata(pdev, ud);
/*
* Start up the backlight at the prescribed default intensity
*/
bldev->props.power = FB_BLANK_UNBLANK;
bldev->props.max_brightness = UBICOM32BL_MAX_BRIGHTNESS;
bldev->props.brightness = pdata->default_intensity;
if (ud->init_fn) {
if (ud->init_fn(ud) != 0) {
retval = -ENODEV;
backlight_device_unregister(ud->bldev);
goto fail;
}
}
ubicom32bl_set_intensity(bldev);
printk(KERN_INFO DRIVER_NAME ": Backlight driver started\n");
return 0;
fail:
platform_set_drvdata(pdev, NULL);
kfree(ud);
return retval;
}
/*
* ubicom32bl_remove
*/
static int __exit ubicom32bl_remove(struct platform_device *pdev)
{
struct ubicom32bl_data *ud = platform_get_drvdata(pdev);
backlight_device_unregister(ud->bldev);
platform_set_drvdata(pdev, NULL);
kfree(ud);
return 0;
}
static struct platform_driver ubicom32bl_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.remove = __exit_p(ubicom32bl_remove),
};
/*
* ubicom32bl_init
*/
static int __init ubicom32bl_init(void)
{
return platform_driver_probe(&ubicom32bl_driver, ubicom32bl_probe);
}
module_init(ubicom32bl_init);
/*
* ubicom32bl_exit
*/
static void __exit ubicom32bl_exit(void)
{
platform_driver_unregister(&ubicom32bl_driver);
}
module_exit(ubicom32bl_exit);
MODULE_AUTHOR("Patrick Tjin <@ubicom.com>");
MODULE_DESCRIPTION("Ubicom32 backlight driver");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,372 @@
/*
* drivers/video/ubicom32lcd.c
* LCD initilization code
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <asm/ip5000.h>
#include <asm/gpio.h>
#include <asm/ubicom32lcd.h>
#include "ubicom32lcd.h"
#define DRIVER_NAME "ubicom32lcd"
struct ubicom32lcd_data {
const struct ubicom32lcd_panel *panel;
int pin_cs;
int pin_rd;
int pin_rs;
int pin_wr;
int pin_reset;
struct ubicom32_io_port *port_data;
int data_shift;
};
/*
* ubicom32lcd_write
* Performs a write cycle on the bus (assumes CS asserted, RD & WR set)
*/
static void ubicom32lcd_write(struct ubicom32lcd_data *ud, int command, u16 data)
{
if (command) {
UBICOM32_GPIO_SET_PIN_LOW(ud->pin_rs);
} else {
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_rs);
}
asm volatile (
"or.4 4(%[port]), 4(%[port]), %[mask] \n\t"
"not.4 %[mask], %[mask] \n\t"
"and.4 8(%[port]), 8(%[port]), %[mask] \n\t"
"or.4 8(%[port]), 8(%[port]), %[cmd] \n\t"
:
: [port] "a" (ud->port_data),
[mask] "d" (0xFFFF << ud->data_shift),
[cmd] "d" (data << ud->data_shift)
: "cc"
);
UBICOM32_GPIO_SET_PIN_LOW(ud->pin_wr);
//ndelay(50);
udelay(1);
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_wr);
udelay(1);
//ndelay(50);
}
/*
* ubicom32lcd_read_data
* Performs a read cycle on the bus (assumes CS asserted, RD & WR set)
*/
static u16 ubicom32lcd_read_data(struct ubicom32lcd_data *ud)
{
u32_t data;
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_rs);
asm volatile (
"and.4 4(%[port]), 4(%[port]), %[mask]\n\t"
:
: [port] "a" (ud->port_data),
[mask] "d" (~(0xFFFF << ud->data_shift))
: "cc"
);
UBICOM32_GPIO_SET_PIN_LOW(ud->pin_rd);
ndelay(300);
asm volatile (
"lsr.4 %[data], 12(%[port]), %[shamt] \n\t"
"and.4 %[data], %[data], %[mask] \n\t"
: [data] "=d" (data)
: [port] "a" (ud->port_data),
[mask] "d" (0xFFFF),
[shamt] "d" (ud->data_shift)
: "cc"
);
ndelay(200);
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_rd);
ndelay(500);
return data;
}
/*
* ubicom32lcd_execute
* Executes a script for performing operations on the LCD (assumes CS set)
*/
static void ubicom32lcd_execute(struct ubicom32lcd_data *ud, const struct ubicom32lcd_step *script)
{
while (1) {
switch (script->op) {
case LCD_STEP_CMD:
ubicom32lcd_write(ud, 1, script->cmd);
break;
case LCD_STEP_DATA:
ubicom32lcd_write(ud, 0, script->data);
break;
case LCD_STEP_CMD_DATA:
ubicom32lcd_write(ud, 1, script->cmd);
ubicom32lcd_write(ud, 0, script->data);
break;
case LCD_STEP_SLEEP:
udelay(script->data);
break;
case LCD_STEP_DONE:
return;
}
script++;
}
}
/*
* ubicom32lcd_goto
* Places the gram pointer at a specific X, Y address
*/
static void ubicom32lcd_goto(struct ubicom32lcd_data *ud, int x, int y)
{
ubicom32lcd_write(ud, 1, ud->panel->horz_reg);
ubicom32lcd_write(ud, 0, x);
ubicom32lcd_write(ud, 1, ud->panel->vert_reg);
ubicom32lcd_write(ud, 0, y);
ubicom32lcd_write(ud, 1, ud->panel->gram_reg);
}
/*
* ubicom32lcd_panel_init
* Initializes the lcd panel.
*/
static int ubicom32lcd_panel_init(struct ubicom32lcd_data *ud)
{
u16 id;
UBICOM32_GPIO_SET_PIN_LOW(ud->pin_reset);
UBICOM32_GPIO_SET_PIN_OUTPUT(ud->pin_reset);
UBICOM32_GPIO_ENABLE(ud->pin_reset);
asm volatile (
"or.4 0x50(%[port]), 0x50(%[port]), %[mask] \n\t"
"not.4 %[mask], %[mask] \n\t"
"and.4 0x04(%[port]), 0x04(%[port]), %[mask] \n\t"
:
: [port] "a" (ud->port_data),
[mask] "d" (0xFFFF << ud->data_shift)
: "cc"
);
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_rs);
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_rd);
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_wr);
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_cs);
UBICOM32_GPIO_SET_PIN_OUTPUT(ud->pin_rs);
UBICOM32_GPIO_SET_PIN_OUTPUT(ud->pin_rd);
UBICOM32_GPIO_SET_PIN_OUTPUT(ud->pin_wr);
UBICOM32_GPIO_SET_PIN_OUTPUT(ud->pin_cs);
UBICOM32_GPIO_ENABLE(ud->pin_rs);
UBICOM32_GPIO_ENABLE(ud->pin_rd);
UBICOM32_GPIO_ENABLE(ud->pin_wr);
UBICOM32_GPIO_ENABLE(ud->pin_cs);
udelay(20);
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_reset);
udelay(20);
UBICOM32_GPIO_SET_PIN_LOW(ud->pin_cs);
id = ubicom32lcd_read_data(ud);
/*
* We will try to figure out what kind of panel we have if we were not told.
*/
if (!ud->panel) {
const struct ubicom32lcd_panel **p = ubicom32lcd_panels;
while (*p) {
if ((*p)->id && ((*p)->id == id)) {
break;
}
p++;
}
if (!*p) {
printk(KERN_WARNING DRIVER_NAME ":Could not find compatible panel, id=%x\n", id);
return -ENODEV;
}
ud->panel = *p;
}
/*
* Make sure panel ID matches if we were supplied a panel type
*/
if (ud->panel->id && (ud->panel->id != id)) {
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_cs);
return -ENODEV;
}
ubicom32lcd_execute(ud, ud->panel->init_seq);
ubicom32lcd_goto(ud, 0, 0);
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_cs);
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_rd);
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_wr);
UBICOM32_GPIO_SET_PIN_HIGH(ud->pin_rs);
printk(KERN_INFO DRIVER_NAME ": Initialized panel %s\n", ud->panel->desc);
return 0;
}
/*
* ubicom32lcd_probe
*/
static int ubicom32lcd_probe(struct platform_device *pdev)
{
const struct ubicom32lcd_platform_data *pdata = pdev->dev.platform_data;
struct ubicom32lcd_data *ud;
int retval;
/*
* Allocate our private data
*/
ud = kzalloc(sizeof(struct ubicom32lcd_data), GFP_KERNEL);
if (!ud) {
return -ENOMEM;
}
if (pdata) {
ud->pin_cs = pdata->pin_cs;
ud->pin_rd = pdata->pin_rd;
ud->pin_wr = pdata->pin_wr;
ud->pin_rs = pdata->pin_rs;
ud->pin_reset = pdata->pin_reset;
ud->port_data = pdata->port_data;
ud->data_shift = pdata->data_shift;
} else {
/*
* Defaults
*/
ud->pin_cs = GPIO_RD_4;
ud->pin_rd = GPIO_RD_5;
ud->pin_rs = GPIO_RD_3;
ud->pin_wr = GPIO_RD_2;
ud->pin_reset = GPIO_RD_7;
ud->port_data = (struct ubicom32_io_port *)RI;
ud->data_shift = 0;
}
/*
* Initialize the display
*/
retval = ubicom32lcd_panel_init(ud);
if (retval) {
kfree(ud);
return retval;
}
printk(KERN_INFO DRIVER_NAME ": LCD initialized\n");
return 0;
}
/*
* ubicom32lcd_remove
*/
static int __exit ubicom32lcd_remove(struct platform_device *pdev)
{
struct ubicom32lcd_data *ud = platform_get_drvdata(pdev);
kfree(ud);
return 0;
}
static struct platform_driver ubicom32lcd_driver = {
.probe = ubicom32lcd_probe,
.remove = ubicom32lcd_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.remove = __exit_p(ubicom32lcd_remove),
};
static struct platform_device *ubicom32lcd_device;
/*
* ubicom32lcd_init
*/
static int __init ubicom32lcd_init(void)
{
int res;
res = platform_driver_register(&ubicom32lcd_driver);
if (res == 0) {
ubicom32lcd_device = platform_device_alloc(DRIVER_NAME, 0);
if (ubicom32lcd_device) {
res = platform_device_add(ubicom32lcd_device);
} else {
res = -ENOMEM;
}
if (res) {
platform_device_put(ubicom32lcd_device);
platform_driver_unregister(&ubicom32lcd_driver);
}
}
return res;
}
module_init(ubicom32lcd_init);
/*
* ubicom32lcd_exit
*/
static void __exit ubicom32lcd_exit(void)
{
platform_device_unregister(ubicom32lcd_device);
platform_driver_unregister(&ubicom32lcd_driver);
}
module_exit(ubicom32lcd_exit);
MODULE_AUTHOR("Patrick Tjin <@ubicom.com>");
MODULE_DESCRIPTION("Ubicom32 LCD driver");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,546 @@
/*
* ubicom32lcd.h
* Ubicom32 lcd panel drivers
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* This Ubicom32 library is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* This Ubicom32 library 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*/
#ifndef _UBICOM32LCD_H_
#define _UBICOM32LCD_H_
enum ubicom32lcd_op {
/*
* Sleep for (data) ms
*/
LCD_STEP_SLEEP,
/*
* Execute write of command
*/
LCD_STEP_CMD,
/*
* Execute write of data
*/
LCD_STEP_DATA,
/*
* Execute write of command/data
*/
LCD_STEP_CMD_DATA,
/*
* Script done
*/
LCD_STEP_DONE,
};
struct ubicom32lcd_step {
enum ubicom32lcd_op op;
u16 cmd;
u16 data;
};
struct ubicom32lcd_panel {
const struct ubicom32lcd_step *init_seq;
const char *desc;
u32 xres;
u32 yres;
u32 stride;
u32 flags;
u16 id;
u16 horz_reg;
u16 vert_reg;
u16 gram_reg;
};
#ifdef CONFIG_LCD_UBICOM32_CFAF240320KTTS
static const struct ubicom32lcd_step cfaf240320ktts_init_0[] = {
{LCD_STEP_CMD_DATA, 0x0001, 0x0000,}, // Driver Output Control Register (R01h) Page 14, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0002, 0x0700,}, // LCD Driving Waveform Control (R02h) Page 15, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0003, 0x50A0,}, // Entry Mode (R03h) 0 degrees
{LCD_STEP_CMD_DATA, 0x0004, 0x0000,}, // Scaling Control register (R04h) Page 16, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0008, 0x0207,}, // Display Control 2 (R08h) Page 17, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0009, 0x0000,}, // Display Control 3 (R09h) Page 18, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x000A, 0x0000,}, // Frame Cycle Control (R0Ah) Page 19, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x000C, 0x0000,}, // External Display Interface Control 1 (R0Ch) Page 20, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x000D, 0x0000,}, // Frame Maker Position (R0Dh) Page 21, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x000F, 0x0000,}, // External Display Interface Control 2 (R0Fh) Page 21, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0010, 0x0000,}, // Power Control 1 (R10h) Page 22, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0011, 0x0007,}, // Power Control 2 (R11h) Page 23, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0012, 0x0000,}, // Power Control 3 (R12h) Page 24, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0013, 0x0000,}, // Power Control 4 (R13h) Page 25, SPFD5408B Datasheet
{LCD_STEP_SLEEP, 0, 200},
{LCD_STEP_CMD_DATA, 0x0007, 0x0101,}, // Display Control (R07h) Page 16, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0010, 0x12B0,}, // Power Control 1 (R10h) Page 22, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0011, 0x0007,}, // Power Control 2 (R11h) Page 23, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0012, 0x01BB,}, // Power Control 3 (R12h) Page 24, SPFD5408B Datasheet
{LCD_STEP_SLEEP, 0, 50},
{LCD_STEP_CMD_DATA, 0x0013, 0x1300,}, // Power Control 4 (R13h) Page 25, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0029, 0x0010,}, // NVM read data 2 (R29h) Page 30, SPFD5408B Datasheet
{LCD_STEP_SLEEP, 0, 50},
{LCD_STEP_CMD_DATA, 0x0030, 0x000A,}, // Gamma Control 1 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0031, 0x1326,}, // Gamma Control 2 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0032, 0x0A29,}, // Gamma Control 3 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0033, 0x290A,}, // Gamma Control 4 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0034, 0x2613,}, // Gamma Control 5 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0035, 0x0A0A,}, // Gamma Control 6 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0036, 0x1E03,}, // Gamma Control 7 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0037, 0x031E,}, // Gamma Control 8 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0038, 0x0706,}, // Gamma Control 9 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0039, 0x0303,}, // Gamma Control 10 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003A, 0x0E04,}, // Gamma Control 11 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003B, 0x0E01,}, // Gamma Control 12 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003C, 0x010E,}, // Gamma Control 13 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003D, 0x040E,}, // Gamma Control 14 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003E, 0x0303,}, // Gamma Control 15 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003F, 0x0607,}, // Gamma Control 16 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0050, 0x0000,}, // Window Horizontal RAM Address Start (R50h) Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0051, 0x00EF,}, // Window Horizontal RAM Address End (R51h) Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0052, 0x0000,}, // Window Vertical RAM Address Start (R52h) Page 33, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0053, 0x013F,}, // Window Vertical RAM Address End (R53h) Page 33, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0060, 0x2700,}, // Driver Output Control (R60h) Page 33, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0061, 0x0001,}, // Driver Output Control (R61h) Page 35, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x006A, 0x0000,}, // Vertical Scroll Control (R6Ah) Page 35, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0080, 0x0000,}, // Display Position - Partial Display 1 (R80h) Page 35, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0081, 0x0000,}, // RAM Address Start - Partial Display 1 (R81h) Page 35, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0082, 0x0000,}, // RAM Address End - Partial Display 1 (R82h) Page 36, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0083, 0x0000,}, // Display Position - Partial Display 2 (R83h) Page 36, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0084, 0x0000,}, // RAM Address Start - Partial Display 2 (R84h) Page 36, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0085, 0x0000,}, // RAM Address End - Partial Display 2 (R85h) Page 36, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0090, 0x0010,}, // Panel Interface Control 1 (R90h) Page 36, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0092, 0x0000,}, // Panel Interface Control 2 (R92h) Page 37, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0093, 0x0103,}, // Panel Interface control 3 (R93h) Page 38, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0095, 0x0210,}, // Panel Interface control 4 (R95h) Page 38, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0097, 0x0000,}, // Panel Interface Control 5 (R97h) Page 40, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0098, 0x0000,}, // Panel Interface Control 6 (R98h) Page 41, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0007, 0x0173,}, // Display Control (R07h) Page 16, SPFD5408B Datasheet
{LCD_STEP_DONE, 0, 0},
};
const struct ubicom32lcd_panel cfaf240320ktts_0 = {
.desc = "CFAF240320KTTS",
.init_seq = cfaf240320ktts_init_0,
.horz_reg = 0x20,
.vert_reg = 0x21,
.gram_reg = 0x22,
.xres = 240,
.yres = 320,
.stride = 240,
.id = 0x5408,
};
#endif
#ifdef CONFIG_LCD_UBICOM32_CFAF240320KTTS_180
static const struct ubicom32lcd_step cfaf240320ktts_init_180[] = {
{LCD_STEP_CMD_DATA, 0x0001, 0x0000,}, // Driver Output Control Register (R01h) Page 14, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0002, 0x0700,}, // LCD Driving Waveform Control (R02h) Page 15, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0003, 0x5000,}, // Entry Mode (R03h) 180 degrees
{LCD_STEP_CMD_DATA, 0x0004, 0x0000,}, // Scaling Control register (R04h) Page 16, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0008, 0x0207,}, // Display Control 2 (R08h) Page 17, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0009, 0x0000,}, // Display Control 3 (R09h) Page 18, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x000A, 0x0000,}, // Frame Cycle Control (R0Ah) Page 19, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x000C, 0x0000,}, // External Display Interface Control 1 (R0Ch) Page 20, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x000D, 0x0000,}, // Frame Maker Position (R0Dh) Page 21, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x000F, 0x0000,}, // External Display Interface Control 2 (R0Fh) Page 21, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0010, 0x0000,}, // Power Control 1 (R10h) Page 22, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0011, 0x0007,}, // Power Control 2 (R11h) Page 23, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0012, 0x0000,}, // Power Control 3 (R12h) Page 24, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0013, 0x0000,}, // Power Control 4 (R13h) Page 25, SPFD5408B Datasheet
{LCD_STEP_SLEEP, 0, 200},
{LCD_STEP_CMD_DATA, 0x0007, 0x0101,}, // Display Control (R07h) Page 16, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0010, 0x12B0,}, // Power Control 1 (R10h) Page 22, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0011, 0x0007,}, // Power Control 2 (R11h) Page 23, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0012, 0x01BB,}, // Power Control 3 (R12h) Page 24, SPFD5408B Datasheet
{LCD_STEP_SLEEP, 0, 50},
{LCD_STEP_CMD_DATA, 0x0013, 0x1300,}, // Power Control 4 (R13h) Page 25, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0029, 0x0010,}, // NVM read data 2 (R29h) Page 30, SPFD5408B Datasheet
{LCD_STEP_SLEEP, 0, 50},
{LCD_STEP_CMD_DATA, 0x0030, 0x000A,}, // Gamma Control 1 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0031, 0x1326,}, // Gamma Control 2 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0032, 0x0A29,}, // Gamma Control 3 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0033, 0x290A,}, // Gamma Control 4 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0034, 0x2613,}, // Gamma Control 5 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0035, 0x0A0A,}, // Gamma Control 6 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0036, 0x1E03,}, // Gamma Control 7 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0037, 0x031E,}, // Gamma Control 8 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0038, 0x0706,}, // Gamma Control 9 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0039, 0x0303,}, // Gamma Control 10 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003A, 0x0E04,}, // Gamma Control 11 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003B, 0x0E01,}, // Gamma Control 12 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003C, 0x010E,}, // Gamma Control 13 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003D, 0x040E,}, // Gamma Control 14 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003E, 0x0303,}, // Gamma Control 15 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x003F, 0x0607,}, // Gamma Control 16 Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0050, 0x0000,}, // Window Horizontal RAM Address Start (R50h) Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0051, 0x00EF,}, // Window Horizontal RAM Address End (R51h) Page 32, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0052, 0x0000,}, // Window Vertical RAM Address Start (R52h) Page 33, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0053, 0x013F,}, // Window Vertical RAM Address End (R53h) Page 33, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0060, 0x2700,}, // Driver Output Control (R60h) Page 33, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0061, 0x0001,}, // Driver Output Control (R61h) Page 35, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x006A, 0x0000,}, // Vertical Scroll Control (R6Ah) Page 35, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0080, 0x0000,}, // Display Position - Partial Display 1 (R80h) Page 35, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0081, 0x0000,}, // RAM Address Start - Partial Display 1 (R81h) Page 35, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0082, 0x0000,}, // RAM Address End - Partial Display 1 (R82h) Page 36, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0083, 0x0000,}, // Display Position - Partial Display 2 (R83h) Page 36, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0084, 0x0000,}, // RAM Address Start - Partial Display 2 (R84h) Page 36, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0085, 0x0000,}, // RAM Address End - Partial Display 2 (R85h) Page 36, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0090, 0x0010,}, // Panel Interface Control 1 (R90h) Page 36, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0092, 0x0000,}, // Panel Interface Control 2 (R92h) Page 37, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0093, 0x0103,}, // Panel Interface control 3 (R93h) Page 38, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0095, 0x0210,}, // Panel Interface control 4 (R95h) Page 38, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0097, 0x0000,}, // Panel Interface Control 5 (R97h) Page 40, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0098, 0x0000,}, // Panel Interface Control 6 (R98h) Page 41, SPFD5408B Datasheet
{LCD_STEP_CMD_DATA, 0x0007, 0x0173,}, // Display Control (R07h) Page 16, SPFD5408B Datasheet
{LCD_STEP_DONE, 0, 0},
};
const struct ubicom32lcd_panel cfaf240320ktts_180 = {
.desc = "CFAF240320KTTS 180",
.init_seq = cfaf240320ktts_init_180,
.horz_reg = 0x20,
.vert_reg = 0x21,
.gram_reg = 0x22,
.xres = 240,
.yres = 320,
.stride = 240,
.id = 0x5408,
};
#endif
#ifdef CONFIG_LCD_UBICOM32_TFT2N0369E_P
static const struct ubicom32lcd_step tft2n0369ep_init[] = {
{LCD_STEP_CMD_DATA, 0x0028, 0x0006},
{LCD_STEP_CMD_DATA, 0x0000, 0x0001},
{LCD_STEP_SLEEP, 0, 15},
{LCD_STEP_CMD_DATA, 0x002B, 0x9532},
{LCD_STEP_CMD_DATA, 0x0003, 0xAAAC},
{LCD_STEP_CMD_DATA, 0x000C, 0x0002},
{LCD_STEP_CMD_DATA, 0x000D, 0x000A},
{LCD_STEP_CMD_DATA, 0x000E, 0x2C00},
{LCD_STEP_CMD_DATA, 0x001E, 0x00AA},
{LCD_STEP_CMD_DATA, 0x0025, 0x8000},
{LCD_STEP_SLEEP, 0, 15},
{LCD_STEP_CMD_DATA, 0x0001, 0x2B3F},
{LCD_STEP_CMD_DATA, 0x0002, 0x0600},
{LCD_STEP_CMD_DATA, 0x0010, 0x0000},
{LCD_STEP_CMD_DATA, 0x0011, 0x6030},
{LCD_STEP_SLEEP, 0, 20},
{LCD_STEP_CMD_DATA, 0x0005, 0x0000},
{LCD_STEP_CMD_DATA, 0x0006, 0x0000},
{LCD_STEP_CMD_DATA, 0x0016, 0xEF1C},
{LCD_STEP_CMD_DATA, 0x0017, 0x0003},
{LCD_STEP_CMD_DATA, 0x0007, 0x0233},
{LCD_STEP_CMD_DATA, 0x000B, 0x5312},
{LCD_STEP_CMD_DATA, 0x000F, 0x0000},
{LCD_STEP_SLEEP, 0, 20},
{LCD_STEP_CMD_DATA, 0x0041, 0x0000},
{LCD_STEP_CMD_DATA, 0x0042, 0x0000},
{LCD_STEP_CMD_DATA, 0x0048, 0x0000},
{LCD_STEP_CMD_DATA, 0x0049, 0x013F},
{LCD_STEP_CMD_DATA, 0x0044, 0xEF00},
{LCD_STEP_CMD_DATA, 0x0045, 0x0000},
{LCD_STEP_CMD_DATA, 0x0046, 0x013F},
{LCD_STEP_CMD_DATA, 0x004A, 0x0000},
{LCD_STEP_CMD_DATA, 0x004B, 0x0000},
{LCD_STEP_SLEEP, 0, 20},
{LCD_STEP_CMD_DATA, 0x0030, 0x0707},
{LCD_STEP_CMD_DATA, 0x0031, 0x0704},
{LCD_STEP_CMD_DATA, 0x0032, 0x0204},
{LCD_STEP_CMD_DATA, 0x0033, 0x0201},
{LCD_STEP_CMD_DATA, 0x0034, 0x0203},
{LCD_STEP_CMD_DATA, 0x0035, 0x0204},
{LCD_STEP_CMD_DATA, 0x0036, 0x0204},
{LCD_STEP_CMD_DATA, 0x0037, 0x0502},
{LCD_STEP_CMD_DATA, 0x003A, 0x0302},
{LCD_STEP_CMD_DATA, 0x003B, 0x0500},
{LCD_STEP_SLEEP, 0, 20},
{LCD_STEP_CMD_DATA, 0x0044, 239 << 8 | 0},
{LCD_STEP_CMD_DATA, 0x0045, 0x0000},
{LCD_STEP_CMD_DATA, 0x0046, 319},
{LCD_STEP_DONE, 0, 0},
};
const struct ubicom32lcd_panel tft2n0369ep = {
.desc = "TFT2N0369E-Portrait",
.init_seq = tft2n0369ep_init,
.horz_reg = 0x4e,
.vert_reg = 0x4f,
.gram_reg = 0x22,
.xres = 240,
.yres = 320,
.stride = 240,
.id = 0x8989,
};
#endif
#ifdef CONFIG_LCD_UBICOM32_TFT2N0369E_L
static const struct ubicom32lcd_step tft2n0369e_init[] = {
{LCD_STEP_CMD_DATA, 0x0028, 0x0006},
{LCD_STEP_CMD_DATA, 0x0000, 0x0001},
{LCD_STEP_SLEEP, 0, 15},
{LCD_STEP_CMD_DATA, 0x002B, 0x9532},
{LCD_STEP_CMD_DATA, 0x0003, 0xAAAC},
{LCD_STEP_CMD_DATA, 0x000C, 0x0002},
{LCD_STEP_CMD_DATA, 0x000D, 0x000A},
{LCD_STEP_CMD_DATA, 0x000E, 0x2C00},
{LCD_STEP_CMD_DATA, 0x001E, 0x00AA},
{LCD_STEP_CMD_DATA, 0x0025, 0x8000},
{LCD_STEP_SLEEP, 0, 15},
{LCD_STEP_CMD_DATA, 0x0001, 0x2B3F},
{LCD_STEP_CMD_DATA, 0x0002, 0x0600},
{LCD_STEP_CMD_DATA, 0x0010, 0x0000},
{LCD_STEP_CMD_DATA, 0x0011, 0x60A8},
{LCD_STEP_SLEEP, 0, 20},
{LCD_STEP_CMD_DATA, 0x0005, 0x0000},
{LCD_STEP_CMD_DATA, 0x0006, 0x0000},
{LCD_STEP_CMD_DATA, 0x0016, 0xEF1C},
{LCD_STEP_CMD_DATA, 0x0017, 0x0003},
{LCD_STEP_CMD_DATA, 0x0007, 0x0233},
{LCD_STEP_CMD_DATA, 0x000B, 0x5312},
{LCD_STEP_CMD_DATA, 0x000F, 0x0000},
{LCD_STEP_SLEEP, 0, 20},
{LCD_STEP_CMD_DATA, 0x0041, 0x0000},
{LCD_STEP_CMD_DATA, 0x0042, 0x0000},
{LCD_STEP_CMD_DATA, 0x0048, 0x0000},
{LCD_STEP_CMD_DATA, 0x0049, 0x013F},
{LCD_STEP_CMD_DATA, 0x0044, 0xEF00},
{LCD_STEP_CMD_DATA, 0x0045, 0x0000},
{LCD_STEP_CMD_DATA, 0x0046, 0x013F},
{LCD_STEP_CMD_DATA, 0x004A, 0x0000},
{LCD_STEP_CMD_DATA, 0x004B, 0x0000},
{LCD_STEP_SLEEP, 0, 20},
{LCD_STEP_CMD_DATA, 0x0030, 0x0707},
{LCD_STEP_CMD_DATA, 0x0031, 0x0704},
{LCD_STEP_CMD_DATA, 0x0032, 0x0204},
{LCD_STEP_CMD_DATA, 0x0033, 0x0201},
{LCD_STEP_CMD_DATA, 0x0034, 0x0203},
{LCD_STEP_CMD_DATA, 0x0035, 0x0204},
{LCD_STEP_CMD_DATA, 0x0036, 0x0204},
{LCD_STEP_CMD_DATA, 0x0037, 0x0502},
{LCD_STEP_CMD_DATA, 0x003A, 0x0302},
{LCD_STEP_CMD_DATA, 0x003B, 0x0500},
{LCD_STEP_SLEEP, 0, 20},
{LCD_STEP_CMD_DATA, 0x0044, 239 << 8 | 0},
{LCD_STEP_CMD_DATA, 0x0045, 0x0000},
{LCD_STEP_CMD_DATA, 0x0046, 319},
{LCD_STEP_DONE, 0, 0},
};
const struct ubicom32lcd_panel tft2n0369e = {
.desc = "TFT2N0369E-Landscape",
.init_seq = tft2n0369e_init,
.horz_reg = 0x4e,
.vert_reg = 0x4f,
.gram_reg = 0x22,
.xres = 320,
.yres = 240,
.stride = 320,
.id = 0x8989,
};
#endif
#ifdef CONFIG_LCD_UBICOM32_CFAF240400D
static const struct ubicom32lcd_step cfaf240400d_init[] = {
{LCD_STEP_CMD_DATA, 0x0606, 0x0000}, // Pin Control (R606h) // Page 41 of SPFD5420A Datasheet
{LCD_STEP_SLEEP, 0, 50},
{LCD_STEP_CMD_DATA, 0x0007, 0x0001}, // Display Control 1 (R007h) // Page 16 of SPFD5420A Datasheet
{LCD_STEP_SLEEP, 0, 50},
{LCD_STEP_CMD_DATA, 0x0110, 0x0001}, // Power Control 6(R110h) // Page 30 of SPFD5420A Datasheet
{LCD_STEP_SLEEP, 0, 50},
{LCD_STEP_CMD_DATA, 0x0100, 0x17B0}, // Power Control 1 (R100h) // Page 26 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0101, 0x0147}, // Power Control 2 (R101h) // Page 27 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0102, 0x019D}, // Power Control 3 (R102h) // Page 28 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0103, 0x3600}, // Power Control 4 (R103h) // Page 29 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0281, 0x0010}, // NVM read data 2 (R281h) // Page 34 of SPFD5420A Datasheet
{LCD_STEP_SLEEP, 0, 50},
{LCD_STEP_CMD_DATA, 0x0102, 0x01BD}, // Power Control 3 (R102h) // Page 28 of SPFD5420A Datasheet
{LCD_STEP_SLEEP, 0, 50},
//--------------- Power control 1~6 ---------------//
{LCD_STEP_CMD_DATA, 0x0100, 0x16B0}, // Power Control 1 (R100h) // Page 26 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0101, 0x0147}, // Power Control 2 (R101h) // Page 27 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0102, 0x01BD}, // Power Control 3 (R102h) // Page 28 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0103, 0x2d00}, // Power Control 4 (R103h) // Page 29 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0107, 0x0000}, // Power Control 5 (R107h) // Page 30 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0110, 0x0001}, // Power Control 6(R110h) // Page 30 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0280, 0x0000}, // NVM read data 1 (R280h) // Page 33 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0281, 0x0006}, // NVM read data 2 (R281h) // Page 34 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0282, 0x0000}, // NVM read data 3 (R282h) // Page 34 of SPFD5420A Datasheet
//------- Gamma 2.2 control (R300h to R30Fh) ------//
{LCD_STEP_CMD_DATA, 0x0300, 0x0101},
{LCD_STEP_CMD_DATA, 0x0301, 0x0b27},
{LCD_STEP_CMD_DATA, 0x0302, 0x132a},
{LCD_STEP_CMD_DATA, 0x0303, 0x2a13},
{LCD_STEP_CMD_DATA, 0x0304, 0x270b},
{LCD_STEP_CMD_DATA, 0x0305, 0x0101},
{LCD_STEP_CMD_DATA, 0x0306, 0x1205},
{LCD_STEP_CMD_DATA, 0x0307, 0x0512},
{LCD_STEP_CMD_DATA, 0x0308, 0x0005},
{LCD_STEP_CMD_DATA, 0x0309, 0x0003},
{LCD_STEP_CMD_DATA, 0x030A, 0x0f04},
{LCD_STEP_CMD_DATA, 0x030B, 0x0f00},
{LCD_STEP_CMD_DATA, 0x030C, 0x000f},
{LCD_STEP_CMD_DATA, 0x030D, 0x040f},
{LCD_STEP_CMD_DATA, 0x030E, 0x0300},
{LCD_STEP_CMD_DATA, 0x030F, 0x0500},
{LCD_STEP_CMD_DATA, 0x0400, 0x3500}, // Base Image Number of Line (R400h) // Page 36 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0401, 0x0001}, // Base Image Display Control (R401h) // Page 39 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0404, 0x0000}, // Based Image Vertical Scroll Control (R404h) // Page 40 of SPFD5420A Datasheet
//--------------- Normal set ---------------//
{LCD_STEP_CMD_DATA, 0x0000, 0x0000}, // ID Read Register (R000h) // Page 13 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0001, 0x0100}, // Driver Output Control Register (R001h) // Page 14 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0002, 0x0100}, // LCD Driving Waveform Control (R002h) // Page 14 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0003, 0x1030}, // Entry Mode (R003h) // Page 15 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0006, 0x0000}, // Display Control 1 (R007h) // Page 16 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0008, 0x0808}, // Display Control 2 (R008h) // Page 17 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0009, 0x0001}, // Display Control 3 (R009h) // Page 18 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x000B, 0x0010}, // Low Power Control (R00Bh) // Page 19 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x000C, 0x0000}, // External Display Interface Control 1 (R00Ch) // Page 19 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x000F, 0x0000}, // External Display Interface Control 2 (R00Fh) // Page 20 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0007, 0x0001}, // Display Control 1 (R007h) // Page 16 of SPFD5420A Datasheet
//--------------- Panel interface control 1~6 ---------------//
{LCD_STEP_CMD_DATA, 0x0010, 0x0012}, // Panel Interface Control 1 (R010h) // Page 20 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0011, 0x0202}, // Panel Interface Control 2 (R011h) // Page 21 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0012, 0x0300}, // Panel Interface control 3 (R012h) // Page 22 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0020, 0x021E}, // Panel Interface control 4 (R020h) // Page 22 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0021, 0x0202}, // Panel Interface Control 5 (021Rh) // Page 24 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0022, 0x0100}, // Panel Interface Control 6 (R022h) // Page 25 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0090, 0x8000}, // Frame Marker Control (R090h) // Page 25 of SPFD5420A Datasheet
//--------------- Partial display ---------------//
{LCD_STEP_CMD_DATA, 0x0210, 0x0000}, // Window Horizontal RAM Address Start (R210h) // Page 35 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0211, 0x00EF}, // Window Horziontal RAM Address End (R211h) // Page 35 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0212, 0x0000}, // Window Vertical RAM Address Start (R212h) // Page 35 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0213, 0x018F}, // Window Vertical RAM Address End (R213h) // Page 35 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0500, 0x0000}, // Display Position - Partial Display 1 (R500h) // Page 40 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0501, 0x0000}, // RAM Address Start - Partial Display 1 (R501h)// Page 40 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0502, 0x0000}, // RAM Address End - Partail Display 1 (R502h) // Page 40 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0503, 0x0000}, // Display Position - Partial Display 2 (R503h) // Page 40 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0504, 0x0000}, // RAM Address Start . Partial Display 2 (R504h)// Page 41 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0505, 0x0000}, // RAM Address End . Partial Display 2 (R505h) // Page 41 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0606, 0x0000}, // Pin Control (R606h) // Page 41 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x06F0, 0x0000}, // NVM Access Control (R6F0h) // Page 41 of SPFD5420A Datasheet
{LCD_STEP_CMD_DATA, 0x0007, 0x0173}, // Display Control 1 (R007h) // Page 16 of SPFD5420A Datasheet
{LCD_STEP_SLEEP, 0, 50},
{LCD_STEP_CMD_DATA, 0x0007, 0x0171}, // Display Control 1 (R007h) // Page 16 of SPFD5420A Datasheet
{LCD_STEP_SLEEP, 0, 10},
{LCD_STEP_CMD_DATA, 0x0007, 0x0173}, // Display Control 1 (R007h) // Page 16 of SPFD5420A Datasheet
{LCD_STEP_DONE, 0, 0},
};
const struct ubicom32lcd_panel cfaf240400d = {
.desc = "CFAF240400D",
.init_seq = cfaf240400d_init,
.horz_reg = 0x0200,
.vert_reg = 0x0201,
.gram_reg = 0x0202,
.xres = 240,
.yres = 400,
.stride = 240,
.id = 0x5420,
};
#endif
#ifdef CONFIG_LCD_UBICOM32_CFAF240400F
static const struct ubicom32lcd_step cfaf320240f_init[] = {
{LCD_STEP_CMD_DATA, 0x0028, 0x0006}, // VCOM OTP Page 55-56 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0000, 0x0001}, // start Oscillator Page 36 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0010, 0x0000}, // Sleep mode Page 49 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0001, 0x32EF}, // Driver Output Control Page 36-39 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0002, 0x0600}, // LCD Driving Waveform Control Page 40-42 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0003, 0x6A38}, // Power Control 1 Page 43-44 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0011, 0x6870}, // Entry Mode Page 50-52 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0X000F, 0x0000}, // Gate Scan Position Page 49 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0X000B, 0x5308}, // Frame Cycle Control Page 45 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x000C, 0x0003}, // Power Control 2 Page 47 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x000D, 0x000A}, // Power Control 3 Page 48 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x000E, 0x2E00}, // Power Control 4 Page 48 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x001E, 0x00BE}, // Power Control 5 Page 53 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0025, 0x8000}, // Frame Frequency Control Page 53 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0026, 0x7800}, // Analog setting Page 54 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x004E, 0x0000}, // Ram Address Set Page 58 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x004F, 0x0000}, // Ram Address Set Page 58 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0012, 0x08D9}, // Sleep mode Page 49 of SSD2119 datasheet
// Gamma Control (R30h to R3Bh) -- Page 56 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0030, 0x0000},
{LCD_STEP_CMD_DATA, 0x0031, 0x0104},
{LCD_STEP_CMD_DATA, 0x0032, 0x0100},
{LCD_STEP_CMD_DATA, 0x0033, 0x0305},
{LCD_STEP_CMD_DATA, 0x0034, 0x0505},
{LCD_STEP_CMD_DATA, 0x0035, 0x0305},
{LCD_STEP_CMD_DATA, 0x0036, 0x0707},
{LCD_STEP_CMD_DATA, 0x0037, 0x0300},
{LCD_STEP_CMD_DATA, 0x003A, 0x1200},
{LCD_STEP_CMD_DATA, 0x003B, 0x0800},
{LCD_STEP_CMD_DATA, 0x0007, 0x0033}, // Display Control Page 45 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0044, 0xEF00}, // Vertical RAM address position Page 57 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0045, 0x0000}, // Horizontal RAM address position Page 57 of SSD2119 datasheet
{LCD_STEP_CMD_DATA, 0x0046, 0x013F}, // Horizontal RAM address position Page 57 of SSD2119 datasheet
{LCD_STEP_SLEEP, 0, 150},
{LCD_STEP_DONE, 0, 0},
};
const struct ubicom32lcd_panel cfaf320240f = {
.desc = "CFAF320240F",
.init_seq = cfaf320240f_init,
.horz_reg = 0x4e,
.vert_reg = 0x4f,
.gram_reg = 0x22,
.xres = 320,
.yres = 240,
.stride = 320,
.id = 0x9919,
};
#endif
const struct ubicom32lcd_panel *ubicom32lcd_panels[] = {
#ifdef CONFIG_LCD_UBICOM32_CFAF240400KTTS_180
&cfaf240320ktts_180,
#endif
#ifdef CONFIG_LCD_UBICOM32_CFAF240400KTTS
&cfaf240320ktts_0,
#endif
#ifdef CONFIG_LCD_UBICOM32_CFAF240400D
&cfaf240400d,
#endif
#ifdef CONFIG_LCD_UBICOM32_TFT2N0369E_P
&tft2n0369ep,
#endif
#ifdef CONFIG_LCD_UBICOM32_TFT2N0369E_L
&tft2n0369e,
#endif
#ifdef CONFIG_LCD_UBICOM32_CFAF240400F
&cfaf320240f,
#endif
NULL,
};
#endif

View File

@@ -0,0 +1,194 @@
/*
* drivers/video/backlight/ubicom32lcdpowerpower.c
* LCD power driver for the Ubicom32 platform
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from (with many thanks):
* arch/m68knommu
* arch/blackfin
* arch/parisc
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/lcd.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <asm/ubicom32lcdpower.h>
#include <asm/ip5000.h>
#define DRIVER_NAME "ubicom32lcdpower"
struct ubicom32lcdpower_data {
/*
* Pointer to the platform data structure. Keep this around since we need values
* from it to set the backlight intensity.
*/
const struct ubicom32lcdpower_platform_data *pdata;
/*
* LCD device, we have to save this for use when we remove ourselves.
*/
struct lcd_device *lcddev;
};
/*
* ubicom32lcdpower_set_power
*/
static int ubicom32lcdpower_set_power(struct lcd_device *ld, int power)
{
struct ubicom32lcdpower_data *ud = (struct ubicom32lcdpower_data *)lcd_get_data(ld);
if (power == FB_BLANK_UNBLANK) {
gpio_direction_output(ud->pdata->vgh_gpio, ud->pdata->vgh_polarity);
return 0;
}
gpio_direction_output(ud->pdata->vgh_gpio, !ud->pdata->vgh_polarity);
return 0;
}
/*
* ubicom32lcdpower_get_power
*/
static int ubicom32lcdpower_get_power(struct lcd_device *ld)
{
struct ubicom32lcdpower_data *ud = (struct ubicom32lcdpower_data *)lcd_get_data(ld);
int vgh = gpio_get_value(ud->pdata->vgh_gpio);
if ((vgh && ud->pdata->vgh_polarity) || (!vgh && !ud->pdata->vgh_polarity)) {
return 1;
}
return 0;
}
static struct lcd_ops ubicom32lcdpower_ops = {
.get_power = ubicom32lcdpower_get_power,
.set_power = ubicom32lcdpower_set_power,
};
/*
* ubicom32lcdpower_probe
*/
static int ubicom32lcdpower_probe(struct platform_device *pdev)
{
const struct ubicom32lcdpower_platform_data *pdata = pdev->dev.platform_data;
struct ubicom32lcdpower_data *ud;
struct lcd_device *lcddev;
int retval;
/*
* Check to see if we have any platform data, if we don't have a LCD to control
*/
if (!pdata) {
return -ENODEV;
}
/*
* Allocate our private data
*/
ud = kzalloc(sizeof(struct ubicom32lcdpower_data), GFP_KERNEL);
if (!ud) {
return -ENOMEM;
}
ud->pdata = pdata;
/*
* Request our GPIOs
*/
retval = gpio_request(pdata->vgh_gpio, "vgh");
if (retval) {
dev_err(&pdev->dev, "Failed to allocate vgh GPIO\n");
goto fail_gpio;
}
/*
* Register our lcd device
*/
lcddev = lcd_device_register(DRIVER_NAME, &pdev->dev, ud, &ubicom32lcdpower_ops);
if (IS_ERR(lcddev)) {
retval = PTR_ERR(lcddev);
goto fail;
}
ud->lcddev = lcddev;
platform_set_drvdata(pdev, ud);
ubicom32lcdpower_set_power(lcddev, FB_BLANK_UNBLANK);
printk(KERN_INFO DRIVER_NAME ": LCD driver started\n");
return 0;
fail:
gpio_free(pdata->vgh_gpio);
fail_gpio:
platform_set_drvdata(pdev, NULL);
kfree(ud);
return retval;
}
/*
* ubicom32lcdpower_remove
*/
static int __exit ubicom32lcdpower_remove(struct platform_device *pdev)
{
struct ubicom32lcdpower_data *ud = platform_get_drvdata(pdev);
lcd_device_unregister(ud->lcddev);
platform_set_drvdata(pdev, NULL);
kfree(ud);
return 0;
}
static struct platform_driver ubicom32lcdpower_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.remove = __exit_p(ubicom32lcdpower_remove),
};
/*
* ubicom32lcdpower_init
*/
static int __init ubicom32lcdpower_init(void)
{
return platform_driver_probe(&ubicom32lcdpower_driver, ubicom32lcdpower_probe);
}
module_init(ubicom32lcdpower_init);
/*
* ubicom32lcdpower_exit
*/
static void __exit ubicom32lcdpower_exit(void)
{
platform_driver_unregister(&ubicom32lcdpower_driver);
}
module_exit(ubicom32lcdpower_exit);
MODULE_AUTHOR("Patrick Tjin <@ubicom.com>");
MODULE_DESCRIPTION("Ubicom32 lcd power driver");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,779 @@
/*
* drivers/video/ubicom32fb.c
* Ubicom32 frame buffer driver
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from (with many thanks):
* arch/m68knommu
* arch/blackfin
* arch/parisc
*/
/*
* This driver was based on skeletonfb.c, Skeleton for a frame buffer device by
* Geert Uytterhoeven.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <asm/ip5000.h>
#include <asm/vdc_tio.h>
#include <asm/ubicom32fb.h>
#define DRIVER_NAME "ubicom32fb"
#define DRIVER_DESCRIPTION "Ubicom32 frame buffer driver"
#define PALETTE_ENTRIES_NO 16
/*
* Option variables
*
* vram_size: VRAM size in kilobytes, subject to alignment
*/
static int vram_size = 0;
module_param(vram_size, int, 0);
MODULE_PARM_DESC(vram, "VRAM size, in kilobytes to allocate, should be at least the size of one screen, subject to alignment");
static int init_value = 0;
module_param(init_value, int, 0);
MODULE_PARM_DESC(init, "Initial value of the framebuffer (16-bit number).");
/*
* fb_fix_screeninfo defines the non-changeable properties of the VDC, depending on what mode it is in.
*/
static struct fb_fix_screeninfo ubicom32fb_fix = {
.id = "Ubicom32",
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_TRUECOLOR,
.accel = FB_ACCEL_UBICOM32,
};
/*
* Filled in at probe time when we find out what the hardware supports
*/
static struct fb_var_screeninfo ubicom32fb_var;
/*
* Private data structure
*/
struct ubicom32fb_drvdata {
struct fb_info *fbinfo;
bool cmap_alloc;
/*
* The address of the framebuffer in memory
*/
void *fb;
void *fb_aligned;
/*
* Total size of vram including alignment allowance
*/
u32 total_vram_size;
/*
* Interrupt to set when changing registers
*/
u32 vp_int;
/*
* Optional: Interrupt used by TIO to signal us
*/
u32 rx_int;
/*
* Base address of the regs for VDC_TIO
*/
volatile struct vdc_tio_vp_regs *regs;
/*
* non-zero if we are in yuv mode
*/
u8_t is_yuv;
/*
* Fake palette of 16 colors
*/
u32 pseudo_palette[PALETTE_ENTRIES_NO];
/*
* Wait queue and lock used to block when we need to wait
* for something to happen.
*/
wait_queue_head_t waitq;
struct mutex lock;
};
/*
* ubicom32fb_set_next_frame
* Sets the next frame buffer to display
*
* if sync is TRUE then this function will block until the hardware
* acknowledges the change
*/
static inline void ubicom32fb_set_next_frame(struct ubicom32fb_drvdata *ud, void *fb, u8_t sync)
{
ud->regs->next_frame_flags = ud->is_yuv ? VDCTIO_NEXT_FRAME_FLAG_YUV : 0;
ud->regs->next_frame = (void *)((u32_t)fb | 1);
/*
* If we have interrupts, then we can wait on it
*/
if (ud->rx_int != -1) {
DEFINE_WAIT(wait);
unsigned long flags;
spin_lock_irqsave(&ud->lock, flags);
prepare_to_wait(&ud->waitq, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irqrestore(&ud->lock, flags);
schedule();
finish_wait(&ud->waitq, &wait);
return;
}
/*
* No interrupt, we will just spin here
*/
while (sync && ((u32_t)ud->regs->next_frame & 1));
}
/*
* ubicom32fb_send_command
* Sends a command/data pair to the VDC
*/
static inline void ubicom32fb_send_command(struct ubicom32fb_drvdata *ud, u16 command, u8_t block)
{
ud->regs->command = command;
ubicom32_set_interrupt(ud->vp_int);
while (block && ud->regs->command);
}
/*
* ubicom32fb_ioctl
* Handles any ioctls sent to us
*/
static int ubicom32fb_ioctl(struct fb_info *fbi, unsigned int cmd,
unsigned long arg)
{
struct ubicom32fb_drvdata *ud = (struct ubicom32fb_drvdata *)fbi->par;
void __user *argp = (void __user *)arg;
int retval = -EFAULT;
switch (cmd) {
case UBICOM32FB_IOCTL_SET_NEXT_FRAME_SYNC:
// check alignment, return -EINVAL if necessary
ubicom32fb_set_next_frame(ud, argp, 1);
retval = 0;
break;
case UBICOM32FB_IOCTL_SET_NEXT_FRAME:
// check alignment, return -EINVAL if necessary
ubicom32fb_set_next_frame(ud, argp, 0);
retval = 0;
break;
case UBICOM32FB_IOCTL_SET_MODE:
if (!(ud->regs->caps & VDCTIO_CAPS_SUPPORTS_SCALING)) {
break;
} else {
struct ubicom32fb_mode mode;
volatile struct vdc_tio_vp_regs *regs = ud->regs;
u32_t flags = 0;
if (copy_from_user(&mode, argp, sizeof(mode))) {
break;
}
regs->x_in = mode.width;
regs->y_in = mode.height;
regs->x_out = regs->xres;
regs->y_out = regs->yres;
if (mode.flags & UBICOM32FB_IOCTL_SET_MODE_FLAG_YUV_SCAN_ORDER) {
flags |= VDCTIO_SCALE_FLAG_YUV_SCAN_ORDER;
}
if (mode.flags & UBICOM32FB_IOCTL_SET_MODE_FLAG_YUV_BLOCK_ORDER) {
flags |= VDCTIO_SCALE_FLAG_YUV_BLOCK_ORDER;
}
ud->is_yuv = mode.flags & UBICOM32FB_IOCTL_SET_MODE_FLAG_YUV;
if (ud->is_yuv) {
flags |= VDCTIO_SCALE_FLAG_YUV;
}
if (mode.flags & UBICOM32FB_IOCTL_SET_MODE_FLAG_VRANGE_16_255) {
flags |= VDCTIO_SCALE_FLAG_VRANGE_16_255;
}
if (mode.flags & UBICOM32FB_IOCTL_SET_MODE_FLAG_VRANGE_0_255) {
flags |= VDCTIO_SCALE_FLAG_VRANGE_0_255;
}
if (mode.flags & UBICOM32FB_IOCTL_SET_MODE_FLAG_VSUB) {
flags |= VDCTIO_SCALE_FLAG_VSUB;
}
if (mode.flags & UBICOM32FB_IOCTL_SET_MODE_FLAG_HSUB_2_1) {
flags |= VDCTIO_SCALE_FLAG_HSUB_2_1;
}
if (mode.flags & UBICOM32FB_IOCTL_SET_MODE_FLAG_HSUB_1_1) {
flags |= VDCTIO_SCALE_FLAG_HSUB_1_1;
}
if (mode.flags & UBICOM32FB_IOCTL_SET_MODE_FLAG_SCALE_ENABLE) {
flags |= VDCTIO_SCALE_FLAG_ENABLE;
}
if (mode.next_frame) {
flags |= VDCTIO_SCALE_FLAG_SET_FRAME_BUFFER;
regs->next_frame = mode.next_frame;
}
regs->scale_flags = flags;
ubicom32fb_send_command(ud, VDCTIO_COMMAND_SET_SCALE_MODE, 1);
retval = 0;
break;
}
default:
retval = -ENOIOCTLCMD;
break;
}
return retval;
}
/*
* ubicom32fb_interrupt
* Called by the OS when the TIO has set the rx_int
*/
static irqreturn_t ubicom32fb_interrupt(int vec, void *appdata)
{
struct ubicom32fb_drvdata *ud = (struct ubicom32fb_drvdata *)appdata;
spin_lock(&ud->lock);
if (waitqueue_active(&ud->waitq)) {
wake_up(&ud->waitq);
}
spin_unlock(&ud->lock);
return IRQ_HANDLED;
}
/*
* ubicom32fb_pan_display
* Pans the display to a given location. Supports only y direction panning.
*/
static int ubicom32fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *fbi)
{
struct ubicom32fb_drvdata *ud = (struct ubicom32fb_drvdata *)fbi->par;
void *new_addr;
/*
* Get the last y line that would be displayed. Since we don't support YWRAP,
* it must be less than our virtual y size.
*/
u32 lasty = var->yoffset + var->yres;
if (lasty > fbi->var.yres_virtual) {
/*
* We would fall off the end of our frame buffer if we panned here.
*/
return -EINVAL;
}
if (var->xoffset) {
/*
* We don't support panning in the x direction
*/
return -EINVAL;
}
/*
* Everything looks sane, go ahead and pan
*
* We have to calculate a new address for the VDC to look at
*/
new_addr = ud->fb_aligned + (var->yoffset * fbi->fix.line_length);
/*
* Send down the command. The buffer will switch at the next vertical blank
*/
ubicom32fb_set_next_frame(ud, (void *)new_addr, 0);
return 0;
}
/*
* ubicom32fb_setcolreg
* Sets a color in our virtual palette
*/
static int ubicom32fb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *fbi)
{
u32 *palette = fbi->pseudo_palette;
if (regno >= PALETTE_ENTRIES_NO) {
return -EINVAL;
}
/*
* We only use 8 bits from each color
*/
red >>= 8;
green >>= 8;
blue >>= 8;
/*
* Convert any grayscale values
*/
if (fbi->var.grayscale) {
u16 gray = red + green + blue;
gray += (gray >> 2) + (gray >> 3) - (gray >> 7);
gray >>= 2;
if (gray > 255) {
gray = 255;
}
red = gray;
blue = gray;
green = gray;
}
palette[regno] = (red << fbi->var.red.offset) | (green << fbi->var.green.offset) |
(blue << fbi->var.blue.offset);
return 0;
}
/*
* ubicom32fb_mmap
*/
static int ubicom32fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct ubicom32fb_drvdata *drvdata = (struct ubicom32fb_drvdata *)info->par;
vma->vm_start = (unsigned long)(drvdata->fb_aligned);
vma->vm_end = vma->vm_start + info->fix.smem_len;
/* For those who don't understand how mmap works, go read
* Documentation/nommu-mmap.txt.
* For those that do, you will know that the VM_MAYSHARE flag
* must be set in the vma->vm_flags structure on noMMU
* Other flags can be set, and are documented in
* include/linux/mm.h
*/
vma->vm_flags |= VM_MAYSHARE | VM_SHARED;
return 0;
}
/*
* ubicom32fb_blank
*/
static int ubicom32fb_blank(int blank_mode, struct fb_info *fbi)
{
return 0;
#if 0
struct ubicom32fb_drvdata *drvdata = to_ubicom32fb_drvdata(fbi);
switch (blank_mode) {
case FB_BLANK_UNBLANK:
/* turn on panel */
ubicom32fb_out_be32(drvdata, REG_CTRL, drvdata->reg_ctrl_default);
break;
case FB_BLANK_NORMAL:
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_POWERDOWN:
/* turn off panel */
ubicom32fb_out_be32(drvdata, REG_CTRL, 0);
default:
break;
}
return 0; /* success */
#endif
}
static struct fb_ops ubicom32fb_ops =
{
.owner = THIS_MODULE,
.fb_pan_display = ubicom32fb_pan_display,
.fb_setcolreg = ubicom32fb_setcolreg,
.fb_blank = ubicom32fb_blank,
.fb_mmap = ubicom32fb_mmap,
.fb_ioctl = ubicom32fb_ioctl,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
/*
* ubicom32fb_release
*/
static int ubicom32fb_release(struct device *dev)
{
struct ubicom32fb_drvdata *ud = dev_get_drvdata(dev);
#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO)
//ubicom32fb_blank(VESA_POWERDOWN, &drvdata->info);
#endif
unregister_framebuffer(ud->fbinfo);
if (ud->cmap_alloc) {
fb_dealloc_cmap(&ud->fbinfo->cmap);
}
if (ud->fb) {
kfree(ud->fb);
}
if (ud->rx_int != -1) {
free_irq(ud->rx_int, ud);
}
/*
* Turn off the display
*/
//ubicom32fb_out_be32(drvdata, REG_CTRL, 0);
//iounmap(drvdata->regs);
framebuffer_release(ud->fbinfo);
dev_set_drvdata(dev, NULL);
return 0;
}
/*
* ubicom32fb_platform_probe
*/
static int __init ubicom32fb_platform_probe(struct platform_device *pdev)
{
struct ubicom32fb_drvdata *ud;
struct resource *irq_resource_rx;
struct resource *irq_resource_tx;
struct resource *mem_resource;
struct fb_info *fbinfo;
int rc;
size_t fbsize;
struct device *dev = &pdev->dev;
int offset;
struct vdc_tio_vp_regs *regs;
/*
* Get our resources
*/
irq_resource_tx = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!irq_resource_tx) {
dev_err(dev, "No tx IRQ resource assigned\n");
return -ENODEV;
}
irq_resource_rx = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
if (!irq_resource_rx) {
dev_err(dev, "No rx IRQ resource assigned\n");
return -ENODEV;
}
mem_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_resource || !mem_resource->start) {
dev_err(dev, "No mem resource assigned\n");
return -ENODEV;
}
regs = (struct vdc_tio_vp_regs *)mem_resource->start;
if (regs->version != VDCTIO_VP_VERSION) {
dev_err(dev, "VDCTIO is not compatible with this driver tio:%x drv:%x\n",
regs->version, VDCTIO_VP_VERSION);
return -ENODEV;
}
/*
* This is the minimum VRAM size
*/
fbsize = regs->xres * regs->yres * (regs->bpp / 8);
if (!vram_size) {
vram_size = (fbsize + 1023) / 1024;
} else {
if (fbsize > (vram_size * 1024)) {
dev_err(dev, "Not enough VRAM for display, need >= %u bytes\n", fbsize);
return -ENOMEM; // should be ebadparam?
}
}
/*
* Allocate the framebuffer instance + our private data
*/
fbinfo = framebuffer_alloc(sizeof(struct ubicom32fb_drvdata), &pdev->dev);
if (!fbinfo) {
dev_err(dev, "Not enough memory to allocate instance.\n");
return -ENOMEM;
}
/*
* Fill in our private data.
*/
ud = (struct ubicom32fb_drvdata *)fbinfo->par;
ud->fbinfo = fbinfo;
ud->regs = (struct vdc_tio_vp_regs *)(mem_resource->start);
dev_set_drvdata(dev, ud);
ud->vp_int = irq_resource_tx->start;
/*
* If we were provided an rx_irq then we need to init the appropriate
* queues, locks, and functions.
*/
ud->rx_int = -1;
if (irq_resource_rx->start != DEVTREE_IRQ_NONE) {
init_waitqueue_head(&ud->waitq);
mutex_init(&ud->lock);
if (request_irq(ud->rx_int, ubicom32fb_interrupt, IRQF_SHARED, "ubicom32fb_rx", ud)) {
dev_err(dev, "Couldn't request rx IRQ\n");
rc = -ENOMEM;
goto fail;
}
ud->rx_int = irq_resource_rx->start;
}
/*
* Allocate and align the requested amount of VRAM
*/
ud->total_vram_size = (vram_size * 1024) + regs->fb_align;
ud->fb = kmalloc(ud->total_vram_size, GFP_KERNEL);
if (ud->fb == NULL) {
dev_err(dev, "Couldn't allocate VRAM\n");
rc = -ENOMEM;
goto fail;
}
offset = (u32_t)ud->fb & (regs->fb_align - 1);
if (!offset) {
ud->fb_aligned = ud->fb;
} else {
offset = regs->fb_align - offset;
ud->fb_aligned = ud->fb + offset;
}
/*
* Clear the entire frame buffer
*/
if (!init_value) {
memset(ud->fb_aligned, 0, vram_size * 1024);
} else {
unsigned short *p = ud->fb_aligned;
int i;
for (i = 0; i < ((vram_size * 1024) / sizeof(u16_t)); i++) {
*p++ = init_value;
}
}
/*
* Fill in the fb_var_screeninfo structure
*/
memset(&ubicom32fb_var, 0, sizeof(ubicom32fb_var));
ubicom32fb_var.bits_per_pixel = regs->bpp;
ubicom32fb_var.red.offset = regs->rshift;
ubicom32fb_var.green.offset = regs->gshift;
ubicom32fb_var.blue.offset = regs->bshift;
ubicom32fb_var.red.length = regs->rbits;
ubicom32fb_var.green.length = regs->gbits;
ubicom32fb_var.blue.length = regs->bbits;
ubicom32fb_var.activate = FB_ACTIVATE_NOW;
#if 0
/*
* Turn on the display
*/
ud->reg_ctrl_default = REG_CTRL_ENABLE;
if (regs->rotate_screen)
ud->reg_ctrl_default |= REG_CTRL_ROTATE;
ubicom32fb_out_be32(ud, REG_CTRL, ud->reg_ctrl_default);
#endif
/*
* Fill in the fb_info structure
*/
ud->fbinfo->device = dev;
ud->fbinfo->screen_base = (void *)ud->fb_aligned;
ud->fbinfo->fbops = &ubicom32fb_ops;
ud->fbinfo->fix = ubicom32fb_fix;
ud->fbinfo->fix.smem_start = (u32)ud->fb_aligned;
ud->fbinfo->fix.smem_len = vram_size * 1024;
ud->fbinfo->fix.line_length = regs->xres * (regs->bpp / 8);
ud->fbinfo->fix.mmio_start = (u32)regs;
ud->fbinfo->fix.mmio_len = sizeof(struct vdc_tio_vp_regs);
/*
* We support panning in the y direction only
*/
ud->fbinfo->fix.xpanstep = 0;
ud->fbinfo->fix.ypanstep = 1;
ud->fbinfo->pseudo_palette = ud->pseudo_palette;
ud->fbinfo->flags = FBINFO_DEFAULT;
ud->fbinfo->var = ubicom32fb_var;
ud->fbinfo->var.xres = regs->xres;
ud->fbinfo->var.yres = regs->yres;
/*
* We cannot pan in the X direction, so xres_virtual is regs->xres
* We can pan in the Y direction, so yres_virtual is vram_size / ud->fbinfo->fix.line_length
*/
ud->fbinfo->var.xres_virtual = regs->xres;
ud->fbinfo->var.yres_virtual = (vram_size * 1024) / ud->fbinfo->fix.line_length;
//ud->fbinfo->var.height = regs->height_mm;
//ud->fbinfo->var.width = regs->width_mm;
/*
* Allocate a color map
*/
rc = fb_alloc_cmap(&ud->fbinfo->cmap, PALETTE_ENTRIES_NO, 0);
if (rc) {
dev_err(dev, "Fail to allocate colormap (%d entries)\n",
PALETTE_ENTRIES_NO);
goto fail;
}
ud->cmap_alloc = true;
/*
* Register new frame buffer
*/
rc = register_framebuffer(ud->fbinfo);
if (rc) {
dev_err(dev, "Could not register frame buffer\n");
goto fail;
}
/*
* Start up the VDC
*/
ud->regs->next_frame = ud->fb;
ubicom32fb_send_command(ud, VDCTIO_COMMAND_START, 0);
/*
* Tell the log we are here
*/
dev_info(dev, "fbaddr=%p align=%p, size=%uKB screen(%ux%u) virt(%ux%u), regs=%p irqtx=%u irqrx=%u\n",
ud->fb, ud->fb_aligned, vram_size, ud->fbinfo->var.xres, ud->fbinfo->var.yres,
ud->fbinfo->var.xres_virtual, ud->fbinfo->var.yres_virtual, ud->regs,
irq_resource_tx->start, irq_resource_rx->start);
/*
* Success
*/
return 0;
fail:
ubicom32fb_release(dev);
return rc;
}
/*
* ubicom32fb_platform_remove
*/
static int ubicom32fb_platform_remove(struct platform_device *pdev)
{
dev_info(&(pdev->dev), "Ubicom32 FB Driver Remove\n");
return ubicom32fb_release(&pdev->dev);
}
static struct platform_driver ubicom32fb_platform_driver = {
.probe = ubicom32fb_platform_probe,
.remove = ubicom32fb_platform_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
};
#ifndef MODULE
/*
* ubicom32fb_setup
* Process kernel boot options
*/
static int __init ubicom32fb_setup(char *options)
{
char *this_opt;
if (!options || !*options) {
return 0;
}
while ((this_opt = strsep(&options, ",")) != NULL) {
if (!*this_opt) {
continue;
}
if (!strncmp(this_opt, "init_value=", 10)) {
init_value = simple_strtoul(this_opt + 11, NULL, 0);
continue;
}
if (!strncmp(this_opt, "vram_size=", 10)) {
vram_size = simple_strtoul(this_opt + 10, NULL, 0);
continue;
}
}
return 0;
}
#endif /* MODULE */
/*
* ubicom32fb_init
*/
static int __devinit ubicom32fb_init(void)
{
#ifndef MODULE
/*
* Get kernel boot options (in 'video=ubicom32fb:<options>')
*/
char *option = NULL;
if (fb_get_options(DRIVER_NAME, &option)) {
return -ENODEV;
}
ubicom32fb_setup(option);
#endif /* MODULE */
return platform_driver_register(&ubicom32fb_platform_driver);
}
module_init(ubicom32fb_init);
/*
* ubicom32fb_exit
*/
static void __exit ubicom32fb_exit(void)
{
platform_driver_unregister(&ubicom32fb_platform_driver);
}
module_exit(ubicom32fb_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Patrick Tjin <@ubicom.com>");
MODULE_DESCRIPTION(DRIVER_DESCRIPTION);

View File

@@ -0,0 +1,780 @@
/*
* drivers/video/ubicom32plio80.c
* Ubicom32 80 bus PLIO buffer driver
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*/
/*
* This driver was based on skeletonfb.c, Skeleton for a frame buffer device by
* Geert Uytterhoeven.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/plio.h>
#define DRIVER_NAME "ubicom32plio80"
#define DRIVER_DESCRIPTION "Ubicom32 80 bus PLIO frame buffer driver"
#define PALETTE_ENTRIES_NO 16
/*
* Option variables
*
* vram_size: VRAM size in kilobytes, subject to alignment
*/
static int vram_size = 0;
module_param(vram_size, int, 0);
MODULE_PARM_DESC(vram_size, "VRAM size, in kilobytes to allocate, should be at least the size of one screen, subject to alignment");
static int xres = 240;
module_param(xres, int, 0);
MODULE_PARM_DESC(xres, "x (horizontal) resolution");
static int yres = 320;
module_param(yres, int, 0);
MODULE_PARM_DESC(yres, "y (vertical) resolution");
static int bgr = 0;
module_param(bgr, int, 0);
MODULE_PARM_DESC(bgr, "display is BGR (Blue is MSB)");
#define BITS_PER_PIXEL 16
/*
* Buffer alignment, must not be 0
*/
#define UBICOM32PLIO80_ALIGNMENT 4
/*
* PLIO FSM
* 16-bit data bus on port I
* CS on EXTCTL[6]
* WR on EXTCTL[4]
*/
static const plio_fctl_t plio_fctl = {
.fctl0 = {
.ptif_port_mode = PLIO_PORT_MODE_DI,
.ptif_portd_cfg = 0,
.ptif_porti_cfg = 3,
.edif_ds = 6,
.edif_cmp_mode = 1,
.ecif_extclk_ena = 0, // enable clock output on PD7 table 2.65/p111 says extctl[0]?
.icif_clk_src_sel = PLIO_CLK_IO,
},
.fctl2 = {
.icif_eclk_div = 10,
.icif_iclk_div = 10,
},
};
static const plio_config_t plio_config = {
.pfsm = {
/*
* Table 12.63
*/
.grpsel[0] = {1,1,1,1,1,1,1,1,1,1},
/*
* Table 12.66 Counter load value
*/
.cs_lut[0] = {0,0,0,0,0,0,0,0},
/*
* Table 2.75 PLIO PFSM Configuration Registers
*/
// 3 2 1 0
.extctl_o_lut[0] = {0x3f, 0x2f, 0x3f, 0x3f},
// 7 6 5 4
.extctl_o_lut[1] = {0x3f, 0x3f, 0x3f, 0x2f},
},
.edif = {
.odr_oe = 0xffff,
},
.ecif = {
.output_ena = (1 << 6) | (1 << 4),
},
};
static const u32_t ubicom32plio80_plio_fsm[] = {
// 0-F
0x00070007, 0x00070007,
0x00070007, 0x00070007,
0x00070007, 0x00070007,
0x00070007, 0x00070007,
0x16260806, 0x16260806,
0x16260806, 0x16260806,
0x16260806, 0x16260806,
0x16260806, 0x16260806,
// 10 - 1f
0x22061806, 0x22061806,
0x22061806, 0x22061806,
0x22061806, 0x22061806,
0x22061806, 0x22061806,
0x22061806, 0x22061806,
0x22061806, 0x22061806,
0x22061806, 0x22061806,
0x22061806, 0x22061806,
// 20 - 2f
0x00070806, 0x00070806,
0x00070806, 0x00070806,
0x00070806, 0x00070806,
0x00070806, 0x00070806,
0x00070806, 0x00070806,
0x00070806, 0x00070806,
0x00070806, 0x00070806,
0x00070806, 0x00070806,
};
/*
* fb_fix_screeninfo defines the non-changeable properties of the VDC, depending on what mode it is in.
*/
static struct fb_fix_screeninfo ubicom32plio80_fix = {
.id = "Ubicom32",
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_TRUECOLOR,
.accel = FB_ACCEL_UBICOM32_PLIO80,
};
/*
* Filled in at probe time when we find out what the hardware supports
*/
static struct fb_var_screeninfo ubicom32plio80_var;
/*
* Private data structure
*/
struct ubicom32plio80_drvdata {
struct fb_info *fbinfo;
bool cmap_alloc;
/*
* The address of the framebuffer in memory
*/
void *fb;
void *fb_aligned;
/*
* Total size of vram including alignment allowance
*/
u32 total_vram_size;
/*
* Fake palette of 16 colors
*/
u32 pseudo_palette[PALETTE_ENTRIES_NO];
int irq_req;
/*
* Current pointer and bytes left to transfer with the PLIO
*/
void *xfer_ptr;
u32 bytes_to_xfer;
u32 busy;
};
static struct platform_device *ubicom32plio80_platform_device;
/*
* ubicom32plio80_isr
*/
static int ubicom32plio80_isr(int irq, void *appdata)
{
struct ubicom32plio80_drvdata *ud = (struct ubicom32plio80_drvdata *)appdata;
if (!ud->bytes_to_xfer) {
ubicom32_disable_interrupt(TX_FIFO_INT(PLIO_PORT));
PLIO_NBR->intmask.txfifo_wm = 0;
ud->busy = 0;
return IRQ_HANDLED;
}
asm volatile (
".rept 8 \n\t"
"move.4 (%[fifo]), (%[data])4++ \n\t"
".endr \n\t"
: [data] "+a" (ud->xfer_ptr)
: [fifo] "a" (&PLIO_NBR->tx_lo)
);
ud->bytes_to_xfer -= 32;
return IRQ_HANDLED;
}
/*
* ubicom32plio80_update
*/
static void ubicom32plio80_update(struct ubicom32plio80_drvdata *ud, u32 *fb)
{
struct ubicom32_io_port *ri = (struct ubicom32_io_port *)RI;
struct ubicom32_io_port *rd = (struct ubicom32_io_port *)RD;
ud->xfer_ptr = fb;
ud->bytes_to_xfer = (xres * yres * 2) - 64;
ud->busy = 1;
ri->gpio_mask = 0;
rd->gpio_mask &= ~((1 << 4) | (1 << 2));
*(u32 *)(&PLIO_NBR->intclr) = ~0;
PLIO_NBR->intmask.txfifo_wm = 1;
PLIO_NBR->fifo_wm.tx = 8;
ubicom32_enable_interrupt(TX_FIFO_INT(PLIO_PORT));
asm volatile (
".rept 16 \n\t"
"move.4 (%[fifo]), (%[data])4++ \n\t"
".endr \n\t"
: [data] "+a" (ud->xfer_ptr)
: [fifo] "a" (&PLIO_NBR->tx_lo)
);
}
/*
* ubicom32plio80_pan_display
* Pans the display to a given location. Supports only y direction panning.
*/
static int ubicom32plio80_pan_display(struct fb_var_screeninfo *var, struct fb_info *fbi)
{
struct ubicom32plio80_drvdata *ud = (struct ubicom32plio80_drvdata *)fbi->par;
void *new_addr;
/*
* Get the last y line that would be displayed. Since we don't support YWRAP,
* it must be less than our virtual y size.
*/
u32 lasty = var->yoffset + var->yres;
if (lasty > fbi->var.yres_virtual) {
/*
* We would fall off the end of our frame buffer if we panned here.
*/
return -EINVAL;
}
if (var->xoffset) {
/*
* We don't support panning in the x direction
*/
return -EINVAL;
}
/*
* Everything looks sane, go ahead and pan
*
* We have to calculate a new address for the VDC to look at
*/
new_addr = ud->fb_aligned + (var->yoffset * fbi->fix.line_length);
return 0;
}
/*
* ubicom32plio80_setcolreg
* Sets a color in our virtual palette
*/
static int ubicom32plio80_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *fbi)
{
u32 *palette = fbi->pseudo_palette;
if (regno >= PALETTE_ENTRIES_NO) {
return -EINVAL;
}
/*
* We only use 8 bits from each color
*/
red >>= 8;
green >>= 8;
blue >>= 8;
/*
* Convert any grayscale values
*/
if (fbi->var.grayscale) {
u16 gray = red + green + blue;
gray += (gray >> 2) + (gray >> 3) - (gray >> 7);
gray >>= 2;
if (gray > 255) {
gray = 255;
}
red = gray;
blue = gray;
green = gray;
}
palette[regno] = (red << fbi->var.red.offset) | (green << fbi->var.green.offset) |
(blue << fbi->var.blue.offset);
return 0;
}
/*
* ubicom32plio80_mmap
*/
static int ubicom32plio80_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct ubicom32plio80_drvdata *ud = (struct ubicom32plio80_drvdata *)info->par;
vma->vm_start = (unsigned long)(ud->fb_aligned);
vma->vm_end = vma->vm_start + info->fix.smem_len;
/* For those who don't understand how mmap works, go read
* Documentation/nommu-mmap.txt.
* For those that do, you will know that the VM_MAYSHARE flag
* must be set in the vma->vm_flags structure on noMMU
* Other flags can be set, and are documented in
* include/linux/mm.h
*/
vma->vm_flags |= VM_MAYSHARE | VM_SHARED;
return 0;
}
/*
* ubicom32plio80_check_var
* Check the var, tweak it but don't change operational parameters.
*/
static int ubicom32plio80_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
struct ubicom32plio80_drvdata *ud = (struct ubicom32plio80_drvdata *)info->par;
u32 line_size = var->xres * (BITS_PER_PIXEL / 8);
/*
* See if we can handle this bpp
*/
if (var->bits_per_pixel > BITS_PER_PIXEL) {
return -EINVAL;
}
var->bits_per_pixel = BITS_PER_PIXEL;
/*
* See if we have enough memory to handle this resolution
*/
if ((line_size * var->yres * BITS_PER_PIXEL / 8) > ud->total_vram_size) {
return -EINVAL;
}
var->xres_virtual = var->xres;
var->yres_virtual = ud->total_vram_size / line_size;
var->red.length = 5;
var->green.length = 6;
var->green.offset = 5;
var->blue.length = 5;
var->transp.offset = var->transp.length = 0;
if (bgr) {
var->red.offset = 0;
var->blue.offset = 11;
} else {
var->red.offset = 11;
var->blue.offset = 0;
}
var->nonstd = 0;
var->height = -1;
var->width = -1;
var->vmode = FB_VMODE_NONINTERLACED;
var->sync = 0;
return 0;
}
/*
* ubicom32plio80_set_par
* Set the video mode according to info->var
*/
static int ubicom32plio80_set_par(struct fb_info *info)
{
/*
* Anything changed?
*/
if ((xres == info->var.xres) && (yres == info->var.yres)) {
return 0;
}
/*
* Implement changes
*/
xres = info->var.xres;
yres = info->var.yres;
info->fix.visual = FB_VISUAL_TRUECOLOR;
info->fix.xpanstep = 0;
info->fix.ypanstep = 1;
info->fix.line_length = xres * (BITS_PER_PIXEL / 8);
return 0;
}
/*
* ubicom32plio80_ops
* List of supported operations
*/
static struct fb_ops ubicom32plio80_ops =
{
.owner = THIS_MODULE,
.fb_pan_display = ubicom32plio80_pan_display,
.fb_setcolreg = ubicom32plio80_setcolreg,
.fb_mmap = ubicom32plio80_mmap,
.fb_check_var = ubicom32plio80_check_var,
.fb_set_par = ubicom32plio80_set_par,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
/*
* ubicom32plio80_release
*/
static int ubicom32plio80_release(struct device *dev)
{
struct ubicom32plio80_drvdata *ud = dev_get_drvdata(dev);
unregister_framebuffer(ud->fbinfo);
if (ud->irq_req) {
free_irq(TX_FIFO_INT(PLIO_PORT), ud);
}
if (ud->cmap_alloc) {
fb_dealloc_cmap(&ud->fbinfo->cmap);
}
if (ud->fb) {
kfree(ud->fb);
}
framebuffer_release(ud->fbinfo);
dev_set_drvdata(dev, NULL);
return 0;
}
/*
* ubicom32plio80_platform_probe
*/
static int __init ubicom32plio80_platform_probe(struct platform_device *pdev)
{
struct ubicom32plio80_drvdata *ud;
struct fb_info *fbinfo;
int rc;
size_t fbsize;
struct device *dev = &pdev->dev;
int offset;
/*
* This is the minimum VRAM size
*/
fbsize = xres * yres * 2;
if (!vram_size) {
vram_size = (fbsize + 1023) / 1024;
} else {
if (fbsize > (vram_size * 1024)) {
dev_err(dev, "Not enough VRAM for display, need >= %u bytes\n", fbsize);
return -ENOMEM; // should be ebadparam?
}
}
/*
* Allocate the framebuffer instance + our private data
*/
fbinfo = framebuffer_alloc(sizeof(struct ubicom32plio80_drvdata), &pdev->dev);
if (!fbinfo) {
dev_err(dev, "Not enough memory to allocate instance.\n");
return -ENOMEM;
}
/*
* Fill in our private data.
*/
ud = (struct ubicom32plio80_drvdata *)fbinfo->par;
ud->fbinfo = fbinfo;
dev_set_drvdata(dev, ud);
/*
* Allocate and align the requested amount of VRAM
*/
ud->total_vram_size = (vram_size * 1024) + UBICOM32PLIO80_ALIGNMENT;
ud->fb = kmalloc(ud->total_vram_size, GFP_KERNEL);
if (ud->fb == NULL) {
dev_err(dev, "Couldn't allocate VRAM\n");
rc = -ENOMEM;
goto fail;
}
offset = (u32_t)ud->fb & (UBICOM32PLIO80_ALIGNMENT - 1);
if (!offset) {
ud->fb_aligned = ud->fb;
} else {
offset = UBICOM32PLIO80_ALIGNMENT - offset;
ud->fb_aligned = ud->fb + offset;
}
/*
* Clear the entire frame buffer
*/
memset(ud->fb_aligned, 0, vram_size * 1024);
/*
* Fill in the fb_var_screeninfo structure
*/
memset(&ubicom32plio80_var, 0, sizeof(ubicom32plio80_var));
ubicom32plio80_var.bits_per_pixel = BITS_PER_PIXEL;
ubicom32plio80_var.red.length = 5;
ubicom32plio80_var.green.length = 6;
ubicom32plio80_var.green.offset = 5;
ubicom32plio80_var.blue.length = 5;
ubicom32plio80_var.activate = FB_ACTIVATE_NOW;
if (bgr) {
ubicom32plio80_var.red.offset = 0;
ubicom32plio80_var.blue.offset = 11;
} else {
ubicom32plio80_var.red.offset = 11;
ubicom32plio80_var.blue.offset = 0;
}
/*
* Fill in the fb_info structure
*/
ud->fbinfo->device = dev;
ud->fbinfo->screen_base = (void *)ud->fb_aligned;
ud->fbinfo->fbops = &ubicom32plio80_ops;
ud->fbinfo->fix = ubicom32plio80_fix;
ud->fbinfo->fix.smem_start = (u32)ud->fb_aligned;
ud->fbinfo->fix.smem_len = vram_size * 1024;
ud->fbinfo->fix.line_length = xres * 2;
ud->fbinfo->fix.mmio_start = (u32)ud;
ud->fbinfo->fix.mmio_len = sizeof(struct ubicom32plio80_drvdata);
/*
* We support panning in the y direction only
*/
ud->fbinfo->fix.xpanstep = 0;
ud->fbinfo->fix.ypanstep = 1;
ud->fbinfo->pseudo_palette = ud->pseudo_palette;
ud->fbinfo->flags = FBINFO_DEFAULT;
ud->fbinfo->var = ubicom32plio80_var;
ud->fbinfo->var.xres = xres;
ud->fbinfo->var.yres = yres;
/*
* We cannot pan in the X direction, so xres_virtual is xres
* We can pan in the Y direction, so yres_virtual is vram_size / ud->fbinfo->fix.line_length
*/
ud->fbinfo->var.xres_virtual = xres;
ud->fbinfo->var.yres_virtual = (vram_size * 1024) / ud->fbinfo->fix.line_length;
/*
* Allocate a color map
*/
rc = fb_alloc_cmap(&ud->fbinfo->cmap, PALETTE_ENTRIES_NO, 0);
if (rc) {
dev_err(dev, "Fail to allocate colormap (%d entries)\n",
PALETTE_ENTRIES_NO);
goto fail;
}
ud->cmap_alloc = true;
/*
* Register new frame buffer
*/
rc = register_framebuffer(ud->fbinfo);
if (rc) {
dev_err(dev, "Could not register frame buffer\n");
goto fail;
}
/*
* request the PLIO IRQ
*/
rc = request_irq(TX_FIFO_INT(PLIO_PORT), ubicom32plio80_isr, IRQF_DISABLED, "ubicom32plio80", ud);
if (rc) {
dev_err(dev, "Could not request IRQ\n");
goto fail;
}
ud->irq_req = 1;
/*
* Clear any garbage out of the TX FIFOs (idif_txfifo_flush)
*
* cast through ubicom32_io_port to make sure the compiler does a word write
*/
((struct ubicom32_io_port *)PLIO_NBR)->int_set = (1 << 18);
/*
* Start up the state machine
*/
plio_init(&plio_fctl, &plio_config, (plio_sram_t *)ubicom32plio80_plio_fsm, sizeof(ubicom32plio80_plio_fsm));
PLIO_NBR->fctl0.pfsm_cmd = 0;
ubicom32plio80_update(ud, ud->fb_aligned);
/*
* Tell the log we are here
*/
dev_info(dev, "fbaddr=%p align=%p, size=%uKB screen(%ux%u) virt(%ux%u)\n",
ud->fb, ud->fb_aligned, vram_size, ud->fbinfo->var.xres, ud->fbinfo->var.yres,
ud->fbinfo->var.xres_virtual, ud->fbinfo->var.yres_virtual);
/*
* Success
*/
return 0;
fail:
ubicom32plio80_release(dev);
return rc;
}
/*
* ubicom32plio80_platform_remove
*/
static int ubicom32plio80_platform_remove(struct platform_device *pdev)
{
dev_info(&(pdev->dev), "Ubicom32 FB Driver Remove\n");
return ubicom32plio80_release(&pdev->dev);
}
static struct platform_driver ubicom32plio80_platform_driver = {
.probe = ubicom32plio80_platform_probe,
.remove = ubicom32plio80_platform_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
};
#ifndef MODULE
/*
* ubicom32plio80_setup
* Process kernel boot options
*/
static int __init ubicom32plio80_setup(char *options)
{
char *this_opt;
if (!options || !*options) {
return 0;
}
while ((this_opt = strsep(&options, ",")) != NULL) {
if (!*this_opt) {
continue;
}
if (!strncmp(this_opt, "vram_size=", 10)) {
vram_size = simple_strtoul(this_opt + 10, NULL, 0);
continue;
}
if (!strncmp(this_opt, "bgr=", 4)) {
bgr = simple_strtoul(this_opt + 4, NULL, 0);
continue;
}
if (!strncmp(this_opt, "xres=", 5)) {
xres = simple_strtoul(this_opt + 5, NULL, 0);
continue;
}
if (!strncmp(this_opt, "yres=", 5)) {
yres = simple_strtoul(this_opt + 5, NULL, 0);
continue;
}
}
return 0;
}
#endif /* MODULE */
/*
* ubicom32plio80_init
*/
static int __devinit ubicom32plio80_init(void)
{
int ret;
#ifndef MODULE
/*
* Get kernel boot options (in 'video=ubicom32plio80:<options>')
*/
char *option = NULL;
if (fb_get_options(DRIVER_NAME, &option)) {
return -ENODEV;
}
ubicom32plio80_setup(option);
#endif /* MODULE */
ret = platform_driver_register(&ubicom32plio80_platform_driver);
if (!ret) {
ubicom32plio80_platform_device = platform_device_alloc(DRIVER_NAME, 0);
if (ubicom32plio80_platform_device)
ret = platform_device_add(ubicom32plio80_platform_device);
else
ret = -ENOMEM;
if (ret) {
platform_device_put(ubicom32plio80_platform_device);
platform_driver_unregister(&ubicom32plio80_platform_driver);
}
}
return ret;
}
module_init(ubicom32plio80_init);
/*
* ubicom32plio80_exit
*/
static void __exit ubicom32plio80_exit(void)
{
platform_device_unregister(ubicom32plio80_platform_device);
platform_driver_unregister(&ubicom32plio80_platform_driver);
}
module_exit(ubicom32plio80_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Patrick Tjin <@ubicom.com>");
MODULE_DESCRIPTION(DRIVER_DESCRIPTION);

View File

@@ -0,0 +1,603 @@
/*
* drivers/video/ubicom32vfb.c
* Ubicom32 virtual frame buffer driver
*
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*/
/*
* This driver was based on skeletonfb.c, Skeleton for a frame buffer device by
* Geert Uytterhoeven.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#define DRIVER_NAME "ubicom32vfb"
#define DRIVER_DESCRIPTION "Ubicom32 virtual frame buffer driver"
#define PALETTE_ENTRIES_NO 16
/*
* Option variables
*
* vram_size: VRAM size in kilobytes, subject to alignment
*/
static int vram_size = 0;
module_param(vram_size, int, 0);
MODULE_PARM_DESC(vram_size, "VRAM size, in kilobytes to allocate, should be at least the size of one screen, subject to alignment");
static int xres = 320;
module_param(xres, int, 0);
MODULE_PARM_DESC(xres, "x (horizontal) resolution");
static int yres = 240;
module_param(yres, int, 0);
MODULE_PARM_DESC(yres, "y (vertical) resolution");
static int bgr = 0;
module_param(bgr, int, 0);
MODULE_PARM_DESC(bgr, "display is BGR (Blue is MSB)");
#define BITS_PER_PIXEL 16
/*
* Buffer alignment, must not be 0
*/
#define UBICOM32VFB_ALIGNMENT 4
/*
* fb_fix_screeninfo defines the non-changeable properties of the VDC, depending on what mode it is in.
*/
static struct fb_fix_screeninfo ubicom32vfb_fix = {
.id = "Ubicom32",
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_TRUECOLOR,
.accel = FB_ACCEL_UBICOM32_VFB,
};
/*
* Filled in at probe time when we find out what the hardware supports
*/
static struct fb_var_screeninfo ubicom32vfb_var;
/*
* Private data structure
*/
struct ubicom32vfb_drvdata {
struct fb_info *fbinfo;
bool cmap_alloc;
/*
* The address of the framebuffer in memory
*/
void *fb;
void *fb_aligned;
/*
* Total size of vram including alignment allowance
*/
u32 total_vram_size;
/*
* Fake palette of 16 colors
*/
u32 pseudo_palette[PALETTE_ENTRIES_NO];
};
static struct platform_device *ubicom32vfb_platform_device;
/*
* ubicom32vfb_pan_display
* Pans the display to a given location. Supports only y direction panning.
*/
static int ubicom32vfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *fbi)
{
struct ubicom32vfb_drvdata *ud = (struct ubicom32vfb_drvdata *)fbi->par;
void *new_addr;
/*
* Get the last y line that would be displayed. Since we don't support YWRAP,
* it must be less than our virtual y size.
*/
u32 lasty = var->yoffset + var->yres;
if (lasty > fbi->var.yres_virtual) {
/*
* We would fall off the end of our frame buffer if we panned here.
*/
return -EINVAL;
}
if (var->xoffset) {
/*
* We don't support panning in the x direction
*/
return -EINVAL;
}
/*
* Everything looks sane, go ahead and pan
*
* We have to calculate a new address for the VDC to look at
*/
new_addr = ud->fb_aligned + (var->yoffset * fbi->fix.line_length);
return 0;
}
/*
* ubicom32vfb_setcolreg
* Sets a color in our virtual palette
*/
static int ubicom32vfb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *fbi)
{
u32 *palette = fbi->pseudo_palette;
if (regno >= PALETTE_ENTRIES_NO) {
return -EINVAL;
}
/*
* We only use 8 bits from each color
*/
red >>= 8;
green >>= 8;
blue >>= 8;
/*
* Convert any grayscale values
*/
if (fbi->var.grayscale) {
u16 gray = red + green + blue;
gray += (gray >> 2) + (gray >> 3) - (gray >> 7);
gray >>= 2;
if (gray > 255) {
gray = 255;
}
red = gray;
blue = gray;
green = gray;
}
palette[regno] = (red << fbi->var.red.offset) | (green << fbi->var.green.offset) |
(blue << fbi->var.blue.offset);
return 0;
}
/*
* ubicom32vfb_mmap
*/
static int ubicom32vfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct ubicom32vfb_drvdata *ud = (struct ubicom32vfb_drvdata *)info->par;
vma->vm_start = (unsigned long)(ud->fb_aligned);
vma->vm_end = vma->vm_start + info->fix.smem_len;
/* For those who don't understand how mmap works, go read
* Documentation/nommu-mmap.txt.
* For those that do, you will know that the VM_MAYSHARE flag
* must be set in the vma->vm_flags structure on noMMU
* Other flags can be set, and are documented in
* include/linux/mm.h
*/
vma->vm_flags |= VM_MAYSHARE | VM_SHARED;
return 0;
}
/*
* ubicom32vfb_check_var
* Check the var, tweak it but don't change operational parameters.
*/
static int ubicom32vfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
struct ubicom32vfb_drvdata *ud = (struct ubicom32vfb_drvdata *)info->par;
u32 line_size = var->xres * (BITS_PER_PIXEL / 8);
/*
* See if we can handle this bpp
*/
if (var->bits_per_pixel > BITS_PER_PIXEL) {
return -EINVAL;
}
var->bits_per_pixel = BITS_PER_PIXEL;
/*
* See if we have enough memory to handle this resolution
*/
if ((line_size * var->yres * BITS_PER_PIXEL / 8) > ud->total_vram_size) {
return -EINVAL;
}
var->xres_virtual = var->xres;
var->yres_virtual = ud->total_vram_size / line_size;
var->red.length = 5;
var->green.length = 6;
var->green.offset = 5;
var->blue.length = 5;
var->transp.offset = var->transp.length = 0;
if (bgr) {
var->red.offset = 0;
var->blue.offset = 11;
} else {
var->red.offset = 11;
var->blue.offset = 0;
}
var->nonstd = 0;
var->height = -1;
var->width = -1;
var->vmode = FB_VMODE_NONINTERLACED;
var->sync = 0;
return 0;
}
/*
* ubicom32vfb_set_par
* Set the video mode according to info->var
*/
static int ubicom32vfb_set_par(struct fb_info *info)
{
/*
* Anything changed?
*/
if ((xres == info->var.xres) && (yres == info->var.yres)) {
return 0;
}
/*
* Implement changes
*/
xres = info->var.xres;
yres = info->var.yres;
info->fix.visual = FB_VISUAL_TRUECOLOR;
info->fix.xpanstep = 0;
info->fix.ypanstep = 1;
info->fix.line_length = xres * (BITS_PER_PIXEL / 8);
return 0;
}
/*
* ubicom32vfb_ops
* List of supported operations
*/
static struct fb_ops ubicom32vfb_ops =
{
.owner = THIS_MODULE,
.fb_pan_display = ubicom32vfb_pan_display,
.fb_setcolreg = ubicom32vfb_setcolreg,
.fb_mmap = ubicom32vfb_mmap,
.fb_check_var = ubicom32vfb_check_var,
.fb_set_par = ubicom32vfb_set_par,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
/*
* ubicom32vfb_release
*/
static int ubicom32vfb_release(struct device *dev)
{
struct ubicom32vfb_drvdata *ud = dev_get_drvdata(dev);
unregister_framebuffer(ud->fbinfo);
if (ud->cmap_alloc) {
fb_dealloc_cmap(&ud->fbinfo->cmap);
}
if (ud->fb) {
kfree(ud->fb);
}
framebuffer_release(ud->fbinfo);
dev_set_drvdata(dev, NULL);
return 0;
}
/*
* ubicom32vfb_platform_probe
*/
static int __init ubicom32vfb_platform_probe(struct platform_device *pdev)
{
struct ubicom32vfb_drvdata *ud;
struct fb_info *fbinfo;
int rc;
size_t fbsize;
struct device *dev = &pdev->dev;
int offset;
/*
* This is the minimum VRAM size
*/
fbsize = xres * yres * 2;
if (!vram_size) {
vram_size = (fbsize + 1023) / 1024;
} else {
if (fbsize > (vram_size * 1024)) {
dev_err(dev, "Not enough VRAM for display, need >= %u bytes\n", fbsize);
return -ENOMEM; // should be ebadparam?
}
}
/*
* Allocate the framebuffer instance + our private data
*/
fbinfo = framebuffer_alloc(sizeof(struct ubicom32vfb_drvdata), &pdev->dev);
if (!fbinfo) {
dev_err(dev, "Not enough memory to allocate instance.\n");
return -ENOMEM;
}
/*
* Fill in our private data.
*/
ud = (struct ubicom32vfb_drvdata *)fbinfo->par;
ud->fbinfo = fbinfo;
dev_set_drvdata(dev, ud);
/*
* Allocate and align the requested amount of VRAM
*/
ud->total_vram_size = (vram_size * 1024) + UBICOM32VFB_ALIGNMENT;
ud->fb = kmalloc(ud->total_vram_size, GFP_KERNEL);
if (ud->fb == NULL) {
dev_err(dev, "Couldn't allocate VRAM\n");
rc = -ENOMEM;
goto fail;
}
offset = (u32_t)ud->fb & (UBICOM32VFB_ALIGNMENT - 1);
if (!offset) {
ud->fb_aligned = ud->fb;
} else {
offset = UBICOM32VFB_ALIGNMENT - offset;
ud->fb_aligned = ud->fb + offset;
}
/*
* Clear the entire frame buffer
*/
memset(ud->fb_aligned, 0, vram_size * 1024);
/*
* Fill in the fb_var_screeninfo structure
*/
memset(&ubicom32vfb_var, 0, sizeof(ubicom32vfb_var));
ubicom32vfb_var.bits_per_pixel = BITS_PER_PIXEL;
ubicom32vfb_var.red.length = 5;
ubicom32vfb_var.green.length = 6;
ubicom32vfb_var.green.offset = 5;
ubicom32vfb_var.blue.length = 5;
ubicom32vfb_var.activate = FB_ACTIVATE_NOW;
if (bgr) {
ubicom32vfb_var.red.offset = 0;
ubicom32vfb_var.blue.offset = 11;
} else {
ubicom32vfb_var.red.offset = 11;
ubicom32vfb_var.blue.offset = 0;
}
/*
* Fill in the fb_info structure
*/
ud->fbinfo->device = dev;
ud->fbinfo->screen_base = (void *)ud->fb_aligned;
ud->fbinfo->fbops = &ubicom32vfb_ops;
ud->fbinfo->fix = ubicom32vfb_fix;
ud->fbinfo->fix.smem_start = (u32)ud->fb_aligned;
ud->fbinfo->fix.smem_len = vram_size * 1024;
ud->fbinfo->fix.line_length = xres * 2;
ud->fbinfo->fix.mmio_start = (u32)ud;
ud->fbinfo->fix.mmio_len = sizeof(struct ubicom32vfb_drvdata);
/*
* We support panning in the y direction only
*/
ud->fbinfo->fix.xpanstep = 0;
ud->fbinfo->fix.ypanstep = 1;
ud->fbinfo->pseudo_palette = ud->pseudo_palette;
ud->fbinfo->flags = FBINFO_DEFAULT;
ud->fbinfo->var = ubicom32vfb_var;
ud->fbinfo->var.xres = xres;
ud->fbinfo->var.yres = yres;
/*
* We cannot pan in the X direction, so xres_virtual is xres
* We can pan in the Y direction, so yres_virtual is vram_size / ud->fbinfo->fix.line_length
*/
ud->fbinfo->var.xres_virtual = xres;
ud->fbinfo->var.yres_virtual = (vram_size * 1024) / ud->fbinfo->fix.line_length;
/*
* Allocate a color map
*/
rc = fb_alloc_cmap(&ud->fbinfo->cmap, PALETTE_ENTRIES_NO, 0);
if (rc) {
dev_err(dev, "Fail to allocate colormap (%d entries)\n",
PALETTE_ENTRIES_NO);
goto fail;
}
ud->cmap_alloc = true;
/*
* Register new frame buffer
*/
rc = register_framebuffer(ud->fbinfo);
if (rc) {
dev_err(dev, "Could not register frame buffer\n");
goto fail;
}
/*
* Tell the log we are here
*/
dev_info(dev, "fbaddr=%p align=%p, size=%uKB screen(%ux%u) virt(%ux%u)\n",
ud->fb, ud->fb_aligned, vram_size, ud->fbinfo->var.xres, ud->fbinfo->var.yres,
ud->fbinfo->var.xres_virtual, ud->fbinfo->var.yres_virtual);
/*
* Success
*/
return 0;
fail:
ubicom32vfb_release(dev);
return rc;
}
/*
* ubicom32vfb_platform_remove
*/
static int ubicom32vfb_platform_remove(struct platform_device *pdev)
{
dev_info(&(pdev->dev), "Ubicom32 FB Driver Remove\n");
return ubicom32vfb_release(&pdev->dev);
}
static struct platform_driver ubicom32vfb_platform_driver = {
.probe = ubicom32vfb_platform_probe,
.remove = ubicom32vfb_platform_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
};
#ifndef MODULE
/*
* ubicom32vfb_setup
* Process kernel boot options
*/
static int __init ubicom32vfb_setup(char *options)
{
char *this_opt;
if (!options || !*options) {
return 0;
}
while ((this_opt = strsep(&options, ",")) != NULL) {
if (!*this_opt) {
continue;
}
if (!strncmp(this_opt, "vram_size=", 10)) {
vram_size = simple_strtoul(this_opt + 10, NULL, 0);
continue;
}
if (!strncmp(this_opt, "bgr=", 4)) {
bgr = simple_strtoul(this_opt + 4, NULL, 0);
continue;
}
if (!strncmp(this_opt, "xres=", 5)) {
xres = simple_strtoul(this_opt + 5, NULL, 0);
continue;
}
if (!strncmp(this_opt, "yres=", 5)) {
yres = simple_strtoul(this_opt + 5, NULL, 0);
continue;
}
}
return 0;
}
#endif /* MODULE */
/*
* ubicom32vfb_init
*/
static int __devinit ubicom32vfb_init(void)
{
int ret;
#ifndef MODULE
/*
* Get kernel boot options (in 'video=ubicom32vfb:<options>')
*/
char *option = NULL;
if (fb_get_options(DRIVER_NAME, &option)) {
return -ENODEV;
}
ubicom32vfb_setup(option);
#endif /* MODULE */
ret = platform_driver_register(&ubicom32vfb_platform_driver);
#ifdef CONFIG_FB_UBICOM32_VIRTUAL_NOAUTO
return ret;
#else
if (!ret) {
ubicom32vfb_platform_device = platform_device_alloc(DRIVER_NAME, 0);
if (ubicom32vfb_platform_device)
ret = platform_device_add(ubicom32vfb_platform_device);
else
ret = -ENOMEM;
if (ret) {
platform_device_put(ubicom32vfb_platform_device);
platform_driver_unregister(&ubicom32vfb_platform_driver);
}
}
return ret;
#endif
}
module_init(ubicom32vfb_init);
/*
* ubicom32vfb_exit
*/
static void __exit ubicom32vfb_exit(void)
{
platform_device_unregister(ubicom32vfb_platform_device);
platform_driver_unregister(&ubicom32vfb_platform_driver);
}
module_exit(ubicom32vfb_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Patrick Tjin <@ubicom.com>");
MODULE_DESCRIPTION(DRIVER_DESCRIPTION);

View File

@@ -0,0 +1,630 @@
/*
* drivers/watchdog/ubi32_wdt.c
* Ubicom32 Watchdog Driver
*
* Originally based on softdog.c
* Copyright 2006-2007 Analog Devices Inc.
* Copyright 2006-2007 Michele d'Amico
* Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>
* (C) Copyright 2009, Ubicom, Inc.
*
* This file is part of the Ubicom32 Linux Kernel Port.
*
* The Ubicom32 Linux Kernel Port is free software: you can redistribute
* it and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not,
* see <http://www.gnu.org/licenses/>.
*
* Ubicom32 implementation derived from (with many thanks):
* arch/m68knommu
* arch/blackfin
* arch/parisc
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <asm/ip5000.h>
#define WATCHDOG_NAME "ubi32-wdt"
#define PFX WATCHDOG_NAME ": "
#define OSC1_FREQ 12000000
#define WATCHDOG_SEC_TO_CYC(x) (OSC1_FREQ * (x))
#define WATCHDOG_MAX_SEC (0xffffffff / OSC1_FREQ)
#define MIN_PROCESSOR_ADDRESS 0x03000000
static DEFINE_SPINLOCK(ubi32_wdt_spinlock);
#define WATCHDOG_TIMEOUT 20
#if defined(CONFIG_WATCHDOG_NOWAYOUT)
#define WATCHDOG_NOWAYOUT 1
#else
#define WATCHDOG_NOWAYOUT 0
#endif
static unsigned int timeout = WATCHDOG_TIMEOUT;
static int nowayout = WATCHDOG_NOWAYOUT;
static struct watchdog_info ubi32_wdt_info;
static unsigned long open_check;
static char expect_close;
#if !defined(CONFIG_SMP)
#define UBI32_WDT_LOCK(lock, flags) local_irq_save(flags)
#define UBI32_WDT_UNLOCK(lock, flags) local_irq_restore(flags)
#define UBI32_WDT_LOCK_CHECK()
#else
#define UBI32_WDT_LOCK(lock, flags) spin_lock_irqsave((lock), (flags));
#define UBI32_WDT_UNLOCK(lock, flags) spin_unlock_irqrestore((lock), (flags));
#define UBI32_WDT_LOCK_CHECK() BUG_ON(!spin_is_locked(&ubi32_wdt_spinlock));
#endif
/*
* ubi32_wdt_remaining()
* Return the approximate number of seconds remaining
*/
static int ubi32_wdt_remaining(void)
{
int compare;
int curr;
UBI32_WDT_LOCK_CHECK();
ubicom32_write_reg(&UBICOM32_IO_TIMER->tkey, TIMER_TKEYVAL);
compare = ubicom32_read_reg(&UBICOM32_IO_TIMER->wdcom);
curr = ubicom32_read_reg(&UBICOM32_IO_TIMER->mptval);
ubicom32_write_reg(&UBICOM32_IO_TIMER->tkey, 0);
return (compare - curr) / OSC1_FREQ;
}
/*
* ubi32_wdt_keepalive()
* Keep the Userspace Watchdog Alive
*
* The Userspace watchdog got a KeepAlive: schedule the next timeout.
*/
static int ubi32_wdt_keepalive(void)
{
UBI32_WDT_LOCK_CHECK();
ubicom32_write_reg(&UBICOM32_IO_TIMER->tkey, TIMER_TKEYVAL);
ubicom32_write_reg(&UBICOM32_IO_TIMER->wdcom,
ubicom32_read_reg(&UBICOM32_IO_TIMER->mptval)
+ WATCHDOG_SEC_TO_CYC(timeout));
ubicom32_write_reg(&UBICOM32_IO_TIMER->tkey, 0);
return 0;
}
/*
* ubi32_wdt_stop()
* Stop the on-chip Watchdog
*/
static int ubi32_wdt_stop(void)
{
UBI32_WDT_LOCK_CHECK();
ubicom32_write_reg(&UBICOM32_IO_TIMER->tkey, TIMER_TKEYVAL);
ubicom32_write_reg(&UBICOM32_IO_TIMER->wdcfg, TIMER_WATCHDOG_DISABLE);
ubicom32_write_reg(&UBICOM32_IO_TIMER->tkey, 0);
return 0;
}
/*
* ubi32_wdt_start()
* Start the on-chip Watchdog
*/
static int ubi32_wdt_start(void)
{
UBI32_WDT_LOCK_CHECK();
ubicom32_write_reg(&UBICOM32_IO_TIMER->tkey, TIMER_TKEYVAL);
ubicom32_write_reg(&UBICOM32_IO_TIMER->wdcom,
ubicom32_read_reg(&UBICOM32_IO_TIMER->mptval)
+ WATCHDOG_SEC_TO_CYC(timeout));
ubicom32_write_reg(&UBICOM32_IO_TIMER->wdcfg, ~TIMER_WATCHDOG_DISABLE);
ubicom32_write_reg(&UBICOM32_IO_TIMER->tkey, 0);
return 0;
}
/*
* ubi32_wdt_running()
* Return true if the watchdog is configured
*/
static int ubi32_wdt_running(void)
{
int enabled;
UBI32_WDT_LOCK_CHECK();
ubicom32_write_reg(&UBICOM32_IO_TIMER->tkey, TIMER_TKEYVAL);
enabled = ubicom32_read_reg(&UBICOM32_IO_TIMER->wdcfg) == ~TIMER_WATCHDOG_DISABLE;
ubicom32_write_reg(&UBICOM32_IO_TIMER->tkey, 0);
return enabled;
}
/*
* ubi32_wdt_set_timeout()
* Set the Userspace Watchdog timeout
*
* - @t: new timeout value (in seconds)
*/
static int ubi32_wdt_set_timeout(unsigned long t)
{
UBI32_WDT_LOCK_CHECK();
if (t > WATCHDOG_MAX_SEC) {
printk(KERN_WARNING PFX "request to large: %ld [1-%d] sec)\n", t, WATCHDOG_MAX_SEC);
return -EINVAL;
}
/*
* If we are running, then reset the time value so
* that the new value has an immediate effect.
*/
timeout = t;
if (ubi32_wdt_running()) {
ubi32_wdt_keepalive();
}
return 0;
}
/*
* ubi32_wdt_open()
* Open the Device
*/
static int ubi32_wdt_open(struct inode *inode, struct file *file)
{
unsigned long flags;
if (test_and_set_bit(0, &open_check))
return -EBUSY;
if (nowayout)
__module_get(THIS_MODULE);
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
ubi32_wdt_start();
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
return nonseekable_open(inode, file);
}
/*
* ubi32_wdt_close()
* Close the Device
*/
static int ubi32_wdt_release(struct inode *inode, struct file *file)
{
unsigned long flags;
/*
* If we don't expect a close, then the watchdog continues
* even though the device is closed. The caller will have
* a full timeout value to reopen the device and continue
* stroking it.
*/
if (expect_close != 42) {
printk(KERN_CRIT PFX
"Unexpected close, not stopping watchdog!\n");
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
ubi32_wdt_keepalive();
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
} else {
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
ubi32_wdt_stop();
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
}
expect_close = 0;
clear_bit(0, &open_check);
return 0;
}
/*
* ubi32_wdt_write()
* Write to Device
*
* If the user writes nothing, nothing happens.
* If the user writes a V, then we expect a close and allow a release.
* If the user writes anything else, it is ignored.
*/
static ssize_t ubi32_wdt_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos)
{
size_t i;
unsigned long flags;
/*
* Every write resets the expect_close. The last write
* must be a V to allow shutdown on close.
*/
expect_close = 0;
/*
* Empty writes still ping.
*/
if (!len) {
goto ping;
}
/*
* If nowayout is set, it does not matter if the caller
* is trying to send the magic 'V' we will not allow a
* close to stop us.
*/
if (nowayout) {
goto ping;
}
/*
* See if the program wrote a 'V' and if so disable
* the watchdog on release.
*/
for (i = 0; i < len; i++) {
char c;
if (get_user(c, data + i)) {
return -EFAULT;
}
if (c == 'V') {
expect_close = 42;
}
}
ping:
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
ubi32_wdt_keepalive();
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
return len;
}
/*
* ubi32_wdt_ioctl()
* Query the watchdog device.
*
* Query basic information from the device or ping it, as outlined by the
* watchdog API.
*/
static long ubi32_wdt_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
switch (cmd) {
case WDIOC_GETSUPPORT:
if (copy_to_user(argp, &ubi32_wdt_info, sizeof(ubi32_wdt_info))) {
return -EFAULT;
}
return 0;
case WDIOC_GETSTATUS: {
unsigned long flags;
int running;
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
running = ubi32_wdt_running();
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
return running;
}
case WDIOC_GETBOOTSTATUS:
return ubicom32_get_reset_reason();
case WDIOC_SETOPTIONS: {
unsigned long flags;
int options, ret = -EINVAL;
/*
* The sample application does not pass a pointer
* but directly passes a value of 1 or 2; however
* all of the implementations (and thus probably
* the real applications) pass a pointer to a value.
*
* It should be noted that WDIOC_SETOPTIONS is defined as
* _IOR(WATCHDOG_IOCTL_BASE, 4, int), which means
* that it should be an int and NOT a pointer.
*
* TODO: Examine this code for future chips.
* TODO: Report the sample code defect.
*/
if ((int)p < MIN_PROCESSOR_ADDRESS) {
options = (int)p;
} else {
if (get_user(options, p))
return -EFAULT;
}
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
if (options & WDIOS_DISABLECARD) {
ubi32_wdt_stop();
ret = 0;
}
if (options & WDIOS_ENABLECARD) {
ubi32_wdt_start();
ret = 0;
}
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
return ret;
}
case WDIOC_KEEPALIVE: {
unsigned long flags;
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
ubi32_wdt_keepalive();
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
return 0;
}
case WDIOC_SETTIMEOUT: {
int new_timeout;
unsigned long flags;
int ret = 0;
if (get_user(new_timeout, p))
return -EFAULT;
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
ret = ubi32_wdt_set_timeout(new_timeout);
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
return ret;
}
case WDIOC_GETTIMEOUT:
return put_user(timeout, p);
case WDIOC_GETTIMELEFT: {
unsigned long flags;
int remaining = 0;
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
remaining = ubi32_wdt_remaining();
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
return put_user(remaining, p);
}
default:
return -ENOTTY;
}
}
/*
* ubi32_wdt_notify_sys()
* Notification callback function for system events.
*
* Turn off the watchdog during a SYS_DOWN or SYS_HALT.
*/
static int ubi32_wdt_notify_sys(struct notifier_block *this,
unsigned long code, void *unused)
{
if (code == SYS_DOWN || code == SYS_HALT) {
unsigned long flags;
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
ubi32_wdt_stop();
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
}
return NOTIFY_DONE;
}
#ifdef CONFIG_PM
static int state_before_suspend;
/*
* ubi32_wdt_suspend()
* suspend the watchdog
*
* Remember if the watchdog was running and stop it.
*/
static int ubi32_wdt_suspend(struct platform_device *pdev, pm_message_t state)
{
unsigned long flags;
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
state_before_suspend = ubi32_wdt_running();
ubi32_wdt_stop();
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
return 0;
}
/*
* ubi32_wdt_resume()
* Resume the watchdog
*
* If the watchdog was running, turn it back on.
*/
static int ubi32_wdt_resume(struct platform_device *pdev)
{
if (state_before_suspend) {
unsigned long flags;
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
ubi32_wdt_set_timeout(timeout);
ubi32_wdt_start();
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
}
return 0;
}
#else
# define ubi32_wdt_suspend NULL
# define ubi32_wdt_resume NULL
#endif
static const struct file_operations ubi32_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = ubi32_wdt_write,
.unlocked_ioctl = ubi32_wdt_ioctl,
.open = ubi32_wdt_open,
.release = ubi32_wdt_release,
};
static struct miscdevice ubi32_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &ubi32_wdt_fops,
};
static struct watchdog_info ubi32_wdt_info = {
.identity = "Ubicom32 Watchdog",
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
};
static struct notifier_block ubi32_wdt_notifier = {
.notifier_call = ubi32_wdt_notify_sys,
};
/*
* ubi32_wdt_probe()
* Probe/register the watchdog module
*
* Registers the misc device and notifier handler. Actual device
* initialization is handled by ubi32_wdt_open().
*/
static int __devinit ubi32_wdt_probe(struct platform_device *pdev)
{
int ret;
ret = register_reboot_notifier(&ubi32_wdt_notifier);
if (ret) {
printk(KERN_ERR PFX
"cannot register reboot notifier (err=%d)\n", ret);
return ret;
}
ret = misc_register(&ubi32_wdt_miscdev);
if (ret) {
printk(KERN_ERR PFX
"cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
unregister_reboot_notifier(&ubi32_wdt_notifier);
return ret;
}
printk(KERN_INFO PFX "initialized: timeout=%d sec (nowayout=%d)\n",
timeout, nowayout);
return 0;
}
/*
* ubi32_wdt_remove()
* Uninstall the module
*
* Unregisters the misc device and notifier handler. Actual device
* deinitialization is handled by ubi32_wdt_close().
*/
static int __devexit ubi32_wdt_remove(struct platform_device *pdev)
{
misc_deregister(&ubi32_wdt_miscdev);
unregister_reboot_notifier(&ubi32_wdt_notifier);
return 0;
}
static struct platform_device *ubi32_wdt_device;
static struct platform_driver ubi32_wdt_driver = {
.probe = ubi32_wdt_probe,
.remove = __devexit_p(ubi32_wdt_remove),
.suspend = ubi32_wdt_suspend,
.resume = ubi32_wdt_resume,
.driver = {
.name = WATCHDOG_NAME,
.owner = THIS_MODULE,
},
};
/*
* ubi32_wdt_init()
* Initialize the watchdog.
*
* Checks the module params and registers the platform device & driver.
* Real work is in the platform probe function.
*/
static int __init ubi32_wdt_init(void)
{
unsigned long flags;
int ret;
/*
* Check that the timeout value is within range
*/
spin_lock_irqsave(&ubi32_wdt_spinlock, flags);
ret = ubi32_wdt_set_timeout(timeout);
spin_unlock_irqrestore(&ubi32_wdt_spinlock, flags);
if (ret) {
return ret;
}
/*
* Since this is an on-chip device and needs no board-specific
* resources, we'll handle all the platform device stuff here.
*/
ret = platform_driver_register(&ubi32_wdt_driver);
if (ret) {
printk(KERN_ERR PFX "unable to register driver\n");
return ret;
}
ubi32_wdt_device = platform_device_register_simple(WATCHDOG_NAME, -1, NULL, 0);
if (IS_ERR(ubi32_wdt_device)) {
printk(KERN_ERR PFX "unable to register device\n");
platform_driver_unregister(&ubi32_wdt_driver);
return PTR_ERR(ubi32_wdt_device);
}
return 0;
}
/*
* ubi32_wdt_exit()
* Deinitialize module
*
* Back out the platform device & driver steps. Real work is in the
* platform remove function.
*/
static void __exit ubi32_wdt_exit(void)
{
platform_device_unregister(ubi32_wdt_device);
platform_driver_unregister(&ubi32_wdt_driver);
}
module_init(ubi32_wdt_init);
module_exit(ubi32_wdt_exit);
MODULE_AUTHOR("Sol Kavy<sol@ubicom.com>");
MODULE_DESCRIPTION("Ubicom32 Watchdog Device Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
module_param(timeout, uint, 0);
MODULE_PARM_DESC(timeout,
"Watchdog timeout in seconds. (1<=timeout<=((2^32)/SCLK), default="
__MODULE_STRING(WATCHDOG_TIMEOUT) ")");
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout,
"Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");