--- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -790,6 +790,16 @@ config RTC To compile this driver as a module, choose M here: the module will be called rtc. +config RTC_PCF8563 + bool 'Philips PCF8563 Real Time Clock (I2C Bus)' + help + Philips PCF8563 Real Time Clock (I2C Bus) + +config RTC_JZ + bool 'Jz47XX On-Chip Real Time Clock' + help + Jz47XX On-Chip Real Time Clock + config JS_RTC tristate "Enhanced Real Time Clock Support" depends on SPARC32 && PCI @@ -1079,6 +1089,7 @@ config DEVPORT default y source "drivers/s390/char/Kconfig" +source "drivers/char/jzchar/Kconfig" endmenu --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -97,6 +97,10 @@ obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu. obj-$(CONFIG_GPIO_TB0219) += tb0219.o obj-$(CONFIG_TELCLOCK) += tlclk.o +obj-$(CONFIG_RTC_PCF8563) += rtc_pcf8563.o +obj-$(CONFIG_RTC_JZ) += rtc_jz.o +obj-$(CONFIG_JZCHAR) += jzchar/ + obj-$(CONFIG_MWAVE) += mwave/ obj-$(CONFIG_AGP) += agp/ obj-$(CONFIG_PCMCIA) += pcmcia/ --- /dev/null +++ b/drivers/char/jzchar/Kconfig @@ -0,0 +1,70 @@ +# +# JzSOC char devices configuration +# + +menu "JZSOC char device support" + depends on SOC_JZ4740 || SOC_JZ4730 || SOC_JZ4750 || SOC_JZ4750D + +config JZCHAR + tristate 'JzSOC char device support' + +config JZ_CAMERA_SENSOR + bool + +config JZ_CIM + tristate 'JzSOC Camera Interface Module (CIM) support' + depends on JZCHAR + select JZ_CAMERA_SENSOR + +config JZ_TPANEL_ATA2508 + tristate 'JzSOC MPEG4 TOUCH PANEL ATA2508 support' + depends on JZCHAR + +config JZ_TPANEL + tristate 'JzSOC touchpanel driver support' + depends on JZCHAR +# select JZ_SADC if SOC_JZ4740 +# select JZ_TPANEL_AK4182 if SOC_JZ4730 + +choice + prompt "Touch Panel ADC type" + depends on JZ_TPANEL + default JZ_SADC if SOC_JZ4740 || SOC_JZ4750 || SOC_JZ4750D + default JZ_TPANEL_AK4182 if SOC_JZ4730 + +config JZ_SADC + bool 'Select the JZ47XX internal SADC' + +config JZ_TPANEL_AK4182 + bool 'Select the AK4182 codec' + +config JZ_TPANEL_UCB1400 + bool 'Select the UCB1400 codec' + +config JZ_TPANEL_WM9712 + bool 'Select the WM9712 codec' + +endchoice + +config JZ_UDC_HOTPLUG + tristate 'JZ UDC hotplug driver support' + depends on JZCHAR + +config JZ_POWEROFF + tristate 'JZ board poweroff support' + depends on JZCHAR + +config JZ_OW + tristate 'JZ One-wire bus support' + depends on JZCHAR + +config JZ_TCSM + tristate 'JZ TCSM support' + depends on JZCHAR + +config JZ_TSSI + tristate 'JZ MPEG2-TS interface support' + depends on JZCHAR && (SOC_JZ4750 || SOC_JZ4750D) + +endmenu + --- /dev/null +++ b/drivers/char/jzchar/Makefile @@ -0,0 +1,24 @@ +# +# Makefile for jzchar +# +obj-$(CONFIG_JZCHAR) += jzchars.o + +obj-$(CONFIG_JZ_SCC) += scc.o +obj-$(CONFIG_JZ_CIM) += cim.o +obj-$(CONFIG_JZ_TPANEL_ATA2508) += ata2508.o +obj-$(CONFIG_JZ_CAMERA_SENSOR) += sensor.o +obj-$(CONFIG_JZ_I2C_EEPROM) += eeprom.o +obj-$(CONFIG_JZ_EJTAG) += ejtag.o +obj-$(CONFIG_JZ_POWEROFF) += poweroff.o + +obj-$(CONFIG_JZ_TPANEL) += jz_ts.o +obj-$(CONFIG_JZ_TPANEL_UCB1400) += ucb1400.o +obj-$(CONFIG_JZ_TPANEL_WM9712) += wm9712.o +obj-$(CONFIG_JZ_TPANEL_AK4182) += ak4182.o +obj-$(CONFIG_JZ_SADC) += sadc.o + +obj-$(CONFIG_JZ_SMART_LCD) += slcd.o +obj-$(CONFIG_JZ_UDC_HOTPLUG) += udc_hotplug.o +obj-$(CONFIG_JZ_OW) += jz_ow.o +obj-$(CONFIG_JZ_TCSM) += tcsm.o +obj-$(CONFIG_JZ_TSSI) += jz_tssi.o --- /dev/null +++ b/drivers/char/jzchar/ak4182.c @@ -0,0 +1,657 @@ +/* + * ak4182.c using national microwire protocol + * + * Touch screen driver interface to the AK4182A . + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "jz_ts.h" +#include "ak4182.h" + +#define TS_PIN GPIO_TS_PENIRQ +#define TS_IRQ (IRQ_GPIO_0 + TS_PIN) + +static int samples = 5; +static int first_time = 0; +static unsigned long last_x, last_y, last_p; + +static int adcsync = 0; + +static struct ak4182 *ak; + +extern unsigned int (*codec_read_battery)(void); + +/*------------------JzSoc SSI configure----------------*/ +static void ak4182_ssi_reset(void) +{ + REG_SSI_CR0 = 0x0000; + REG_SSI_CR1 = 0x00007960; + REG_SSI_SR = 0x00000098; + REG_SSI_ITR = 0x0000; + REG_SSI_ICR = 0x00; + REG_SSI_GR = 0x0000; + + __ssi_disable(); + __ssi_flush_fifo(); + __ssi_clear_errors(); + __ssi_select_ce(); +} + +static void ak4182_ssi_enable(void) +{ + __ssi_enable(); +} + +#ifdef CONFIG_PM +static void ak4182_ssi_disable(void) +{ + __ssi_disable(); +} +#endif + +static void ak4182_ssi_set_trans_mode_format(void) +{ + __ssi_microwire_format(); + __ssi_set_msb(); + __ssi_set_microwire_command_length(8); + __ssi_set_frame_length(12); +} + +static void ak4182_ssi_set_clk_div_ratio(int dev_clk, int ssi_clk) +{ + __ssi_set_clk(dev_clk, ssi_clk); +} + +static void ak4182_ssi_set_normal_mode(void) +{ + __ssi_normal_mode(); +} + +static void ak4182_ssi_set_IRQ(void) +{ + __ssi_disable_tx_intr(); + __ssi_disable_rx_intr(); +} + +/*------------------ AK4182 routines ------------------*/ +static inline void ak4182_reg_write(unsigned short val) +{ + __ssi_transmit_data(val); +} + +static inline unsigned int ak4182_reg_read(void) +{ + unsigned int val; + val = __ssi_receive_data(); + return val; +} + +static unsigned int ak4182_adc_read(int cmd_code, int sync) +{ + unsigned int val, timeout = 10000; + unsigned int status,valid1,valid2,dataentry; + + ak4182_reg_write(cmd_code); + udelay(2);//wait 2 D_CLK + for (;;) { + status =0; + status = REG_SSI_SR; + valid1 = (status>>7) & 1; + valid2 = (status>>6) & 1; + if( valid1==1 && valid2==0 )//SSI transfer is finished + { + //Receive FIFO data entry number + dataentry = val = 0; + dataentry = (status>>8) & 0x1F; + if( dataentry > 5 ) + { + printk("R-FIFO entry=%d,SSI transfer is wrong!\n",dataentry); + while(dataentry > 0) + { + ak4182_reg_read(); + dataentry--; + } + return 0; + } + while(dataentry > 0) + { + val = ak4182_reg_read(); + dataentry--; + } + return val; + } + + if (--timeout == 0) + break; + udelay(1); + } + return 0; +} + + +//enable pen down IRQ +static void ak4182_enable_irq(void) +{ + unsigned long flags; + + spin_lock_irqsave(&ak->lock, flags); + __gpio_unmask_irq(TS_PIN); + spin_unlock_irqrestore(&ak->lock, flags); +} + +//disable pen down IRQ +static void ak4182_disable_irq(void) +{ + unsigned long flags; + + spin_lock_irqsave(&ak->lock, flags); + __gpio_mask_irq(TS_PIN); +// spin_unlock_irqrestore(&ucb->lock, flags); + spin_unlock_irqrestore(&ak->lock, flags); +} +/* + * Switch to X position mode and measure Y plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ak4182_ts_read_xpos(void) +{ + return ak4182_adc_read(0xD0, adcsync);//X-axis,0xD0 for 12bit,0xD8 for 8bit +} + + +/* + * Switch to pressure mode, and read pressure. We don't need to wait + * here, since both plates are being driven. + */ +static inline unsigned int ak4182_ts_read_pressure(void) +{ + unsigned int z1,z2,xpos,pressureval=0;//300 Om + //Z1 pressure + z1 = ak4182_adc_read(0xB0, adcsync);//0xB0 for 12bit,0xB8 for 8bit + if(z1>0) + { + //Z2 pressure + z2 = ak4182_adc_read(0xC0, adcsync);//0xC0 for 12bit,0xC8 for 8bit + if(z2>z1) + { + xpos = ak4182_ts_read_xpos(); + pressureval = (300*xpos*(z2-z1))/(4096*z1); + } + } + + return pressureval; +} + + +/* + * Switch to Y position mode and measure X plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ak4182_ts_read_ypos(void) +{ + return ak4182_adc_read(0x90, adcsync);//Y-axis,0x90 for 12bit,0x98 for 8bit +} + +/*------------------------------------------------------------ + * Read the battery voltage + */ + +unsigned int ak4182_read_battery(void) +{ + unsigned int v; + int bat_val[5]; + int total = 0, max_bat, min_bat; + + v = ak4182_adc_read(0xA7, adcsync); + v = ak4182_adc_read(0xA7, adcsync); + for(v = 0;v <= 4;v++) + bat_val[v] = ak4182_adc_read(0xA7, adcsync); + + ak4182_adc_read(0xA4, adcsync); + max_bat = min_bat = bat_val[0]; + for(v = 0;v <= 4;v++) { + total += bat_val[v]; + if(bat_val[v] > max_bat) + max_bat = bat_val[v]; + if(bat_val[v] < min_bat) + min_bat = bat_val[v]; + } + total = total - max_bat - min_bat; + v = total / 3; + return v; +} + +/*------------------ Calibrate samples -------------------*/ + +#define DIFF(a,b) ((a>b)?(a-b):(b-a)) + +static int calibrate_samples(void *xbuf, void *ybuf, void *pbuf, int count) +{ + unsigned long *xp = (unsigned long *)xbuf; + unsigned long *yp = (unsigned long *)ybuf; + unsigned long *pp = (unsigned long *)pbuf; + unsigned long x_cal = 0, y_cal = 0, p_cal = 0, tmp; + int ignored, i, j; + int valid = 0; + + /* throw away the max cases */ + tmp = xp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (xp[i] > tmp) { + tmp = xp[i]; + ignored = i; + } + }//find the max val + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + xp[j++] = xp[i]; + }//shift val and delete the max val + + tmp = yp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (yp[i] > tmp) { + tmp = yp[i]; + ignored = i; + } + } + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + yp[j++] = yp[i]; + } + + tmp = pp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (pp[i] > tmp) { + tmp = pp[i]; + ignored = i; + } + } + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + pp[j++] = pp[i]; + } + + /* throw away the min cases */ + + count -= 1; // decrement by 1 + + tmp = xp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (xp[i] < tmp) { + tmp = xp[i]; + ignored = i; + } + } + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + xp[j++] = xp[i]; + } + + tmp = yp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (yp[i] < tmp) { + tmp = yp[i]; + ignored = i; + } + } + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + yp[j++] = yp[i]; + } + + tmp = pp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (pp[i] < tmp) { + tmp = pp[i]; + ignored = i; + } + } + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + pp[j++] = pp[i]; + } + + count -= 1; // decrement by 1 + + /* calculate the average of the rest */ + for (i = 0; i < count; i++) { + x_cal += xp[i]; + y_cal += yp[i]; + p_cal += pp[i]; + } + x_cal /= count; + y_cal /= count; + p_cal /= count; + + if (first_time) { + first_time = 0; + last_x = x_cal; + last_y = y_cal; + last_p = p_cal; + valid = 1; + } + else { + if ((DIFF(x_cal, last_x) > 100) || + (DIFF(y_cal, last_y) > 100)) + valid = 0; + else + valid = 1; + } + + //printk("x_cal=%d y_cal=%d p_cal=%d valid=%d\n", x_cal, y_cal, p_cal, valid); + + if (valid) { + *xp = last_x = x_cal; + *yp = last_y = y_cal; + *pp = last_p = p_cal; + } + + return valid; +} + + +#define TSMAXX 945 +#define TSMAXY 830 +#define TSMINX 90 +#define TSMINY 105 + +#define SCREEN_X 480 +#define SCREEN_Y 272 + +static unsigned long transform_to_screen_x(struct jz_ts_t *ts, unsigned long x ) +{ + + if (ts->minx) + { + if (x < ts->minx) x = ts->minx; + if (x > ts->maxx) x = ts->maxx; + + return (x - ts->minx) * SCREEN_X / (ts->maxx - ts->minx); + } + else + { + if (x < TSMINX) x = TSMINX; + if (x > TSMAXX) x = TSMAXX; + + return (x - TSMINX) * SCREEN_X / (TSMAXX - TSMINX); + } +} + +static unsigned long transform_to_screen_y(struct jz_ts_t *ts, unsigned long y) +{ + if (ts->miny) + { + if (y < ts->miny) y = ts->miny; + if (y > ts->maxy) y = ts->maxy; + + return (y - ts->miny) * SCREEN_Y / (ts->maxy - ts->miny); + } + else + { + if (y < TSMINY) y = TSMINY; + if (y > TSMAXY) y = TSMAXY; + + return (y - TSMINY) * SCREEN_Y / (TSMAXY - TSMINY); + } +} + +/*------------------ Common routines -------------------*/ + +void ts_enable_irq(void) +{ + /* interrupt mode */ + ak4182_enable_irq(); + enable_irq(TS_IRQ); +} + +void ts_disable_irq(void) +{ + ak4182_disable_irq(); + disable_irq(TS_IRQ); +} + +int ts_request_irq(u32 *irq, + irqreturn_t (*handler)(int, void *), + const char *devname, + void *dev_id) +{ + int retval; + + /* return the irq number */ + *irq = TS_IRQ; + /* initializate ssi for AK4182 */ + ak4182_ssi_reset(); + ak4182_ssi_set_trans_mode_format(); + ak4182_ssi_set_normal_mode(); + ak4182_ssi_set_clk_div_ratio(JZ_EXTAL, 200*1000);//DCLK is 1.5M Hz max + ak4182_ssi_set_IRQ(); + + ak4182_enable_irq(); + + /* enable gpio irq */ + __gpio_as_irq_fall_edge(TS_PIN); + + /* register irq handler */ + retval = request_irq(TS_IRQ, handler, IRQF_DISABLED, devname, dev_id); + ak4182_ssi_enable(); + udelay(10); + return retval; +} + +void ts_free_irq(struct jz_ts_t *ts) +{ + free_irq(ts->pendown_irq, ts); + //Close SSI mode + ak4182_ssi_reset(); +} + +void ts_irq_callback(void) +{ + /* clear interrupt status */ + __gpio_ack_irq(TS_PIN); + first_time = 1; // first time to acquire sample +} + +int PenIsDown(void) +{ + unsigned int p; + p = ak4182_ts_read_pressure(); + return (p > 100) ? 1 : 0; +} + +/* + * Acquire Raw pen coodinate data and compute touch screen + * pressure resistance. Hold spinlock when calling. + */ +int AcquireEvent(struct jz_ts_t *ts, struct ts_event *event) +{ + unsigned int x_raw[8], y_raw[8], p_raw[8]; + int valid, i; + + for (i = 0; i < samples; i++) { + x_raw[i] = ak4182_ts_read_xpos(); + } + for (i = 0; i < samples; i++) { + y_raw[i] = ak4182_ts_read_ypos(); + } + for (i = 0; i < samples; i++) { + p_raw[i] = ak4182_ts_read_pressure(); + } + + valid = calibrate_samples(x_raw, y_raw, p_raw, samples); + + if (valid) { + unsigned int x_scr, y_scr; + + if(ts->filter) { + x_scr = transform_to_screen_x(ts, x_raw[0]); + y_scr = transform_to_screen_y(ts, y_raw[0]); + + if (ts->prints) + printk("filter:x_raw:%d,y_raw:%d,x_tran:%d,y_tran:%d\n", x_raw[0], y_raw[0], x_scr, y_scr); + } + else { + x_scr = x_raw[0]; + y_scr = y_raw[0]; + + if (ts->prints) + printk("no filter:x_raw=%d y_raw=%d \n", x_raw[0], y_raw[0]); + } + + event->x = x_scr; + event->y = y_scr; + event->pressure = (u16)p_raw[0]; + event->status = PENDOWN; + return 1; + } + return 0; +} + +#ifdef CONFIG_PM + +/* + * Suspend the Touch pad. + */ +static int ak4182_suspend(struct ak4182 *ak , int state) +{ + ak4182_ssi_disable(); + + return 0; +} + +/* + * Resume the Touch panel. + */ +static int ak4182_resume(struct ak4182 *ak) +{ + /* initializate ssi for AK4182 */ + ak4182_ssi_reset(); + ak4182_ssi_set_trans_mode_format(); + ak4182_ssi_set_normal_mode(); + ak4182_ssi_set_clk_div_ratio(JZ_EXTAL, 200*1000);//DCLK is 1.5M Hz max + ak4182_ssi_set_IRQ(); + + ak4182_enable_irq(); + + ak4182_ssi_enable(); + + return 0; +} + +static int ak4182_pm_callback(struct pm_dev *pm_dev, pm_request_t rqst, void *data) +{ + int ret; + struct ak4182 *akinfo = pm_dev->data; + + if (!akinfo) + return -EINVAL; + + + switch (rqst) { + case PM_SUSPEND: + ret = ak4182_suspend(akinfo, (int)data); + break; + + case PM_RESUME: + ret = ak4182_resume(akinfo); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +#endif /* CONFIG_PM */ + + +/* + * Module init and exit + */ + +int __init ak4182_init(void) +{ + ak = kmalloc(sizeof(struct ak4182), GFP_KERNEL); + if (!ak) return -ENOMEM; + + memset(ak, 0, sizeof(struct ak4182)); + + codec_read_battery = ak4182_read_battery; + + spin_lock_init(&ak->lock); + sema_init(&ak->adc_sem, 1); + + //initialize AK4182 register + __gpio_clear_pin(73); + __gpio_as_output(73); + mdelay(2); + __gpio_set_pin(73); + __gpio_as_ssi(); + + ak4182_read_battery(); + +#ifdef CONFIG_PM + ak->pmdev = pm_register(PM_SYS_DEV, PM_SYS_UNKNOWN, ak4182_pm_callback); + if (ak->pmdev) + { + ak->pmdev->data = ak; + } +#endif + + printk("AK4182 touch screen driver initialized\n"); + + return 0; +} + +void ak4182_cleanup(void) +{ +} + +module_init(ak4182_init); +module_exit(ak4182_cleanup); + --- /dev/null +++ b/drivers/char/jzchar/ak4182.h @@ -0,0 +1,16 @@ +#ifndef __AK4182_H__ +#define __AK4182_H__ + +/* Device data structure */ + +struct ak4182 { + spinlock_t lock; + struct pm_dev *pmdev; + struct semaphore adc_sem; + u16 adc_cr; + u16 irq_fal_enbl; + u16 irq_ris_enbl; + int irq_enabled; +}; + +#endif /* __AK4182_H__ */ --- /dev/null +++ b/drivers/char/jzchar/ata2508.c @@ -0,0 +1,227 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define MP4_KEY_RST (32*3+3) +#define MP4_KEY_TINT (32*3+2) +#define MP4_KEY_SCL (32*3+1) +#define MP4_KEY_SDA (32*3+0) +#define MP4_TINT_IRQ (IRQ_GPIO_0 + MP4_KEY_TINT) + +#define ADDR_WARM_RESET 0xFF +#define ATA2508_SENSOR_MASK 0x1F + +const unsigned char init_data_burst[] = {//Address:0x0D-0x3E + 0x04, // BETA + 0x27, // AIC_WAIT + //0x32, // REF_DELAY + 0x16, // REF_DELAY + 0x02, // HYSTERESIS01 + 0x02, // HYSTERESIS1 + 0x02, // HYSTERESIS2 + 0x02, // HYSTERESIS3 + 0x02, // HYSTERESIS4 + 0x02, // HYSTERESIS51 + 0x02, // HYSTERESIS61 + 0x02, // HYSTERESIS7 + 0x02, // HYSTERESIS8 + 0x02, // HYSTERESIS9 + 0x02, // HYSTERESIS10 + 0x02, // HYSTERESIS11 + 0x64, // STRENGTH_THRESHOLD0 + 0x64, // STRENGTH_THRESHOLD1 + 0x64, // STRENGTH_THRESHOLD2 + 0x64, // STRENGTH_THRESHOLD3 + 0x64, // STRENGTH_THRESHOLD4 + 0x64, // STRENGTH_THRESHOLD5 + 0x64, // STRENGTH_THRESHOLD6 + 0x64, // STRENGTH_THRESHOLD7 + 0x64, // STRENGTH_THRESHOLD8 + 0x64, // STRENGTH_THRESHOLD9 + 0x64, // STRENGTH_THRESHOLD10 + 0x64, // STRENGTH_THRESHOLD11 + 0x0f, // Sampling Interval + 0xC8, // INTEGRATION TIME + 0x0f, // IDLE TIME + 0x00, // SIF_SETUP(RESERVED) + 0x01, // MODE + 0x00, // GPIO_REG_L + 0x00, // GPIO_REG_H + 0x00, // GPIO_CONFIGURATION_L + 0x00, // GPIO_CONFIGURATION_H + 0x00, // GPIO_DIR_L + 0x00, // GPIO_DIR_H + 0x0c, // CONTROL + 0x38, // INT_MASK + 0x00, // INT_CLEAR + 0xFF, // INT_edge + 0x02, // CONTROL_2 + 0xAF, // BEEP_TIME + 0x7F, // BEEP_FREQUENCY + 0x30, // CALIBRATION INTERVAL + 0x00, // EINT_ENABLE + 0x00, // EINT_POL + 0x00, // FILTER_PERIOD + 0x00, // FILTER_THRESHOLD +}; +const unsigned char init_data_alpha[] = {//Address:0x00-0x0C + 0x02, // APIS + 0x08, // ALPHA0 + 0x08, // ALPHA1 + 0x08, // ALPHA2 + 0x08, // ALPHA3 + 0x08, // ALPHA4 + 0x28, // ALPHA5 + 0x28, // ALPHA6 + 0x28, // ALPHA7 + 0x28, // ALPHA8 + 0x28, // ALPHA9 + 0x28, // ALPHA10 + 0x28, // ALPHA11 +}; +static unsigned int i2c_addr = 0x58; +static unsigned int i2c_clk = 100000; + +static void write_reg(u8 reg, u8 val) +{ + int ret; + i2c_open(); + i2c_setclk(i2c_clk); + ret = i2c_write(i2c_addr, &val, reg, 1); + i2c_close(); +} + +static u8 read_reg(u8 reg) +{ + u8 val; + + i2c_open(); + i2c_setclk(i2c_clk); + i2c_read(i2c_addr, &val, reg, 1); + i2c_close(); + return val; +} + +/* + * Interrupt handler + */ +static irqreturn_t mp4_tint_irq(int irq, void *dev_id) +{ + int key_num = 0; + u8 value0, value1; + + __gpio_ack_irq(MP4_KEY_TINT); + value0 = read_reg(0x75); + value1 = read_reg(0x76); + value0 &= ATA2508_SENSOR_MASK; + if (value0 == 0) { + printk("\nRelease key!\n"); + return IRQ_HANDLED; + } + while(value0 >> 1){ + value0 >>= 1; + key_num++; + } + + printk("\nPress key %d!\n", key_num); + return IRQ_HANDLED; +} + +static int __init init_ata2508(void) +{ + int i; + unsigned char data1; + int retval; + + __gpio_as_output(MP4_KEY_RST); + __gpio_set_pin(MP4_KEY_RST); + mdelay(100); + __gpio_clear_pin(MP4_KEY_RST); + mdelay(800); + __gpio_set_pin(MP4_KEY_RST); + __gpio_mask_irq(MP4_KEY_TINT); + + /*write registers*/ + for(i=0; i<13; i++) + { + data1 = init_data_alpha[i]; + write_reg(i, data1); + } + + for(i=13; i<63; i++) + { + data1 = init_data_burst[i-13]; + write_reg(i, data1); + } +#if 0 + for (i = 0; i < 63; i++) + { + data1 = read_reg(i); + printk("REG0x%02x = 0x%02x\n", i, data1); + } +#endif + + /* wait for 1 ms*/ + mdelay(1); +#if 0 + while(1) + { + data1 = read_reg(0x68); + printk("REG0x68 = %d\n", data1); + data1 = read_reg(0x75); + printk("REG0x75 = 0x%02x\n", data1); + data1 = read_reg(0x76); + printk("REG0x76 = 0x%02x\n", data1); + mdelay(2000); + } +#endif + data1 = read_reg(0x68); + printk("REG0x68 = %d\n", data1); + + /* to activate all the new settings, give a WARM RESET.*/ + write_reg(ADDR_WARM_RESET, 0x00); //ADDR_WARM_RESET=0xFF + + //printk("REG0x68 = %d\n", data1); + + /* wait for 1 ~ 10 ms.*/ + mdelay(10); + data1 = read_reg(0x68); + + /* Enable INT that connected to ATA2508's TINT.*/ + __gpio_as_irq_rise_edge(MP4_KEY_TINT); + + retval = request_irq(MP4_TINT_IRQ, mp4_tint_irq, + IRQF_DISABLED, "mp4_key_tint", NULL); + if (retval) { + printk("Could not get mp4 key irq %d\n", MP4_TINT_IRQ); + return retval; + } + + printk("MP4 touch panel register!\n"); + + return 0; +} + +static void __exit exit_ata2508(void) +{ + free_irq(MP4_TINT_IRQ, NULL); +} + +module_init(init_ata2508); +module_exit(exit_ata2508); --- /dev/null +++ b/drivers/char/jzchar/cim.c @@ -0,0 +1,366 @@ +/* + * linux/drivers/char/jzchar/cim.c + * + * Camera Interface Module (CIM) driver for JzSOC + * This driver is independent of the camera sensor + * + * Copyright (C) 2005 JunZheng semiconductor + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "jzchars.h" + +#define CIM_NAME "cim" + +MODULE_AUTHOR("Jianli Wei"); +MODULE_DESCRIPTION("JzSOC Camera Interface Module driver"); +MODULE_LICENSE("GPL"); + +/* + * Define the Max Image Size + */ +#define MAX_IMAGE_WIDTH 640 +#define MAX_IMAGE_HEIGHT 480 +#define MAX_IMAGE_BPP 16 +#define MAX_FRAME_SIZE (MAX_IMAGE_WIDTH * MAX_IMAGE_HEIGHT * MAX_IMAGE_BPP / 8) + +typedef struct +{ + u32 width; + u32 height; + u32 bpp; +} img_param_t; + +typedef struct +{ + u32 cfg; + u32 ctrl; + u32 mclk; +} cim_config_t; + +/* + * IOCTL_XXX commands + */ +#define IOCTL_SET_IMG_PARAM 0 // arg type: img_param_t * +#define IOCTL_CIM_CONFIG 1 // arg type: cim_config_t * + +/* Actual image size, must less than max values */ +static int img_width = MAX_IMAGE_WIDTH, img_height = MAX_IMAGE_HEIGHT, img_bpp = MAX_IMAGE_BPP; + +/* + * CIM DMA descriptor + */ +struct cim_desc { + u32 nextdesc; /* Physical address of next desc */ + u32 framebuf; /* Physical address of frame buffer */ + u32 frameid; /* Frame ID */ + u32 dmacmd; /* DMA command */ +}; + +/* + * CIM device structure + */ +struct cim_device { + unsigned char *framebuf; + unsigned int frame_size; + unsigned int page_order; + wait_queue_head_t wait_queue; + struct cim_desc frame_desc __attribute__ ((aligned (16))); +}; + +// global +static struct cim_device *cim_dev; + +/*========================================================================== + * CIM init routines + *========================================================================*/ + +static void cim_config(cim_config_t *c) +{ + REG_CIM_CFG = c->cfg; + REG_CIM_CTRL = c->ctrl; + // Set the master clock output +#if defined(CONFIG_SOC_JZ4730) + __cim_set_master_clk(__cpm_get_sclk(), c->mclk); +#elif defined(CONFIG_SOC_JZ4740) || defined(CONFIG_SOC_JZ4750) + __cim_set_master_clk(__cpm_get_hclk(), c->mclk); +#else + __cim_set_master_clk(__cpm_get_sclk(), c->mclk); +#endif + // Enable sof, eof and stop interrupts + __cim_enable_sof_intr(); + __cim_enable_eof_intr(); + __cim_enable_stop_intr(); +} + +/*========================================================================== + * CIM start/stop operations + *========================================================================*/ + +static int cim_start_dma(char *ubuf) +{ + __cim_disable(); + + dma_cache_wback((unsigned long)cim_dev->framebuf, (2 ^ (cim_dev->page_order)) * 4096); + + // set the desc addr + __cim_set_da(virt_to_phys(&(cim_dev->frame_desc))); + + __cim_clear_state(); // clear state register + __cim_reset_rxfifo(); // resetting rxfifo + __cim_unreset_rxfifo(); + __cim_enable_dma(); // enable dma + + // start + __cim_enable(); + + // wait for interrupts + interruptible_sleep_on(&cim_dev->wait_queue); + + // copy frame data to user buffer + memcpy(ubuf, cim_dev->framebuf, cim_dev->frame_size); + + return cim_dev->frame_size; +} + +static void cim_stop(void) +{ + __cim_disable(); + __cim_clear_state(); +} + +/*========================================================================== + * Framebuffer allocation and destroy + *========================================================================*/ + +static void cim_fb_destroy(void) +{ + if (cim_dev->framebuf) { + free_pages((unsigned long)(cim_dev->framebuf), cim_dev->page_order); + cim_dev->framebuf = NULL; + } +} + +static int cim_fb_alloc(void) +{ + cim_dev->frame_size = img_width * img_height * (img_bpp/8); + cim_dev->page_order = get_order(cim_dev->frame_size); + + /* frame buffer */ + cim_dev->framebuf = (unsigned char *)__get_free_pages(GFP_KERNEL, cim_dev->page_order); + if ( !(cim_dev->framebuf) ) { + return -ENOMEM; + } + + cim_dev->frame_desc.nextdesc = virt_to_phys(&(cim_dev->frame_desc)); + cim_dev->frame_desc.framebuf = virt_to_phys(cim_dev->framebuf); + cim_dev->frame_desc.frameid = 0x52052018; + cim_dev->frame_desc.dmacmd = CIM_CMD_EOFINT | CIM_CMD_STOP | (cim_dev->frame_size >> 2); // stop after capturing a frame + + dma_cache_wback((unsigned long)(&(cim_dev->frame_desc)), 16); + + return 0; +} + +/*========================================================================== + * File operations + *========================================================================*/ + +static int cim_open(struct inode *inode, struct file *filp); +static int cim_release(struct inode *inode, struct file *filp); +static ssize_t cim_read(struct file *filp, char *buf, size_t size, loff_t *l); +static ssize_t cim_write(struct file *filp, const char *buf, size_t size, loff_t *l); +static int cim_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); + +static struct file_operations cim_fops = +{ + open: cim_open, + release: cim_release, + read: cim_read, + write: cim_write, + ioctl: cim_ioctl +}; + +static int cim_open(struct inode *inode, struct file *filp) +{ + try_module_get(THIS_MODULE); + return 0; +} + +static int cim_release(struct inode *inode, struct file *filp) +{ + cim_stop(); + + module_put(THIS_MODULE); + return 0; +} + +static ssize_t cim_read(struct file *filp, char *buf, size_t size, loff_t *l) +{ + if (size < cim_dev->frame_size) + return -EINVAL; + + return cim_start_dma(buf); +} + +static ssize_t cim_write(struct file *filp, const char *buf, size_t size, loff_t *l) +{ + printk("cim error: write is not implemented\n"); + return -1; +} + +static int cim_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case IOCTL_SET_IMG_PARAM: + { + img_param_t i; + + if (copy_from_user((void *)&i, (void *)arg, sizeof(img_param_t))) + return -EFAULT; + + img_width = i.width; + img_height = i.height; + img_bpp = i.bpp; + + if ((img_width * img_height * img_bpp/8) > MAX_FRAME_SIZE) { + /* realloc the buffer */ + cim_fb_destroy(); + if (cim_fb_alloc() < 0) + return -ENOMEM; + } + + cim_dev->frame_size = img_width * img_height * (img_bpp/8); + + cim_dev->frame_desc.dmacmd = CIM_CMD_EOFINT | CIM_CMD_STOP | (cim_dev->frame_size >> 2); // stop after capturing a frame + + dma_cache_wback((unsigned long)(&(cim_dev->frame_desc)), 16); + + break; + } + case IOCTL_CIM_CONFIG: + { + cim_config_t c; + + if (copy_from_user((void *)&c, (void *)arg, sizeof(cim_config_t))) + return -EFAULT; + + cim_config(&c); + + break; + } + default: + printk("Not supported command: 0x%x\n", cmd); + return -EINVAL; + break; + } + return 0; +} + +/*========================================================================== + * Interrupt handler + *========================================================================*/ + +static irqreturn_t cim_irq_handler(int irq, void *dev_id) +{ + u32 state = REG_CIM_STATE; +#if 0 + if (state & CIM_STATE_DMA_EOF) { + wake_up_interruptible(&cim_dev->wait_queue); + } +#endif + if (state & CIM_STATE_DMA_STOP) { + // Got a frame, wake up wait routine + wake_up_interruptible(&cim_dev->wait_queue); + } + + // clear status flags + REG_CIM_STATE = 0; + return IRQ_HANDLED; +} + +/*========================================================================== + * Module init and exit + *========================================================================*/ + +static int __init cim_init(void) +{ + struct cim_device *dev; + int ret; + + /* allocate device */ + dev = kmalloc(sizeof(struct cim_device), GFP_KERNEL); + if (!dev) return -ENOMEM; + + /* record device */ + cim_dev = dev; + + /* allocate a frame buffer */ + if (cim_fb_alloc() < 0) { + kfree(dev); + return -ENOMEM; + } + + init_waitqueue_head(&dev->wait_queue); + + ret = jz_register_chrdev(CIM_MINOR, CIM_NAME, &cim_fops, dev); + if (ret < 0) { + cim_fb_destroy(); + kfree(dev); + return ret; + } + + if ((ret = request_irq(IRQ_CIM, cim_irq_handler, IRQF_DISABLED, + CIM_NAME, dev))) { + cim_fb_destroy(); + kfree(dev); + printk(KERN_ERR "CIM could not get IRQ"); + return ret; + } + + printk("JzSOC Camera Interface Module (CIM) driver registered\n"); + + return 0; +} + +static void __exit cim_exit(void) +{ + free_irq(IRQ_CIM, cim_dev); + jz_unregister_chrdev(CIM_MINOR, CIM_NAME); + cim_fb_destroy(); + kfree(cim_dev); +} + +module_init(cim_init); +module_exit(cim_exit); --- /dev/null +++ b/drivers/char/jzchar/cim.h @@ -0,0 +1,36 @@ +/* + * JzSOC CIM driver + * + * Copyright (C) 2005 Ingenic Semiconductor Inc. + * + * This program 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __CIM_H__ +#define __CIM_H__ + +typedef struct +{ + u32 width; + u32 height; + u32 bpp; +} IMG_PARAM; + +/* + * IOCTL_XXX commands + */ +#define IOCTL_SET_IMG_PARAM 0 // arg type: IMG_PARAM * + +#endif /* __CIM_H__ */ --- /dev/null +++ b/drivers/char/jzchar/jz_ow.c @@ -0,0 +1,497 @@ +/* + * linux/drivers/char/jzchar/jz_ow.c + * + * One Wire Bus test driver + * + * Copyright (C) 2006 Ingenic Semiconductor Inc. + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "jzchars.h" + +#define OW_CPU_READ_ROM 1 +#define OW_INTC_READ_ROM 1 +#define OW_CPU_SEARCH_ROM 0 +#define OW_INTC_SEARCH_ROM 0 + +#define OW_DEBUG 0 +#if OW_DEBUG +#define OWI_MAX 10 +static char CFG[OWI_MAX]; +static char CTL[OWI_MAX]; +static char STS[OWI_MAX]; +static char DAT[OWI_MAX]; +static char DIV[OWI_MAX]; +static void owi_register_dump(int i) +{ + CFG[i]= REG_OWI_CFG; + CTL[i]= REG_OWI_CTL; + STS[i]= REG_OWI_STS; + DAT[i]= REG_OWI_DAT; + DIV[i]= REG_OWI_DIV; +} +static void owi_register_print(int i) +{ + printk(" REG_OWI_CFG: 0x%08x\n", CFG[i]); + printk(" REG_OWI_CTL: 0x%08x\n", CTL[i]); + printk(" REG_OWI_STS: 0x%08x\n", STS[i]); + printk(" REG_OWI_DAT: 0x%08x\n", DAT[i]); + printk(" REG_OWI_DIV: 0x%08x\n", DIV[i]); +} +#endif + +static DECLARE_WAIT_QUEUE_HEAD (ow_wait_queue); + +/* + * fops routines + */ +static int ow_open(struct inode *inode, struct file *filp); +static int ow_release(struct inode *inode, struct file *filp); +static ssize_t ow_read(struct file *filp, char *buf, size_t size, loff_t *l); +static ssize_t ow_write(struct file *filp, const char *buf, size_t size, loff_t *l); +static int ow_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); + +static void do_ow_rddata(void); +static void do_ow_wrdata(void); +static void do_ow_wr1rd(void); +static void do_ow_wr0(void); +static void do_ow_rst(void); + +static void do_interrupt_mode_test(void); +static void do_cpu_mode_test(void); + +static struct file_operations ow_fops = +{ + open: ow_open, + release: ow_release, + read: ow_read, + write: ow_write, + ioctl: ow_ioctl, +}; + +static int ow_open(struct inode *inode, struct file *filp) +{ + try_module_get(THIS_MODULE); + return 0; +} + +static int ow_release(struct inode *inode, struct file *filp) +{ + module_put(THIS_MODULE); + return 0; +} + +static ssize_t ow_read(struct file *filp, char *buf, size_t size, loff_t *l) +{ + printk("OW: read is not implemented\n"); + return -1; +} + +static ssize_t ow_write(struct file *filp, const char *buf, size_t size, loff_t *l) +{ + printk("ow: write is not implemented\n"); + return -1; +} + +static int ow_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + switch (cmd) { + + default: + printk("Not supported command: 0x%x\n", cmd); + return -EINVAL; + break; + } + return ret; +} + +static void do_ow_rddata(void) +{ + __owi_clr_sts(); + __owi_set_rddata(); + __owi_enable_ow_ops(); +} + +static void do_ow_wrdata(void) +{ + __owi_clr_sts(); + __owi_set_wrdata(); + __owi_enable_ow_ops(); +} + +static void do_ow_wr1rd(void) +{ + __owi_clr_sts(); + __owi_set_wr1rd(); + __owi_enable_ow_ops(); +} + +static void do_ow_wr0(void) +{ + __owi_clr_sts(); + __owi_set_wr0(); + __owi_enable_ow_ops(); +} + +static void do_ow_rst(void) +{ + __owi_clr_sts(); + __owi_set_rst(); + __owi_enable_ow_ops(); +} + +static irqreturn_t ow_interrupt(int irq, void *dev_id) +{ + __owi_clr_sts(); + wake_up(&ow_wait_queue); + + return IRQ_HANDLED; +} + +static void ow_intcm_read_rom(char *rom) +{ + int i; + + __owi_select_regular_mode(); + REG_OWI_DIV = 23; + __owi_clr_sts(); + __intc_unmask_irq(IRQ_OWI); + __owi_enable_all_interrupts(); + + do_ow_rst(); + sleep_on(&ow_wait_queue); + + REG_OWI_DAT = 0x33; + do_ow_wrdata(); + sleep_on(&ow_wait_queue); + + for(i=0; i<8; i++){ + do_ow_rddata(); + sleep_on(&ow_wait_queue); + rom[i] = REG_OWI_DAT; + } + __intc_mask_irq(IRQ_OWI); +} + +static void ow_intcm_search_rom(void) +{ + int i, j; + int normal, reverse; +#if 1 + unsigned char rom[8]={0x01, 0xf9, 0x35, 0x53, 0x11, 0x00, 0x00, 0x3e}; +#else + unsigned char rom[8]={0x01, 0xd8, 0x10, 0x02, 0x10, 0x00, 0x00, 0x22}; +#endif + __owi_select_regular_mode(); + REG_OWI_DIV = __cpm_get_extalclk()/1000000 - 1; + __owi_clr_sts(); + __intc_unmask_irq(IRQ_OWI); + __owi_enable_all_interrupts(); + + /* reset */ + do_ow_rst(); + sleep_on(&ow_wait_queue); + + /* send search ROM command */ + REG_OWI_DAT = 0xf0; + do_ow_wrdata(); + sleep_on(&ow_wait_queue); + + for( i=0; i<8; i++){ + for (j=0; j<8; j++){ + do_ow_wr1rd(); + sleep_on(&ow_wait_queue); + normal = ( __owi_get_rdst() !=0); + printk("normal: %d\n",normal); + + do_ow_wr1rd(); + sleep_on(&ow_wait_queue); + reverse = ( __owi_get_rdst() !=0); + printk("reverse: %d\n",reverse); + + if(normal ==1 && reverse ==1){ + printk("Search rom INTC mode: 11 NO device found\n"); + __intc_mask_irq(IRQ_OWI); + return; + } +#if 1 + if ( (rom[i]>>j) & 1 ){ + printk("write 1\n"); + do_ow_wr1rd(); + sleep_on(&ow_wait_queue); + } + else{ + printk("write 0\n"); + do_ow_wr0(); + sleep_on(&ow_wait_queue); + } + +#else + if(normal ==0 && reverse ==0){ + if (!((rom[i]>>j) & 1) ){ + printk("write 1\n"); + do_ow_wr1rd(); + sleep_on(&ow_wait_queue); + } + else{ + printk("write 0\n"); + do_ow_wr0(); + sleep_on(&ow_wait_queue); + } + }else{ + + if(normal ==0){ + printk("write 0\n"); + do_ow_wr0(); + sleep_on(&ow_wait_queue); + } + if(normal ==1){ + printk("write 1\n"); + do_ow_wr1rd(); + sleep_on(&ow_wait_queue); + } + } +#endif + + } + printk("\n\n"); + } + + printk("\nSearch rom INTC mode: device found SUCCESSFULLY\n"); + __intc_mask_irq(IRQ_OWI); + +} + +static void ow_cpum_read_rom(char *rom) +{ + int i; + + __owi_select_regular_mode(); + REG_OWI_DIV = __cpm_get_extalclk()/1000000 - 1; + __owi_clr_sts(); + __owi_disable_all_interrupts(); + + do_ow_rst(); + __owi_wait_ops_rdy(); + + if(!__owi_get_sts_pst()) + printk("read rom no device found\n"); + + REG_OWI_DAT = 0x33; + do_ow_wrdata(); + __owi_wait_ops_rdy(); + + for(i=0; i<8; i++){ + do_ow_rddata(); + __owi_wait_ops_rdy(); + rom[i] = REG_OWI_DAT; + } +} + + +static void ow_comm_bit(unsigned comm) +{ + int i; + for(i=0; i<8; i++){ + if ( comm & (1<>j) & 1 ){ + printk("write 1\n"); + do_ow_wr1rd(); + while(!__owi_get_sts_bit_rdy()) ; + } + else{ + printk("write 0\n"); + do_ow_wr0(); + while(!__owi_get_sts_bit_rdy()) ; + } + +#else + if(normal ==0 && reverse ==0){ + if (!((rom[i]>>j) & 1) ){ + printk("write 1\n"); + do_ow_wr1rd(); + while(!__owi_get_sts_bit_rdy()) ; + } + else{ + printk("write 0\n"); + do_ow_wr0(); + while(!__owi_get_sts_bit_rdy()) ; + } + }else{ + + if(normal ==0){ + printk("write 0\n"); + do_ow_wr0(); + while(!__owi_get_sts_bit_rdy()) ; + } + if(normal ==1){ + printk("write 1\n"); + do_ow_wr1rd(); + while(!__owi_get_sts_bit_rdy()) ; + } + } +#endif + + } + printk("\n\n"); + } + printk("\nSearch rom CPU mode: device found SUCCESSFULLY\n"); +} + +static void do_interrupt_mode_test(void) +{ + int ret, i; + unsigned char rom[8]; + + /* interrupt mode */ + ret = request_irq(IRQ_OWI, ow_interrupt, IRQF_DISABLED, + "JZ_OWI", NULL); + if(ret) + printk("failed irq \n"); + +#if OW_INTC_READ_ROM + ow_intcm_read_rom(rom); + printk("\n\nAfter intc mode read ROM ops: \n"); + printk("ROM: "); + for(i=0; i<8; i++) + printk("0x%02x,",rom[i]); +#endif + +#if OW_INTC_SEARCH_ROM + ow_intcm_search_rom(); +#endif + +} + +static void do_cpu_mode_test(void) +{ + +#if OW_CPU_READ_ROM + int i; + unsigned char rom[8]; + + ow_cpum_read_rom(rom); + printk("\n\nAfter CPU mode read ROM ops: \n"); + printk("ROM: "); + for(i=0; i<8; i++) + printk("0x%02x,",rom[i]); +#endif + +#if OW_CPU_SEARCH_ROM + ow_cpum_search_rom(); +#endif +} + +/* + * Module init and exit + */ +static int __init ow_init(void) +{ + int ret; + + ret = jz_register_chrdev(OW_MINOR, "ow", &ow_fops, NULL); + if (ret < 0) { + return ret; + } + __gpio_as_func1(153); + + REG_OWI_CFG=0; + REG_OWI_CTL=0; + REG_OWI_STS=0; + REG_OWI_DAT=0; + REG_OWI_DIV=0; + + do_interrupt_mode_test(); + do_cpu_mode_test(); + + printk("Ingenic OW driver registered\n"); + + return 0; +} + +static void __exit ow_exit(void) +{ + free_irq(IRQ_OWI, NULL); + jz_unregister_chrdev(OW_MINOR, "ow"); +} + +module_init(ow_init); +module_exit(ow_exit); + +MODULE_AUTHOR("Yurong Tan"); +MODULE_DESCRIPTION("One Wire Bus test Driver"); +MODULE_LICENSE("GPL"); --- /dev/null +++ b/drivers/char/jzchar/jz_ts.c @@ -0,0 +1,443 @@ +/* + * jz_ts.c + * + * Touch screen driver for the Ingenic JZ47XX. + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "jz_ts.h" + +MODULE_AUTHOR("Peter Wei "); +MODULE_DESCRIPTION("Ingenic Touch Screen Driver"); +MODULE_LICENSE("GPL"); + +#define TS_NAME "jz-ts" +#define TS_MINOR 16 /* MAJOR: 10, MINOR: 16 */ +#define PFX TS_NAME + +//#define JZ_TS_DEBUG + +#ifdef JZ_TS_DEBUG +#define dbg(format, arg...) printk(KERN_DEBUG PFX ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif +#define err(format, arg...) printk(KERN_ERR PFX ": " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO PFX ": " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING PFX ": " format "\n" , ## arg) + +static struct jz_ts_t jz_ts; + +unsigned int (*codec_read_battery)(void) = NULL; + +// hold the spinlock before calling. +static void event_add(struct jz_ts_t *ts, struct ts_event *event) +{ + unsigned long flags; + + spin_lock_irqsave(&ts->lock, flags); + + // add this event to the event queue + ts->event_buf[ts->nextIn] = *event; + ts->nextIn = (ts->nextIn + 1) & (EVENT_BUFSIZE - 1); + if (ts->event_count < EVENT_BUFSIZE) { + ts->event_count++; + } else { + // throw out the oldest event + ts->nextOut = (ts->nextOut + 1) & (EVENT_BUFSIZE - 1); + } + + spin_unlock_irqrestore(&ts->lock, flags); + + // async notify + if (ts->fasync) + kill_fasync(&ts->fasync, SIGIO, POLL_IN); + // wake up any read call + if (waitqueue_active(&ts->wait)) + wake_up_interruptible(&ts->wait); +} + +static int event_pull(struct jz_ts_t *ts, struct ts_event *event) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&ts->lock, flags); + ret = ts->event_count; + if (ts->event_count) { + *event = ts->event_buf[ts->nextOut]; + ts->nextOut = (ts->nextOut + 1) & (EVENT_BUFSIZE - 1); + ts->event_count--; + } + spin_unlock_irqrestore(&ts->lock, flags); + + return ret; +} + +static int pen_is_down = 0; + +static irqreturn_t pendown_interrupt(int irq, void * dev_id) +{ + struct jz_ts_t* ts = &jz_ts; + struct ts_event event; + + dbg("pen down"); +#if defined(CONFIG_SOC_JZ4740) + if (ts->sleeping) { + ts->sleeping = 0; + ts_data_ready(); + return IRQ_HANDLED; + } +#endif + spin_lock(&ts->lock); + + if (ts->irq_enabled) { + ts->irq_enabled = 0; + } + else + ts->irq_enabled = 1; + + + if (pen_is_down) + pen_is_down = 0; + else + pen_is_down = 1; + + // callback routine to clear irq status + ts_irq_callback(); + + if ( (pen_is_down == 0)){ + del_timer(&ts->acq_timer); + spin_unlock(&ts->lock); + event.x = event.y = event.pressure = 0; + event.status = PENUP; + ts->first_read = 0; + event_add(ts, &event); + return IRQ_HANDLED; + } + + if ( (pen_is_down == 1)) + { + ts->acq_timer.expires = jiffies + HZ / 100; + del_timer(&ts->acq_timer); + ts->first_read = 1; + add_timer(&ts->acq_timer); + spin_unlock(&ts->lock); + } + return IRQ_HANDLED; +} + + +/* + * Raw X,Y,pressure acquisition timer function. It gets scheduled + * only while pen is down. Its duration between calls is the polling + * rate. + */ +static void +jz_acq_timer(unsigned long data) +{ + struct jz_ts_t *ts = (struct jz_ts_t *)data; + struct ts_event event; + int pen_was_down = ts->pen_is_down; + + spin_lock(&ts->lock); + + if (PenIsDown()) { + + ts->pen_is_down = 1; + + if (AcquireEvent(ts, &event)) // check event is valid or not? + event_add(ts, &event); + + // schedule next acquire + ts->acq_timer.expires = jiffies + HZ / 100; + del_timer(&ts->acq_timer); + add_timer(&ts->acq_timer); + } else { + + if (!ts->irq_enabled) { + ts->irq_enabled = 1; + } + ts->pen_is_down = 0; + if (pen_was_down) { + event.x = event.y = event.pressure = 0; + event.status = PENUP; + event_add(ts, &event); + } + } + + spin_unlock(&ts->lock); +} + +/* +++++++++++++ Read battery voltage routine ++++++++++++++*/ + +unsigned int jz_read_battery(void) +{ + unsigned int v = 0; + struct jz_ts_t *ts = &jz_ts; + + spin_lock(&ts->lock); + + if (codec_read_battery) + v = codec_read_battery(); + + spin_unlock(&ts->lock); + + return v; +} + +/* +++++++++++++ File operations ++++++++++++++*/ + +static int +jz_fasync(int fd, struct file *filp, int mode) +{ + struct jz_ts_t *ts = (struct jz_ts_t *)filp->private_data; + return fasync_helper(fd, filp, mode, &ts->fasync); +} + + +static unsigned int +jz_poll(struct file * filp, poll_table * wait) +{ + struct jz_ts_t* ts = (struct jz_ts_t*)filp->private_data; + poll_wait(filp, &ts->wait, wait); + if (ts->event_count) + return POLLIN | POLLRDNORM; + return 0; +} + +static ssize_t +jz_read(struct file * filp, char * buffer, size_t count, loff_t * ppos) +{ + DECLARE_WAITQUEUE(wait, current); + struct jz_ts_t* ts = (struct jz_ts_t*)filp->private_data; + char *ptr = buffer; + struct ts_event event; + int err = 0; + + dbg("jz_read"); + + add_wait_queue(&ts->wait, &wait); + while (count >= sizeof(struct ts_event)) { + err = -ERESTARTSYS; + if (signal_pending(current)) + break; + + + if (event_pull(ts, &event)) { + err = copy_to_user(ptr, &event, + sizeof(struct ts_event)); + if (err) + break; + ptr += sizeof(struct ts_event); + count -= sizeof(struct ts_event); + } else { + set_current_state(TASK_INTERRUPTIBLE); + err = -EAGAIN; + if (filp->f_flags & O_NONBLOCK) + break; + schedule(); + } + } + + current->state = TASK_RUNNING; + remove_wait_queue(&ts->wait, &wait); + + return ptr == buffer ? err : ptr - buffer; +} + + +static int +jz_open(struct inode * inode, struct file * filp) +{ + struct jz_ts_t *ts; + int retval; + + dbg("open ts device"); + filp->private_data = ts = &jz_ts; + + spin_lock(&ts->lock); + + ts->pen_is_down = 0; // start with pen up + ts->sleeping = 0; + // flush event queue + ts->nextIn = ts->nextOut = ts->event_count = 0; + + // Init acquisition timer function + init_timer(&ts->acq_timer); + ts->acq_timer.function = jz_acq_timer; + ts->acq_timer.data = (unsigned long)ts; + + ts->irq_enabled = 1; + + spin_unlock(&ts->lock); + + /* Since ts interrupt can happen immediately after request_irq, + * we wait until we've completed init of all relevent driver + * state variables. Now we grab the PenDown IRQ + */ + retval = ts_request_irq(&ts->pendown_irq, pendown_interrupt, TS_NAME, ts); + if (retval) { + err("unable to get PenDown IRQ %d", ts->pendown_irq); + return retval; + } + + try_module_get(THIS_MODULE); + return 0; +} + +static int +jz_release(struct inode * inode, struct file * filp) +{ + struct jz_ts_t* ts = (struct jz_ts_t*)filp->private_data; + + ts_free_irq(ts); + jz_fasync(-1, filp, 0); + del_timer_sync(&ts->acq_timer); + + module_put(THIS_MODULE); + return 0; +} + +static int jz_ioctl(struct inode *inode, struct file *file, unsigned int ioctl_num, unsigned long ioctl_param) +{ + struct txy { + int minx; + int miny; + int maxx; + int maxy; + }; + + struct txy ch; + + /* + * Switch according to the ioctl called + */ + switch (ioctl_num) + { + case IOCTL_SET_MSG: + jz_ts.filter=1; + break; + case IOCTL_SET_NUM: + if (copy_from_user((void *)&ch, (void *)ioctl_param, sizeof(ch))) + return -EFAULT; + jz_ts.minx = ch.minx; + jz_ts.miny = ch.miny; + jz_ts.maxx = ch.maxx; + jz_ts.maxy = ch.maxy; + break; + } + + return 0; +} + +static struct file_operations ts_fops = { + owner: THIS_MODULE, + read: jz_read, + poll: jz_poll, + fasync: jz_fasync, + ioctl: jz_ioctl, + open: jz_open, + release: jz_release, +}; + +/* +++++++++++++ End File operations ++++++++++++++*/ + +static int __init minx_setup(char *str) +{ + int i; + + if (get_option(&str,&i)) jz_ts.minx = i; + jz_ts.filter=i; + return 1; +} + +__setup("ts_minx=", minx_setup); + +static int __init miny_setup(char *str) +{ + int i; + if (get_option(&str,&i)) jz_ts.miny = i; + return 1; +} + +__setup("ts_miny=", miny_setup); + +static int __init maxx_setup(char *str) +{ + int i; + if (get_option(&str,&i)) jz_ts.maxx = i; + return 1; +} + +__setup("ts_maxx=", maxx_setup); + +static int __init maxy_setup(char *str) +{ + int i; + if (get_option(&str,&i)) jz_ts.maxy = i; + return 1; +} + +__setup("ts_maxy=", maxy_setup); + +static int __init printraw_setup(char *str) +{ + if (str) + jz_ts.prints = 1; + + return 0; +} + +__setup("ts_debug", printraw_setup); + + +static struct miscdevice jz_ts_dev = { + minor: TS_MINOR, + name: TS_NAME, + fops: &ts_fops, +}; + +static int __init jzts_init_module(void) +{ + struct jz_ts_t *ts = &jz_ts; + int ret; + + if ((ret = misc_register(&jz_ts_dev)) < 0) { + err("can't register misc device"); + return ret; + } + +// memset(ts, 0, sizeof(struct jz_ts_t)); + init_waitqueue_head(&ts->wait); + spin_lock_init(&ts->lock); + + printk("Jz generic touch screen driver registered\n"); + + return 0; +} + +static void jzts_cleanup_module(void) +{ + misc_deregister(&jz_ts_dev); +} + +module_init(jzts_init_module); +module_exit(jzts_cleanup_module); --- /dev/null +++ b/drivers/char/jzchar/jz_ts.h @@ -0,0 +1,54 @@ +#ifndef __JZ_TS_H__ +#define __JZ_TS_H__ + +/* + * IOCTL commands + */ +#define IOCTL_SET_MSG 0 +#define IOCTL_SET_NUM 1 + + +/* + * TS Event type + */ +struct ts_event { + u16 status; + u16 x; + u16 y; + u16 pressure; + u16 pad; +}; + +/* TS event status */ +#define PENUP 0x00 +#define PENDOWN 0x01 + +#define EVENT_BUFSIZE 64 // must be power of two + +struct jz_ts_t { + int pendown_irq; // IRQ of pendown interrupt + int pen_is_down; // 1 = pen is down, 0 = pen is up + int irq_enabled; + struct ts_event event_buf[EVENT_BUFSIZE];// The event queue + int nextIn, nextOut; + int event_count; + struct fasync_struct *fasync; // asynch notification + struct timer_list acq_timer; // Timer for triggering acquisitions + wait_queue_head_t wait; // read wait queue + spinlock_t lock; + int minx, miny, maxx, maxy; + int filter, prints; + int sleeping; + int first_read; +}; + +extern void ts_enable_irq(void); +extern void ts_disable_irq(void); +extern int ts_request_irq(u32 *irq,irqreturn_t (*handler)(int, void *), const char *devname, void *dev_id); +extern void ts_free_irq(struct jz_ts_t *ts); +extern int PenIsDown(void); +extern int AcquireEvent(struct jz_ts_t *ts, struct ts_event *event); +extern void ts_irq_callback(void); +extern void ts_data_ready(void); + +#endif /* __JZ_TS_H__ */ --- /dev/null +++ b/drivers/char/jzchar/jz_tssi.c @@ -0,0 +1,457 @@ +/* + * jz_tssi.c + * + * MPEG2-TS interface driver for the Ingenic JZ47XX. + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "jzchars.h" + +#include "jz_tssi.h" + + +MODULE_AUTHOR("Lucifer Liu "); +MODULE_DESCRIPTION("Ingenic MPEG2-TS interface Driver"); +MODULE_LICENSE("GPL"); + +#define TSSI_NAME "JZ MPEG2-TS SI" +#define TSSI_MINOR 204 /* MAJOR: 10, MINOR: 16 */ +#define TSSI_IRQ IRQ_TSSI +#define PFX TSSI_NAME +#define RING_BUF_NUM 100 + +#define USE_DMA +#define TRIG_PIN ( 32 * 2 + 15 ) +#define DMA_ID_TSSI 5 +//#define JZ_TSSI_DEBUG + +#ifdef JZ_TSSISI_DEBUG +#define dbg(format, arg...) printk(KERN_DEBUG PFX ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif +#define err(format, arg...) printk(KERN_ERR PFX ": " format "\n" , ## arg) +#define info(format, arg...) printk(KERN_INFO PFX ": " format "\n" , ## arg) +#define warn(format, arg...) printk(KERN_WARNING PFX ": " format "\n" , ## arg) + +static struct jz_tssi_t jz_tssi_g; +static struct jz_tssi_buf_ring_t jz_tssi_ring_g; +static int tssi_dma_reinit(int dma_chan, unsigned char *dma_buf, int size); + +static void print_reg( void ) +{ + printk("REG_TSSI_ENA %8x \n ", REG8( TSSI_ENA )); + printk("REG_TSSI_CFG %8x \n ", REG16( TSSI_CFG )); + printk("REG_TSSI_CTRL %8x \n ", REG8( TSSI_CTRL )); + printk("REG_TSSI_STAT %8x \n ", REG8( TSSI_STAT )); + printk("REG_TSSI_FIFO %8x \n ", REG32( TSSI_FIFO )); + printk("REG_TSSI_PEN %8x \n ", REG32( TSSI_PEN )); + printk("REG_TSSI_PID0 %8x \n ", REG32( TSSI_PID0 )); + printk("REG_TSSI_PID1 %8x \n ", REG32( TSSI_PID1 )); + printk("REG_TSSI_PID2 %8x \n ", REG32( TSSI_PID2 )); + printk("REG_TSSI_PID3 %8x \n ", REG32( TSSI_PID3 )); + printk("REG_TSSI_PID4 %8x \n ", REG32( TSSI_PID4 )); + printk("REG_TSSI_PID5 %8x \n ", REG32( TSSI_PID5 )); + printk("REG_TSSI_PID6 %8x \n ", REG32( TSSI_PID6 )); + printk("REG_TSSI_PID7 %8x \n ", REG32( TSSI_PID7 )); +} + +void dump_dma_channel(unsigned int dmanr) +{ + printk("DMA%d Registers:\n", dmanr); + printk(" DMACR = 0x%8x\n", REG_DMAC_DMACR(0)); + printk(" DSAR = 0x%8x\n", REG_DMAC_DSAR(dmanr)); + printk(" DTAR = 0x%8x\n", REG_DMAC_DTAR(dmanr)); + printk(" DTCR = 0x%8x\n", REG_DMAC_DTCR(dmanr)); + printk(" DRSR = 0x%8x\n", REG_DMAC_DRSR(dmanr)); + printk(" DCCSR = 0x%8x\n", REG_DMAC_DCCSR(dmanr)); + printk(" DCMD = 0x%8x\n", REG_DMAC_DCMD(dmanr)); + printk(" DDA = 0x%8x\n", REG_DMAC_DDA(dmanr)); + printk(" DMADBR = 0x%8x\n", REG_DMAC_DMADBR(1)); +} + +static int tssi_buf_init( struct jz_tssi_buf_ring_t * ring ) +{ + int i; + struct jz_tssi_buf * bp,* ap, *cp; + + ap = cp = bp = (struct jz_tssi_buf *)kmalloc( sizeof( struct jz_tssi_buf ) ,GFP_KERNEL ); //the first + if ( !bp ) { + printk("Can not malloc buffer! \n"); + return -1; + } + + for ( i = 0; i < RING_BUF_NUM; i ++ ) { + bp = ap; + bp->buf = (unsigned int *) kmalloc(MPEG2_TS_PACHAGE_SIZE / 4 * sizeof(unsigned int) ,GFP_KERNEL); + if ( !bp->buf ) { + printk("Can not malloc buffer! \n"); + return -1; + } + bp->index = i; + bp->pos = 0; + ap = (struct jz_tssi_buf *)kmalloc( sizeof( struct jz_tssi_buf ) ,GFP_KERNEL ); + if ( !ap ) { + printk("Can not malloc buffer! \n"); + return -1; + } + + bp->next = ap; //point to next ! + } + + bp->next = cp; //point loop to first! + ring->front = cp; + ring->rear = cp; + ring->fu_num = 0; + kfree(ap); + return 0; +} + +static void tssi_free_buf( struct jz_tssi_buf_ring_t * ring ) +{ + int i; + struct jz_tssi_buf * ap; + for ( i = 0; i < RING_BUF_NUM; i ++ ) + { + ap = ring->front; + ring->front = ring->front->next; + kfree( ap ); + } +} + +#if 0 +static void tssi_read_fifo(void *dev_id) +{ + struct jz_tssi_t* tssi = ( struct jz_tssi_t* )dev_id; + struct jz_tssi_buf_ring_t * ring = tssi->cur_buf; + struct jz_tssi_buf *buf = ring->rear; + int i; +#if 0 + if ( ring->fu_num > RING_BUF_NUM ) + { + printk("Ring buffer full ! %d \n",ring->fu_num); + return; + } +#endif + + for ( i = 0; i < 8 ; i ++ ) + { + ring->front->buf[ring->front->pos++] = REG_TSSI_FIFO; + } + + if ( ring->front->pos >= MPEG2_TS_PACHAGE_SIZE ) + { + ring->fu_num ++; + ring->front = ring->front->next; + ring->front->pos = 0; + } +} +#endif + +static void tssi_config_filting( void ) +{ + __tssi_soft_reset(); + __gpio_as_tssi(); + __tssi_disable_ovrn_irq(); //use dma ,no need irq + __tssi_disable_trig_irq(); + __tssi_set_tigger_num( 8 ); //trig is 4 word! +// __tssi_filter_enable(); + __tssi_clear_state(); + __tssi_filter_disable(); + __tssi_state_clear_overrun(); +// __tssi_clear_trig_irq_flag(); +#ifdef USE_DMA + __tssi_dma_enable(); +#else + __tssi_dma_disable(); +#endif + + __tssi_enable_ovrn_irq(); +// __tssi_enable_trig_irq(); + + //set config +// __tssi_set_bt_1(); + __tssi_set_wd_1(); + __tssi_set_data_use_data7(); + __tssi_set_data_pola_high(); +// __tssi_select_serail_mode(); + __tssi_select_paral_mode(); + __tssi_select_clk_fast(); + __tssi_select_clk_posi_edge(); + __tssi_select_frm_act_high(); + __tssi_select_str_act_high(); + __tssi_select_fail_act_high(); +// __tssi_select_fail_act_low(); + __tssi_disable_filte_pid0(); //we disable pid0 filter for ever! +} + +static void tssi_add_pid(int pid_num, int pid) +{ + unsigned int addr ; + int n = pid_num / 2, hl = pid_num % 2; + if ( hl ) //use high pid, pid1 + { + addr = TSSI_PID0 + ( n * 4 ); + REG32( addr ) |= ( (pid & 0x1fff) << 16 ); //13bit + REG_TSSI_PEN |= ( 1 << (16 + n) ); + } + else //use low pid, pid0 + { + addr = TSSI_PID0 + ( n * 4 ); + REG32( addr ) |= pid & 0x1fff; //13bit + REG_TSSI_PEN |= ( 1 << n ); + } +} + +static irqreturn_t tssi_dma_irq(int irq, void * dev_id) +{ + struct jz_tssi_t *tssi = (struct jz_tssi_t *)dev_id; + struct jz_tssi_buf_ring_t *buf = tssi->cur_buf; + + REG_DMAC_DCCSR(tssi->dma_chan) &= ~DMAC_DCCSR_EN; /* disable DMA */ + + if (__dmac_channel_transmit_end_detected(tssi->dma_chan)) { + __dmac_channel_clear_transmit_end(tssi->dma_chan); + if ( buf->fu_num < RING_BUF_NUM ) + { + buf->front = buf->front->next; + REG_DMAC_DSAR(tssi->dma_chan) = CPHYSADDR(TSSI_FIFO); + REG_DMAC_DTAR(tssi->dma_chan) = CPHYSADDR((unsigned int)buf->front->buf); + REG_DMAC_DTCR(tssi->dma_chan) = MPEG2_TS_PACHAGE_SIZE / 32; + REG_DMAC_DCCSR(tssi->dma_chan) = DMAC_DCCSR_NDES | DMAC_DCCSR_EN; + buf->fu_num ++; + } + __tssi_clear_state(); + } + + if (__dmac_channel_transmit_halt_detected(tssi->dma_chan)) { + printk("DMA HALT\n"); + __dmac_channel_clear_transmit_halt(tssi->dma_chan); + } + + if (__dmac_channel_address_error_detected(tssi->dma_chan)) { + printk("DMA ADDR ERROR\n"); + __dmac_channel_clear_address_error(tssi->dma_chan); + } + + if (__dmac_channel_descriptor_invalid_detected(tssi->dma_chan)) { + printk("DMA DESC INVALID\n"); + __dmac_channel_clear_descriptor_invalid(tssi->dma_chan); + } + + if (__dmac_channel_count_terminated_detected(tssi->dma_chan)) { + printk("DMA CT\n"); + __dmac_channel_clear_count_terminated(tssi->dma_chan); + } + + return IRQ_HANDLED; +} + +static irqreturn_t tssi_interrupt(int irq, void * dev_id) +{ + __intc_mask_irq(TSSI_IRQ); +#if 1 + if ( REG_TSSI_STAT & TSSI_STAT_OVRN ) + { + printk("tssi over run occur! %x\n",REG8( TSSI_STAT )); + __tssi_clear_state(); + printk("clear ! %x\n",REG8( TSSI_STAT )); + } +#endif + if ( REG_TSSI_STAT & TSSI_STAT_TRIG ) + { + printk("tssi trig irq occur! \n"); + tssi_read_fifo( dev_id ); + } + + __intc_ack_irq(TSSI_IRQ); + __intc_unmask_irq(TSSI_IRQ); + return IRQ_HANDLED; +} + +static ssize_t jz_read(struct file * filp, char * buffer, size_t count, loff_t * ppos) +{ + jz_char_dev_t *adev = (jz_char_dev_t *)filp->private_data; + struct jz_tssi_t* tssi = (struct jz_tssi_t*)adev->private; + struct jz_tssi_buf_ring_t* ring = tssi->cur_buf; + + int i; + + count /= MPEG2_TS_PACHAGE_SIZE; + + if ( count > ring->fu_num ) + count = ring->fu_num; + + for ( i = 0; i < count; i ++ ) + { + memcpy( buffer + ( i * MPEG2_TS_PACHAGE_SIZE), + ring->rear->buf, MPEG2_TS_PACHAGE_SIZE ); + ring->rear->pos = 0; + ring->rear = ring->rear->next; + } + ring->fu_num -= count; + return count * MPEG2_TS_PACHAGE_SIZE; +} + +static int tssi_dma_reinit(int dma_chan, unsigned char *dma_buf, int size) +{ + static unsigned int dma_src_phys_addr, dma_dst_phys_addr; + REG_DMAC_DMACKE(0) = 0xff; + dma_src_phys_addr = CPHYSADDR(TSSI_FIFO); + dma_dst_phys_addr = CPHYSADDR((unsigned int)dma_buf); + REG_DMAC_DMACR(dma_chan/HALF_DMA_NUM) = 0; + REG_DMAC_DCCSR(dma_chan) = 0; + REG_DMAC_DRSR(dma_chan) = DMAC_DRSR_RS_TSSIIN; + REG_DMAC_DSAR(dma_chan) = dma_src_phys_addr; + REG_DMAC_DTAR(dma_chan) = dma_dst_phys_addr; + REG_DMAC_DTCR(dma_chan) = size / 32; + REG_DMAC_DCMD(dma_chan) = DMAC_DCMD_DAI | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_32BYTE | DMAC_DCMD_TIE; + REG_DMAC_DCCSR(dma_chan) = DMAC_DCCSR_NDES | DMAC_DCCSR_EN; + REG_DMAC_DMACR(dma_chan/HALF_DMA_NUM) = DMAC_DMACR_DMAE; /* global DMA enable bit */ + return 0; +} + +static int jz_open(struct inode * inode, struct file * filp) +{ + try_module_get(THIS_MODULE); + + __tssi_soft_reset(); + __intc_mask_irq(TSSI_IRQ); + tssi_config_filting(); + + return 0; +} + +static int jz_release(struct inode * inode, struct file * filp) +{ + __intc_mask_irq(TSSI_IRQ); + module_put(THIS_MODULE); + return 0; +} + +static int jz_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + jz_char_dev_t *adev = (jz_char_dev_t *)file->private_data; + struct jz_tssi_t* tssi = (struct jz_tssi_t*)adev->private; + + switch (cmd) + { + case IOCTL_TSSI_ENABLE : + __intc_ack_irq(TSSI_IRQ); + __intc_unmask_irq(TSSI_IRQ); + __tssi_enable(); + print_reg(); + + break; + case IOCTL_TSSI_DISABLE : + __tssi_disable(); + + break; + case IOCTL_TSSI_SOFTRESET : + __tssi_soft_reset(); + + break; + case IOCTL_TSSI_ENFILTER : + __tssi_filter_enable(); + break; + case IOCTL_TSSI_DEFILTER : + __tssi_filter_disable(); + break; + case IOCTL_TSSI_ADDPID : //add one pid to filter + if ( tssi->pid_num < 15 ) + { + tssi_add_pid(tssi->pid_num, arg); + tssi->pid_num ++ ; + } + break; + + case IOCTL_TSSI_FLUSHPID : //set all filting pid to false + REG_TSSI_PEN = 0x0; + REG_TSSI_PID0 = 0x0; + REG_TSSI_PID1 = 0x0; + REG_TSSI_PID2 = 0x0; + REG_TSSI_PID3 = 0x0; + REG_TSSI_PID4 = 0x0; + REG_TSSI_PID5 = 0x0; + REG_TSSI_PID6 = 0x0; + REG_TSSI_PID7 = 0x0; + break; + + case IOCTL_TSSI_INIT_DMA: + tssi_dma_reinit(tssi->dma_chan, tssi->cur_buf->front->buf, MPEG2_TS_PACHAGE_SIZE); + break; + case IOCTL_TSSI_DISABLE_DMA: + REG_DMAC_DCCSR(tssi->dma_chan) &= ~DMAC_DCCSR_EN; /* disable DMA */ + break; + } + + return 0; +} + +static struct file_operations tssi_fops = { + owner: THIS_MODULE, + read: jz_read, + poll: NULL, + fasync: NULL, + ioctl: jz_ioctl, + open: jz_open, + release: jz_release, +}; + +static int __init jztssi_init_module(void) +{ + int retval; + struct jz_tssi_t *tssi = &jz_tssi_g; + + __cpm_start_tssi(); + __cpm_start_dmac(); + tssi_buf_init( &jz_tssi_ring_g ); + tssi->cur_buf = &jz_tssi_ring_g; + tssi->pid_num = 0; + retval = request_irq(TSSI_IRQ, tssi_interrupt, IRQF_DISABLED, TSSI_NAME, &jz_tssi_g); + + if (retval) { + printk("unable to get IRQ %d",TSSI_IRQ); + return retval; + } + + tssi->dma_chan = jz_request_dma(DMA_ID_TSSI, "tssi", tssi_dma_irq, + IRQF_DISABLED, &jz_tssi_g); + if ( tssi->dma_chan < 0 ) + { + printk("MPEG2-TS request irq fail! \n"); + return -1; + } + + jz_register_chrdev(TSSI_MINOR, TSSI_NAME, &tssi_fops, &jz_tssi_g); + + printk("Jz MPEG2-TS interface driver registered %x %d\n",&jz_tssi_g,tssi->dma_chan); + return 0; +} + +static void jztssi_cleanup_module(void) +{ + free_irq(TSSI_IRQ,0); + jz_free_dma(jz_tssi_g.dma_chan); + tssi_free_buf( &jz_tssi_ring_g ); + jz_unregister_chrdev(TSSI_MINOR, TSSI_NAME); +} + +module_init(jztssi_init_module); +module_exit(jztssi_cleanup_module); --- /dev/null +++ b/drivers/char/jzchar/jz_tssi.h @@ -0,0 +1,76 @@ +#ifndef __JZ_TSSI_H__ +#define __JZ_TSSI_H__ + +/* + * IOCTL commands + */ +#define IOCTL_TSSI_ENABLE 0x01 +#define IOCTL_TSSI_DISABLE 0x02 +#define IOCTL_TSSI_SOFTRESET 0x03 +#define IOCTL_TSSI_ENFILTER 0x04 +#define IOCTL_TSSI_DEFILTER 0x05 +#define IOCTL_TSSI_ADDPID 0x06 +#define IOCTL_TSSI_FLUSHPID 0x07 +#define IOCTL_TSSI_INIT_DMA 0x08 +#define IOCTL_TSSI_DISABLE_DMA 0x09 + +#if 0 +#define IOCTL_TSSI_SET_CFG 0x06 +#define IOCTL_TSSI_GET_CFG 0x07 +#define IOCTL_TSSI_ENIRQ_TRIG 0x08 +#define IOCTL_TSSI_DEIRQ_TRIG 0x09 +#define IOCTL_TSSI_ENIRQ_OVRN 0x0a +#define IOCTL_TSSI_DEIRQ_OVRN 0x0b +#define IOCTL_TSSI_ENPID0 0x0c +#define IOCTL_TSSI_DEPID0 0x0d +#define IOCTL_TSSI_ENPIDN 0x0e +#define IOCTL_TSSI_DEPIDN 0x0f +#define IOCTL_TSSI_SETPIDN 0x10 +#define IOCTL_TSSI_SET_TRIG 0x11 +#endif + +#define MAX_PID_NUM 15 +#define MPEG2_TS_PACHAGE_SIZE 19200 + +struct jz_tssi_cfg_t +{ + unsigned char wordorder; + unsigned char byteorder; + unsigned char dataploa; + unsigned char use0; + unsigned char clkch; + unsigned char mode; + unsigned char clkpola; + unsigned char frmpola; + unsigned char strpola; + unsigned char failpola; + unsigned char trignum; + + unsigned short pid; + unsigned char pid_index; //0 to 15 +}; + +struct jz_tssi_buf +{ + unsigned int *buf; + unsigned int pos; + unsigned int index; + struct jz_tssi_buf *next; +}; + +struct jz_tssi_buf_ring_t +{ + struct jz_tssi_buf *front; + struct jz_tssi_buf *rear; + unsigned int fu_num; +}; + +struct jz_tssi_t +{ + struct jz_tssi_cfg_t cur_config; + struct jz_tssi_buf_ring_t *cur_buf; + struct semaphore tssi_sem; + int dma_chan, pid_num; +}; + +#endif /* __JZ_TSSI_H__ */ --- /dev/null +++ b/drivers/char/jzchar/jzchars.c @@ -0,0 +1,158 @@ +/* + * linux/drivers/char/jzchar/jzchars.c + * + * JzSOC char device family common layer. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "jzchars.h" + +LIST_HEAD(jz_char_devs); + +int jz_register_chrdev(unsigned char minor, const char *name, + struct file_operations *fops, void *private) +{ + struct list_head *p; + jz_char_dev_t *new; + list_for_each(p, &jz_char_devs) { + jz_char_dev_t *dev = (jz_char_dev_t *)p; + if (minor == dev->dev_minor) + return -EBUSY; + } + new = (jz_char_dev_t *)kmalloc(sizeof(jz_char_dev_t), GFP_KERNEL); + new->dev_minor = minor; + new->name = (char *)name; + new->fops = fops; + new->private = private; + list_add_tail((struct list_head *)new, &jz_char_devs); + return 0; +} + +int jz_unregister_chrdev(unsigned char minor, const char *name) +{ + struct list_head *p; + jz_char_dev_t *dev = NULL; + list_for_each(p, &jz_char_devs) { + jz_char_dev_t *one = (jz_char_dev_t *)p; + if (minor == one->dev_minor) { + dev = one; + break; + } + } + if (dev == NULL) + return -EINVAL; + list_del((struct list_head *)dev); + kfree(dev); + return 0; +} + +static ssize_t jz_char_read(struct file *, char *, size_t, loff_t *); +static ssize_t jz_char_write(struct file *, const char *, size_t, loff_t *); +static int jz_char_open(struct inode *, struct file *); +static int jz_char_release(struct inode *, struct file *); +static int jz_char_ioctl(struct inode *, struct file *, + unsigned int, unsigned long); + +static struct file_operations jz_char_fops = +{ + read: jz_char_read, + write: jz_char_write, + ioctl: jz_char_ioctl, + open: jz_char_open, + release: jz_char_release +}; + +static int __init jz_char_family_init(void) +{ + printk("JzSOC: char device family.\n"); + return register_chrdev(JZ_CHAR_MAJOR, "JzChar", &jz_char_fops); +} + +static void __exit jz_char_family_exit(void) +{ + printk("JzSOC: exit char device family.\n"); + unregister_chrdev(JZ_CHAR_MAJOR, "JzChar"); +} + +module_init(jz_char_family_init); +module_exit(jz_char_family_exit); + +static int jz_char_open(struct inode *inode, struct file *filp) +{ + jz_char_dev_t *dev = NULL; + unsigned int minor = iminor(inode); //minor extend to 20bit! + struct list_head *p; + list_for_each(p, &jz_char_devs) { + jz_char_dev_t *one = (jz_char_dev_t *)p; + if (one->dev_minor == minor) { + dev = one; + filp->private_data = dev; + return dev->fops->open(inode, filp); + } + } + printk("JzChar: No such device\n"); + return -EINVAL; +} + +static int jz_char_release(struct inode *inode, struct file *filp) +{ + jz_char_dev_t *dev = (jz_char_dev_t *)filp->private_data; + if (dev->fops->release) + return dev->fops->release(inode, filp); + return 0; +} + +static int jz_char_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + jz_char_dev_t *dev = (jz_char_dev_t *)filp->private_data; + if (dev->fops->ioctl) + return dev->fops->ioctl(inode, filp, cmd, arg); + return 0; +} + +static ssize_t jz_char_read(struct file *filp, char *buf, + size_t count, loff_t *ppos) +{ + jz_char_dev_t *dev = (jz_char_dev_t *)filp->private_data; + if (dev->fops->read) + return dev->fops->read(filp, buf, count, ppos); + return 0; +} + +static ssize_t jz_char_write(struct file *filp, const char *buf, + size_t count, loff_t *ppos) +{ + jz_char_dev_t *dev = (jz_char_dev_t *)filp->private_data; + if (dev->fops->write) + return dev->fops->write(filp, buf, count, ppos); + return 0; +} + +EXPORT_SYMBOL(jz_register_chrdev); +EXPORT_SYMBOL(jz_unregister_chrdev); --- /dev/null +++ b/drivers/char/jzchar/jzchars.h @@ -0,0 +1,47 @@ +#ifndef __JZ_CHARS_H__ +#define __JZ_CHARS_H__ + +#include +#include + +#define JZ_CHAR_MAJOR 238 + +#define UPRT_MINOR 0 // Micro printer +#define CIM_MINOR 1 // Camera interface module +#define TPANEL_MINOR 2 // Touchpanel +#define KEYPAD_MINOR 3 // Keypad +#define MEMCARD_MINOR 4 // Memory card +#define MAGCARD_MINOR 5 // Magcard +#define VFD_MINOR 6 // VFD +#define POWERFAIL_MINOR 7 // Powerfail +#define EJTAG_MINOR 8 // EJTAG emulation +#define REMR0_MINOR 9 // Remote output receive 0 +#define REMR1_MINOR 10 // Remote output receive 1 +#define USPI_MINOR 11 // Ultra-speed SPI device +#define SADC_MINOR 12 // SAR-ADC +#define SLCD_MINOR 13 // Smart LCD + +// 32 to 47 are reserved for SCC +#define SCC_MINOR 32 +// 48 to 63 are reserved for Camera sensor +#define SENSOR_MINOR 48 +// 64 to 71 are for EEPROM +#define EEPROM_MINOR_BASE 64 +// 72 for OWI +#define OW_MINOR 72 +// 73 for TCSM_MINOR +#define TCSM_MINOR 73 + +typedef struct { + struct list_head list; + char *name; + struct file_operations *fops; + void *private; + unsigned short dev_minor; +} jz_char_dev_t; + +extern int jz_register_chrdev(unsigned char minor, const char *name, + struct file_operations *fops, void * private); +extern int jz_unregister_chrdev(unsigned char minor, const char *name); + +#endif /* __JZ_CHARS_H__ */ --- /dev/null +++ b/drivers/char/jzchar/poweroff.c @@ -0,0 +1,383 @@ +/* + * linux/drivers/char/jzchar/poweroff.c + * + * Power off handling. + * + * Copyright (C) 2005-2007 Ingenic Semiconductor Inc. + * + * This program 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "jzchars.h" + +MODULE_AUTHOR("Jianli Wei "); +MODULE_DESCRIPTION("Poweroff handling"); +MODULE_LICENSE("GPL"); + +#undef DEBUG +//#define DEBUG +#ifdef DEBUG +#define dprintk(x...) printk(x) +#else +#define dprintk(x...) +#endif + +//#define USE_SUSPEND_HOTPLUG + +#ifdef CONFIG_SOC_JZ4730 +#define GPIO_PW_I 97 +#define GPIO_PW_O 66 +#define POWEROFF_PIN_DOWN 1 +#define SET_POWEROFF_PIN_AS_IRQ __gpio_as_irq_rise_edge(POWEROFF_PIN) +#define DO_SHUTDOWN_SYSTEM __gpio_clear_pin(GPIO_PW_O) +#define DO_SUSPEND jz_pm_suspend() + +#define GPIO_DISP_OFF_N 93 +#define __lcd_set_backlight_level(n) \ +do { \ + REG_PWM_DUT(0) = n; \ + REG_PWM_PER(0) = 7; \ + REG_PWM_CTR(0) = 0x81; \ +} while (0) +#define __lcd_close_backlight() \ +do { \ + __lcd_set_backlight_level(0); \ +} while (0) +#endif + +#ifdef CONFIG_SOC_JZ4740 +#define GPIO_PW_I 125 +#define POWEROFF_PIN_DOWN 0 +#define SET_POWEROFF_PIN_AS_IRQ __gpio_as_irq_fall_edge(POWEROFF_PIN) +#define DO_SHUTDOWN_SYSTEM jz_pm_hibernate() +#define DO_SUSPEND { \ + jz_pm_sleep();\ + suspend_flag = 0;\ + SET_POWEROFF_PIN_AS_IRQ;\ + } + +#define GPIO_DISP_OFF_N 118 +#define GPIO_PWM 123 +#define __lcd_close_backlight() \ +do { \ +__gpio_as_output(GPIO_PWM); \ +__gpio_clear_pin(GPIO_PWM); \ +} while (0) +#endif + +#ifdef CONFIG_SOC_JZ4750 +#define GPIO_PW_I GPIO_WAKEUP +#define POWEROFF_PIN_DOWN 0 +#define SET_POWEROFF_PIN_AS_IRQ __gpio_as_irq_fall_edge(POWEROFF_PIN) +#define DO_SHUTDOWN_SYSTEM jz_pm_hibernate() +#define DO_SUSPEND { \ + jz_pm_sleep();\ + suspend_flag = 0;\ + SET_POWEROFF_PIN_AS_IRQ;\ + } +#endif + +#define POWEROFF_PIN GPIO_PW_I +#define POWEROFF_IRQ (IRQ_GPIO_0 + POWEROFF_PIN) + +#define POWEROFF_PERIOD 1000 /* unit: ms */ +#define POWEROFF_DELAY 100 /* unit: ms */ + +static struct timer_list poweroff_timer; +static struct timer_list poweroff_delaytimer; +static struct work_struct suspend_work; + +static int poweroff_flag = 0; +static int suspend_flag = 0; +static int num_seconds = 0; + +#ifdef CONFIG_JZ_UDC_HOTPLUG +extern int jz_udc_active; +#endif + +extern void jz_pm_suspend(void); +extern int jz_pm_hibernate(void); +extern int jz_pm_sleep(void); + +static void poweroff_timer_routine(unsigned long dummy) +{ + if (__gpio_get_pin(POWEROFF_PIN) == POWEROFF_PIN_DOWN) { + if (++num_seconds > 3) + { + printk("\nShutdown system now ..\n"); + +#ifndef USE_SUSPEND_HOTPLUG + /* Turn off LCD to inform user that the system is shutting down. + * But the information of shutting down system will be shown + * by userspace program if hotplug is used. + */ + __lcd_close_backlight(); +#endif + + /* + * Wait until the power key is up, or the system will reset with + * power key down after entering hibernate. + */ + while(__gpio_get_pin(POWEROFF_PIN)==POWEROFF_PIN_DOWN); + + poweroff_flag = 1; + schedule_work(&suspend_work); /* inform user to poweroff */ + } + else { + del_timer(&poweroff_timer); + init_timer(&poweroff_timer); + poweroff_timer.expires = jiffies + POWEROFF_PERIOD/10; + poweroff_timer.data = 0; + poweroff_timer.function = poweroff_timer_routine; + add_timer(&poweroff_timer); + } + } + else + { + printk("\nSuspend system now ..\n"); + num_seconds = 0; + suspend_flag = 1; + poweroff_flag = 0; + schedule_work(&suspend_work); /* we are entering suspend */ + } +} + +static void poweroff_delaytimer_routine(unsigned long dummy) +{ + __gpio_as_input(POWEROFF_PIN); + if (__gpio_get_pin(POWEROFF_PIN)==POWEROFF_PIN_DOWN) { + if (suspend_flag) { + suspend_flag = 0; + del_timer(&poweroff_delaytimer); + SET_POWEROFF_PIN_AS_IRQ; + __gpio_unmask_irq(POWEROFF_PIN); + return; + } + del_timer(&poweroff_delaytimer); + del_timer(&poweroff_timer); + init_timer(&poweroff_timer); + poweroff_timer.expires = jiffies + POWEROFF_PERIOD/100; + poweroff_timer.data = 0; + poweroff_timer.function = poweroff_timer_routine; + add_timer(&poweroff_timer); + } + else { + del_timer(&poweroff_delaytimer); + SET_POWEROFF_PIN_AS_IRQ; + __gpio_unmask_irq(POWEROFF_PIN); + + printk("This is a dummy key\n"); + } +} + +/* + * Poweroff pin interrupt handler + */ +static irqreturn_t poweroff_irq(int irq, void *dev_id) +{ + __gpio_ack_irq(POWEROFF_PIN); + __gpio_mask_irq(POWEROFF_PIN); + __gpio_as_input(POWEROFF_PIN); +#ifdef CONFIG_JZ_UDC_HOTPLUG + if (__gpio_get_pin(POWEROFF_PIN)==POWEROFF_PIN_DOWN && jz_udc_active == 0){ +#else + if (__gpio_get_pin(POWEROFF_PIN)==POWEROFF_PIN_DOWN){ +#endif + del_timer(&poweroff_delaytimer); + init_timer(&poweroff_delaytimer); + poweroff_delaytimer.expires = jiffies + POWEROFF_DELAY/10; + poweroff_delaytimer.data = 0; + poweroff_delaytimer.function = poweroff_delaytimer_routine; + add_timer(&poweroff_delaytimer); + } + else { + +/* + * If it reaches here without jz_udc_active == 0, then it indicates POWEROFF_PIN was + * changed to WAKEUP key in pm.c for hand is not able to rise up so quickly, so the + * irq handler entered because of WAKEUP key not POWEROFF_PIN. + */ + +#ifdef CONFIG_JZ_UDC_HOTPLUG + if (jz_udc_active == 1) + printk("\nUSB is working; Operation is denied\n"); +#endif + SET_POWEROFF_PIN_AS_IRQ; + __gpio_unmask_irq(POWEROFF_PIN); + } + + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM + +static struct pm_dev *poweroff_dev; + +static int poweroff_suspend(int *poweroff , int state) +{ + suspend_flag = 1; + poweroff_flag = 0; + + return 0; +} + +static int poweroff_resume(int *poweroff) +{ + suspend_flag = 0; + SET_POWEROFF_PIN_AS_IRQ; + __gpio_unmask_irq(POWEROFF_PIN); + + return 0; +} + +static int poweroff_pm_callback(struct pm_dev *pm_dev, pm_request_t rqst, void *data) +{ + int ret; + int *poweroff_info = pm_dev->data; + + if (!poweroff_info) + return -EINVAL; + + switch (rqst) { + case PM_SUSPEND: + ret = poweroff_suspend(poweroff_info, (int)data); + break; + case PM_RESUME: + ret = poweroff_resume(poweroff_info); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +#endif /* CONFIG_PM */ + +#ifdef USE_SUSPEND_HOTPLUG +static void run_sbin_hotplug(int state) +{ + int i; + char *argv[3], *envp[8]; + char media[64], slotnum[16]; + if (!uevent_helper[0]) + return; + + i = 0; + argv[i++] = uevent_helper; + //argv[i++] = "home/lhhuang/hotplug"; + + if ( poweroff_flag == 1 ) + argv[i++] = "poweroff"; + else + argv[i++] = "suspend"; + + argv[i] = 0; + + /* minimal command environment */ + i = 0; + envp[i++] = "HOME=/"; + envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + + /* other stuff we want to pass to /sbin/hotplug */ + sprintf(slotnum, "SLOT=0"); + + if ( poweroff_flag == 1 ) + sprintf(media, "MEDIA=poweroff"); + else + sprintf(media, "MEDIA=suspend"); + + envp[i++] = slotnum; + envp[i++] = media; + + if (state) + envp[i++] = "ACTION=enter"; + else + envp[i++] = "ACTION=exit"; + + envp[i] = 0; + + dprintk("SUSPEND: hotplug path=%s state=%d\n", argv[0], state); + + SET_POWEROFF_PIN_AS_IRQ; + __gpio_unmask_irq(POWEROFF_PIN); /* set it because call hotplug with call_usermodehelper() \ + might failed, especially when using nfsroot */ + + call_usermodehelper (argv [0], argv, envp, -1); +} +#endif + +static void suspend_handler(struct work_struct *work) +{ +#ifdef USE_SUSPEND_HOTPLUG + int state = 1; + run_sbin_hotplug(state); +#else + if (poweroff_flag) { + dprintk("DO_SHUTDOWN_SYSTEM\n"); + DO_SHUTDOWN_SYSTEM; + } else { + dprintk("DO_SUSPEND\n"); + DO_SUSPEND; + } +#endif +} + +static int __init poweroff_init(void) +{ + int retval; + + retval = request_irq(POWEROFF_IRQ, poweroff_irq, + IRQF_DISABLED, "poweroff", NULL); + + SET_POWEROFF_PIN_AS_IRQ; + + if (retval) { + printk("Could not get poweroff irq %d\n", POWEROFF_IRQ); + return retval; + } + +#ifdef CONFIG_PM + poweroff_dev = pm_register(PM_SYS_DEV, PM_SYS_UNKNOWN, poweroff_pm_callback); + if (poweroff_dev) { + poweroff_dev->data = &poweroff_dev; + } +#endif + + INIT_WORK(&suspend_work, suspend_handler); + + return 0; +} + +static void __exit poweroff_exit(void) +{ + free_irq(POWEROFF_IRQ, NULL); +} + +module_init(poweroff_init); +module_exit(poweroff_exit); + --- /dev/null +++ b/drivers/char/jzchar/sadc.c @@ -0,0 +1,580 @@ +/* + * linux/drivers/char/jzchar/sadc.c + * + * SAR-ADC driver for JZ4740. + * + * Copyright (C) 2006 Ingenic Semiconductor Inc. + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "jzchars.h" +#include "jz_ts.h" + +MODULE_AUTHOR("Jianli Wei"); +MODULE_DESCRIPTION("JZ4740 SADC driver"); +MODULE_LICENSE("GPL"); + +#define SADC_NAME "sadc" +static DECLARE_WAIT_QUEUE_HEAD (sadc_wait_queue); + +struct sadc_device { + int mode; + int dma_chan; + char *ts_buf; + char *pbat_buf; +}; + +static struct sadc_device *sadc_dev; + +static int samples = 3; /* we sample 3 every time */ +static int first_time = 0; +static unsigned long last_x, last_y, last_p; + +typedef struct datasource { + u16 xbuf; + u16 ybuf; + u16 zbuf; +}datasource_t; + +static datasource_t data_s; +static unsigned int p; +static unsigned int old_x, old_y; +extern unsigned int (*codec_read_battery)(void); + +/* + * set adc clock to 12MHz/div. A/D works at freq between 500KHz to 6MHz. + */ +static void sadc_init_clock(int div) +{ + if (div < 2) div = 2; + if (div > 23) div = 23; +#if defined(CONFIG_SOC_JZ4740) + REG_SADC_CFG &= ~SADC_CFG_CLKDIV_MASK; + REG_SADC_CFG |= (div - 1) << SADC_CFG_CLKDIV_BIT; +#endif +#if defined(CONFIG_SOC_JZ4750) + REG_SADC_ADCLK &= ~SADC_ADCLK_CLKDIV_MASK; + REG_SADC_ADCLK |= (div - 1) << SADC_ADCLK_CLKDIV_BIT; + REG_SADC_ADCLK &= ~SADC_ADCLK_CLKDIV_BIT; + REG_SADC_ADCLK |= 39 << SADC_ADCLK_CLKDIV_10_BIT; /* if div ==3,here is 39 */ +#endif +} + +void start_sadcin(void) +{ + REG_SADC_CTRL &= ~SADC_CTRL_SRDYM; /* enable interrupt */ + REG_SADC_ENA |= SADC_ENA_SADCINEN; +} + +void start_pbat_adc(void) +{ + REG_SADC_CFG |= SADC_CFG_PBAT_HIGH ; /* full baterry voltage >= 2.5V */ +// REG_SADC_CFG |= SADC_CFG_PBAT_LOW; /* full baterry voltage < 2.5V */ + + REG_SADC_ENA |= SADC_ENA_PBATEN; /* Enable pbat adc */ +} + +void start_ts_adc(void) +{ + REG_SADC_SAMETIME = 10; /* about 0.1 ms,you can change it */ + REG_SADC_WAITTIME = 2; /* about 0.02 ms,you can change it */ + + REG_SADC_CFG &= ~(SADC_CFG_TS_DMA | SADC_CFG_XYZ_MASK | SADC_CFG_SNUM_MASK); + REG_SADC_CFG |= (SADC_CFG_EXIN | SADC_CFG_XYZ | SADC_CFG_SNUM_3); + REG_SADC_CTRL |= (SADC_CTRL_TSRDYM|SADC_CTRL_PBATRDYM|SADC_CTRL_PENUM |SADC_CTRL_SRDYM); + REG_SADC_CTRL &= ~SADC_CTRL_PENDM; + REG_SADC_ENA |= SADC_ENA_TSEN; +} + +static int jz4740_adc_read(struct jz_ts_t *ts) +{ + struct datasource *ds = &data_s; + u32 xybuf,z; + + if (!(REG_SADC_STATE & SADC_STATE_TSRDY)) { + /* sleep */ + REG_SADC_CTRL &= ~SADC_CTRL_TSRDYM; + ts->sleeping = 1; + sleep_on(&sadc_wait_queue); + } + ts->sleeping = 0; + + xybuf = REG_SADC_TSDAT; + ds->xbuf = (xybuf>>16) & 0x0fff; + ds->ybuf = (xybuf)& 0x0fff; + z = REG_SADC_TSDAT; + ds->zbuf = z& 0x0fff; + REG_SADC_STATE &= ~SADC_STATE_TSRDY; + return 0; +} + +/*------------------------------------------------------------ + * Read the battery voltage + */ +unsigned int jz4740_read_battery(void) +{ + unsigned int v; + unsigned int timeout = 0x3ff; + u16 pbat; + + if(!(REG_SADC_STATE & SADC_STATE_PBATRDY) ==1) + start_pbat_adc(); + + while(!(REG_SADC_STATE & SADC_STATE_PBATRDY) && --timeout) + ; + + pbat = REG_SADC_BATDAT; + v = pbat & 0x0fff; + REG_SADC_STATE = SADC_STATE_PBATRDY; + return v; +} + +/*------------------ Calibrate samples -------------------*/ + +#define DIFF(a,b) (((a)>(b))?((a)-(b)):((b)-(a))) +#define MIN(a,b) (((a)<(b))?(a):(b)) + +#if 0 +#define XM 36 /* XM and YM may be changed for your screen */ +#define YM 20 +static int calibrate_samples(void *xbuf, void *ybuf, void *pbuf, int count) +{ + unsigned long usd0,usd1,usd2; + int xMaxError = XM,yMaxError = YM; + int x_valid = 0,y_valid = 0,valid = 0; + unsigned long x_cal = 0, y_cal = 0, p_cal = 0; + unsigned long *xp = (unsigned long *)xbuf; + unsigned long *yp = (unsigned long *)ybuf; + unsigned long *pp = (unsigned long *)pbuf; + + usd0 = (xp[0] > xp[1]) ? (xp[0] - xp[1]) : (xp[1] - xp[0]); + usd1 = (xp[1] > xp[2]) ? (xp[1] - xp[2]) : (xp[2] - xp[1]); + usd2 = (xp[2] > xp[0]) ? (xp[2] - xp[0]) : (xp[0] - xp[2]); + + if ( usd0 < usd1) + x_cal = xp[0] + ((usd2 < usd0) ? xp[2] : xp[1]); + else + x_cal= xp[2] + ((usd2 < usd1) ? xp[0] : xp[1]); + x_cal >>= 1; + + if ( (usd0 < xMaxError) && (usd1 < xMaxError) && (usd2 < xMaxError) ) + x_valid = 1; + + usd0 = (yp[0] > yp[1]) ? (yp[0] - yp[1]) : (yp[1] - yp[0]); + usd1 = (yp[1] > yp[2]) ? (yp[1] - yp[2]) : (yp[2] - yp[1]); + usd2 = (yp[2] > yp[0]) ? (yp[2] - yp[0]) : (yp[0] - yp[2]); + + if ( usd0 < usd1) + y_cal = yp[0] + ((usd2 < usd0) ? yp[2] : yp[1]); + else + y_cal = yp[2] + ((usd2 < usd1) ? yp[0] : yp[1]); + + y_cal >>= 1; + + if ( (usd0 < yMaxError) && (usd1 < yMaxError) && (usd2 < yMaxError) ) + y_valid = 1; + + if( x_valid && y_valid) + valid = 1; + + usd0 = (pp[0] > pp[1]) ? (pp[0] - pp[1]) : (pp[1] - pp[0]); + usd1 = (pp[1] > pp[2]) ? (pp[1] - pp[2]) : (pp[2] - pp[1]); + usd2 = (pp[2] > pp[0]) ? (pp[2] - pp[0]) : (pp[0] - pp[2]); + + if ( usd0 < usd1) + p_cal = pp[0] + ((usd2 < usd0) ? pp[2] : pp[1]); + else + p_cal= pp[2] + ((usd2 < usd1) ? pp[0] : pp[1]); + + p_cal >>= 1; + + if (first_time) { + first_time = 0; + last_x = x_cal; + last_y = y_cal; + last_p = p_cal; + } + else{ + if ((DIFF(x_cal, last_x) > 50) || + (DIFF(y_cal, last_y) > 50)) + valid = 0; + else + valid = 1; + } + *xp = last_x = x_cal; + *yp = last_y = y_cal; + *pp = last_p = p_cal; + + return valid; +} +#endif + +static int calibrate_samples(void *xbuf, void *ybuf, void *pbuf, int count) +{ + unsigned long *xp = (unsigned long *)xbuf; + unsigned long *yp = (unsigned long *)ybuf; + unsigned long *pp = (unsigned long *)pbuf; + unsigned long x_cal = 0, y_cal = 0, p_cal = 0; + int i; + int valid = 1; + + /* calculate the average of the rest */ + for (i = 0; i < count; i++) { + x_cal += xp[i]; + y_cal += yp[i]; + p_cal += pp[i]; + } + x_cal /= count; + y_cal /= count; + p_cal /= count; + + if (first_time) { + first_time = 0; + last_x = x_cal; + last_y = y_cal; + last_p = p_cal; + } + else { + if ((DIFF(x_cal, last_x) > 50) || + (DIFF(y_cal, last_y) > 50)) + valid = 0; + else + valid = 1; + } + + *xp = last_x = x_cal; + *yp = last_y = y_cal; + *pp = last_p = p_cal; + + return valid; +} + +#define TSMAXX 945 +#define TSMAXY 830 +#define TSMINX 90 +#define TSMINY 105 + +#define SCREEN_X 480 +#define SCREEN_Y 272 + +static unsigned long transform_to_screen_x(struct jz_ts_t *ts, unsigned long x ) +{ + + if (ts->minx) + { + if (x < ts->minx) x = ts->minx; + if (x > ts->maxx) x = ts->maxx; + + return (x - ts->minx) * SCREEN_X / (ts->maxx - ts->minx); + } + else + { + if (x < TSMINX) x = TSMINX; + if (x > TSMAXX) x = TSMAXX; + + return (x - TSMINX) * SCREEN_X / (TSMAXX - TSMINX); + } +} + +static unsigned long transform_to_screen_y(struct jz_ts_t *ts, unsigned long y) +{ + if (ts->minx) + { + if (y < ts->minx) y = ts->miny; + if (y > ts->maxx) y = ts->maxy; + + return (y - ts->miny) * SCREEN_Y / (ts->maxy - ts->miny); + } + else + { + if (y < TSMINX) y = TSMINY; + if (y > TSMAXX) y = TSMAXY; + + return (y - TSMINY) * SCREEN_Y / (TSMAXY - TSMINY); + } +} + +/* + * File operations + */ +static int sadc_open(struct inode *inode, struct file *filp); +static int sadc_release(struct inode *inode, struct file *filp); +static ssize_t sadc_read(struct file *filp, char *buf, size_t size, loff_t *l); +static ssize_t sadc_write(struct file *filp, const char *buf, size_t size, loff_t *l); +static int sadc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); + +static struct file_operations sadc_fops = +{ + open: sadc_open, + release: sadc_release, + read: sadc_read, + write: sadc_write, + ioctl: sadc_ioctl +}; + +static int sadc_open(struct inode *inode, struct file *filp) +{ + try_module_get(THIS_MODULE); + return 0; +} + +static int sadc_release(struct inode *inode, struct file *filp) +{ + module_put(THIS_MODULE); + return 0; +} + +static ssize_t sadc_read(struct file *filp, char *buf, size_t size, loff_t *l) +{ + return size; +} + +static ssize_t sadc_write(struct file *filp, const char *buf, size_t size, loff_t *l) +{ + return size; +} + +static int sadc_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + default: + printk("Not supported command: 0x%x\n", cmd); + return -EINVAL; + break; + } + return 0; +} + +/*------------------ Common routines -------------------*/ + +void ts_enable_irq(void) +{ + REG_SADC_CTRL &= ~SADC_CTRL_PENDM; +} + +void ts_disable_irq(void) +{ + REG_SADC_CTRL |= (SADC_CTRL_PENDM | SADC_CTRL_PENUM); +} + +void ts_free_irq(struct jz_ts_t *ts) +{ + free_irq(ts->pendown_irq, ts); +} + +void ts_data_ready(void) +{ + REG_SADC_CTRL |= SADC_CTRL_TSRDYM; + wake_up(&sadc_wait_queue); +} + +/* + * Interrupt handler + */ +void ts_irq_callback(void) +{ + u32 state; + + state = REG_SADC_STATE; + if (!(REG_SADC_CTRL&SADC_CTRL_PENDM)&&(REG_SADC_STATE & SADC_STATE_PEND)) { + REG_SADC_STATE = SADC_STATE_PEND; + REG_SADC_STATE = SADC_STATE_PENU; + REG_SADC_CTRL |= SADC_CTRL_PENDM; + REG_SADC_CTRL &= ~SADC_CTRL_PENUM; + p = 1; + } + + if (!(REG_SADC_CTRL&SADC_CTRL_PENUM)&&(REG_SADC_STATE & SADC_STATE_PENU)) { + REG_SADC_STATE = SADC_STATE_PENU; + REG_SADC_CTRL |= SADC_CTRL_PENUM; + REG_SADC_CTRL &= ~SADC_CTRL_PENDM; + p = 0; + } + + first_time = 1; // first time to acquire sample +} + +int PenIsDown(void) +{ + return p; +} + +int ts_request_irq(u32 *irq, + irqreturn_t (*handler)(int, void *), + const char *devname, + void *dev_id) +{ + int ret; + + /* return the irq number */ + *irq = IRQ_SADC; + ts_disable_irq(); + /* interrupt mode */ + ret = request_irq(IRQ_SADC, handler, IRQF_DISABLED, + devname, dev_id); + if(ret) + printk("failed irq \n"); + + start_ts_adc(); + return ret; +} + +/* + * Acquire Raw pen coodinate data and compute touch screen + * pressure resistance. Hold spinlock when calling. + */ +int AcquireEvent(struct jz_ts_t *ts, struct ts_event *event) +{ + unsigned int x_raw[8], y_raw[8], p_raw[8]; + int valid, i; + unsigned int avl_x, avl_y, diff_x, diff_y; + struct datasource *ds = &data_s; + avl_x = avl_y = 0; + + for (i = 0; i < samples; i++) { + if (jz4740_adc_read(ts)) { + return 0; + } + + x_raw[i] = ds->ybuf; + y_raw[i] = ds->xbuf; + p_raw[i] = ds->zbuf; + avl_x += x_raw[i]; + avl_y += y_raw[i]; +#if 0 + printk("x_raw=%x y_raw=%x z_raw=%x\n",x_raw[i],y_raw[i],p_raw[i]); +#endif + } + + avl_x /= samples; + avl_y /= samples; +#define MAX_DELTA 20 + valid = 1; + + for (i = 1; i < samples; i++) + { + if ((100 * DIFF(x_raw[i],x_raw[i-1])/MIN(x_raw[i],x_raw[i-1])) > MAX_DELTA) { + valid = 0; + break; + } + + if ((100 * DIFF(y_raw[i],y_raw[i-1])/MIN(y_raw[i],y_raw[i-1])) > MAX_DELTA) { + valid = 0; + break; + } + + if ((100 * DIFF(p_raw[i],p_raw[i-1])/MIN(p_raw[i],p_raw[i-1])) > MAX_DELTA) { + valid = 0; + break; + } + } + + if (valid) { + if (ts->first_read) { + ts->first_read = 0; + old_x = avl_x; + old_y = avl_y; + } + diff_x = DIFF(old_x, avl_x); + diff_y = DIFF(old_y, avl_y); + if (diff_x < 100 && diff_y < 100) { + old_x = avl_x; + old_y = avl_y; + } else + valid = 0; + } + if (valid) { + valid = calibrate_samples(x_raw, y_raw, p_raw, samples); + } + + if (valid) { + unsigned int x_scr, y_scr; + + if(ts->filter) { + x_scr = transform_to_screen_x(ts, x_raw[0]); + y_scr = transform_to_screen_y(ts, y_raw[0]); + + if (ts->prints) + printk("x_raw=%d y_raw=%d x_transform=%d y_transform=%d\n", x_raw[0], y_raw[0], x_scr, y_scr); + } + else { + x_scr = x_raw[0]; + y_scr = y_raw[0]; + + if (ts->prints) + printk("x_raw=%d y_raw=%d \n", x_raw[0], y_raw[0]); + } + + event->x = x_scr; + event->y = y_scr; + event->pressure = (u16)p_raw[0]; + event->status = PENDOWN; + return 1; + } + return 0; +} + +/* + * Module init and exit + */ +static int __init sadc_init(void) +{ + struct sadc_device *dev; + int ret; + + /* allocate device */ + dev = kmalloc(sizeof(struct sadc_device), GFP_KERNEL); + if (!dev) return -ENOMEM; + + sadc_dev = dev; + ret = jz_register_chrdev(SADC_MINOR, SADC_NAME, &sadc_fops, dev); + if (ret < 0) { + kfree(dev); + return ret; + } + + codec_read_battery = jz4740_read_battery; + sadc_init_clock(3); + + printk("JZ4740 SAR-ADC driver registered\n"); + return 0; +} + +static void __exit sadc_exit(void) +{ + struct sadc_device *dev = sadc_dev; + + free_irq(IRQ_SADC, dev); + jz_unregister_chrdev(SADC_MINOR, SADC_NAME); + kfree(dev); +} + +module_init(sadc_init); +module_exit(sadc_exit); --- /dev/null +++ b/drivers/char/jzchar/sensor.c @@ -0,0 +1,182 @@ +/* + * linux/drivers/char/jzchar/sensor.c + * + * Common CMOS Camera Sensor Driver + * + * Copyright (C) 2006 Ingenic Semiconductor Inc. + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "jzchars.h" + +MODULE_AUTHOR("Jianli Wei"); +MODULE_DESCRIPTION("Common CMOS Camera Sensor Driver"); +MODULE_LICENSE("GPL"); + +/* + * ioctl commands + */ +#define IOCTL_SET_ADDR 0 /* set i2c address */ +#define IOCTL_SET_CLK 1 /* set i2c clock */ +#define IOCTL_WRITE_REG 2 /* write sensor register */ +#define IOCTL_READ_REG 3 /* read sensor register */ + +/* + * i2c related + */ +static unsigned int i2c_addr = 0x42; +static unsigned int i2c_clk = 100000; + +static void write_reg(u8 reg, u8 val) +{ + i2c_open(); + i2c_setclk(i2c_clk); + i2c_write((i2c_addr >> 1), &val, reg, 1); + i2c_close(); +} + +static u8 read_reg(u8 reg) +{ + u8 val; + + i2c_open(); + i2c_setclk(i2c_clk); + i2c_read((i2c_addr >> 1), &val, reg, 1); + i2c_close(); + return val; +} + +/* + * fops routines + */ + +static int sensor_open(struct inode *inode, struct file *filp); +static int sensor_release(struct inode *inode, struct file *filp); +static ssize_t sensor_read(struct file *filp, char *buf, size_t size, loff_t *l); +static ssize_t sensor_write(struct file *filp, const char *buf, size_t size, loff_t *l); +static int sensor_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); + +static struct file_operations sensor_fops = +{ + open: sensor_open, + release: sensor_release, + read: sensor_read, + write: sensor_write, + ioctl: sensor_ioctl, +}; + +static int sensor_open(struct inode *inode, struct file *filp) +{ + try_module_get(THIS_MODULE); + return 0; +} + +static int sensor_release(struct inode *inode, struct file *filp) +{ + module_put(THIS_MODULE); + return 0; +} + +static ssize_t sensor_read(struct file *filp, char *buf, size_t size, loff_t *l) +{ + printk("sensor: read is not implemented\n"); + return -1; +} + +static ssize_t sensor_write(struct file *filp, const char *buf, size_t size, loff_t *l) +{ + printk("sensor: write is not implemented\n"); + return -1; +} + +static int sensor_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + case IOCTL_SET_ADDR: + if (copy_from_user(&i2c_addr, (void *)arg, 4)) + return -EFAULT; + break; + case IOCTL_SET_CLK: + if (copy_from_user(&i2c_clk, (void *)arg, 4)) + return -EFAULT; + break; + case IOCTL_WRITE_REG: + { + u8 regval[2]; + + if (copy_from_user(regval, (void *)arg, 2)) + return -EFAULT; + + write_reg(regval[0], regval[1]); + break; + } + case IOCTL_READ_REG: + { + u8 reg, val; + + if (copy_from_user(®, (void *)arg, 1)) + return -EFAULT; + + val = read_reg(reg); + + if (copy_to_user((void *)(arg + 1), &val, 1)) + return -EFAULT; + break; + } + default: + printk("Not supported command: 0x%x\n", cmd); + return -EINVAL; + break; + } + return ret; +} + +/* + * Module init and exit + */ + +static int __init sensor_init(void) +{ + int ret; + + ret = jz_register_chrdev(SENSOR_MINOR, "sensor", &sensor_fops, NULL); + if (ret < 0) { + return ret; + } + + printk("Ingenic CMOS camera sensor driver registered\n"); + + return 0; +} + +static void __exit sensor_exit(void) +{ + jz_unregister_chrdev(SENSOR_MINOR, "sensor"); +} + +module_init(sensor_init); +module_exit(sensor_exit); --- /dev/null +++ b/drivers/char/jzchar/tcsm.c @@ -0,0 +1,123 @@ +/* + * linux/drivers/char/jzchar/tcsm.c + * + * Virtual device driver with tricky appoach to manage TCSM + * + * Copyright (C) 2006 Ingenic Semiconductor Inc. + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "jzchars.h" + +MODULE_AUTHOR("Jianli Wei"); +MODULE_DESCRIPTION("Virtual Driver of TCSM"); +MODULE_LICENSE("GPL"); + +/* + * fops routines + */ + +static int tcsm_open(struct inode *inode, struct file *filp); +static int tcsm_release(struct inode *inode, struct file *filp); +static ssize_t tcsm_read(struct file *filp, char *buf, size_t size, loff_t *l); +static ssize_t tcsm_write(struct file *filp, const char *buf, size_t size, loff_t *l); +static int tcsm_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); + +static struct file_operations tcsm_fops = +{ + open: tcsm_open, + release: tcsm_release, + read: tcsm_read, + write: tcsm_write, + ioctl: tcsm_ioctl, +}; + +static int tcsm_open(struct inode *inode, struct file *filp) +{ + // printk("%d[%s]\n", __LINE__, __FILE__); + struct pt_regs *info = task_pt_regs(current); + printk("pt_regs =%p\n", info); + printk("cp0 status=0x%08x\n", info->cp0_status); + info->cp0_status &= ~0x10; + info->cp0_status |= 0x08000000; // a tricky + printk("cp0 status=0x%08x\n", info->cp0_status); + return 0; +} + +static int tcsm_release(struct inode *inode, struct file *filp) +{ + struct pt_regs *info = task_pt_regs(current); + info->cp0_status |= 0x10; + info->cp0_status &= ~0x08000000; // a tricky + return 0; +} + +static ssize_t tcsm_read(struct file *filp, char *buf, size_t size, loff_t *l) +{ + printk("tcsm: read is not implemented\n"); + return -1; +} + +static ssize_t tcsm_write(struct file *filp, const char *buf, size_t size, loff_t *l) +{ + printk("tcsm: write is not implemented\n"); + return -1; +} + +static int tcsm_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + printk("tcsm: ioctl is not implemented\n"); + return ret; +} + +/* + * Module init and exit + */ + +static int __init tcsm_init(void) +{ + int ret; + + ret = jz_register_chrdev(TCSM_MINOR, "tcsm", &tcsm_fops, NULL); + if (ret < 0) { + return ret; + } + + printk("Virtual Driver of TCSM registered\n"); + return 0; +} + +static void __exit tcsm_exit(void) +{ + jz_unregister_chrdev(TCSM_MINOR, "tcsm"); +} + +module_init(tcsm_init); +module_exit(tcsm_exit); --- /dev/null +++ b/drivers/char/jzchar/ucb1400.c @@ -0,0 +1,585 @@ +/* + * ucb1400.c + * + * Touch screen driver interface to the UCB1400 codec. + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "jz_ts.h" +#include "ucb1400.h" + +#ifndef GPIO_TS_PENIRQ +#define GPIO_TS_PENIRQ 68 +#endif + +#define TS_PIN GPIO_TS_PENIRQ +#define TS_IRQ (IRQ_GPIO_0 + TS_PIN) + +static int samples = 5; /* we sample 5 every time, and throw away the max and min cases, then use the average of the other 3 samples */ +static int first_time = 0; +static unsigned long last_x, last_y, last_p; + +static int adcsync = 0; + +static unsigned int ucb_id = 0; +static struct ucb1400 *ucb; + +extern struct ac97_codec * find_ac97_codec(void); + +extern unsigned int (*codec_read_battery)(void); + +/*------------------ UCB1400 routines ------------------*/ + +static inline void ucb1400_reg_write(unsigned char reg, unsigned short val) +{ + struct ac97_codec *codec = find_ac97_codec(); + if (!codec) + return; + codec->codec_write(codec, reg, val); +} + +static inline unsigned int ucb1400_reg_read(unsigned char reg) +{ + struct ac97_codec *codec = find_ac97_codec(); + if (!codec) + return 0; + return codec->codec_read(codec, reg); +} + +static void ucb1400_adc_enable(void) +{ + down(&ucb->adc_sem); + + ucb->adc_cr |= UCB_ADC_ENA; + ucb1400_reg_write(UCB_ADC_CR, ucb->adc_cr); +} + +static unsigned int ucb1400_adc_read(int adc_channel, int sync) +{ + unsigned int val, timeout = 10000; + + if (sync) + adc_channel |= UCB_ADC_SYNC_ENA; + + ucb1400_reg_write(UCB_ADC_CR, ucb->adc_cr | adc_channel); + ucb1400_reg_write(UCB_ADC_CR, ucb->adc_cr | adc_channel | UCB_ADC_START); + + for (;;) { + val = ucb1400_reg_read(UCB_ADC_DATA); + if (val & UCB_ADC_DAT_VAL) + break; + if (--timeout == 0) + break; + udelay(1); + } + + return UCB_ADC_DAT(val); +} + +static void ucb1400_adc_disable(void) +{ + ucb->adc_cr &= ~UCB_ADC_ENA; + ucb1400_reg_write(UCB_ADC_CR, ucb->adc_cr); + + up(&ucb->adc_sem); +} + +static void ucb1400_enable_irq(unsigned int idx, int edges) +{ + unsigned long flags; + + if (idx < 16) { + spin_lock_irqsave(&ucb->lock, flags); + + /* This prevents spurious interrupts on the UCB1400 */ + ucb1400_reg_write(UCB_IE_CLEAR, 1 << idx); + ucb1400_reg_write(UCB_IE_CLEAR, 0); + + if (edges & UCB_RISING) { + ucb->irq_ris_enbl |= 1 << idx; + ucb1400_reg_write(UCB_IE_RIS, ucb->irq_ris_enbl); + } + if (edges & UCB_FALLING) { + ucb->irq_fal_enbl |= 1 << idx; + ucb1400_reg_write(UCB_IE_FAL, ucb->irq_fal_enbl); + } + spin_unlock_irqrestore(&ucb->lock, flags); + } +} + +static void ucb1400_disable_irq(unsigned int idx, int edges) +{ + unsigned long flags; + + if (idx < 16) { + spin_lock_irqsave(&ucb->lock, flags); + + if (edges & UCB_RISING) { + ucb->irq_ris_enbl &= ~(1 << idx); + ucb1400_reg_write(UCB_IE_RIS, ucb->irq_ris_enbl); + } + if (edges & UCB_FALLING) { + ucb->irq_fal_enbl &= ~(1 << idx); + ucb1400_reg_write(UCB_IE_FAL, ucb->irq_fal_enbl); + } + spin_unlock_irqrestore(&ucb->lock, flags); + } +} + +/* + * Switch to interrupt mode. + */ +static inline void ucb1400_ts_mode_int(void) +{ + if (!ucb_id) { + ucb_id = ucb1400_reg_read(UCB_ID); + } + + if (ucb_id == UCB_ID_1400_BUGGY) + ucb1400_reg_write(UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_INT); + else + ucb1400_reg_write(UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_INT); +} + +/* + * Switch to pressure mode, and read pressure. We don't need to wait + * here, since both plates are being driven. + */ +static inline unsigned int ucb1400_ts_read_pressure(void) +{ + ucb1400_reg_write(UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + + return ucb1400_adc_read(UCB_ADC_INP_TSPY, adcsync); +} + +/* + * Switch to X position mode and measure Y plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1400_ts_read_xpos(void) +{ + ucb1400_reg_write(UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(55); + + return ucb1400_adc_read(UCB_ADC_INP_TSPY, adcsync); +} + +/* + * Switch to Y position mode and measure X plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1400_ts_read_ypos(void) +{ + ucb1400_reg_write(UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(55); + + return ucb1400_adc_read(UCB_ADC_INP_TSPX, adcsync); +} + +/*------------------------------------------------------------ + * Read the battery voltage + */ + +unsigned int ucb1400_read_battery(void) +{ + unsigned int v; + + ucb1400_adc_enable(); + + // read twice to reduce fault value + v = ucb1400_adc_read(UCB_ADC_INP_AD0, adcsync); + v = ucb1400_adc_read(UCB_ADC_INP_AD0, adcsync); + + ucb1400_adc_disable(); + +// printk("ucb1400_read_battery v=%d\n", v); + + return v; +} + +/*------------------ Calibrate samples -------------------*/ + +#define DIFF(a,b) ((a>b)?(a-b):(b-a)) + +static int calibrate_samples(void *xbuf, void *ybuf, void *pbuf, int count) +{ + unsigned long *xp = (unsigned long *)xbuf; + unsigned long *yp = (unsigned long *)ybuf; + unsigned long *pp = (unsigned long *)pbuf; + unsigned long x_cal = 0, y_cal = 0, p_cal = 0, tmp; + int ignored, i, j; + int valid = 0; + + /* throw away the max cases */ + tmp = xp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (xp[i] > tmp) { + tmp = xp[i]; + ignored = i; + } + } + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + xp[j++] = xp[i]; + } + + tmp = yp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (yp[i] > tmp) { + tmp = yp[i]; + ignored = i; + } + } + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + yp[j++] = yp[i]; + } + + tmp = pp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (pp[i] > tmp) { + tmp = pp[i]; + ignored = i; + } + } + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + pp[j++] = pp[i]; + } + + /* throw away the min cases */ + + count -= 1; // decrement by 1 + + tmp = xp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (xp[i] < tmp) { + tmp = xp[i]; + ignored = i; + } + } + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + xp[j++] = xp[i]; + } + + tmp = yp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (yp[i] < tmp) { + tmp = yp[i]; + ignored = i; + } + } + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + yp[j++] = yp[i]; + } + + tmp = pp[0]; + ignored = 0; + for (i = 1; i < count; i++) { + if (pp[i] < tmp) { + tmp = pp[i]; + ignored = i; + } + } + j = 0; + for (i = 0; i < count; i++) { + if (i == ignored) + continue; + pp[j++] = pp[i]; + } + + count -= 1; // decrement by 1 + + /* calculate the average of the rest */ + for (i = 0; i < count; i++) { + x_cal += xp[i]; + y_cal += yp[i]; + p_cal += pp[i]; + } + x_cal /= count; + y_cal /= count; + p_cal /= count; + + if (first_time) { + first_time = 0; + last_x = x_cal; + last_y = y_cal; + last_p = p_cal; + valid = 1; + } + else { + if ((DIFF(x_cal, last_x) > 50) || + (DIFF(y_cal, last_y) > 50)) + valid = 0; + else + valid = 1; + } + +// printk("x_cal=%d y_cal=%d p_cal=%d valid=%d\n", x_cal, y_cal, p_cal, valid); + + if (valid) { + *xp = last_x = x_cal; + *yp = last_y = y_cal; + *pp = last_p = p_cal; + } + + return valid; +} + + +#define TSMAXX 945 +#define TSMAXY 830 +#define TSMINX 90 +#define TSMINY 105 + +#define SCREEN_X 480 +#define SCREEN_Y 272 + +static unsigned long transform_to_screen_x(struct jz_ts_t *ts, unsigned long x ) +{ + + if (ts->minx) + { + if (x < ts->minx) x = ts->minx; + if (x > ts->maxx) x = ts->maxx; + + return (x - ts->minx) * SCREEN_X / (ts->maxx - ts->minx); + } + else + { + if (x < TSMINX) x = TSMINX; + if (x > TSMAXX) x = TSMAXX; + + return (x - TSMINX) * SCREEN_X / (TSMAXX - TSMINX); + } +} + +static unsigned long transform_to_screen_y(struct jz_ts_t *ts, unsigned long y) +{ + if (ts->minx) + { + if (y < ts->minx) y = ts->miny; + if (y > ts->maxx) y = ts->maxy; + + return (y - ts->miny) * SCREEN_Y / (ts->maxy - ts->miny); + } + else + { + if (y < TSMINX) y = TSMINY; + if (y > TSMAXX) y = TSMAXY; + + return (y - TSMINY) * SCREEN_Y / (TSMAXY - TSMINY); + } +} + +/*------------------ Common routines -------------------*/ + +void ts_enable_irq(void) +{ + /* interrupt mode */ + ucb1400_ts_mode_int(); + ucb1400_enable_irq(UCB_IRQ_TSPX, UCB_FALLING); + + enable_irq(TS_IRQ); +} + +void ts_disable_irq(void) +{ + ucb1400_disable_irq(UCB_IRQ_TSPX, UCB_FALLING); + disable_irq(TS_IRQ); +} + +int ts_request_irq(u32 *irq, + void (*handler)(int, void *, struct pt_regs *), + const char *devname, + void *dev_id) +{ + int retval; + + /* return the irq number */ + *irq = TS_IRQ; + + /* interrupt mode */ + ucb1400_ts_mode_int(); + ucb1400_enable_irq(UCB_IRQ_TSPX, UCB_FALLING); + + /* enable gpio irq */ + __gpio_as_irq_rise_edge(TS_PIN); + + /* register irq handler */ + retval = request_irq(TS_IRQ, handler, SA_INTERRUPT, devname, dev_id); + + return retval; +} + +void ts_free_irq(struct jz_ts_t *ts) +{ + free_irq(ts->pendown_irq, ts); +} + +void ts_irq_callback(void) +{ + /* clear interrupt status */ + ucb1400_reg_write(UCB_IE_CLEAR, ucb1400_reg_read(UCB_IE_STATUS)); + __gpio_ack_irq(TS_PIN); + + first_time = 1; // first time to acquire sample +} + +int PenIsDown(void) +{ + unsigned int p; + + ucb1400_adc_enable(); + p = ucb1400_ts_read_pressure(); + ucb1400_adc_disable(); + + return (p > 100) ? 1 : 0; +} + +/* + * Acquire Raw pen coodinate data and compute touch screen + * pressure resistance. Hold spinlock when calling. + */ +int AcquireEvent(struct jz_ts_t *ts, struct ts_event *event) +{ + unsigned int x_raw[8], y_raw[8], p_raw[8]; + int valid, i; + + /* Enable ADC */ + ucb1400_adc_enable(); + + for (i = 0; i < samples; i++) { + x_raw[i] = ucb1400_ts_read_xpos(); + } + for (i = 0; i < samples; i++) { + y_raw[i] = ucb1400_ts_read_ypos(); + } + for (i = 0; i < samples; i++) { + p_raw[i] = ucb1400_ts_read_pressure(); + } + + /* Disable ADC */ + ucb1400_adc_disable(); + + valid = calibrate_samples(x_raw, y_raw, p_raw, samples); + + if (valid) { + unsigned int x_scr, y_scr; + + if(ts->filter) { + x_scr = transform_to_screen_x(ts, x_raw[0]); + y_scr = transform_to_screen_y(ts, y_raw[0]); + + if (ts->prints) + printk("x_raw=%d y_raw=%d x_transform=%d y_transform=%d\n", x_raw[0], y_raw[0], x_scr, y_scr); + } + else { + x_scr = x_raw[0]; + y_scr = y_raw[0]; + + if (ts->prints) + printk("x_raw=%d y_raw=%d \n", x_raw[0], y_raw[0]); + } + + event->x = x_scr; + event->y = y_scr; + event->pressure = (u16)p_raw[0]; + event->status = PENDOWN; + return 1; + } + return 0; +} + +/* + * Module init and exit + */ + +int __init ucb1400_init(void) +{ + ucb = kmalloc(sizeof(struct ucb1400), GFP_KERNEL); + if (!ucb) return -ENOMEM; + + memset(ucb, 0, sizeof(struct ucb1400)); + + codec_read_battery = ucb1400_read_battery; + + spin_lock_init(&ucb->lock); + sema_init(&ucb->adc_sem, 1); + + return 0; +} + +void ucb1400_cleanup(void) +{ + kfree(ucb); +} + +module_init(ucb1400_init); +module_exit(ucb1400_cleanup); --- /dev/null +++ b/drivers/char/jzchar/ucb1400.h @@ -0,0 +1,113 @@ +#ifndef __UCB1400_H__ +#define __UCB1400_H__ + +/* ucb1400 aclink register mappings */ + +#define UCB_IO_DATA 0x5a +#define UCB_IO_DIR 0x5c +#define UCB_IE_RIS 0x5e +#define UCB_IE_FAL 0x60 +#define UCB_IE_STATUS 0x62 +#define UCB_IE_CLEAR 0x62 +#define UCB_TS_CR 0x64 +#define UCB_ADC_CR 0x66 +#define UCB_ADC_DATA 0x68 +#define UCB_ID 0x7e /* 7c is mfr id, 7e part id (from aclink spec) */ + +#define UCB_ADC_DAT(x) ((x) & 0x3ff) + +/* register bits */ + +#define UCB_IO_0 (1 << 0) +#define UCB_IO_1 (1 << 1) +#define UCB_IO_2 (1 << 2) +#define UCB_IO_3 (1 << 3) +#define UCB_IO_4 (1 << 4) +#define UCB_IO_5 (1 << 5) +#define UCB_IO_6 (1 << 6) +#define UCB_IO_7 (1 << 7) +#define UCB_IO_8 (1 << 8) +#define UCB_IO_9 (1 << 9) + +#define UCB_IE_ADC (1 << 11) +#define UCB_IE_TSPX (1 << 12) +#define UCB_IE_TSMX (1 << 13) +#define UCB_IE_TCLIP (1 << 14) +#define UCB_IE_ACLIP (1 << 15) + +#define UCB_IRQ_TSPX 12 + +#define UCB_TC_A_LOOP (1 << 7) /* UCB1200 */ +#define UCB_TC_A_AMPL (1 << 7) /* UCB1300 */ + +#define UCB_TC_B_VOICE_ENA (1 << 3) +#define UCB_TC_B_CLIP (1 << 4) +#define UCB_TC_B_ATT (1 << 6) +#define UCB_TC_B_SIDE_ENA (1 << 11) +#define UCB_TC_B_MUTE (1 << 13) +#define UCB_TC_B_IN_ENA (1 << 14) +#define UCB_TC_B_OUT_ENA (1 << 15) + +#define UCB_AC_B_LOOP (1 << 8) +#define UCB_AC_B_MUTE (1 << 13) +#define UCB_AC_B_IN_ENA (1 << 14) +#define UCB_AC_B_OUT_ENA (1 << 15) + +#define UCB_TS_CR_TSMX_POW (1 << 0) +#define UCB_TS_CR_TSPX_POW (1 << 1) +#define UCB_TS_CR_TSMY_POW (1 << 2) +#define UCB_TS_CR_TSPY_POW (1 << 3) +#define UCB_TS_CR_TSMX_GND (1 << 4) +#define UCB_TS_CR_TSPX_GND (1 << 5) +#define UCB_TS_CR_TSMY_GND (1 << 6) +#define UCB_TS_CR_TSPY_GND (1 << 7) +#define UCB_TS_CR_MODE_INT (0 << 8) +#define UCB_TS_CR_MODE_PRES (1 << 8) +#define UCB_TS_CR_MODE_POS (2 << 8) +#define UCB_TS_CR_BIAS_ENA (1 << 11) +#define UCB_TS_CR_TSPX_LOW (1 << 12) +#define UCB_TS_CR_TSMX_LOW (1 << 13) + +#define UCB_ADC_SYNC_ENA (1 << 0) +#define UCB_ADC_VREFBYP_CON (1 << 1) +#define UCB_ADC_INP_TSPX (0 << 2) +#define UCB_ADC_INP_TSMX (1 << 2) +#define UCB_ADC_INP_TSPY (2 << 2) +#define UCB_ADC_INP_TSMY (3 << 2) +#define UCB_ADC_INP_AD0 (4 << 2) +#define UCB_ADC_INP_AD1 (5 << 2) +#define UCB_ADC_INP_AD2 (6 << 2) +#define UCB_ADC_INP_AD3 (7 << 2) +#define UCB_ADC_EXT_REF (1 << 5) +#define UCB_ADC_START (1 << 7) +#define UCB_ADC_ENA (1 << 15) + +#define UCB_ADC_DAT_VAL (1 << 15) + +#define UCB_ID_1200 0x1004 +#define UCB_ID_1300 0x1005 +#define UCB_ID_1400 0x4304 +#define UCB_ID_1400_BUGGY 0x4303 /* fake ID */ + +#define UCB_MODE_DYN_VFLAG_ENA (1 << 12) +#define UCB_MODE_AUD_OFF_CAN (1 << 13) + +/* + * Which edges of the IRQ do you want to control today? + */ +#define UCB_RISING (1 << 0) +#define UCB_FALLING (1 << 1) + +/* Device data structure */ + +struct ucb1400 { + spinlock_t lock; + struct pm_dev *pmdev; + struct semaphore adc_sem; + u16 adc_cr; + u16 irq_fal_enbl; + u16 irq_ris_enbl; + int irq_enabled; +}; + +#endif /* __UCB1400_H__ */ --- /dev/null +++ b/drivers/char/jzchar/udc_hotplug.c @@ -0,0 +1,451 @@ +/* + * linux/drivers/char/jzchar/udc_hotplug.c + * + * New UDC hotplug driver. + * + * Copyright (C) 2007 Ingenic Semiconductor Inc. + * + * This program 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "jzchars.h" + +#ifndef GPIO_UDC_HOTPLUG +#define GPIO_UDC_HOTPLUG 86 +#endif + +#define UDC_HOTPLUG_PIN GPIO_UDC_HOTPLUG +#define UDC_HOTPLUG_IRQ (IRQ_GPIO_0 + UDC_HOTPLUG_PIN) + +#define dprintk(x,...) + +//simple meaning define +#define NOT_CONNECT 0 +#define YES_CONNECT 1 +#define MAX_GPIO_TIME 50 + +#define EVENT_USB_ADD 1 +#define EVENT_USB_REMOVE 2 +#define EVENT_POWER_ADD 3 +#define EVENT_POWER_REMOVE 4 +#define EVENT_POWER_TO_USB 5 +#define EVENT_USB_SUSPEND_POWER 6 + +struct udc_pnp_stat +{ + char cable_stat, old_cable_stat; + char protl_stat, old_protl_stat; + char object_stat1; + char object_stat2; +}; + +static struct udc_pnp_stat cur_pnp_stat; + +static struct file_operations cable_fops = { + owner: THIS_MODULE, +}; + +static struct miscdevice cable_dev= +{ + 231, + "udc_cable", + &cable_fops +}; + +static struct file_operations power_fops = { + owner: THIS_MODULE, +}; + +static struct miscdevice power_dev= +{ + 232, + "power_cable", + &power_fops +}; + +int jz_udc_active = 0; /* 0: Have no actions; 1: Have actions */ + +static int udc_pin_level; +static int udc_old_state; +static int udc_pin_time; + +static struct timer_list udc_long_timer, udc_gpio_timer; + +/* Kernel thread to deliver event to user space */ +static struct task_struct *kudcd_task; + +static void udc_gpio_timer_routine(unsigned long data) +{ + wake_up_process(kudcd_task); +} + +static void udc_long_timer_routine(unsigned long data) +{ + dprintk("udc_timer\n"); + if (jz_udc_active) + udc_old_state = 1; + if (!jz_udc_active && udc_old_state) //udc irq timeout! do suspend + { + dprintk("udc suspend!\n"); + udc_old_state = 0; + cur_pnp_stat.protl_stat = NOT_CONNECT; + del_timer(&udc_long_timer); + wake_up_process(kudcd_task); + return; + } + jz_udc_active = 0; + udc_long_timer.expires = jiffies + 3 * HZ; /* about 3 s */ + add_timer(&udc_long_timer); +} + +static int udc_get_pnp_stat(void) +{ + udc_pin_level = __gpio_get_pin(UDC_HOTPLUG_PIN); + udc_pin_time = 1; + + init_timer(&udc_gpio_timer); + del_timer(&udc_gpio_timer); + udc_gpio_timer.function = udc_gpio_timer_routine; + udc_gpio_timer.expires = jiffies + 1; /* about 10 ms */ + add_timer(&udc_gpio_timer); + + while(1) + { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + + if (__gpio_get_pin(UDC_HOTPLUG_PIN) != udc_pin_level) + { + udc_pin_level = __gpio_get_pin(UDC_HOTPLUG_PIN); + udc_pin_time = 1; + dprintk("udc gpio detect restart! \n"); + } + + udc_pin_time ++; + if (udc_pin_time > MAX_GPIO_TIME) + break; + + del_timer(&udc_gpio_timer); + udc_gpio_timer.function = udc_gpio_timer_routine; + udc_gpio_timer.expires = jiffies + 1; /* about 10 ms */ + add_timer(&udc_gpio_timer); + } + + del_timer(&udc_gpio_timer); + if (__gpio_get_pin(UDC_HOTPLUG_PIN)) + return YES_CONNECT; + else + return NOT_CONNECT; +} + +static void udc_get_cable(void) +{ + u32 intr_usb; + + __intc_mask_irq(IRQ_UDC); + + /* Now enable PHY to start detect */ +#ifdef CONFIG_SOC_JZ4740 + REG_CPM_SCR |= CPM_SCR_USBPHY_ENABLE; +#elif CONFIG_SOC_JZ4750 + REG_CPM_OPCR |= CPM_OPCR_UDCPHY_ENABLE; +#endif + /* Clear IRQs */ + REG16(USB_REG_INTRINE) = 0; + REG16(USB_REG_INTROUTE) = 0; + REG8(USB_REG_INTRUSBE) = 0; + + /* disable UDC IRQs first */ + REG16(USB_REG_INTRINE) = 0; + REG16(USB_REG_INTROUTE) = 0; + REG8(USB_REG_INTRUSBE) = 0; + + /* Disable DMA */ + REG32(USB_REG_CNTL1) = 0; + REG32(USB_REG_CNTL2) = 0; + + /* Enable HS Mode */ + REG8(USB_REG_POWER) |= USB_POWER_HSENAB; + /* Enable soft connect */ + REG8(USB_REG_POWER) |= USB_POWER_SOFTCONN; + + dprintk("enable phy! %x %x %x %x %x\n", + REG8(USB_REG_POWER), + REG_CPM_SCR, + REG16(USB_REG_INTRINE), + REG16(USB_REG_INTROUTE), + REG8(USB_REG_INTRUSBE)); + + init_timer(&udc_gpio_timer); + del_timer(&udc_gpio_timer); + udc_gpio_timer.function = udc_gpio_timer_routine; + udc_gpio_timer.expires = jiffies + 11; /* about 100 ms */ + add_timer(&udc_gpio_timer); + /* Sleep a short time to see result */ + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + + del_timer(&udc_gpio_timer); + intr_usb = REG8(USB_REG_INTRUSB); + if ((intr_usb & USB_INTR_RESET) || + (intr_usb & USB_INTR_RESUME) || + (intr_usb & USB_INTR_SUSPEND)) + { + cur_pnp_stat.protl_stat = YES_CONNECT; + dprintk("cable is usb! \n"); + } + else + { + cur_pnp_stat.protl_stat = NOT_CONNECT; + dprintk("cable is power! \n"); + } + + /* Detect finish ,clean every thing */ + /* Disconnect from usb */ + REG8(USB_REG_POWER) &= ~USB_POWER_SOFTCONN; + /* Disable the USB PHY */ +#ifdef CONFIG_SOC_JZ4740 + REG_CPM_SCR &= ~CPM_SCR_USBPHY_ENABLE; +#elif CONFIG_SOC_JZ4750 + REG_CPM_OPCR &= ~CPM_OPCR_UDCPHY_ENABLE; +#endif + /* Clear IRQs */ + REG16(USB_REG_INTRINE) = 0; + REG16(USB_REG_INTROUTE) = 0; + REG8(USB_REG_INTRUSBE) = 0; + __intc_ack_irq(IRQ_UDC); + __intc_unmask_irq(IRQ_UDC); +} + +static void send_event_udev(int event) +{ + dprintk("Send udev message: cable=%d old=%d protl=%d old=%d \n", + cur_pnp_stat.cable_stat, + cur_pnp_stat.old_cable_stat, + cur_pnp_stat.protl_stat, + cur_pnp_stat.old_protl_stat); + + switch (event) + { + case EVENT_USB_ADD: + printk("usb cable insert! \n"); + misc_register(&cable_dev); + kobject_uevent(&cable_dev.this_device->kobj, KOBJ_ADD); + init_timer(&udc_long_timer); + del_timer(&udc_long_timer); + udc_long_timer.function = udc_long_timer_routine; + udc_long_timer.expires = jiffies + 3 * HZ; /* about 3 s */ + add_timer(&udc_long_timer); + break; + case EVENT_USB_REMOVE: + printk("usb cable remove! \n"); + kobject_uevent(&cable_dev.this_device->kobj, KOBJ_REMOVE); + misc_deregister(&cable_dev); + del_timer(&udc_long_timer); + break; + case EVENT_POWER_ADD: + printk("power cable insert! \n"); + misc_register(&power_dev); + kobject_uevent(&power_dev.this_device->kobj, KOBJ_ADD); + break; + case EVENT_POWER_REMOVE: + printk("power cable remove! \n"); + kobject_uevent(&power_dev.this_device->kobj, KOBJ_REMOVE); + misc_deregister(&power_dev); + break; + case EVENT_POWER_TO_USB: + printk("change power cable to usb! \n"); + kobject_uevent(&power_dev.this_device->kobj, KOBJ_REMOVE); + misc_deregister(&power_dev); + misc_register(&cable_dev); + kobject_uevent(&cable_dev.this_device->kobj, KOBJ_ADD); + break; + case EVENT_USB_SUSPEND_POWER: + printk("usb cable suspend! \n"); + printk("as power cable insert! \n"); + kobject_uevent(&cable_dev.this_device->kobj, KOBJ_REMOVE); + misc_deregister(&cable_dev); + misc_register(&power_dev); + kobject_uevent(&power_dev.this_device->kobj, KOBJ_ADD); + break; + }; +} + +static void udc_pnp_detect(void) +{ + if (cur_pnp_stat.cable_stat == YES_CONNECT) /* already connected! */ + { + if (udc_get_pnp_stat() == NOT_CONNECT) + { + dprintk("cable real out! \n"); + cur_pnp_stat.cable_stat = NOT_CONNECT; + cur_pnp_stat.protl_stat = NOT_CONNECT; + /* Deliver this event to user space in udev model */ + if (cur_pnp_stat.old_protl_stat) + send_event_udev(EVENT_USB_REMOVE); + else + send_event_udev(EVENT_POWER_REMOVE); + cur_pnp_stat.old_cable_stat = cur_pnp_stat.cable_stat; + cur_pnp_stat.old_protl_stat = cur_pnp_stat.protl_stat; + } + else + { + if (cur_pnp_stat.old_protl_stat != cur_pnp_stat.protl_stat) + { + send_event_udev(EVENT_USB_SUSPEND_POWER); + cur_pnp_stat.old_cable_stat = cur_pnp_stat.cable_stat; + cur_pnp_stat.old_protl_stat = cur_pnp_stat.protl_stat; + } + else //change power to cable + { +#if 0 //not support yet! + udc_get_cable(); + if (cur_pnp_stat.old_protl_stat != cur_pnp_stat.protl_stat) + send_event_udev(EVENT_POWER_TO_USB); + cur_pnp_stat.old_cable_stat = cur_pnp_stat.cable_stat; + cur_pnp_stat.old_protl_stat = cur_pnp_stat.protl_stat; +#endif + } + } + } + else + { + if (udc_get_pnp_stat() == YES_CONNECT) + { + dprintk("cable real in! \n"); + cur_pnp_stat.cable_stat = YES_CONNECT; + udc_get_cable(); + /* Deliver this event to user space in udev model */ + if (cur_pnp_stat.protl_stat) + send_event_udev(EVENT_USB_ADD); + else + send_event_udev(EVENT_POWER_ADD); + cur_pnp_stat.old_cable_stat = cur_pnp_stat.cable_stat; + cur_pnp_stat.old_protl_stat = cur_pnp_stat.protl_stat; + } + else + dprintk("cable false in! \n"); + + } +} + +static void udc_pnp_set_gpio(void) +{ + if (cur_pnp_stat.cable_stat == YES_CONNECT) + __gpio_as_irq_fall_edge(UDC_HOTPLUG_PIN); + else + __gpio_as_irq_rise_edge(UDC_HOTPLUG_PIN); + + /* clear interrupt pending status */ + __gpio_ack_irq(UDC_HOTPLUG_PIN); + /* unmask interrupt */ + __gpio_unmask_irq(UDC_HOTPLUG_PIN); +} + +static int udc_pnp_thread(void *unused) +{ + printk(KERN_NOTICE "UDC starting pnp monitor thread\n"); + + while(1) + { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + + dprintk("pnp thread wake up! \n"); + /* wake up here */ + udc_pnp_detect(); + /* Reset gpio state last */ + udc_pnp_set_gpio(); + } +} + +static irqreturn_t udc_pnp_irq(int irq, void *dev_id) +{ + + /* clear interrupt pending status */ + __gpio_ack_irq(UDC_HOTPLUG_PIN); + /* mask interrupt */ + __gpio_mask_irq(UDC_HOTPLUG_PIN); + /* wake up pnp detect thread */ + wake_up_process(kudcd_task); + + return IRQ_HANDLED; +} + +/* + * Module init and exit + */ +static int __init udc_hotplug_init(void) +{ + int retval; + /* Init pnp stat first */ + cur_pnp_stat.cable_stat = NOT_CONNECT; + cur_pnp_stat.protl_stat = NOT_CONNECT; + cur_pnp_stat.old_cable_stat = NOT_CONNECT; + cur_pnp_stat.old_protl_stat = NOT_CONNECT; + cur_pnp_stat.object_stat1 = NOT_CONNECT; + cur_pnp_stat.object_stat2 = NOT_CONNECT; + udc_old_state = 0; + + /* create pnp thread and register IRQ */ + kudcd_task = kthread_run(udc_pnp_thread, NULL, "kudcd"); + if (IS_ERR(kudcd_task)) { + printk(KERN_ERR "jz_udc_hotplug: Failed to create system monitor thread.\n"); + return PTR_ERR(kudcd_task); + } + + retval = request_irq(UDC_HOTPLUG_IRQ, udc_pnp_irq, + IRQF_DISABLED, "udc_pnp", NULL); + if (retval) { + printk("Could not get udc hotplug irq %d\n", UDC_HOTPLUG_IRQ); + return retval; + } + + /* get current pin level */ + __gpio_disable_pull(UDC_HOTPLUG_PIN); + __gpio_as_input(UDC_HOTPLUG_PIN); + udelay(1); + udc_pin_level = __gpio_get_pin(UDC_HOTPLUG_PIN); + + if (udc_pin_level) { + dprintk("Cable already in! \n"); + /* Post a event */ + wake_up_process(kudcd_task); + } + else { + __gpio_as_irq_rise_edge(UDC_HOTPLUG_PIN); + dprintk("Cable not in! \n"); + } + + printk("JZ UDC hotplug driver registered\n"); + + return 0; +} + +static void __exit udc_hotplug_exit(void) +{ + free_irq(UDC_HOTPLUG_IRQ, NULL); +} + +module_init(udc_hotplug_init); +module_exit(udc_hotplug_exit); + +EXPORT_SYMBOL(jz_udc_active); + +MODULE_AUTHOR("Lucifer "); +MODULE_DESCRIPTION("JzSOC OnChip udc hotplug driver"); +MODULE_LICENSE("GPL"); --- /dev/null +++ b/drivers/char/jzchar/wm9712.c @@ -0,0 +1,334 @@ +/* + * wm9712.c + * + * Touch screen driver interface to the Wolfson WM9712 codec. + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "jz_ts.h" +#include "wm9712.h" + +#define POLL_TIMES 10 + +static int samples = 1; +static int inited = 0, started = 0; + +extern struct ac97_codec * find_ac97_codec(void); +extern int PenIsDown(void); + + +static inline void wm9712_reg_write(unsigned int reg, unsigned int val) +{ + struct ac97_codec *codec = find_ac97_codec(); + if (!codec) + return; + codec->codec_write(codec, reg, val); +} + +static inline unsigned int wm9712_reg_read(unsigned int reg) +{ + struct ac97_codec *codec = find_ac97_codec(); + if (!codec) + return 0; + return codec->codec_read(codec, reg); +} + +static unsigned int wm9712_adc_read(int adc_channel) +{ + unsigned int val; + + if (!PenIsDown()) + return 0; + + val = wm9712_reg_read(DIGI_REG1); + wm9712_reg_write(DIGI_REG1, val|adc_channel|DIGI_REG1_POLL); + + for (;;) { + if (wm9712_reg_read(0x54) & (1 << 12)) { + val = wm9712_reg_read(DIGI_READBACK); + break; + } + } + + /* stop the measure */ + wm9712_reg_write(DIGI_REG1, 0); + + return (val & 0x0fff); +} + +static struct timer_list pndn_timer; +static void (*irq_handler)(int, void *, struct pt_regs *) = NULL; + +void ts_irq_callback(void) +{ +#ifdef TS_IRQ + __gpio_ack_irq(TS_IRQ); +#else +#endif +} + +void ts_enable_irq(void) +{ + if (!inited) + return; +#ifdef TS_IRQ + enable_irq(TS_IRQ); +#else + pndn_timer.expires = jiffies + HZ/POLL_TIMES; + add_timer(&pndn_timer); +#endif +} + +void ts_disable_irq(void) +{ + if (!inited) + return; +#ifdef TS_IRQ + disable_irq(TS_IRQ); +#endif +} + +#ifndef TS_IRQ +static void pndn_detect(unsigned long data) +{ + if (PenIsDown()) { + if (!started) + return; + if (irq_handler) + irq_handler(NULL, data, NULL); + } else { + pndn_timer.expires = jiffies + HZ/POLL_TIMES; + add_timer(&pndn_timer); + } +} +#endif + +void ts_free_irq(struct jz_ts_t *ts) +{ +#ifdef TS_IRQ + free_irq(ts->pendown_irq, ts); +#else + started = 0; + del_timer_sync(&pndn_timer); +#endif +} + +int ts_request_irq(u32 *irq, + void (*handler)(int, void *, struct pt_regs *), + const char *devname, + void *dev_id) +{ + /* 4wire, Ip=400uA, Rpu=64Kohm/64, wake-up on pendown without + * reset, meassure on pen down. Do not use wait mode. + */ + started = 1; + if (!inited) { + wm9712_reg_write(DIGI_REG2, + DIGI_REG2_WIRE_4 | + DIGI_REG2_PIL_200uA | + (31 << DIGI_REG2_RPU_BIT) | + DIGI_REG2_PRP_ALLON | + DIGI_REG2_RPR_NWOR); + /* Polling mode and no measurement */ + wm9712_reg_write(DIGI_REG1, 0); + } + +#ifdef TS_IRQ + /* Generate irq request on PENDOWN pin, pendown cause the level high */ + wm9712_reg_write(0x56, wm9712_reg_read(0x56) & ~(1 << 3)); + wm9712_reg_write(0x4c, wm9712_reg_read(0x4c) & ~(1 << 3)); + + *irq = TS_IRQ; + return request_irq(TS_IRQ, handler, SA_INTERRUPT, devname, dev_id); +#else + if (!inited) { + irq_handler = handler; + init_timer(&pndn_timer); + pndn_timer.function = pndn_detect; + pndn_timer.data = (unsigned long)dev_id; + pndn_timer.expires = jiffies + HZ/POLL_TIMES; + add_timer(&pndn_timer); + inited = 1; + } else { + pndn_timer.expires = jiffies + HZ/POLL_TIMES; + add_timer(&pndn_timer); + } + return 0; +#endif +} + +int PenIsDown(void) +{ + if (wm9712_reg_read(DIGI_READBACK) & DIGI_READBACK_PNDN) + return 1; + return 0; +} + +#if defined(CONFIG_MIPS_JZ4730_GPS) +#define adj_data(r1, r2, r3, s) \ +do { \ + if (r1 < 0x90) \ + r1 = 0x90; \ + if (r2 < 0xed) \ + r2 = 0xed; \ + r1 = ((r1 - 0x90) * 240) / 3354; \ + r2 = ((r2 - 0xed) * 320) / 3671; \ + if (r1 > 239) \ + r1 = 239; \ + if (r2 > 319) \ + r2 = 319; \ + \ + *s = r2; \ + *(s+1) = 239 - r1; \ + *(s+2) = z_raw; \ +} while (0) +#endif + +#ifndef adj_data +#define adj_data(r1, r2, r3, s) +#endif + +static int read_adc(unsigned int *sdata) +{ + unsigned long x_raw=0, y_raw=0, z_raw=0, t, fail = 0; + int i; + + for (i=0; i 1) { + x_raw = (x_raw + (samples>>1)) / samples; + y_raw = (y_raw + (samples>>1)) / samples; + z_raw = (z_raw + (samples>>1)) / samples; + } + + adj_data (x_raw, y_raw, z_raw, sdata); + + return 1; +} + + +#define TSMAXX 945 +#define TSMAXY 830 +#define TSMINX 90 +#define TSMINY 105 + +#define SCREEN_X 480 +#define SCREEN_Y 272 + +static unsigned long transform_to_screen_x(struct jz_ts_t *ts, unsigned long x ) +{ + + if (ts->minx) + { + if (x < ts->minx) x = ts->minx; + if (x > ts->maxx) x = ts->maxx; + + return (x - ts->minx) * SCREEN_X / (ts->maxx - ts->minx); + } + else + { + if (x < TSMINX) x = TSMINX; + if (x > TSMAXX) x = TSMAXX; + + return (x - TSMINX) * SCREEN_X / (TSMAXX - TSMINX); + } +} + +static unsigned long transform_to_screen_y(struct jz_ts_t *ts, unsigned long y) +{ + if (ts->minx) + { + if (y < ts->minx) y = ts->miny; + if (y > ts->maxx) y = ts->maxy; + + return (y - ts->miny) * SCREEN_Y / (ts->maxy - ts->miny); + } + else + { + if (y < TSMINX) y = TSMINY; + if (y > TSMAXX) y = TSMAXY; + + return (y - TSMINY) * SCREEN_Y / (TSMAXY - TSMINY); + } +} + + +/* + * Acquire Raw pen coodinate data and compute touch screen + * pressure resistance. Hold spinlock when calling. + */ +int AcquireEvent(struct jz_ts_t *ts, struct ts_event *event) +{ + unsigned int s[3]; + unsigned int x_scr, y_scr; + if (!read_adc(s)) + return 0; + if(ts->filter) { + x_scr = transform_to_screen_x(ts, s[0]); + y_scr = transform_to_screen_y(ts, s[1]); + + if (ts->prints) + printk("x_raw=%d y_raw=%d x_transform=%d y_transform=%d\n", s[0], s[1], x_scr, y_scr); } + else { + x_scr = s[0]; + y_scr = s[1]; + + if (ts->prints) + printk("x_raw=%d y_raw=%d \n", s[0], s[1]); + } + event->x = x_scr; + event->y = y_scr; + event->pressure = (u16)s[2]; + event->status = PENDOWN; + return 1; +#if 0 + do_gettimeofday(&event->stamp); +#endif +} + +int __init wm9712_init(void) +{ + return 0; +} + +void wm9712_cleanup(void) +{ +} + +module_init(wm9712_init); +module_exit(wm9712_cleanup); + --- /dev/null +++ b/drivers/char/jzchar/wm9712.h @@ -0,0 +1,58 @@ +#ifndef __WM9712_H__ +#define __WM9712_H__ + +#define DIGI_REG1 0x76 +#define DIGI_REG2 0x78 +#define DIGI_READBACK 0x7A + +#define ADCSEL_BIT 12 +#define ADCSEL_MASK (7 << ADCSEL_BIT) +#define ADCSEL_NONE (0 << ADCSEL_BIT) +#define ADCSEL_XPOS (1 << ADCSEL_BIT) +#define ADCSEL_YPOS (2 << ADCSEL_BIT) +#define ADCSEL_PRESSURE (3 << ADCSEL_BIT) +#define ADCSEL_COMP1 (4 << ADCSEL_BIT) +#define ADCSEL_COMP2 (5 << ADCSEL_BIT) +#define ADCSEL_BMON (6 << ADCSEL_BIT) +#define ADCSEL_WIPER (7 << ADCSEL_BIT) + +#define DIGI_REG1_CTC (1 << 10) +#define DIGI_REG1_POLL (1 << 15) +#define DIGI_REG1_CR_BIT 8 +#define DIGI_REG1_CR_MASK (3 << DIGI_REG1_CR_BIT) +#define DIGI_REG1_COO (1 << 11) +#define DIGI_REG1_SLEN (1 << 3) +#define DIGI_REG1_SLT_BIT 0 +#define DIGI_REG1_SLT_MASK (7 << DIGI_REG1_SLT_BIT) +#define DIGI_REG1_SLT_5 (0 << DIGI_REG1_SLT_BIT) +#define DIGI_REG1_SLT_6 (1 << DIGI_REG1_SLT_BIT) +#define DIGI_REG1_SLT_7 (2 << DIGI_REG1_SLT_BIT) +#define DIGI_REG1_SLT_8 (3 << DIGI_REG1_SLT_BIT) +#define DIGI_REG1_SLT_9 (4 << DIGI_REG1_SLT_BIT) +#define DIGI_REG1_SLT_10 (5 << DIGI_REG1_SLT_BIT) +#define DIGI_REG1_SLT_11 (6 << DIGI_REG1_SLT_BIT) +#define DIGI_REG1_SLT_RES (7 << DIGI_REG1_SLT_BIT) +#define DIGI_REG1_DEL_BIT 4 +#define DIGI_REG1_DEL_MASK (0x0f << DIGI_REG1_DEL_BIT) + +#define DIGI_REG2_WIRE_5 (1 << 12) +#define DIGI_REG2_WIRE_4 (0 << 12) +#define DIGI_REG2_RPU_BIT 0 +#define DIGI_REG2_RPU_MASK (0x3f << DIGI_REG2_RPU_BIT) +#define DIGI_REG2_PIL_400uA (1 << 8) +#define DIGI_REG2_PIL_200uA (0 << 8) +#define DIGI_REG2_PRP_BIT 14 +#define DIGI_REG2_PRP_MASK (3 << DIGI_REG2_PRP_BIT) +#define DIGI_REG2_PRP_ALLOFF (0 << DIGI_REG2_PRP_BIT) +#define DIGI_REG2_PRP_WOP (1 << DIGI_REG2_PRP_BIT) +#define DIGI_REG2_PRP_NWOP (2 << DIGI_REG2_PRP_BIT) +#define DIGI_REG2_PRP_ALLON (3 << DIGI_REG2_PRP_BIT) +#define DIGI_REG2_RPR_WOR (0 << 13) +#define DIGI_REG2_RPR_NWOR (1 << 13) +#define DIGI_REG2_PDEN (1 << 11) +#define DIGI_REG2_WAIT (1 << 9) + +#define DIGI_READBACK_PNDN (1 << 15) + +#endif /* __WM9712_H__ */ + --- /dev/null +++ b/drivers/char/rtc_jz.c @@ -0,0 +1,503 @@ +/* + * Jz OnChip Real Time Clock interface for Linux + * + * NOTE: we need to wait rtc write ready before read or write RTC registers. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* get the user-level API */ +#include +#include + +#include "rtc_jz.h" + + +char sbin_rtc_alarm_handler_path[] = "/sbin/rtcalarm"; +//call_usermodehelper(char *path, char **argv, char **envp, int wait) +//extern int call_usermodehelper(char *path, char **argv, char **envp); + +extern spinlock_t rtc_lock; + +static int rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); + + +static void get_rtc_time (struct rtc_time *rtc_tm); +static int set_rtc_time (struct rtc_time *rtc_tm); +static void get_rtc_alm_time (struct rtc_time *alm_tm); +static int set_rtc_alm_time (struct rtc_time *alm_tm); + +static void set_rtc_irq_bit(int bit); +static void mask_rtc_irq_bit(int bit); + +static unsigned int rtc_status = 0; +static unsigned int epoch = 1900; + +static void get_rtc_time(struct rtc_time *rtc_tm) +{ + unsigned long lval; + struct rtc_time ltm; + + spin_lock_irq(&rtc_lock); + while ( !__rtc_write_ready() ) ; + lval = REG_RTC_RSR; + rtc_time_to_tm(lval, <m); + if(rtc_valid_tm(<m) == 0) { + /* is valid */ + rtc_tm->tm_sec = ltm.tm_sec; + rtc_tm->tm_min = ltm.tm_min; + rtc_tm->tm_hour = ltm.tm_hour; + rtc_tm->tm_mday = ltm.tm_mday; + rtc_tm->tm_wday = ltm.tm_wday; + rtc_tm->tm_mon = ltm.tm_mon; + rtc_tm->tm_year = ltm.tm_year; + } else { + printk("invlaid data / time!\n"); + } + spin_unlock_irq(&rtc_lock); +} + +static int set_rtc_time(struct rtc_time *rtc_tm) +{ + unsigned long lval; + + rtc_tm_to_time(rtc_tm, &lval); + + spin_lock_irq(&rtc_lock); + while ( !__rtc_write_ready() ) ; + REG_RTC_RSR = lval; + + spin_unlock_irq(&rtc_lock); + + return 0; + +} + +static void get_rtc_alm_time(struct rtc_time *alm_tm) +{ + unsigned long lval; + struct rtc_time altm; + + spin_lock_irq(&rtc_lock); + while ( !__rtc_write_ready() ) ; + lval = REG_RTC_RSAR; + rtc_time_to_tm(lval, &altm); + if(rtc_valid_tm(&altm) == 0) { + /* is valid */ + alm_tm->tm_sec = altm.tm_sec; + alm_tm->tm_min = altm.tm_min; + alm_tm->tm_hour = altm.tm_hour; + alm_tm->tm_mday = altm.tm_mday; + alm_tm->tm_wday = altm.tm_wday; + alm_tm->tm_mon = altm.tm_mon; + alm_tm->tm_year = altm.tm_year; + } else { + printk("invlaid data / time in Line:%d!\n",__LINE__); + } + spin_unlock_irq(&rtc_lock); +} + +static int set_rtc_alm_time(struct rtc_time *alm_tm) +{ + unsigned long lval; + + rtc_tm_to_time(alm_tm, &lval); + + spin_lock_irq(&rtc_lock); + while ( !__rtc_write_ready() ) ; + REG_RTC_RSAR = lval; + + while ( !__rtc_write_ready() ) ; /* set alarm function */ + if ( !((REG_RTC_RCR>>2) & 0x1) ) { + while ( !__rtc_write_ready() ) ; + __rtc_enable_alarm(); + } + + while ( !__rtc_write_ready() ) ; + if ( !(REG_RTC_RCR & RTC_RCR_AIE) ) { /* Enable alarm irq */ + __rtc_enable_alarm_irq(); + } + + spin_unlock_irq(&rtc_lock); + + return 0; +} + +static void get_rtc_wakeup_alarm(struct rtc_wkalrm *wkalm) +{ + int enabled, pending; + + get_rtc_alm_time(&wkalm->time); + + spin_lock_irq(&rtc_lock); + while ( !__rtc_write_ready() ) ; + enabled = (REG_RTC_HWCR & 0x1); + pending = 0; + if ( enabled ) { + if ( (u32)REG_RTC_RSAR > (u32)REG_RTC_RSR ) /* 32bit val */ + pending = 1; + } + + wkalm->enabled = enabled; + wkalm->pending = pending; + spin_unlock_irq(&rtc_lock); +} + +static int set_rtc_wakeup_alarm(struct rtc_wkalrm *wkalm) +{ + int enabled; + //int pending; + + enabled = wkalm->enabled; + //pending = wkalm->pending; /* Fix me, what's pending mean??? */ + + while ( !__rtc_write_ready() ) ; /* set wakeup alarm enable */ + if ( enabled != (REG_RTC_HWCR & 0x1) ) { + while ( !__rtc_write_ready() ) ; + REG_RTC_HWCR = (REG_RTC_HWCR & ~0x1) | enabled; + } + while ( !__rtc_write_ready() ) ; /* set alarm function */ + if ( enabled != ((REG_RTC_RCR>>2) & 0x1) ) { + while ( !__rtc_write_ready() ) ; + REG_RTC_RCR = (REG_RTC_RCR & ~(1<<2)) | (enabled<<2); + } + + if ( !enabled ) /* if disabled wkalrm, rturn. */ + { + return 0; + } + + while ( !__rtc_write_ready() ) ; + if ( !(REG_RTC_RCR & RTC_RCR_AIE) ) { /* Enable alarm irq */ + __rtc_enable_alarm_irq(); + } + + set_rtc_alm_time(&wkalm->time); + + return 0; +} + + +static void set_rtc_irq_bit( int bit ) +{ + spin_lock_irq(&rtc_lock); + + while ( !__rtc_write_ready() ) ; + REG_RTC_RCR |= (1<= 0xc0 + * means "don't care" or "match all". Only the tm_hour, + * tm_min, and tm_sec values are filled in. + */ + + get_rtc_alm_time(&wtime); + return copy_to_user((void *)arg, &wtime, sizeof wtime) ? -EFAULT : 0; + + case RTC_ALM_SET: /* Store a time into the alarm */ + { + struct rtc_time alm_tm; + + if (copy_from_user(&alm_tm, (struct rtc_time*)arg, + sizeof(struct rtc_time))) + return -EFAULT; + if(rtc_valid_tm(&alm_tm) != 0) { + printk("invalid time set in Line:%d! \n",__LINE__); + return -EFAULT; + } + + return set_rtc_alm_time(&alm_tm); + } + case RTC_RD_TIME: /* Read the time/date from RTC */ + get_rtc_time(&wtime); + return copy_to_user((void *)arg, &wtime, sizeof wtime) ? -EFAULT : 0; + case RTC_SET_TIME: /* Set the RTC */ + { + struct rtc_time rtc_tm; + + if (!capable(CAP_SYS_TIME)) + return -EACCES; + + if (copy_from_user(&rtc_tm, (struct rtc_time*)arg, + sizeof(struct rtc_time))) + return -EFAULT; + if(rtc_valid_tm(&rtc_tm) != 0) { + printk("invalid time set in Line:%d! \n",__LINE__); + return -EFAULT; + } + + return set_rtc_time(&rtc_tm); + } + case RTC_EPOCH_READ: /* Read the epoch. */ + return put_user (epoch, (unsigned long *)arg); + case RTC_EPOCH_SET: /* Set the epoch. */ + /* + * There were no RTC clocks before 1900. + */ + if (arg < 1900) + return -EINVAL; + + if (!capable(CAP_SYS_TIME)) + return -EACCES; + + epoch = arg; + return 0; + case RTC_WKALM_SET: /* Wake alarm set. */ + { + struct rtc_wkalrm wkalrm; + + if (copy_from_user(&wkalrm, (struct rtc_wkalrm*)arg, + sizeof(struct rtc_wkalrm))) + return -EFAULT; + return set_rtc_wakeup_alarm(&wkalrm); + } + case RTC_WKALM_RD: /* Wake alarm read. */ + { + struct rtc_wkalrm wkalrm; + get_rtc_wakeup_alarm(&wkalrm); + return copy_to_user((void *)arg, &wkalrm, sizeof(struct rtc_wkalrm)) ? -EFAULT : 0; + } + /* set power down: shut down the machine. */ + case RTC_POWER_DOWN: /* enter HIBERNATE mode */ + dprintk("Power down. Bye....\n"); + while ( !__rtc_write_ready() ) ; + REG_RTC_HCR = 0x1; + return 0; +#ifdef DEBUG + case RTC_PRINT_REG: /* Print RTC registers */ + print_rtc_registers(); + return 0; +#endif + default: + return -EINVAL; + } + return 0; +} + +/* + * We enforce only one user at a time here with the open/close. + * Also clear the previous interrupt data on an open, and clean + * up things on a close. + */ + +/* We use rtc_lock to protect against concurrent opens. So the BKL is not + * needed here. Or anywhere else in this driver. */ +static int rtc_open(struct inode *inode, struct file *file) +{ + spin_lock_irq (&rtc_lock); + + if(rtc_status) + goto out_busy; + + rtc_status = 1; + + spin_unlock_irq (&rtc_lock); + return 0; + +out_busy: + return -EBUSY; +} + +static int rtc_release(struct inode *inode, struct file *file) +{ + + rtc_status = 0; + /* No need for locking -- nobody else can do anything until this rmw is + * committed, and no timer is running. */ + return 0; +} + +/* + * The various file operations we support. + */ + +static struct file_operations rtc_fops = { + owner: THIS_MODULE, + llseek: no_llseek, + ioctl: rtc_ioctl, + open: rtc_open, + release: rtc_release, +}; + + +static void run_sbin_rtc_alarm( void ) +{ + int i; + char *argv[2], *envp[3]; + + if (!sbin_rtc_alarm_handler_path[0]) + return; + + print_dbg(": sbin_rtc_alarm_handler_path=%s\n", sbin_rtc_alarm_handler_path); + + i = 0; + argv[i++] = sbin_rtc_alarm_handler_path; + argv[i] = 0; + + /* minimal command environment */ + i = 0; + envp[i++] = "HOME=/"; + envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + + /* other stuff we want to pass to /sbin/hotplug */ + + envp[i] = 0; + + call_usermodehelper (argv [0], argv, envp, 0); +} + +static void rtc_alarm_task_handler(struct work_struct *work) +{ + run_sbin_rtc_alarm(); +} + +static struct work_struct rtc_alarm_task; + +static irqreturn_t jz_rtc_interrupt(int irq, void *dev_id) +{ + REG_RTC_HCR = 0x0; + printk("%s:%s:%d\n",__FILE__,__FUNCTION__,__LINE__); + spin_lock_irq(&rtc_lock); + + if ( __rtc_get_1Hz_flag() ) { + while ( !__rtc_write_ready() ) ; + __rtc_clear_1Hz_flag(); + dprintk("RTC 1Hz interrupt occur.\n"); + } + + if ( __rtc_get_alarm_flag() ) { /* rtc alarm interrupt */ + while ( !__rtc_write_ready() ) ; + __rtc_clear_alarm_flag(); + dprintk("RTC alarm interrupt occur.\n"); + //schedule_task( &rtc_alarm_task ); + schedule_work( &rtc_alarm_task ); + } + spin_unlock_irq(&rtc_lock); + + return IRQ_HANDLED; +} + + +#define RTC_MINOR 135 + +static struct miscdevice rtc_dev= +{ + RTC_MINOR, + "rtc", + &rtc_fops +}; + +int __init Jz_rtc_init(void) +{ + + INIT_WORK(&rtc_alarm_task, rtc_alarm_task_handler); + + /* Enabled rtc function, enable rtc alarm function */ + while ( !__rtc_write_ready() ) ; /* need we wait for WRDY??? */ + if ( !(REG_RTC_RCR & RTC_RCR_RTCE) || !(REG_RTC_RCR &RTC_RCR_AE) ) { + REG_RTC_RCR |= RTC_RCR_AE | RTC_RCR_RTCE; + } + /* clear irq flags */ + __rtc_clear_1Hz_flag(); + /* In a alarm reset, we expect a alarm interrupt. + * We can do something in the interrupt handler. + * So, do not clear alarm flag. + */ +/* __rtc_clear_alarm_flag(); */ + + if (request_irq(IRQ_RTC, jz_rtc_interrupt, 0, "rtc", NULL) < 0) + return -EBUSY; + + misc_register(&rtc_dev); + + printk("JzSOC onchip RTC installed !!!\n"); + return 0; + +} + +void __exit Jz_rtc_exit (void) +{ + misc_deregister(&rtc_dev); + free_irq (IRQ_RTC, NULL); +} + +module_init(Jz_rtc_init); +module_exit(Jz_rtc_exit); + --- /dev/null +++ b/drivers/char/rtc_jz.h @@ -0,0 +1,74 @@ +#ifndef __RTC_JZ_H__ +#define __RTC_JZ_H__ + +//#define DEBUG 1 +#undef DEBUG + +#ifdef DEBUG +#define dprintk(x...) printk(x) +#define print_dbg(f, arg...) \ + printk("%s, %s[%d]:" f , __FUNCTION__, __FILE__, __LINE__ , ##arg ) +#else +#define dprintk(x...) +#define print_dbg(n, arg...) +#endif + + +#ifdef DEBUG + +static void print_rtc_time( struct rtc_time * tm ) +{ + printk("%02d%02d-%02d:%02d:%02d-%d\n", tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_year); + printk("sec:\t%d\n", tm->tm_sec); + printk("min:\t%d\n", tm->tm_min); + printk("hour:\t%d\n", tm->tm_hour); + printk("mday:\t%d\n", tm->tm_mday); + printk("mon:\t%d\n", tm->tm_mon); + printk("year:\t%d\n", tm->tm_year); + printk("wday:\t%d\n", tm->tm_wday); + printk("yday:\t%d\n", tm->tm_yday); + printk("isdst:\t%d\n", tm->tm_isdst); + +} + +static void print_rtc_registers( void ) +{ + while ( !__rtc_write_ready() ) ; + printk("REG_RTC_RCR:\t 0x%8.8x\n", REG_RTC_RCR ); + printk("REG_RTC_RSR:\t 0x%8.8x\n", REG_RTC_RSR ); + printk("REG_RTC_RSAR:\t 0x%8.8x\n", REG_RTC_RSAR ); + printk("REG_RTC_RGR:\t 0x%8.8x\n", REG_RTC_RGR ); + printk("REG_RTC_HCR:\t 0x%8.8x\n", REG_RTC_HCR ); + printk("REG_RTC_HWFCR:\t 0x%8.8x\n", REG_RTC_HWFCR ); + printk("REG_RTC_HRCR:\t 0x%8.8x\n", REG_RTC_HRCR ); + printk("REG_RTC_HWCR:\t 0x%8.8x\n", REG_RTC_HWCR ); + printk("REG_RTC_HWRSR:\t 0x%8.8x\n", REG_RTC_HWRSR ); + printk("REG_RTC_HSPR:\t 0x%8.8x\n", REG_RTC_HSPR ); +} + +#define RTC_PRINT_REG _IOR('p', 0x12, unsigned long)/* Set power down */ +#endif /* #ifdef DEBUG */ + + +/* + * JZSOC ioctl calls that are permitted to the /dev/rtc interface + */ + +#define RTC_ENABLED _IO('p', 0x11) /* enable rtc */ +#define RTC_DISABLED _IO('p', 0x12) /* disable rtc */ +#define RTC_ALM_ON _IO('p', 0x13) /* enable rtc */ +#define RTC_ALM_OFF _IO('p', 0x14) /* disable rtc */ +#define RTC_1HZIE_ON _IO('p', 0x15) /* 1Hz int. enable on */ +#define RTC_1HZIE_OFF _IO('p', 0x16) /* ... off */ + +#define RTC_POWER_DOWN _IOR('p', 0x11, unsigned long)/* Set power down */ + +/* Registers define */ +/* RTC Control register */ +#define RTC_AIE 3 /* jz4740_06_rtc_spec.pdf, RTC Control Register */ +#define RTC_1HZIE 5 /* ... */ +#define RTC_ALM_EN 2 /* ... */ +#define RTC_EN 0 /* ... */ + +#endif /* #define __RTC_JZ_H__ */ --- /dev/null +++ b/drivers/char/rtc_pcf8563.c @@ -0,0 +1,448 @@ +/* + * PCF8563 Real Time Clock interface for Linux + * + * It only support 24Hour Mode, And the stored values are in BCD format. + * The alarm register is start at minute reg, no second alarm register. + */ + +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* get the user-level API */ +#include +#include + +/********************************************************************** + * register summary + **********************************************************************/ +#define RTC_SECONDS 2 +#define RTC_MINUTES 3 +#define RTC_HOURS 4 +#define RTC_DAY_OF_MONTH 5 +#define RTC_DAY_OF_WEEK 6 +#define RTC_MONTH 7 +#define RTC_YEAR 8 + +#define RTC_MINUTES_ALARM 9 +#define RTC_HOURS_ALARM 0x0a +#define RTC_DAY_ALARM 0x0b +#define RTC_WEEKDAY_ALARM 0x0c + +/* control registers - Moto names + */ +#define RTC_CONTROL 0x00 +#define RTC_STATUS 0x01 +#define RTC_CLKOUT 0x0d +#define RTC_TIMERCTL 0x0e +#define RTC_TIMERCOUNTDOWN 0x0f + + +/* example: !(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) + * determines if the following two #defines are needed + */ +#ifndef BCD2BIN +#define BCD2BIN(val) (((val) & 0x0f) + ((val) >> 4) * 10) +#endif + +#ifndef BIN2BCD +#define BIN2BCD(val) ((((val) / 10) << 4) + (val) % 10) +#endif + +extern spinlock_t rtc_lock; +extern void i2c_open(void); +extern void i2c_close(void); +extern int i2c_read(unsigned char device, unsigned char *buf, + unsigned char address, int count); +extern int i2c_write(unsigned char device, unsigned char *buf, + unsigned char address, int count); +/* + * We sponge a minor off of the misc major. No need slurping + * up another valuable major dev number for this. If you add + * an ioctl, make sure you don't conflict with SPARC's RTC + * ioctls. + */ + +static int rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); + + +static void get_rtc_time (struct rtc_time *rtc_tm); +static void get_rtc_alm_time (struct rtc_time *alm_tm); + +/* + * rtc_status is never changed by rtc_interrupt, and ioctl/open/close is + * protected by the big kernel lock. However, ioctl can still disable the timer + * in rtc_status and then with del_timer after the interrupt has read + * rtc_status but before mod_timer is called, which would then reenable the + * timer (but you would need to have an awful timing before you'd trip on it) + */ +static unsigned long rtc_status = 0; /* bitmapped status byte. */ + +/* + * If this driver ever becomes modularised, it will be really nice + * to make the epoch retain its value across module reload... + */ +static unsigned int epoch = 1900; +static const unsigned char days_in_mo[] = +{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +static unsigned char rtcframe[16]; + +static void read_rtcframe(void) +{ + i2c_open(); + i2c_read(0x51, rtcframe, 0, 16); + i2c_close(); +} + +static void write_rtcframe(void) +{ + i2c_open(); + i2c_write(0x51, rtcframe, 0, 16); + i2c_close(); +} + +static void write_rtc(unsigned char addr, unsigned char val) +{ + volatile unsigned char v = val; + i2c_open(); + i2c_write(0x51, (unsigned char *)&v, addr, 1); + i2c_close(); +} + +static unsigned char read_rtc(unsigned char addr) +{ + volatile unsigned char v; + i2c_open(); + i2c_read(0x51, (unsigned char *)&v, addr, 1); + i2c_close(); + return v; +} + +static void CMOS_WRITE(unsigned char addr, unsigned char val) +{ + rtcframe[addr] = val; +} + +static unsigned char CMOS_READ(unsigned char addr) +{ + return rtcframe[addr]; +} + +static void get_rtc_time(struct rtc_time *rtc_tm) +{ + unsigned char sec,mon,mday,wday,year,hour,min; + + /* + * Only the values that we read from the RTC are set. We leave + * tm_wday, tm_yday and tm_isdst untouched. Even though the + * RTC has RTC_DAY_OF_WEEK, we ignore it, as it is only updated + * by the RTC when initially set to a non-zero value. + */ + + spin_lock_irq(&rtc_lock); + read_rtcframe(); + sec = CMOS_READ(RTC_SECONDS) & ~0x80; + min = CMOS_READ(RTC_MINUTES) & ~0x80; + hour = CMOS_READ(RTC_HOURS) & ~0xc0; + mday = CMOS_READ(RTC_DAY_OF_MONTH) & ~0xc0; + wday = CMOS_READ(RTC_DAY_OF_WEEK) & ~0xf8; + mon = CMOS_READ(RTC_MONTH) & ~0xe0; + year = CMOS_READ(RTC_YEAR) ; + + rtc_tm->tm_sec = BCD2BIN(sec); + rtc_tm->tm_min = BCD2BIN(min); + rtc_tm->tm_hour = BCD2BIN(hour); + rtc_tm->tm_mday = BCD2BIN(mday); + rtc_tm->tm_wday = wday; + /* Don't use centry, but start from year 1970 */ + rtc_tm->tm_mon = BCD2BIN(mon); + year = BCD2BIN(year); + if ((year += (epoch - 1900)) <= 69) + year += 100; + rtc_tm->tm_year = year; + + spin_unlock_irq(&rtc_lock); + + + /* + * Account for differences between how the RTC uses the values + * and how they are defined in a struct rtc_time; + */ + rtc_tm->tm_mon--; +} + +static void get_rtc_alm_time(struct rtc_time *alm_tm) +{ + unsigned char sec, min, hour; + + /* + * Only the values that we read from the RTC are set. That + * means only tm_hour, tm_min, and tm_sec. + */ + spin_lock_irq(&rtc_lock); + read_rtcframe(); + sec = 0; + min = CMOS_READ(RTC_MINUTES_ALARM); + hour = CMOS_READ(RTC_HOURS_ALARM); + + alm_tm->tm_sec = sec;//not set sec + alm_tm->tm_min = BCD2BIN(min); + alm_tm->tm_hour = BCD2BIN(hour); + + spin_unlock_irq(&rtc_lock); +} + +static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct rtc_time wtime; + switch (cmd) { + case RTC_ALM_READ: /* Read the present alarm time */ + { + /* + * This returns a struct rtc_time. Reading >= 0xc0 + * means "don't care" or "match all". Only the tm_hour, + * tm_min, and tm_sec values are filled in. + */ + + get_rtc_alm_time(&wtime); + break; + } + case RTC_ALM_SET: /* Store a time into the alarm */ + { + unsigned char hrs, min, sec; + struct rtc_time alm_tm; + + if (copy_from_user(&alm_tm, (struct rtc_time*)arg, + sizeof(struct rtc_time))) + return -EFAULT; + + hrs = alm_tm.tm_hour; + min = alm_tm.tm_min; + sec = alm_tm.tm_sec; + + + + if (hrs >= 24) + return -EINVAL; + + hrs = BIN2BCD(hrs); + + if (min >= 60) + return -EINVAL; + + min = BIN2BCD(min); + + if (sec >= 60) + return -EINVAL; + + spin_lock_irq(&rtc_lock); + read_rtcframe(); + CMOS_WRITE(RTC_HOURS_ALARM, hrs | 0x80); + CMOS_WRITE(RTC_MINUTES_ALARM, min | 0x80); + + CMOS_WRITE(RTC_DAY_ALARM, CMOS_READ(RTC_DAY_ALARM) | 0x80); + CMOS_WRITE(RTC_WEEKDAY_ALARM, CMOS_READ(RTC_WEEKDAY_ALARM) | 0x80); + CMOS_WRITE(RTC_STATUS, CMOS_READ(RTC_STATUS) | 0x02);/*open alarm int*/ + write_rtcframe(); + spin_unlock_irq(&rtc_lock); + break; + } + case RTC_RD_TIME: /* Read the time/date from RTC */ + { + get_rtc_time(&wtime); + break; + } + case RTC_SET_TIME: /* Set the RTC */ + { + struct rtc_time rtc_tm; + unsigned char mon, day, hrs, min, sec, leap_yr, date; + unsigned int yrs; +// unsigned char ctr; + + if (!capable(CAP_SYS_TIME)) + return -EACCES; + + if (copy_from_user(&rtc_tm, (struct rtc_time*)arg, + sizeof(struct rtc_time))) + return -EFAULT; + + + yrs = rtc_tm.tm_year + 1900; + mon = rtc_tm.tm_mon + 1; /* tm_mon starts at zero */ + day = rtc_tm.tm_wday; + date = rtc_tm.tm_mday; + hrs = rtc_tm.tm_hour; + min = rtc_tm.tm_min; + sec = rtc_tm.tm_sec; + + + if (yrs < 1970) + return -EINVAL; + + leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400)); + + if ((mon > 12) || (date == 0)) + return -EINVAL; + + if (date > (days_in_mo[mon] + ((mon == 2) && leap_yr))) + return -EINVAL; + + if ((hrs >= 24) || (min >= 60) || (sec >= 60)) + return -EINVAL; + + if ((yrs -= epoch) > 255) /* They are unsigned */ + return -EINVAL; + + spin_lock_irq(&rtc_lock); + /* These limits and adjustments are independant of + * whether the chip is in binary mode or not. + */ + if (yrs > 169) { + spin_unlock_irq(&rtc_lock); + return -EINVAL; + } + + if (yrs >= 100) + yrs -= 100; + + min = BIN2BCD(min); + sec = BIN2BCD(sec); + hrs = BIN2BCD(hrs); + mon = BIN2BCD(mon); + yrs = BIN2BCD(yrs); + date = BIN2BCD(date); + + read_rtcframe(); + CMOS_WRITE(RTC_SECONDS, sec ); + CMOS_WRITE(RTC_MINUTES, min); + CMOS_WRITE(RTC_HOURS, hrs); + CMOS_WRITE(RTC_DAY_OF_MONTH, date); + CMOS_WRITE(RTC_DAY_OF_WEEK, day); + CMOS_WRITE(RTC_MONTH, mon); + CMOS_WRITE(RTC_YEAR, yrs); + write_rtcframe(); + + spin_unlock_irq(&rtc_lock); + return 0; + } + case RTC_EPOCH_READ: /* Read the epoch. */ + { + return put_user (epoch, (unsigned long *)arg); + } + case RTC_EPOCH_SET: /* Set the epoch. */ + { + /* + * There were no RTC clocks before 1900. + */ + if (arg < 1900) + return -EINVAL; + + if (!capable(CAP_SYS_TIME)) + return -EACCES; + + epoch = arg; + return 0; + } + default: + return -EINVAL; + } + return copy_to_user((void *)arg, &wtime, sizeof wtime) ? -EFAULT : 0; +} + +/* + * We enforce only one user at a time here with the open/close. + * Also clear the previous interrupt data on an open, and clean + * up things on a close. + */ + +/* We use rtc_lock to protect against concurrent opens. So the BKL is not + * needed here. Or anywhere else in this driver. */ +static int rtc_open(struct inode *inode, struct file *file) +{ + spin_lock_irq (&rtc_lock); + + if(rtc_status) + goto out_busy; + + rtc_status = 1; + + spin_unlock_irq (&rtc_lock); + return 0; + +out_busy: + spin_unlock_irq (&rtc_lock); + return -EBUSY; +} + +static int rtc_release(struct inode *inode, struct file *file) +{ + + + /* No need for locking -- nobody else can do anything until this rmw is + * committed, and no timer is running. */ + rtc_status = 0; + return 0; +} + +/* + * The various file operations we support. + */ + +static struct file_operations rtc_fops = { + owner: THIS_MODULE, + llseek: no_llseek, + ioctl: rtc_ioctl, + open: rtc_open, + release: rtc_release, +}; + +#define RTC_MINOR 135 + +static struct miscdevice rtc_dev= +{ + RTC_MINOR, + "rtc", + &rtc_fops +}; + +int __init pcf_rtc_init(void) +{ + int r; + unsigned char ctr; + r = misc_register(&rtc_dev); + + ctr = read_rtc(RTC_CONTROL); + write_rtc(RTC_CONTROL,0x00 ); + + read_rtcframe(); + CMOS_WRITE(RTC_STATUS, 0x00); + CMOS_WRITE(RTC_CLKOUT, 0x80); + /* RTC clock out, 32.768k */ + + CMOS_WRITE(RTC_TIMERCTL, 0x00); + CMOS_WRITE(RTC_TIMERCOUNTDOWN, 0x00); + write_rtcframe(); + + printk("PCF8563 RTC installed !!!\n"); + return 0; + +} + +void __exit pcf_rtc_exit (void) +{ + misc_deregister(&rtc_dev); +} + +module_init(pcf_rtc_init); +module_exit(pcf_rtc_exit); --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -7,6 +7,14 @@ menu "I2C Hardware Bus support" comment "PC SMBus host controller drivers" depends on PCI +config I2C_JZ47XX + tristate "JZ47XX I2C Interface support" + depends on SOC_JZ4730 || SOC_JZ4740 + help + If you have devices in the Ingenic JZ4730/JZ4740 I2C bus, say yes to + this option. This driver can also be built as a module. If so, the + module will be called i2c-jz47xx. + config I2C_ALI1535 tristate "ALI 1535" depends on PCI --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o +obj-$(CONFIG_I2C_JZ47XX) += i2c-jz47xx.o # External I2C/SMBus adapter drivers obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o --- /dev/null +++ b/drivers/i2c/busses/i2c-jz47xx.c @@ -0,0 +1,330 @@ +/* + * i2c_jz47xx.c + * I2C adapter for the INGENIC I2C bus access. + * + * Copyright (C) 2006 - 2008 Ingenic Semiconductor Inc. + * Author: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "i2c-jz47xx.h" + +/* I2C protocol */ +#define I2C_READ 1 +#define I2C_WRITE 0 + +#define TIMEOUT 1000 +unsigned short sub_addr = 0; +int addr_val = 0; +struct jz_i2c { + spinlock_t lock; + wait_queue_head_t wait; + struct i2c_msg *msg; + unsigned int msg_num; + unsigned int slave_addr; + struct i2c_adapter adap; + struct clk *clk; +}; + +/* + * I2C bus protocol basic routines + */ +static int i2c_put_data(unsigned char data) +{ + unsigned int timeout = TIMEOUT*10; + + __i2c_write(data); + __i2c_set_drf(); + while (__i2c_check_drf() != 0); + while (!__i2c_transmit_ended()); + while (!__i2c_received_ack() && timeout) + timeout--; + + if (timeout) + return 0; + else + return -ETIMEDOUT; +} + +static int i2c_get_data(unsigned char *data, int ack) +{ + int timeout = TIMEOUT*10; + + if (!ack) + __i2c_send_nack(); + else + __i2c_send_ack(); + + while (__i2c_check_drf() == 0 && timeout) + timeout--; + + if (timeout) { + if (!ack) + __i2c_send_stop(); + *data = __i2c_read(); + __i2c_clear_drf(); + return 0; + } else + return -ETIMEDOUT; +} + +/* + * I2C interface + */ +void i2c_jz_setclk(unsigned int i2cclk) +{ + __i2c_set_clk(jz_clocks.extalclk, i2cclk); +} + +static int xfer_read(unsigned char device, struct i2c_adapter *adap, unsigned char *buf, int length) +{ + int cnt = length; + int timeout = 5; + + device = (0xa << 3) | ((sub_addr & 0x0700) >> 8); + sub_addr = sub_addr & 0xff; + +L_try_again: + + if (timeout < 0) + goto L_timeout; + + __i2c_send_nack(); /* Master does not send ACK, slave sends it */ + + if (addr_val) { + __i2c_send_start(); + if (i2c_put_data( (device << 1) | I2C_WRITE ) < 0) + goto device_werr; + if (i2c_put_data(sub_addr) < 0) + goto address_err; + } + __i2c_send_start(); + + if (i2c_put_data((device << 1) | I2C_READ ) < 0) + goto device_rerr; + + __i2c_send_ack(); /* Master sends ACK for continue reading */ + __i2c_send_start(); + + while (cnt) { + if (cnt == 1) { + if (i2c_get_data(buf, 0) < 0) + break; + } else { + if (i2c_get_data(buf, 1) < 0) + break; + } + cnt--; + buf++; + } + addr_val = 0; + return length - cnt; + device_rerr: + device_werr: + address_err: + timeout --; + __i2c_send_stop(); + goto L_try_again; + +L_timeout: + __i2c_send_stop(); + printk("Read I2C device 0x%2x failed.\n", device); + return -ENODEV; +} + +static int xfer_write(unsigned char device, struct i2c_adapter *adap, unsigned char *buf, int length) +{ + int cnt = length; + int cnt_in_pg; + int timeout = 5; + unsigned char *tmpbuf; + unsigned char tmpaddr; + + device = (0xa << 3) | ((sub_addr & 0x0700) >> 8); + sub_addr = sub_addr & 0xff; + + __i2c_send_nack(); /* Master does not send ACK, slave sends it */ + + W_try_again: + if (timeout < 0) + goto W_timeout; + + cnt = length; + tmpbuf = (unsigned char *)buf; + tmpaddr = device; + start_write_page: + cnt_in_pg = 0; + __i2c_send_start(); + if (i2c_put_data( (device << 1) | I2C_WRITE ) < 0) + goto device_err; + if (addr_val) { + if (i2c_put_data(sub_addr) < 0) + goto address_err; + } + while (cnt) { + if (++cnt_in_pg > 8) { + __i2c_send_stop(); + mdelay(1); + sub_addr += 8; + goto start_write_page; + } + if (i2c_put_data(*tmpbuf) < 0) + break; + cnt--; + tmpbuf++; + } + __i2c_send_stop(); + addr_val = 0; + return length - cnt; + device_err: + address_err: + timeout--; + __i2c_send_stop(); + goto W_try_again; + +W_timeout: + printk(KERN_DEBUG "Write I2C device 0x%2x failed.\n", device); + __i2c_send_stop(); + return -ENODEV; +} + +static int i2c_jz_xfer(struct i2c_adapter *adap, struct i2c_msg *pmsg, int num) +{ + int ret, i; + + dev_dbg(&adap->dev, "jz47xx_xfer: processing %d messages:\n", num); + for (i = 0; i < num; i++) { + dev_dbg(&adap->dev, " #%d: %sing %d byte%s %s 0x%02x\n", i, + pmsg->flags & I2C_M_RD ? "read" : "writ", + pmsg->len, pmsg->len > 1 ? "s" : "", + pmsg->flags & I2C_M_RD ? "from" : "to", pmsg->addr); + if (pmsg->len && pmsg->buf) { /* sanity check */ + if (pmsg->flags & I2C_M_RD) + ret = xfer_read(pmsg->addr, adap, pmsg->buf, pmsg->len); + else + ret = xfer_write(pmsg->addr, adap, pmsg->buf, pmsg->len); + + if (ret) + return ret; + /* Wait until transfer is finished */ + } + dev_dbg(&adap->dev, "transfer complete\n"); + pmsg++; /* next message */ + } + return i; +} + +static u32 i2c_jz_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm i2c_jz_algorithm = { + .master_xfer = i2c_jz_xfer, + .functionality = i2c_jz_functionality, +}; + +static int i2c_jz_probe(struct platform_device *dev) +{ + struct jz_i2c *i2c; + struct i2c_jz_platform_data *plat = dev->dev.platform_data; + int ret; + + __i2c_set_clk(jz_clocks.extalclk, 10000); /* default 10 KHz */ + __i2c_enable(); + + i2c = kzalloc(sizeof(struct jz_i2c), GFP_KERNEL); + if (!i2c) { + printk("There is no enough memory\n"); + ret = -ENOMEM; + goto emalloc; + } + + i2c->adap.owner = THIS_MODULE; + i2c->adap.algo = &i2c_jz_algorithm; + i2c->adap.retries = 5; + spin_lock_init(&i2c->lock); + init_waitqueue_head(&i2c->wait); + sprintf(i2c->adap.name, "jz_i2c-i2c.%u", dev->id); + i2c->adap.algo_data = i2c; + i2c->adap.dev.parent = &dev->dev; + + if (plat) { + i2c->adap.class = plat->class; + } + + /* + * If "dev->id" is negative we consider it as zero. + * The reason to do so is to avoid sysfs names that only make + * sense when there are multiple adapters. + */ + i2c->adap.nr = dev->id != -1 ? dev->id : 0; + /* ret = i2c_add_adapter(&i2c->adap); */ + ret = i2c_add_numbered_adapter(&i2c->adap); + if (ret < 0) { + printk(KERN_INFO "I2C: Failed to add bus\n"); + goto eadapt; + } + + platform_set_drvdata(dev, i2c); + dev_info(&dev->dev, "JZ47xx i2c bus driver.\n"); + return 0; +eadapt: + __i2c_disable(); +emalloc: + return ret; +} + +static int i2c_jz_remove(struct platform_device *dev) +{ + struct i2c_adapter *adapter = platform_get_drvdata(dev); + int rc; + + rc = i2c_del_adapter(adapter); + platform_set_drvdata(dev, NULL); + return rc; +} + +static struct platform_driver i2c_jz_driver = { + .probe = i2c_jz_probe, + .remove = i2c_jz_remove, + .driver = { + .name = "jz_i2c", + }, +}; + +static int __init i2c_adap_jz_init(void) +{ + return platform_driver_register(&i2c_jz_driver); +} + +static void __exit i2c_adap_jz_exit(void) +{ + return platform_driver_unregister(&i2c_jz_driver); +} + +MODULE_LICENSE("GPL"); + +module_init(i2c_adap_jz_init); +module_exit(i2c_adap_jz_exit); --- /dev/null +++ b/drivers/i2c/busses/i2c-jz47xx.h @@ -0,0 +1,20 @@ +/* + * i2c_jz47xx.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _I2C_JZ_H_ +#define _I2C_JZ_H_ + +struct i2c_slave_client; + +struct i2c_jz_platform_data { + unsigned int slave_addr; + struct i2c_slave_client *slave; + unsigned int class; +}; + +extern void jz_set_i2c_info(struct i2c_jz_platform_data *info); +#endif --- a/drivers/i2c/i2c-dev.c +++ b/drivers/i2c/i2c-dev.c @@ -37,8 +37,9 @@ #include #include +extern unsigned short sub_addr; +extern int addr_val; static struct i2c_driver i2cdev_driver; - /* * An i2c_dev represents an i2c_adapter ... an I2C or SMBus master, not a * slave (i2c_client) with which messages will be exchanged. It's coupled @@ -424,6 +425,14 @@ static long i2cdev_ioctl(struct file *fi case I2C_TIMEOUT: client->adapter->timeout = arg; break; + case I2C_SET_SUB_ADDRESS: + addr_val = 1; + sub_addr = arg; + break; + case I2C_SET_CLOCK: + i2c_jz_setclk(arg); + break; + default: /* NOTE: returning a fault code here could cause trouble * in buggy userspace code. Some old kernel bugs returned --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -280,9 +280,27 @@ config KEYBOARD_AAED2000 To compile this driver as a module, choose M here: the module will be called aaed2000_kbd. +config KEYBOARD_JZ + tristate "JZ keypad support" + depends on JZSOC + help + Enable Y here to support JZ keypad. + + To compile this driver as a module, choose M here: the + module will be called jz-keypad. + +config 5x5_KEYBOARD_JZ + tristate "JZ 5x5 keypad support" + depends on JZSOC + help + Enable Y here to support JZ keypad. + + To compile this driver as a module, choose M here: the + module will be called jz-keypad. + config KEYBOARD_GPIO - tristate "GPIO Buttons" - depends on GENERIC_GPIO + tristate "JZ GPIO Buttons support" +# depends on GENERIC_GPIO help This driver implements support for buttons connected to GPIO pins of various CPUs (and some other chips). @@ -293,7 +311,7 @@ config KEYBOARD_GPIO with configuration data saying which GPIOs are used. To compile this driver as a module, choose M here: the - module will be called gpio-keys. + module will be called gpio_keys. config KEYBOARD_MAPLE tristate "Maple bus keyboard" --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -27,3 +27,5 @@ obj-$(CONFIG_KEYBOARD_HP7XX) += jornada obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o obj-$(CONFIG_KEYBOARD_BFIN) += bf54x-keys.o obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o +obj-$(CONFIG_KEYBOARD_JZ) += jz_keypad.o +obj-$(CONFIG_5x5_KEYBOARD_JZ) += jz_keypad_5x5.o --- /dev/null +++ b/drivers/input/keyboard/jz_keypad.c @@ -0,0 +1,357 @@ +/* + * linux/drivers/input/keyboard/jz_keypad.c + * + * JZ Keypad Driver + * + * Copyright (c) 2005 - 2008 Ingenic Semiconductor Inc. + * + * Author: Richard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define KB_ROWS 3 +#define KB_COLS 3 + +#define SCAN_INTERVAL (10) + +static unsigned short col[KB_COLS] = {85,87,91}; +static unsigned short row[KB_ROWS] = {60,61,62}; +static unsigned short s0[KB_COLS]; +static unsigned short s1[KB_COLS]={7,7,7}; +static unsigned short precol,prerow; + +static const unsigned int jz_kbd_keycode[KB_COLS * KB_ROWS] = { + KEY_1, KEY_4, KEY_7, + KEY_2, KEY_5, 0, + KEY_3, KEY_6, 0, +}; + +struct jz_kbd { + unsigned int keycode[ARRAY_SIZE(jz_kbd_keycode)]; + struct input_dev *input; + char phys[32]; + + spinlock_t lock; + struct timer_list timer; + + unsigned int suspended; + unsigned long suspend_jiffies; +}; + +static struct jz_kbd g_jz_kbd; + +static inline void jz_scan_kbd(unsigned short *s) +{ + int i; + + if (!s) + return; + + for (i = 0; i < KB_COLS; i++) { + + __gpio_as_input(85); /* row */ + __gpio_as_input(87); /* row */ + __gpio_as_input(91); /* row */ + + __gpio_as_input(60); /* col */ + __gpio_as_input(61); /* col */ + __gpio_as_input(62); /* col */ + + __gpio_clear_pin(col[i]); + __gpio_as_output(col[i]); + + udelay(1000); + s[i]=(__gpio_get_pin(60) << 0) | (__gpio_get_pin(61) << 1) | + (__gpio_get_pin(62) << 2); + } +} + +static void jz_kbd_scankeyboard(struct jz_kbd *kbd_data) +{ + unsigned int row,col; + unsigned long flags; + unsigned int num_pressed; + + if (kbd_data->suspended) + return; + + spin_lock_irqsave(&kbd_data->lock, flags); + + num_pressed = 0; + jz_scan_kbd(s0); + + /* look for key if pressed down on not, col & row */ + if (s0[0] == 7 && s0[1] == 7 && s0[2] == 7) { + if (s1[0] != 7 || s1[1] != 7 || s1[2] != 7) { + /* up */ + input_report_key(kbd_data->input, kbd_data->keycode[prerow * KB_COLS + precol], 0); + input_sync(kbd_data->input); + } + precol = prerow = -1; + s1[0] = s1[1] = s1[2] = 7; + spin_unlock_irqrestore(&kbd_data->lock, flags); + return; + } + + if (s0[0] == 6 && s0[1] == 7 && s0[2] == 7) { + row = 0;//K7 + col = 2; + goto find_row_col; + } + if (s0[0] == 7 && s0[1] == 3 && s0[2] == 7) { + row = 2;//k6 + col = 1; + goto find_row_col; + } + if (s0[0] == 7 && s0[1] == 5 && s0[2] == 7) { + row = 1;//k5 + col = 1; + goto find_row_col; + } + if (s0[0] == 7 && s0[1] == 6 && s0[2] == 7) { + row = 0;//k4 + col = 1; + goto find_row_col; + } + if (s0[0] == 7 && s0[1] == 7 && s0[2] == 3) { + row = 2;//k3 + col = 0; + goto find_row_col; + } + if (s0[0] == 7 && s0[1] == 7 && s0[2] == 5) { + row = 1;//k2 + col = 0; + goto find_row_col; + } + if (s0[0] == 7 && s0[1] == 7 && s0[2] == 6) { + row = 0;//k1 + col = 0; + goto find_row_col; + } + /* 2 or 3 buttons are pressed */ + s0[0] = s0[1] = s0[2] = 7; + s1[0] = s1[1] = s1[2] = 7; + prerow = precol = -1; + spin_unlock_irqrestore(&kbd_data->lock, flags); + return; +find_row_col: + if (s1[0] == 7 && s1[1] == 7 && s1[2] == 7) { + /* down */ + input_report_key(kbd_data->input, kbd_data->keycode[row * KB_COLS + col], 1); + input_sync(kbd_data->input); + s1[0] = s0[0]; + s1[1] = s0[1]; + s1[2] = s0[2]; + + precol = col; + prerow = row; + spin_unlock_irqrestore(&kbd_data->lock, flags); + return; + } + if (s1[0] != 7 || s1[1] != 7 || s1[2] != 7) { + /* is the same as the preview key */ + if (s0[0] == s1[0] && s0[1] == s1[1] && s0[2] == s1[2]) { + input_report_key(kbd_data->input, kbd_data->keycode[row * KB_COLS + col], 1); + input_sync(kbd_data->input); + s1[0] = s0[0]; + s1[1] = s0[1]; + s1[2] = s0[2]; + + precol = col; + prerow = row; + spin_unlock_irqrestore(&kbd_data->lock, flags); + return; + } else { + /* the preview key is up and other key is down */ + if (s0[0] != s1[0] || s0[1] != s1[1] || s0[2] != s1[2]) { + input_report_key(kbd_data->input, kbd_data->keycode[prerow * KB_COLS + precol], 0); + input_sync(kbd_data->input); + input_report_key(kbd_data->input, kbd_data->keycode[row * KB_COLS + col], 1); + input_sync(kbd_data->input); + s1[0] = s0[0]; + s1[1] = s0[1]; + s1[2] = s0[2]; + precol = col; + prerow = row; + spin_unlock_irqrestore(&kbd_data->lock, flags); + return; + } + } + } +} + +static void jz_kbd_timer_callback(unsigned long data) +{ + jz_kbd_scankeyboard(&g_jz_kbd); + mod_timer(&g_jz_kbd.timer, jiffies + SCAN_INTERVAL); +} + +#ifdef CONFIG_PM +static int jz_kbd_suspend(struct platform_device *dev, pm_message_t state) +{ + struct jz_kbd *jz_kbd = platform_get_drvdata(dev); + jz_kbd->suspended = 1; + + return 0; +} + +static int jz_kbd_resume(struct platform_device *dev) +{ + struct jz_kbd *jz_kbd = platform_get_drvdata(dev); + + jz_kbd->suspend_jiffies = jiffies; + jz_kbd->suspended = 0; + + return 0; +} +#else +#define jz_kbd_suspend NULL +#define jz_kbd_resume NULL +#endif + +static int __init jz_kbd_probe(struct platform_device *dev) +{ + struct input_dev *input_dev; + int i, error; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + platform_set_drvdata(dev, &g_jz_kbd); + + strcpy(g_jz_kbd.phys, "input/kbd0"); + + spin_lock_init(&g_jz_kbd.lock); + + g_jz_kbd.suspend_jiffies = jiffies; + g_jz_kbd.input = input_dev; + + input_dev->private = &g_jz_kbd; + input_dev->name = "JZ Keypad"; + input_dev->phys = g_jz_kbd.phys; + input_dev->cdev.dev = &dev->dev; + + input_dev->id.bustype = BUS_PARPORT; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_SYN); + input_dev->keycode = g_jz_kbd.keycode; /* keycode array address */ + input_dev->keycodesize = sizeof(unsigned int); + input_dev->keycodemax = ARRAY_SIZE(jz_kbd_keycode); + + memcpy(g_jz_kbd.keycode, jz_kbd_keycode, sizeof(g_jz_kbd.keycode)); + + for (i = 0; i < ARRAY_SIZE(jz_kbd_keycode); i++) + set_bit(g_jz_kbd.keycode[i], input_dev->keybit); + + //clear_bit(0, input_dev->keybit); + + __gpio_as_input(85); + __gpio_as_input(87); + __gpio_as_input(91); + +#if 0 + __gpio_as_input(60); + __gpio_as_input(61); + __gpio_as_input(62); +#endif + + /* Init Keyboard rescan timer */ + init_timer(&g_jz_kbd.timer); + g_jz_kbd.timer.function = jz_kbd_timer_callback; + g_jz_kbd.timer.data = (unsigned long)&g_jz_kbd; + mod_timer(&g_jz_kbd.timer, jiffies + SCAN_INTERVAL); + + error = input_register_device(input_dev); + if (error) { + pr_err("gpio-keys: Unable to register input device, " + "error: %d\n", error); + } + printk("input: JZ Keypad Registered\n"); + + return 0; +} + +static int jz_kbd_remove(struct platform_device *dev) +{ + struct jz_kbd *jz_kbd = platform_get_drvdata(dev); + + del_timer_sync(&jz_kbd->timer); + + __gpio_as_input(85); + __gpio_as_input(87); + __gpio_as_input(91); + + /* These pins is conficting with cs8900a's CS RD WE pins on JZ4740-PAVO board */ + __gpio_as_input(60); + __gpio_as_input(61); + __gpio_as_input(62); + + input_unregister_device(jz_kbd->input); + + return 0; +} + +static struct platform_driver jz_kbd_driver = { + .probe = jz_kbd_probe, + .remove = jz_kbd_remove, + .suspend = jz_kbd_suspend, + .resume = jz_kbd_resume, + .driver = { + .name = "jz-keypad", + }, +}; + +/* + * Jz Keyboard Device + */ +static struct platform_device jzkbd_device = { + .name = "jz-keypad", + .id = -1, +}; + +static int __init jz_kbd_init(void) +{ + platform_device_register(&jzkbd_device); + return platform_driver_register(&jz_kbd_driver); +} + +static void __exit jz_kbd_exit(void) +{ + platform_device_unregister(&jzkbd_device); + platform_driver_unregister(&jz_kbd_driver); +} + +module_init(jz_kbd_init); +module_exit(jz_kbd_exit); + +MODULE_AUTHOR("Richard"); +MODULE_DESCRIPTION("JZ keypad driver"); +MODULE_LICENSE("GPL"); --- /dev/null +++ b/drivers/input/keyboard/jz_keypad_5x5.c @@ -0,0 +1,329 @@ +/* + * JZ Keypad ( 5 x 5 ) Driver + * + * Copyright (c) 2005 - 2008 Ingenic Semiconductor Inc. + * + * Author: Jason 20090210 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define KB_ROWS 5 +#define KB_COLS 5 + +#define SCAN_INTERVAL (10) + +#define ROW_KEYBIT_MASK 0xFFE0 + +#define SET_GPIOS_AS_INPUT() \ +do { \ + unsigned short i; \ + \ + for (i = 0; i < KB_ROWS; i++) { \ + __gpio_as_input(jz_row_gpios[i]); \ + __gpio_as_input(jz_col_gpios[i]); \ + } \ +} while (0) + + +#define GET_ROW_GPIO_PINS() \ +({ \ + unsigned short _pins = 0, i; \ + for (i = 0; \ + i < KB_ROWS; \ + _pins |= __gpio_get_pin(jz_row_gpios[i]) << i, i++) \ + ; \ + _pins; \ +}) + +#define CHECK_IF_KEY_PRESSED(s) \ +({ \ + unsigned short i; \ + for (i = 0; i < KB_COLS && s[i] == 0x1F ; i++) \ + ; \ + i != KB_ROWS; \ +}) + +#define CLEAN_SCAN_RESULT(s) \ +do { \ + unsigned short i; \ + for (i = 0; i < KB_COLS; s[i++] = 0x1F) \ + ; \ +} while (0) + + +static const unsigned short jz_col_gpios[KB_ROWS] = {76, 75, 74, 73, 72}; +static const unsigned short jz_row_gpios[KB_COLS] = {181, 182, 79, 78, 77}; + +static const unsigned int jz_kbd_keycode[KB_ROWS * KB_COLS] = { + KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, + KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, + KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, + KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, + KEY_LEFTSHIFT, KEY_CAPSLOCK, KEY_SPACE, KEY_BACKSPACE, KEY_Y +}; + +struct jz_kbd { + unsigned int keycode[ARRAY_SIZE(jz_kbd_keycode)]; + struct input_dev *input; + char phys[32]; + + spinlock_t lock; + struct timer_list timer; + + unsigned int suspended; + unsigned long suspend_jiffies; +}; +static struct jz_kbd g_jz_kbd; + +static unsigned short scan_result[KB_COLS]; +static unsigned short pre_scan_result[KB_COLS] = {0x1F, 0x1F, 0x1F, 0x1F, 0x1F}; +static unsigned short pre_col, pre_row; + +/** + * Scan keypad by reading GPIO pins. + */ +static inline void jz_do_scan(unsigned short *s) +{ + unsigned short i; + + if (!s) + return ; + + for (i = 0; i < KB_COLS; i++) { + + SET_GPIOS_AS_INPUT(); + __gpio_clear_pin(jz_col_gpios[i]); + __gpio_as_output(jz_col_gpios[i]); + + udelay(1000); + + s[i] = GET_ROW_GPIO_PINS(); + } +} + +/** + * Call scan function and handle 'GPIO event'(like key down, key up), + * and report it to upper layer of input subsystem ... if necessary + */ +static void jz_kbd_scan(struct jz_kbd *kbd_data) +{ + unsigned short row, col, i; + unsigned long flags; + + if (kbd_data->suspended) + return; + + spin_lock_irqsave(&kbd_data->lock, flags); + + jz_do_scan(scan_result); + + /* check if any key was pressed or not */ + if (!CHECK_IF_KEY_PRESSED(scan_result)) { + + /* key up */ + if (CHECK_IF_KEY_PRESSED(pre_scan_result)) { + input_report_key(kbd_data->input, kbd_data->keycode[pre_row * KB_COLS + pre_col], 0); + input_sync(kbd_data->input); + } + pre_col = pre_row = 0xFFFF; + CLEAN_SCAN_RESULT(pre_scan_result); + + spin_unlock_irqrestore(&kbd_data->lock, flags); + return; + } + + /* find the key */ + for (row = 0; row < KB_ROWS; row++) { + for (i = scan_result[row], col = 0; col < KB_COLS; col++) { + if ( !(i & 0x01) ) + break; + i >>= 1; + } + if (col != KB_COLS) + break; + } + + //printk("[DRIVER] row = %d, col = %d, key code: 0x%02X\n", row, col, kbd_data->keycode[row * KB_COLS + col]); + + /* the same as the preview one || new key */ + if ( (col == pre_col && row == pre_row) + || (pre_col == 0xFFFF && pre_row == 0xFFFF) ) { + + input_report_key(kbd_data->input, kbd_data->keycode[row * KB_COLS + col], 1); + input_sync(kbd_data->input); + + } else { + /* the preview key is up and other key is down */ + input_report_key(kbd_data->input, kbd_data->keycode[pre_row * KB_COLS + col], 0); + input_sync(kbd_data->input); + input_report_key(kbd_data->input, kbd_data->keycode[row * KB_COLS + col], 1); + input_sync(kbd_data->input); + } + + for (i = 0; i < KB_ROWS; i++) { + pre_scan_result[i] = scan_result[i]; + } + + pre_col = col; + pre_row = row; + + spin_unlock_irqrestore(&kbd_data->lock, flags); + + return; +} + +static void jz_kbd_timer_callback(unsigned long data) +{ + jz_kbd_scan(&g_jz_kbd); + mod_timer(&g_jz_kbd.timer, jiffies + SCAN_INTERVAL); +} + +#ifdef CONFIG_PM +static int jz_kbd_suspend(struct platform_device *dev, pm_message_t state) +{ + struct jz_kbd *jz_kbd = platform_get_drvdata(dev); + jz_kbd->suspended = 1; + + return 0; +} + +static int jz_kbd_resume(struct platform_device *dev) +{ + struct jz_kbd *jz_kbd = platform_get_drvdata(dev); + + jz_kbd->suspend_jiffies = jiffies; + jz_kbd->suspended = 0; + + return 0; +} + +#else +#define jz_kbd_suspend NULL +#define jz_kbd_resume NULL +#endif + +/** + * Driver init + */ +static int __init jz_kbd_probe(struct platform_device *dev) +{ + struct input_dev *input_dev; + int i, error; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + platform_set_drvdata(dev, &g_jz_kbd); + + strcpy(g_jz_kbd.phys, "input/kbd0"); + + spin_lock_init(&g_jz_kbd.lock); + + g_jz_kbd.suspend_jiffies = jiffies; + g_jz_kbd.input = input_dev; + + input_dev->private = &g_jz_kbd; + input_dev->name = "JZ 5x5 Keypad"; + input_dev->phys = g_jz_kbd.phys; + input_dev->cdev.dev = &dev->dev; + + input_dev->id.bustype = BUS_PARPORT; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_SYN); + input_dev->keycode = g_jz_kbd.keycode; + input_dev->keycodesize = sizeof(unsigned int); + input_dev->keycodemax = ARRAY_SIZE(jz_kbd_keycode); + + memcpy(g_jz_kbd.keycode, jz_kbd_keycode, sizeof(g_jz_kbd.keycode)); + + for ( i = 0; i < ARRAY_SIZE(jz_kbd_keycode); i++) + set_bit(g_jz_kbd.keycode[i], input_dev->keybit); + + init_timer(&g_jz_kbd.timer); + g_jz_kbd.timer.function = jz_kbd_timer_callback; + g_jz_kbd.timer.data = (unsigned long)&g_jz_kbd; + mod_timer(&g_jz_kbd.timer, jiffies + SCAN_INTERVAL); + + error = input_register_device(input_dev); + if (error) { + pr_err("gpio-keys: Unable to register input device, " + "error: %d\n", error); + } + printk("input: JZ 5x5 Keypad Registered.\n"); + + return 0; +} + +static int jz_kbd_remove(struct platform_device *dev) +{ + struct jz_kbd *kbd = platform_get_drvdata(dev); + + del_timer_sync(&kbd->timer); + + SET_GPIOS_AS_INPUT(); + + input_unregister_device(kbd->input); + + return 0; +} + +static struct platform_driver jz_kbd_driver = { + .probe = jz_kbd_probe, + .remove = jz_kbd_remove, + .suspend= jz_kbd_suspend, + .resume = jz_kbd_resume, + .driver = { + .name = "jz-5x5-keypad", + }, +}; + +static struct platform_device jzkbd_device = { + .name = "jz-5x5-keypad", + .id = -1, +}; + +static int __init jz_kbd_init(void) +{ + platform_device_register(&jzkbd_device); + return platform_driver_register(&jz_kbd_driver); +} + +static void __exit jz_kbd_exit(void) +{ + platform_device_unregister(&jzkbd_device); + platform_driver_unregister(&jz_kbd_driver); +} + +module_init(jz_kbd_init); +module_exit(jz_kbd_exit); + +MODULE_AUTHOR("Jason "); +MODULE_DESCRIPTION("JZ 5x5 keypad driver"); +MODULE_LICENSE("GPL"); --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -597,6 +597,15 @@ config VIDEO_VINO Say Y here to build in support for the Vino video input system found on SGI Indy machines. +config VIDEO_JZ_CIM + tristate 'JzSOC Camera Interface Module (CIM) support' + depends on VIDEO_V4L2 + select VIDEO_JZ_SENSOR + +config VIDEO_JZ_SENSOR + tristate "Jz generic camera sensor driver" + depends on VIDEO_JZ_CIM + config VIDEO_STRADIS tristate "Stradis 4:2:2 MPEG-2 video driver (EXPERIMENTAL)" depends on EXPERIMENTAL && PCI && VIDEO_V4L1 && VIRT_TO_BUS --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -20,6 +20,9 @@ endif obj-$(CONFIG_VIDEO_TUNER) += tuner.o +obj-$(CONFIG_VIDEO_JZ_CIM) += jz_cim.o +obj-$(CONFIG_VIDEO_JZ_SENSOR) += jz_sensor.o + obj-$(CONFIG_VIDEO_BT848) += bt8xx/ obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_TVAUDIO) += tvaudio.o --- /dev/null +++ b/drivers/media/video/jz_cim.c @@ -0,0 +1,622 @@ +/* + * linux/drivers/char/jzchar/cim.c + * + * Camera Interface Module (CIM) driver for JzSOC + * This driver is independent of the camera sensor + * + * Copyright (C) 2005 JunZheng semiconductor + * + * This program 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define CIM_NAME "cim" + +MODULE_AUTHOR("Jianli Wei"); +MODULE_DESCRIPTION("JzSOC Camera Interface Module driver"); +MODULE_LICENSE("GPL"); + +#undef DEBUG +//#define DEBUG +#ifdef DEBUG +#define dprintk(x...) printk(x) +#else +#define dprintk(x...) +#endif +/* + * Define the Max Image Size + */ +#define MAX_IMAGE_WIDTH 2048 +#define MAX_IMAGE_HEIGHT 2048 +#define MAX_IMAGE_BPP 16 +#define MAX_FRAME_SIZE (MAX_IMAGE_WIDTH * MAX_IMAGE_HEIGHT * MAX_IMAGE_BPP / 8) +#define CIM_RAM_ADDR (CIM_BASE + 0x1000) + +typedef struct +{ + u32 width; + u32 height; + u32 bpp; +} img_param_t; + +typedef struct +{ + u32 cfg; + u32 ctrl; + u32 mclk; +} cim_config_t; + +/* + * IOCTL_XXX commands + */ +#define IOCTL_SET_IMG_PARAM 0 // arg type: img_param_t * +#define IOCTL_CIM_CONFIG 1 // arg type: cim_config_t * +#define IOCTL_STOP_CIM 2 // arg type: void +#define IOCTL_GET_IMG_PARAM 3 // arg type: img_param_t * +#define IOCTL_GET_CIM_CONFIG 4 // arg type: cim_config_t * +#define IOCTL_TEST_CIM_RAM 5 // no arg type * + +/* + * CIM DMA descriptor + */ +struct cim_desc { + u32 nextdesc; /* Physical address of next desc */ + u32 framebuf; /* Physical address of frame buffer */ + u32 frameid; /* Frame ID */ + u32 dmacmd; /* DMA command */ + u32 pagenum; +}; + +/* + * CIM device structure + */ +struct cim_device { + struct video_device *jz_cim; + unsigned char *framebuf; + unsigned int frame_size; + unsigned int page_order; + wait_queue_head_t wait_queue; + struct cim_desc *frame_desc __attribute__ ((aligned (16))); +}; + +/* global*/ +static struct cim_device *cim_dev; + +/*========================================================================== + * CIM init routines + *========================================================================*/ +#if defined(CONFIG_SOC_JZ4750) +static void cim_image_area(img_param_t *c) { + /*set the image data area start 0, 0, lines_per_frame and pixels_per_line*/ + REG_CIM_SIZE = 0; + REG_CIM_OFFSET = 0; + if (REG_CIM_CTRL & CIM_CTRL_SIZEEN_MASK) { + REG_CIM_SIZE = (c->height << CIM_SIZE_LPF_BIT) | (c->width << CIM_SIZE_PPL_BIT); + REG_CIM_OFFSET = (0 << CIM_OFFSET_V_BIT) | (0 << CIM_OFFSET_H_BIT); +// REG_CIM_OFFSET = (100 << CIM_OFFSET_V_BIT) | (50 << CIM_OFFSET_H_BIT); + } +} +#endif + +static void cim_config(cim_config_t *c) +{ + REG_CIM_CFG = c->cfg; + REG_CIM_CTRL = c->ctrl; + + /*Set the master clock output*/ +#if defined(CONFIG_SOC_JZ4730) + __cim_set_master_clk(__cpm_get_sclk(), c->mclk); +#elif defined(CONFIG_SOC_JZ4740) + __cim_set_master_clk(__cpm_get_hclk(), c->mclk); +#elif defined(CONFIG_SOC_JZ4750) + __cim_set_master_clk(__cpm_get_hclk(), c->mclk); +#else + __cim_set_master_clk(__cpm_get_hclk(), c->mclk); +#endif + /* Enable sof, eof and stop interrupts*/ + __cim_enable_sof_intr(); + __cim_enable_eof_intr(); + __cim_enable_stop_intr(); +} + +/*========================================================================== + * CIM start/stop operations + *========================================================================*/ +static int cim_start_dma(char *ubuf) +{ + struct cim_desc *jz_frame_desc; + int cim_frame_size = 0; + jz_frame_desc = cim_dev->frame_desc; + dprintk("framedesc = %x\n", (u32) jz_frame_desc); + __cim_disable(); + dprintk("__cim_disable\n"); + __cim_set_da(virt_to_phys(cim_dev->frame_desc)); + __cim_clear_state(); // clear state register + __cim_reset_rxfifo(); // resetting rxfifo + __cim_unreset_rxfifo(); + __cim_enable_dma(); // enable dma + __cim_enable(); + + dprintk("__cim_enable\n"); +// while(1) { +// mdelay(10); +// dprintk("REG_CIM_DA = 0x%08x\n", REG_CIM_DA); +// dprintk("REG_CIM_FA = 0x%08x\n", REG_CIM_FA); +// dprintk("REG_CIM_FID = 0x%08x\n", REG_CIM_FID); +// dprintk("REG_CIM_CMD = 0x%08x\n", REG_CIM_CMD); +// dprintk("REG_CIM_CFG = 0x%08x\n", REG_CIM_CFG); +// dprintk("REG_CIM_STATE = 0x%08x\n", REG_CIM_STATE); +// dprintk("REG_CIM_CTRL = 0x%08x\n", REG_CIM_CTRL); +// dprintk("REG_CIM_SIZE = 0x%08x\n", REG_CIM_SIZE); +// dprintk("REG_CIM_OFFSET = 0x%08x\n", REG_CIM_OFFSET); +// mdelay(100); +// } + // wait for interrupts + interruptible_sleep_on(&cim_dev->wait_queue); + dprintk("interruptible_sleep_on\n"); + dprintk("REG_CIM_DA = 0x%08x\n", REG_CIM_DA); + dprintk("REG_CIM_FA = 0x%08x\n", REG_CIM_FA); + dprintk("REG_CIM_FID = 0x%08x\n", REG_CIM_FID); + dprintk("REG_CIM_CMD = 0x%08x\n", REG_CIM_CMD); + dprintk("REG_CIM_CFG = 0x%08x\n", REG_CIM_CFG); + dprintk("REG_CIM_STATE = 0x%08x\n", REG_CIM_STATE); + dprintk("REG_CIM_CTRL = 0x%08x\n", REG_CIM_CTRL); + dprintk("REG_CIM_SIZE = 0x%08x\n", REG_CIM_SIZE); + dprintk("REG_CIM_OFFSET = 0x%08x\n", REG_CIM_OFFSET); + dprintk("REG_CIM_CMD_3 = %x\n", REG_CIM_CMD); + dprintk("REG_CIM_FA = %x\n", REG_CIM_FA); + /* copy frame data to user buffer */ + jz_frame_desc = cim_dev->frame_desc; + + while(jz_frame_desc != NULL) + { + dprintk("ubuf = %x, framebuf = %x,frame_size= %d\n", (u32)ubuf,(u32) jz_frame_desc->framebuf, jz_frame_desc->dmacmd & 0xffffff); + memcpy(ubuf, phys_to_virt(jz_frame_desc->framebuf), ((jz_frame_desc->dmacmd & CIM_CMD_LEN_MASK) * 4)); + ubuf += (jz_frame_desc->dmacmd & CIM_CMD_LEN_MASK) * 4; + cim_frame_size += (jz_frame_desc->dmacmd & CIM_CMD_LEN_MASK) * 4; + jz_frame_desc = (struct cim_desc *)phys_to_virt(jz_frame_desc->nextdesc); + } + return cim_dev->frame_size; +} +static void cim_stop(void) +{ + __cim_disable(); + __cim_clear_state(); +} + +/*========================================================================== + * Framebuffer allocation and destroy + *========================================================================*/ +static void cim_fb_destroy(void) +{ + int pages; + struct cim_desc *jz_frame_desc, *p_desc; + if (cim_dev->frame_desc == NULL) { + printk("Original memory is NULL\n"); + return; + } + jz_frame_desc = cim_dev->frame_desc; + while (jz_frame_desc != NULL) { + dprintk("framebuf = %x,thisdesc = %x,frame_size= %d\n", (u32) jz_frame_desc->framebuf, (unsigned int)jz_frame_desc, (jz_frame_desc->dmacmd & 0xffffff) * 4); + p_desc = (struct cim_desc *)phys_to_virt(jz_frame_desc->nextdesc); + pages = jz_frame_desc->pagenum; + dprintk("page_order = %d\n", pages); + free_pages((unsigned long)phys_to_virt(jz_frame_desc->framebuf), pages); + kfree(jz_frame_desc); + jz_frame_desc = p_desc; + } + cim_dev->frame_desc = NULL; +} + +static struct cim_desc *get_desc_list(int page_order) +{ + int num, page_nums = 0; + unsigned char *p_buf; + struct cim_desc *desc_list_head __attribute__ ((aligned (16))); + struct cim_desc *desc_list_tail __attribute__ ((aligned (16))); + struct cim_desc *p_desc; +// num = page_order - 1; + num = page_order; + desc_list_head = desc_list_tail = NULL; + + while(page_nums < (1 << page_order)) { + p_desc = (struct cim_desc *)kmalloc(sizeof(struct cim_desc), GFP_KERNEL); + if (NULL == p_desc) + return NULL; + //return -ENOMEM; + cim_realloc_pages: + p_buf = (unsigned char *)__get_free_pages(GFP_KERNEL, num); + if ( !(p_buf) && num != 0) { + num --; + goto cim_realloc_pages; + } + else if ( !(p_buf) && num == 0) { + printk("No memory can be alloc!\n"); + //return -ENOMEM; + return NULL; + } + else { + if (desc_list_head == NULL) { + dprintk("Page_list_head\n"); + desc_list_head = p_desc; + } + + else + desc_list_tail->nextdesc = virt_to_phys(p_desc); + + desc_list_tail = p_desc; + desc_list_tail->framebuf = virt_to_phys(p_buf); + dprintk("framebuf addr is 0x%08x\n", (u32)desc_list_tail->framebuf); + dprintk("frame_desc addr is 0x%08x\n",(u32)virt_to_phys(desc_list_tail)); + + desc_list_tail->frameid = 0x52052018; + desc_list_tail->pagenum = num; + if ((page_nums + (1<< num)) < (1 << page_order)) { + desc_list_tail->dmacmd = ((1 << num) * 4096) >> 2 ; + } + else + desc_list_tail->dmacmd = + (cim_dev->frame_size - page_nums * 4096) >> 2 ; + dprintk("the desc_list_tail->dmacmd is 0x%08x\n", desc_list_tail->dmacmd); + page_nums += (1 << num); + dprintk("the pages_num is %d\n", page_nums); + } + } + + desc_list_tail->nextdesc = virt_to_phys(NULL); + /* stop after capturing a frame */ + desc_list_tail->dmacmd |= (CIM_CMD_STOP | CIM_CMD_EOFINT); + dprintk("the desc_list_tail->dmacmd is 0x%08x\n", desc_list_tail->dmacmd); + + return desc_list_head; +} + +static int cim_fb_alloc(int img_width, int img_height, int img_bpp) +{ +#if defined(CONFIG_SOC_JZ4750) + if ((REG_CIM_CFG & (CIM_CFG_DF_MASK | CIM_CFG_BYPASS_MASK)) == 0) + cim_dev->frame_size = img_width * (img_height-1) * (img_bpp/8); + else + cim_dev->frame_size = img_width * img_height * (img_bpp/8); +#else + cim_dev->frame_size = img_width * img_height * (img_bpp/8); +#endif + cim_dev->page_order = get_order(cim_dev->frame_size); + dprintk("cim_dev->page_order=%d\n", cim_dev->page_order); + /* frame buffer ?? need large mem ??*/ + cim_dev->frame_desc = get_desc_list(cim_dev->page_order); + if (cim_dev->frame_desc == NULL) + return -ENOMEM; + dma_cache_wback((unsigned long)(cim_dev->frame_desc), 16); + return 0; +} + +/*========================================================================== + * File operations + *========================================================================*/ + +static int cim_open(struct inode *inode, struct file *filp); +static int cim_release(struct inode *inode, struct file *filp); +static ssize_t cim_read(struct file *filp, char *buf, size_t size, loff_t *l); +static ssize_t cim_write(struct file *filp, const char *buf, size_t size, loff_t *l); +static int cim_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); +static int cim_mmap(struct file *file, struct vm_area_struct *vma); + +static struct file_operations cim_fops = +{ + open: cim_open, + release: cim_release, + read: cim_read, + write: cim_write, + ioctl: cim_ioctl, + compat_ioctl: v4l_compat_ioctl32, + mmap: cim_mmap +}; + +static struct video_device jz_v4l_device = { + .name = "jz cim", + //.type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE | + // VID_TYPE_CLIPPING | VID_TYPE_SCALES, VID_TYPE_OVERLAY + .fops = &cim_fops, + .minor = -1, + .owner = THIS_MODULE, + .release = video_device_release, +}; + +static int cim_open(struct inode *inode, struct file *filp) +{ + + try_module_get(THIS_MODULE); + return 0; +} + +static int cim_release(struct inode *inode, struct file *filp) +{ + cim_fb_destroy(); + cim_stop(); + + module_put(THIS_MODULE); + return 0; +} + +static ssize_t cim_read(struct file *filp, char *buf, size_t size, loff_t *l) +{ + if (size < cim_dev->frame_size) + return -EINVAL; + dprintk("read cim\n"); + return cim_start_dma(buf); +} + +static ssize_t cim_write(struct file *filp, const char *buf, size_t size, loff_t *l) +{ + printk("cim error: write is not implemented\n"); + return -1; +} + +static int cim_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + switch (cmd) { + case IOCTL_GET_IMG_PARAM: + { + img_param_t i; + return copy_to_user(argp, &i, sizeof(img_param_t)) ? -EFAULT : 0; + } + case IOCTL_SET_IMG_PARAM: + { + img_param_t i; + int img_width, img_height, img_bpp; + if (copy_from_user((void *)&i, (void *)arg, sizeof(img_param_t))) + return -EFAULT; +#if defined(CONFIG_SOC_JZ4750) + cim_image_area(&i); +#endif + img_width = i.width; + img_height = i.height; + img_bpp = i.bpp; + dprintk("ioctl_set_cim_param\n"); + if ((img_width * img_height * img_bpp/8) > MAX_FRAME_SIZE){ + printk("ERROR! Image is too large!\n"); + return -EINVAL; + } + /* allocate frame buffers */ + if (cim_dev->frame_desc == NULL){ + if (cim_fb_alloc(img_width, img_height, img_bpp) < 0){ + printk("ERROR! Init & alloc cim fail!\n"); + return -ENOMEM; + } + } + else + if ((img_width * img_height * img_bpp/8) > cim_dev->frame_size){ + /* realloc the buffer */ + cim_fb_destroy(); + if (cim_fb_alloc(img_width, img_height, img_bpp) < 0){ + printk("ERRROR! Init & alloc cim fail!\n"); + return -ENOMEM; + } + } + break; + } + case IOCTL_CIM_CONFIG: + { + cim_config_t c; + + if (copy_from_user((void *)&c, (void *)arg, sizeof(cim_config_t))) + return -EFAULT; + + cim_config(&c); + + break; + } + case IOCTL_TEST_CIM_RAM: + { + + int i; + volatile unsigned int *ptr; + ptr = (volatile unsigned int *)(CIM_RAM_ADDR); + printk("RAM test!\n"); + printk("CIM_RAM_ADDR = 0x%08x\n", CIM_RAM_ADDR); + for (i = 0; i < 1024; ptr++, i++) + *ptr = i; + ptr = (volatile unsigned int *)(CIM_RAM_ADDR); + dma_cache_wback((unsigned long)CIM_RAM_ADDR,0xffc); + + for (i = 0; i < 1024; i++) { + if (i != *ptr) + printk("*ptr!=i, *ptr=%d, i=%d\n", *ptr, i); + if (i%32 == 0) { + if (i%128 == 0) + printk("\n"); + printk("*ptr=%04d, i=%04d | ", *ptr, i); + } + ptr++; + } + printk("\n"); + break; + } + default: + printk("Not supported command: 0x%x\n", cmd); + return -EINVAL; + break; + } + return 0; +} + +/* Use mmap /dev/fb can only get a non-cacheable Virtual Address. */ +static int cim_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long start; + unsigned long off; + u32 len; + + off = vma->vm_pgoff << PAGE_SHIFT; + //fb->fb_get_fix(&fix, PROC_CONSOLE(info), info); + + /* frame buffer memory */ + start = cim_dev->frame_desc->framebuf; + len = PAGE_ALIGN((start & ~PAGE_MASK) + (cim_dev->frame_desc->dmacmd & CIM_CMD_LEN_MASK)); + start &= PAGE_MASK; + + if ((vma->vm_end - vma->vm_start + off) > len) + return -EINVAL; + off += start; + + vma->vm_pgoff = off >> PAGE_SHIFT; + vma->vm_flags |= VM_IO; + +#if defined(CONFIG_MIPS32) + pgprot_val(vma->vm_page_prot) &= ~_CACHE_MASK; +// pgprot_val(vma->vm_page_prot) |= _CACHE_CACHABLE_NO_WA; /* WT cachable */ + pgprot_val(vma->vm_page_prot) |= _CACHE_UNCACHED; +#endif + + if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) + vma->vm_flags |= VM_IO; + return -EAGAIN; + + return 0; +} +/*========================================================================== + * Interrupt handler + *========================================================================*/ + +static irqreturn_t cim_irq_handler(int irq, void *dev_id) +{ + u32 state = REG_CIM_STATE; + dprintk("REG_CIM_STATE = %x\n", REG_CIM_STATE); + dprintk("REG_CIM_CTRL = %x\n", REG_CIM_CTRL); +#if 1 + if (state & CIM_STATE_RXF_OF) { + dprintk("OverFlow interrupt!\n"); + } +#endif + if (state & CIM_STATE_DMA_EOF) { + dprintk("EOF interrupt!\n"); + __cim_disable_dma(); + __cim_disable(); + wake_up_interruptible(&cim_dev->wait_queue); + dprintk("EOF interrupt wake up!\n"); + } + + if (state & CIM_STATE_DMA_STOP) { + // Got a frame, wake up wait routine + __cim_disable_dma(); + __cim_disable(); + dprintk("Stop interrupt!\n"); + wake_up_interruptible(&cim_dev->wait_queue); + } +#if 1 + if (state & CIM_STATE_RXF_TRIG) { + dprintk("Trig!\n"); + } +#endif + + /* clear status flags*/ + REG_CIM_STATE = 0; + return IRQ_HANDLED; +} + +static int v4l_device_init(void) +{ + cim_dev = kzalloc(sizeof(struct cim_device), GFP_KERNEL); + if (!cim_dev) return -ENOMEM; + cim_dev->jz_cim = video_device_alloc(); + if (!cim_dev->jz_cim) { + return -ENOMEM; + } + memcpy(cim_dev->jz_cim, &jz_v4l_device, sizeof(struct video_device)); + cim_dev->frame_desc = NULL; + cim_dev->frame_size = 0; + cim_dev->page_order = 0; + return 0; +} +/*========================================================================== + * Module init and exit + *========================================================================*/ + +static int __init jz_cim_init(void) +{ + struct cim_device *dev; + int ret; + /* allocate device */ + ret = v4l_device_init(); + if (ret) + return ret; + /* record device */ + dev = cim_dev; + init_waitqueue_head(&dev->wait_queue); + + ret = video_register_device(dev->jz_cim, VFL_TYPE_GRABBER, -1); + if (ret < 0) { + printk(KERN_ERR "CIM Video4Linux-device " + "registration failed\n"); + return -EINVAL; + } + + if (ret < 0) { + cim_fb_destroy(); + kfree(dev); + return ret; + } + + if ((ret = request_irq(IRQ_CIM, cim_irq_handler, IRQF_DISABLED, + CIM_NAME, dev))) { + printk(KERN_ERR "request_irq return error, ret=%d\n", ret); + cim_fb_destroy(); + kfree(dev); + printk(KERN_ERR "CIM could not get IRQ\n"); + return ret; + } + + printk("JzSOC Camera Interface Module (CIM) driver registered\n"); + + return 0; +} + +static void __exit jz_cim_exit(void) +{ + free_irq(IRQ_CIM, cim_dev); + kfree(cim_dev); + video_unregister_device(cim_dev->jz_cim); +} + +module_init(jz_cim_init); +module_exit(jz_cim_exit); --- /dev/null +++ b/drivers/media/video/jz_cim.h @@ -0,0 +1,36 @@ +/* + * JzSOC CIM driver + * + * Copyright (C) 2005 Ingenic Semiconductor Inc. + * + * This program 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __JZ_CIM_H__ +#define __JZ_CIM_H__ + +typedef struct +{ + u32 width; + u32 height; + u32 bpp; +} IMG_PARAM; + +/* + * IOCTL_XXX commands + */ +#define IOCTL_SET_IMG_PARAM 0 // arg type: IMG_PARAM * + +#endif /* __JZ__CIM_H__ */ --- /dev/null +++ b/drivers/media/video/jz_sensor.c @@ -0,0 +1,202 @@ +/* + * linux/drivers/char/jzchar/sensor.c + * + * Common CMOS Camera Sensor Driver + * + * Copyright (C) 2006 Ingenic Semiconductor Inc. + * + * This program 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. + */ + +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +//#include "jz-chars.h" + +#include +#include +#include + +#include +#include +#include + +MODULE_AUTHOR("Jianli Wei"); +MODULE_DESCRIPTION("Common CMOS Camera Sensor Driver"); +MODULE_LICENSE("GPL"); + +/* + * ioctl commands + */ +#define IOCTL_SET_ADDR 0 /* set i2c address */ +#define IOCTL_SET_CLK 1 /* set i2c clock */ +#define IOCTL_WRITE_REG 2 /* write sensor register */ +#define IOCTL_READ_REG 3 /* read sensor register */ + +/* + * i2c related + */ +static unsigned int i2c_addr = 0x42; +static unsigned int i2c_clk = 100000; + +struct video_device *jz_sensor; + +static void write_reg(u8 reg, u8 val) +{ + i2c_open(); + i2c_setclk(i2c_clk); + i2c_write((i2c_addr >> 1), &val, reg, 1); + i2c_close(); +} + +static u8 read_reg(u8 reg) +{ + u8 val; + + i2c_open(); + i2c_setclk(i2c_clk); + i2c_read((i2c_addr >> 1), &val, reg, 1); + i2c_close(); + return val; +} + +/* + * fops routines + */ + +static int sensor_open(struct inode *inode, struct file *filp); +static int sensor_release(struct inode *inode, struct file *filp); +static ssize_t sensor_read(struct file *filp, char *buf, size_t size, loff_t *l); +static ssize_t sensor_write(struct file *filp, const char *buf, size_t size, loff_t *l); +static int sensor_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); + +static struct file_operations sensor_fops = +{ + open: sensor_open, + release: sensor_release, + read: sensor_read, + write: sensor_write, + ioctl: sensor_ioctl, +}; + +static int sensor_open(struct inode *inode, struct file *filp) +{ + try_module_get(THIS_MODULE); + return 0; +} + +static int sensor_release(struct inode *inode, struct file *filp) +{ + module_put(THIS_MODULE); + return 0; +} + +static ssize_t sensor_read(struct file *filp, char *buf, size_t size, loff_t *l) +{ + printk("sensor: read is not implemented\n"); + return -1; +} + +static ssize_t sensor_write(struct file *filp, const char *buf, size_t size, loff_t *l) +{ + printk("sensor: write is not implemented\n"); + return -1; +} + +static int sensor_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + case IOCTL_SET_ADDR: + if (copy_from_user(&i2c_addr, (void *)arg, 4)) + return -EFAULT; + break; + case IOCTL_SET_CLK: + if (copy_from_user(&i2c_clk, (void *)arg, 4)) + return -EFAULT; + break; + case IOCTL_WRITE_REG: + { + u8 regval[2]; + + if (copy_from_user(regval, (void *)arg, 2)) + return -EFAULT; + + write_reg(regval[0], regval[1]); + break; + } + case IOCTL_READ_REG: + { + u8 reg, val; + + if (copy_from_user(®, (void *)arg, 1)) + return -EFAULT; + + val = read_reg(reg); + + if (copy_to_user((void *)(arg + 1), &val, 1)) + return -EFAULT; + break; + } + default: + printk("Not supported command: 0x%x\n", cmd); + return -EINVAL; + break; + } + return ret; +} + +static struct video_device jz_v4l_device = { + .name = "jz sensor", + //.type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE | + // VID_TYPE_CLIPPING | VID_TYPE_SCALES, VID_TYPE_OVERLAY + .fops = &sensor_fops, + .minor = -1, +}; + +/* + * Module init and exit + */ + +static int __init jz_sensor_init(void) +{ + int ret; +// cim_dev = kzalloc(sizeof(struct cim_device), GFP_KERNEL); + jz_sensor = video_device_alloc(); + memcpy(jz_sensor, &jz_v4l_device, sizeof(struct video_device)); + jz_sensor->release = video_device_release; +// ret = jz_register_chrdev(SENSOR_MINOR, "sensor", &sensor_fops, NULL); + ret = video_register_device(jz_sensor, VFL_TYPE_GRABBER, -1); + if (ret < 0) { + return ret; + } + + printk("Ingenic CMOS camera sensor driver registered\n"); + + return 0; +} + +static void __exit jz_sensor_exit(void) +{ +// jz_unregister_chrdev(SENSOR_MINOR, "sensor"); + video_unregister_device(jz_sensor); +} + +module_init(jz_sensor_init); +module_exit(jz_sensor_exit); --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -226,7 +226,11 @@ static int mmc_blk_issue_rq(struct mmc_q brq.mrq.cmd = &brq.cmd; brq.mrq.data = &brq.data; +#ifdef CONFIG_JZ4750_BOOT_FROM_MSC0 + brq.cmd.arg = req->sector + 8192; +#else brq.cmd.arg = req->sector; +#endif if (!mmc_card_blockaddr(card)) brq.cmd.arg <<= 9; brq.cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -140,8 +140,13 @@ static int mmc_decode_csd(struct mmc_car e = UNSTUFF_BITS(resp, 47, 3); m = UNSTUFF_BITS(resp, 62, 12); - csd->capacity = (1 + m) << (e + 2); +#ifdef CONFIG_JZ4750_BOOT_FROM_MSC0 + csd->capacity = (1 + m) << (e + 2); + csd->capacity -= 8192; +#else + csd->capacity = (1 + m) << (e + 2); +#endif csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4); csd->read_partial = UNSTUFF_BITS(resp, 79, 1); csd->write_misalign = UNSTUFF_BITS(resp, 78, 1); @@ -440,7 +445,8 @@ static int mmc_init_card(struct mmc_host if (err) goto free_card; - mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); + /* all mmc v4 support 8 bit mmc card */ + mmc_set_bus_width(card->host, MMC_BUS_WIDTH_8); } if (!oldcard) --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -109,8 +109,13 @@ static int mmc_decode_csd(struct mmc_car e = UNSTUFF_BITS(resp, 47, 3); m = UNSTUFF_BITS(resp, 62, 12); - csd->capacity = (1 + m) << (e + 2); +#ifdef CONFIG_JZ4750_BOOT_FROM_MSC0 + csd->capacity = (1 + m) << (e + 2); + csd->capacity -= 8192; +#else + csd->capacity = (1 + m) << (e + 2); +#endif csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4); csd->read_partial = UNSTUFF_BITS(resp, 79, 1); csd->write_misalign = UNSTUFF_BITS(resp, 78, 1); @@ -137,8 +142,13 @@ static int mmc_decode_csd(struct mmc_car csd->cmdclass = UNSTUFF_BITS(resp, 84, 12); m = UNSTUFF_BITS(resp, 48, 22); - csd->capacity = (1 + m) << 10; +#ifdef CONFIG_JZ4750_BOOT_FROM_MSC0 + csd->capacity = (1 + m) << 10; + csd->capacity -= 8192; +#else + csd->capacity = (1 + m) << 10; +#endif csd->read_blkbits = 9; csd->read_partial = 0; csd->write_misalign = 0; @@ -268,9 +278,11 @@ static int mmc_switch_hs(struct mmc_card goto out; if ((status[16] & 0xF) != 1) { +#if 0 printk(KERN_WARNING "%s: Problem switching card " "into high-speed mode!\n", mmc_hostname(card->host)); +#endif } else { mmc_card_set_highspeed(card); mmc_set_timing(card->host, MMC_TIMING_SD_HS); @@ -426,6 +438,9 @@ static int mmc_sd_init_card(struct mmc_h goto free_card; mmc_decode_cid(card); + + /* set 24MHz clock again, why?? */ + mmc_set_clock(host, 24000000); } /* --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -4,6 +4,104 @@ comment "MMC/SD/SDIO Host Controller Drivers" +config MMC_JZ + tristate "JZ SD/Multimedia Card Interface support" + depends on SOC_JZ4730 || SOC_JZ4740 + help + This selects the Ingenic JZ4730/JZ4740 SD/Multimedia card Interface. + If you have abIngenic platform with a Multimedia Card slot, + say Y or M here. + + If unsure, say N. +choice + depends on MMC_JZ + prompt "MMC BUS Width" + default JZ_MMC_BUS_4 + help + This defines the BUS Width of the Ingenic JZ4730/JZ4740 SD/Multimedia card Interface. + +config JZ_MMC_BUS_1 + bool "1 Bit Bus" + help + 1 Bit SD/Multimedia Card Bus + +config JZ_MMC_BUS_4 + bool "4 Bit Bus" + help + 4 Bit SD/Multimedia Card Bus + +endchoice + +config MSC0_JZ4750 + tristate "JZ4750 SD/Multimedia Card 0 Interface support" + depends on SOC_JZ4750 || SOC_JZ4750D + help + This selects the Ingenic JZ4750 SD/Multimedia card 0 Interface. + If you have a Ingenic platform with a Multimedia Card slot, + say Y or M here. + + If unsure, say N. + +choice + depends on MSC0_JZ4750 + prompt "MSC0 BUS Width" + default JZ4750_MSC0_BUS_4 + help + This defines the BUS Width of the Ingenic JZ4750 SD/Multimedia card Interface. + +config JZ4750_MSC0_BUS_1 + bool "1 Bit Bus" + help + 1 Bit SD/Multimedia Card Bus + +config JZ4750_MSC0_BUS_4 + bool "4 Bit Bus" + help + 4 Bit SD/Multimedia Card Bus + +config JZ4750_MSC0_BUS_8 + bool "8 Bit Bus" + help + 8 Bit Multimedia Card Bus + +endchoice + +config MSC1_JZ4750 + tristate "JZ4750 SD/Multimedia Card 1 Interface support" + depends on SOC_JZ4750 || SOC_JZ4750D + help + This selects the Ingenic JZ4750 SD/Multimedia card 1 Interface. + If you have a Ingenic platform with a Multimedia Card slot, + say Y or M here. + + If unsure, say N. + +choice + depends on MSC1_JZ4750 + prompt "MSC1 BUS Width" + default JZ4750_MSC1_BUS_4 + help + This defines the BUS Width of the Ingenic JZ4750 SD/Multimedia card Interface. + +config JZ4750_MSC1_BUS_1 + bool "1 Bit Bus" + help + 1 Bit SD/Multimedia Card Bus + +config JZ4750_MSC1_BUS_4 + bool "4 Bit Bus" + help + 4 Bit SD/Multimedia Card Bus +endchoice + +config JZ4750_BOOT_FROM_MSC0 + tristate "JZ4750 Boot from SD/Multimedia Card Interface support" + depends on SOC_JZ4750 || SOC_JZ4750D + help + This selects boot from the Sd/Multimedia Card. + + If unsure,say N. + config MMC_ARMMMCI tristate "ARM AMBA Multimedia Card Interface support" depends on ARM_AMBA --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -6,6 +6,9 @@ ifeq ($(CONFIG_MMC_DEBUG),y) EXTRA_CFLAGS += -DDEBUG endif +obj-$(CONFIG_MMC_JZ) += jz_mmc.o +obj-$(CONFIG_MSC0_JZ4750) += jz4750_mmc.o +obj-$(CONFIG_MSC1_JZ4750) += jz4750_mmc.o obj-$(CONFIG_MMC_ARMMMCI) += mmci.o obj-$(CONFIG_MMC_PXA) += pxamci.o obj-$(CONFIG_MMC_IMX) += imxmmc.o --- /dev/null +++ b/drivers/mmc/host/jz4750_mmc.c @@ -0,0 +1,1051 @@ +/* + * linux/drivers/mmc/jz_mmc.c - JZ SD/MMC driver + * + * Copyright (C) 2005 - 2008 Ingenic Semiconductor Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "jz4750_mmc.h" + +#define DRIVER_NAME "jz-mmc" + +#define USE_DMA + +static int r_type = 0; +static int rxdmachan = 0; +static int txdmachan = 0; +static int mmc_slot_enable = 0; +static int auto_select_bus = MSC_4BIT_BUS; /* default 4 bit bus*/ + +/* Start the MMC clock and operation */ +static inline int jz_mmc_start_op(void) +{ + REG_MSC_STRPCL(MSC_ID) = MSC_STRPCL_START_OP; + + return MMC_NO_ERROR; +} + +static inline u32 jz_mmc_calc_clkrt(int is_low, u32 rate) +{ + u32 clkrt; + u32 clk_src = is_low ? 24000000 : 48000000; + + clkrt = 0; + while (rate < clk_src) { + clkrt++; + clk_src >>= 1; + } + return clkrt; +} + +/* Select the MMC clock frequency */ +static int jz_mmc_set_clock(u32 rate) +{ + int clkrt; + + /* __cpm_select_msc_clk_high will select 48M clock for MMC/SD card + * perhaps this will made some card with bad quality init fail,or + * bad stabilization. + */ + if (rate > SD_CLOCK_FAST) { + __cpm_select_msc_clk_high(MSC_ID,1); /* select clock source from CPM */ + clkrt = jz_mmc_calc_clkrt(0, rate); + } else { + __cpm_select_msc_clk(MSC_ID,1); /* select clock source from CPM */ + clkrt = jz_mmc_calc_clkrt(1, rate); + } + +#ifndef CONFIG_FPGA + REG_MSC_CLKRT(MSC_ID) = clkrt; +#else + REG_MSC_CLKRT(MSC_ID) = 7; +#endif + return MMC_NO_ERROR; +} + +static void jz_mmc_enable_irq(struct jz_mmc_host *host, unsigned int mask) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + host->imask &= ~mask; + REG_MSC_IMASK(MSC_ID) = host->imask; + spin_unlock_irqrestore(&host->lock, flags); +} + +static void jz_mmc_disable_irq(struct jz_mmc_host *host, unsigned int mask) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + host->imask |= mask; + REG_MSC_IMASK(MSC_ID) = host->imask; + spin_unlock_irqrestore(&host->lock, flags); +} + +void jz_set_dma_block_size(int dmanr, int nbyte); + +#ifdef USE_DMA +static inline void +jz_mmc_start_dma(int chan, unsigned long phyaddr, int count, int mode) +{ + unsigned long flags; + + flags = claim_dma_lock(); + disable_dma(chan); + clear_dma_ff(chan); + jz_set_dma_block_size(chan, 32); + set_dma_mode(chan, mode); + set_dma_addr(chan, phyaddr); + set_dma_count(chan, count + 31); + enable_dma(chan); + release_dma_lock(flags); +} + +static irqreturn_t jz_mmc_dma_rx_callback(int irq, void *devid) +{ + int chan = rxdmachan; + + disable_dma(chan); + if (__dmac_channel_address_error_detected(chan)) { + printk(KERN_DEBUG "%s: DMAC address error.\n", + __FUNCTION__); + __dmac_channel_clear_address_error(chan); + } + if (__dmac_channel_transmit_end_detected(chan)) { + __dmac_channel_clear_transmit_end(chan); + } + return IRQ_HANDLED; +} +static irqreturn_t jz_mmc_dma_tx_callback(int irq, void *devid) +{ + int chan = txdmachan; + + disable_dma(chan); + if (__dmac_channel_address_error_detected(chan)) { + printk(KERN_DEBUG "%s: DMAC address error.\n", + __FUNCTION__); + __dmac_channel_clear_address_error(chan); + } + if (__dmac_channel_transmit_end_detected(chan)) { + __dmac_channel_clear_transmit_end(chan); + } + return IRQ_HANDLED; +} + +/* Prepare DMA to start data transfer from the MMC card */ +static void jz_mmc_rx_setup_data(struct jz_mmc_host *host, + struct mmc_data *data) +{ + unsigned int nob = data->blocks; + int channelrx = rxdmachan; + int i; + u32 size; + + if (data->flags & MMC_DATA_STREAM) + nob = 0xffff; + + REG_MSC_NOB(MSC_ID) = nob; + REG_MSC_BLKLEN(MSC_ID) = data->blksz; + size = nob * data->blksz; + + if (data->flags & MMC_DATA_READ) { + host->dma.dir = DMA_FROM_DEVICE; + } else { + host->dma.dir = DMA_TO_DEVICE; + } + + host->dma.len = + dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + host->dma.dir); + + for (i = 0; i < host->dma.len; i++) { + host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]); + host->sg_cpu[i].dcmd = sg_dma_len(&data->sg[i]); + dma_cache_wback_inv((unsigned long) + CKSEG0ADDR(sg_dma_address(data->sg)) + + data->sg->offset, + host->sg_cpu[i].dcmd); + jz_mmc_start_dma(channelrx, host->sg_cpu[i].dtadr, + host->sg_cpu[i].dcmd, DMA_MODE_READ); + } +} + +/* Prepare DMA to start data transfer from the MMC card */ +static void jz_mmc_tx_setup_data(struct jz_mmc_host *host, + struct mmc_data *data) +{ + unsigned int nob = data->blocks; + int channeltx = txdmachan; + int i; + u32 size; + + if (data->flags & MMC_DATA_STREAM) + nob = 0xffff; + + REG_MSC_NOB(MSC_ID) = nob; + REG_MSC_BLKLEN(MSC_ID) = data->blksz; + size = nob * data->blksz; + + if (data->flags & MMC_DATA_READ) { + host->dma.dir = DMA_FROM_DEVICE; + } else { + host->dma.dir = DMA_TO_DEVICE; + } + + host->dma.len = + dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + host->dma.dir); + + for (i = 0; i < host->dma.len; i++) { + host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]); + host->sg_cpu[i].dcmd = sg_dma_len(&data->sg[i]); + dma_cache_wback_inv((unsigned long) + CKSEG0ADDR(sg_dma_address(data->sg)) + + data->sg->offset, + host->sg_cpu[i].dcmd); + jz_mmc_start_dma(channeltx, host->sg_cpu[i].dtadr, + host->sg_cpu[i].dcmd, DMA_MODE_WRITE); + } +} +#else +static void jz_mmc_receive_pio(struct jz_mmc_host *host) +{ + + struct mmc_data *data = 0; + int sg_len = 0, max = 0, count = 0; + u32 *buf = 0; + struct scatterlist *sg; + unsigned int nob; + + data = host->mrq->data; + nob = data->blocks; + REG_MSC_NOB(MSC_ID) = nob; + REG_MSC_BLKLEN(MSC_ID) = data->blksz; + + max = host->pio.len; + if (host->pio.index < host->dma.len) { + sg = &data->sg[host->pio.index]; + buf = sg_virt(sg) + host->pio.offset; + + /* This is the space left inside the buffer */ + sg_len = sg_dma_len(&data->sg[host->pio.index]) - host->pio.offset; + /* Check to if we need less then the size of the sg_buffer */ + if (sg_len < max) max = sg_len; + } + max = max / 4; + for(count = 0; count < max; count++) { + while (REG_MSC_STAT(MSC_ID) & MSC_STAT_DATA_FIFO_EMPTY) + ; + *buf++ = REG_MSC_RXFIFO(MSC_ID); + } + host->pio.len -= count; + host->pio.offset += count; + + if (sg_len && count == sg_len) { + host->pio.index++; + host->pio.offset = 0; + } +} + +static void jz_mmc_send_pio(struct jz_mmc_host *host) +{ + + struct mmc_data *data = 0; + int sg_len, max, count = 0; + u32 *wbuf = 0; + struct scatterlist *sg; + unsigned int nob; + + data = host->mrq->data; + nob = data->blocks; + + REG_MSC_NOB(MSC_ID) = nob; + REG_MSC_BLKLEN(MSC_ID) = data->blksz; + + /* This is the pointer to the data buffer */ + sg = &data->sg[host->pio.index]; + wbuf = sg_virt(sg) + host->pio.offset; + + /* This is the space left inside the buffer */ + sg_len = data->sg[host->pio.index].length - host->pio.offset; + + /* Check to if we need less then the size of the sg_buffer */ + max = (sg_len > host->pio.len) ? host->pio.len : sg_len; + max = max / 4; + for(count = 0; count < max; count++ ) { + while (REG_MSC_STAT(MSC_ID) & MSC_STAT_DATA_FIFO_FULL) + ; + REG_MSC_TXFIFO(MSC_ID) = *wbuf++; + } + + host->pio.len -= count; + host->pio.offset += count; + + if (count == sg_len) { + host->pio.index++; + host->pio.offset = 0; + } +} + +static int +jz_mmc_prepare_data(struct jz_mmc_host *host, struct mmc_data *data) +{ + int datalen = data->blocks * data->blksz; + + host->dma.dir = DMA_BIDIRECTIONAL; + host->dma.len = dma_map_sg(mmc_dev(host->mmc), data->sg, + data->sg_len, host->dma.dir); + if (host->dma.len == 0) + return -ETIMEDOUT; + + host->pio.index = 0; + host->pio.offset = 0; + host->pio.len = datalen; + return 0; +} +#endif + +static int jz_mmc_cmd_done(struct jz_mmc_host *host, unsigned int stat); + +static void jz_mmc_finish_request(struct jz_mmc_host *host, struct mmc_request *mrq) +{ + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + mmc_request_done(host->mmc, mrq); +} + +static void jz_mmc_start_cmd(struct jz_mmc_host *host, + struct mmc_command *cmd, unsigned int cmdat) +{ + u32 timeout = 0x3fffff; + unsigned int stat; + struct jz_mmc_host *hst = host; + WARN_ON(host->cmd != NULL); + host->cmd = cmd; + + /* mask interrupts */ + REG_MSC_IMASK(MSC_ID) = 0xffff; + + /* clear status */ + REG_MSC_IREG(MSC_ID) = 0xffff; + + if (cmd->flags & MMC_RSP_BUSY) + cmdat |= MSC_CMDAT_BUSY; + +#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE)) + switch (RSP_TYPE(mmc_resp_type(cmd))) { + case RSP_TYPE(MMC_RSP_R1): /* r1,r1b, r6, r7 */ + cmdat |= MSC_CMDAT_RESPONSE_R1; + r_type = 1; + break; + case RSP_TYPE(MMC_RSP_R3): + cmdat |= MSC_CMDAT_RESPONSE_R3; + r_type = 1; + break; + case RSP_TYPE(MMC_RSP_R2): + cmdat |= MSC_CMDAT_RESPONSE_R2; + r_type = 2; + break; + default: + break; + } + + REG_MSC_CMD(MSC_ID) = cmd->opcode; + + /* Set argument */ +#ifdef CONFIG_MSC0_JZ4750 +#ifdef CONFIG_JZ4750_MSC0_BUS_1 + if (cmd->opcode == 6) { + /* set 1 bit sd card bus*/ + if (cmd->arg ==2) + REG_MSC_ARG(MSC_ID) = 0; + + /* set 1 bit mmc card bus*/ + if (cmd->arg == 0x3b70101) { + REG_MSC_ARG(MSC_ID) = 0x3b70001; + } + } else + REG_MSC_ARG(MSC_ID) = cmd->arg; + +#elif defined CONFIG_JZ4750_MSC0_BUS_8 + if (cmd->opcode == 6) { + /* set 8 bit mmc card bus*/ + if (cmd->arg == 0x3b70101) + REG_MSC_ARG(MSC_ID) = 0x3b70201; + else + REG_MSC_ARG(MSC_ID) = cmd->arg; + + } else + REG_MSC_ARG(MSC_ID) = cmd->arg; +#else + REG_MSC_ARG(MSC_ID) = cmd->arg; +#endif /* CONFIG_JZ4750_MSC0_BUS_1 */ +#else +#ifdef CONFIG_JZ4750_MSC1_BUS_1 + if (cmd->opcode == 6) { + /* set 1 bit sd card bus*/ + if (cmd->arg ==2) + REG_MSC_ARG(MSC_ID) = 0; + + /* set 1 bit mmc card bus*/ + if (cmd->arg == 0x3b70101) { + REG_MSC_ARG(MSC_ID) = 0x3b70001; + } + } else + REG_MSC_ARG(MSC_ID) = cmd->arg; + +#else + REG_MSC_ARG(MSC_ID) = cmd->arg; +#endif /* CONFIG_JZ4750_MSC1_BUS_1 */ +#endif /* CONFIG_MSC0_JZ4750*/ + + /* Set command */ + REG_MSC_CMDAT(MSC_ID) = cmdat; + + /* Send command */ + jz_mmc_start_op(); + + while (timeout-- && !(REG_MSC_STAT(MSC_ID) & MSC_STAT_END_CMD_RES)) + ; + + REG_MSC_IREG(MSC_ID) = MSC_IREG_END_CMD_RES; /* clear irq flag */ + if (cmd->opcode == 12) { + while (timeout-- && !(REG_MSC_IREG(MSC_ID) & MSC_IREG_PRG_DONE)) + ; + REG_MSC_IREG(MSC_ID) = MSC_IREG_PRG_DONE; /* clear status */ + } + if (!mmc_slot_enable) { + /* It seems that MSC can't report the MSC_STAT_TIME_OUT_RES when + * card was removed. We force to return here. + */ + cmd->error = -ETIMEDOUT; + jz_mmc_finish_request(hst, hst->mrq); + return; + } + + if (SD_IO_SEND_OP_COND == cmd->opcode) { + /* + * Don't support SDIO card currently. + */ + cmd->error = -ETIMEDOUT; + jz_mmc_finish_request(hst, hst->mrq); + return; + } + + /* Check for status */ + stat = REG_MSC_STAT(MSC_ID); + jz_mmc_cmd_done(hst, stat); + if (host->data) { + if (cmd->opcode == MMC_WRITE_BLOCK || cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) +#ifdef USE_DMA + jz_mmc_tx_setup_data(host, host->data); +#else + jz_mmc_send_pio(host); + else + jz_mmc_receive_pio(host); +#endif + } +} + +static int jz_mmc_cmd_done(struct jz_mmc_host *host, unsigned int stat) +{ + struct mmc_command *cmd = host->cmd; + int i, temp[16]; + u8 *buf; + u32 data, v, w1, w2; + + if (!cmd) + return 0; + + host->cmd = NULL; + buf = (u8 *) temp; + switch (r_type) { + case 1: + { + data = REG_MSC_RES(MSC_ID); + buf[0] = (data >> 8) & 0xff; + buf[1] = data & 0xff; + data = REG_MSC_RES(MSC_ID); + buf[2] = (data >> 8) & 0xff; + buf[3] = data & 0xff; + data = REG_MSC_RES(MSC_ID); + buf[4] = data & 0xff; + cmd->resp[0] = + buf[1] << 24 | buf[2] << 16 | buf[3] << 8 | + buf[4]; + break; + } + case 2: + { + data = REG_MSC_RES(MSC_ID); + v = data & 0xffff; + for (i = 0; i < 4; i++) { + data = REG_MSC_RES(MSC_ID); + w1 = data & 0xffff; + data = REG_MSC_RES(MSC_ID); + w2 = data & 0xffff; + cmd->resp[i] = v << 24 | w1 << 8 | w2 >> 8; + v = w2; + } + break; + } + case 0: + break; + } + if (stat & MSC_STAT_TIME_OUT_RES) { + printk("MSC_STAT_TIME_OUT_RES\n"); + cmd->error = -ETIMEDOUT; + } else if (stat & MSC_STAT_CRC_RES_ERR && cmd->flags & MMC_RSP_CRC) { + printk("MSC_STAT_CRC\n"); + if (cmd->opcode == MMC_ALL_SEND_CID || + cmd->opcode == MMC_SEND_CSD || + cmd->opcode == MMC_SEND_CID) { + /* a bogus CRC error can appear if the msb of + the 15 byte response is a one */ + if ((cmd->resp[0] & 0x80000000) == 0) + cmd->error = -EILSEQ; + } + } + /* + * Did I mention this is Sick. We always need to + * discard the upper 8 bits of the first 16-bit word. + */ + if (host->data && cmd->error == 0) + jz_mmc_enable_irq(host, MSC_IMASK_DATA_TRAN_DONE); + else + jz_mmc_finish_request(host, host->mrq); + + return 1; +} + +static int jz_mmc_data_done(struct jz_mmc_host *host, unsigned int stat) +{ + struct mmc_data *data = host->data; + + if (!data) + return 0; + REG_MSC_IREG(MSC_ID) = MSC_IREG_DATA_TRAN_DONE; /* clear status */ + dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len, + host->dma_dir); + if (stat & MSC_STAT_TIME_OUT_READ) { + printk("MMC/SD timeout, MMC_STAT 0x%x\n", stat); + data->error = -ETIMEDOUT; + } else if (REG_MSC_STAT(MSC_ID) & + (MSC_STAT_CRC_READ_ERROR | MSC_STAT_CRC_WRITE_ERROR)) { + printk("MMC/SD CRC error, MMC_STAT 0x%x\n", stat); + data->error = -EILSEQ; + } + /* + * There appears to be a hardware design bug here. There seems to + * be no way to find out how much data was transferred to the card. + * This means that if there was an error on any block, we mark all + * data blocks as being in error. + */ + if (data->error == 0) + data->bytes_xfered = data->blocks * data->blksz; + else + data->bytes_xfered = 0; + + jz_mmc_disable_irq(host, MSC_IMASK_DATA_TRAN_DONE); + host->data = NULL; + if (host->mrq->stop) { + jz_mmc_start_cmd(host, host->mrq->stop, 0); + } else { + jz_mmc_finish_request(host, host->mrq); + } + return 1; +} + +static void jz_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct jz_mmc_host *host = mmc_priv(mmc); + unsigned int cmdat; + + /* Save current request for the future processing */ + host->mrq = mrq; + host->data = mrq->data; + cmdat = host->cmdat; + host->cmdat &= ~MSC_CMDAT_INIT; + + if (mrq->data) { + cmdat &= ~MSC_CMDAT_BUSY; +#ifdef USE_DMA + if ((mrq->cmd->opcode == 51) | (mrq->cmd->opcode == 8) | (mrq->cmd->opcode == 6)) + + cmdat |= + MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN | + MSC_CMDAT_DMA_EN; + else { +#ifdef CONFIG_MSC0_JZ4750 +#ifdef CONFIG_JZ4750_MSC0_BUS_1 + cmdat &= ~MSC_CMDAT_BUS_WIDTH_MASK; + cmdat |= MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN | + MSC_CMDAT_DMA_EN; +#elif defined CONFIG_JZ4750_MSC0_BUS_4 + if(auto_select_bus == MSC_1BIT_BUS) { + cmdat &= ~MSC_CMDAT_BUS_WIDTH_MASK; + cmdat |= MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN | + MSC_CMDAT_DMA_EN; + } else { + cmdat &= ~MSC_CMDAT_BUS_WIDTH_MASK; + cmdat |= MSC_CMDAT_BUS_WIDTH_4BIT | MSC_CMDAT_DATA_EN | + MSC_CMDAT_DMA_EN; + } +#else + cmdat |= MSC_CMDAT_DATA_EN | MSC_CMDAT_DMA_EN; +#endif /* CONFIG_JZ4750_MSC0_BUS_1 */ +#else +#ifdef CONFIG_JZ4750_MSC1_BUS_1 + cmdat &= ~MSC_CMDAT_BUS_WIDTH_MASK; + cmdat |= MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN | + MSC_CMDAT_DMA_EN; +#else + cmdat |= MSC_CMDAT_DATA_EN | MSC_CMDAT_DMA_EN; +#endif /* CONFIG_JZ4750_MSC1_BUS_1 */ +#endif /* CONFIG_MSC0_JZ4750 */ + } + if (mrq->data->flags & MMC_DATA_WRITE) + cmdat |= MSC_CMDAT_WRITE; + + if (mrq->data->flags & MMC_DATA_STREAM) + cmdat |= MSC_CMDAT_STREAM_BLOCK; + if (mrq->cmd->opcode != MMC_WRITE_BLOCK + && mrq->cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK) + jz_mmc_rx_setup_data(host, mrq->data); +#else /*USE_DMA*/ + + if ((mrq->cmd->opcode == 51) | (mrq->cmd->opcode == 8) | (mrq->cmd->opcode == 6)) + cmdat |= MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN; + else { +#ifdef CONFIG_MSC0_JZ4750 +#ifdef CONFIG_JZ4750_MSC0_BUS_1 + cmdat &= ~MSC_CMDAT_BUS_WIDTH_MASK; + cmdat |= MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN; +#elif defined CONFIG_JZ4750_MSC0_BUS_4 + if(auto_select_bus == MSC_1BIT_BUS) { + cmdat &= ~MSC_CMDAT_BUS_WIDTH_MASK; + cmdat |= MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN; + } else { + cmdat &= ~MSC_CMDAT_BUS_WIDTH_MASK; + cmdat |= MSC_CMDAT_BUS_WIDTH_4BIT | MSC_CMDAT_DATA_EN; + } +#else + cmdat |= MSC_CMDAT_DATA_EN; +#endif +#else +#ifdef CONFIG_JZ4750_MSC1_BUS_1 + cmdat &= ~MSC_CMDAT_BUS_WIDTH_MASK; + cmdat |= MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN; +#else + cmdat |= MSC_CMDAT_DATA_EN; +#endif /* CONFIG_JZ4750_MSC1_BUS_1 */ +#endif /* CONFIG_MSC0_JZ4750 */ + } + if (mrq->data->flags & MMC_DATA_WRITE) + cmdat |= MSC_CMDAT_WRITE; + + if (mrq->data->flags & MMC_DATA_STREAM) + cmdat |= MSC_CMDAT_STREAM_BLOCK; + jz_mmc_prepare_data(host, host->data); +#endif /*USE_DMA*/ + } + jz_mmc_start_cmd(host, mrq->cmd, cmdat); +} + +static irqreturn_t jz_mmc_irq(int irq, void *devid) +{ + struct jz_mmc_host *host = devid; + unsigned int ireg; + int handled = 0; + + ireg = REG_MSC_IREG(MSC_ID); + + if (ireg) { + unsigned stat = REG_MSC_STAT(MSC_ID); + if (ireg & MSC_IREG_DATA_TRAN_DONE) + handled |= jz_mmc_data_done(host, stat); + } + return IRQ_RETVAL(handled); +} + +/* Returns true if MMC slot is empty */ +static int jz_mmc_slot_is_empty(int slot) +{ + int empty; + +#ifdef CONFIG_FPGA + return 0; +#endif + +#ifdef CONFIG_MSC1_JZ4750 + empty = (__msc1_card_detected(slot) == 0) ? 1 : 0; +#else + empty = (__msc0_card_detected(slot) == 0) ? 1 : 0; + +#endif + if (empty) { + /* wait for card insertion */ +#ifdef CONFIG_MSC1_JZ4750 + __gpio_as_irq_rise_edge(MSC_HOTPLUG_PIN); +#else + __gpio_as_irq_fall_edge(MSC_HOTPLUG_PIN); +#endif + } else { + /* wait for card removal */ +#ifdef CONFIG_MSC1_JZ4750 + __gpio_as_irq_fall_edge(MSC_HOTPLUG_PIN); +#else + __gpio_as_irq_rise_edge(MSC_HOTPLUG_PIN); +#endif + } + + return empty; +} + +static irqreturn_t jz_mmc_detect_irq(int irq, void *devid) +{ + struct jz_mmc_host *host = (struct jz_mmc_host *) devid; + + auto_select_bus = MSC_4BIT_BUS; + if (jz_mmc_slot_is_empty(0)) { + mmc_slot_enable = 0; + mmc_detect_change(host->mmc, 50); + } else { + mmc_slot_enable = 1; + mmc_detect_change(host->mmc, 50); + } + return IRQ_HANDLED; +} + +static int jz_mmc_get_ro(struct mmc_host *mmc) +{ + struct jz_mmc_host *host = mmc_priv(mmc); + + if (host->pdata && host->pdata->get_ro) + return host->pdata->get_ro(mmc_dev(mmc)); + /* Host doesn't support read only detection so assume writeable */ + return 0; +} + +/* set clock and power */ +static void jz_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct jz_mmc_host *host = mmc_priv(mmc); + + if (ios->clock) + jz_mmc_set_clock(ios->clock); + + if (host->power_mode != ios->power_mode) { + host->power_mode = ios->power_mode; + + if (ios->power_mode == MMC_POWER_ON) + host->cmdat |= CMDAT_INIT; + } + + if (ios->bus_width == MMC_BUS_WIDTH_4) { + auto_select_bus = MSC_4BIT_BUS; + host->cmdat |= MSC_CMDAT_BUS_WIDTH_4BIT; + } + else if (ios->bus_width == MMC_BUS_WIDTH_8) { + host->cmdat |= MSC_CMDAT_BUS_WIDTH_8BIT; + auto_select_bus = MSC_8BIT_BUS; + } else { + /* 1 bit bus*/ + host->cmdat &= ~MSC_CMDAT_BUS_WIDTH_8BIT; + auto_select_bus = MSC_1BIT_BUS; + } +} + +static const struct mmc_host_ops jz_mmc_ops = { + .request = jz_mmc_request, + .get_ro = jz_mmc_get_ro, + .set_ios = jz_mmc_set_ios, +}; +static int jz_mmc_pm_callback(struct pm_dev *pm_dev, + pm_request_t req, void *data); + +static int jz_mmc_probe(struct platform_device *pdev) +{ + int retval; + struct mmc_host *mmc; + struct jz_mmc_host *host = NULL; + int irq; + struct resource *r; + +#ifdef CONFIG_MSC0_JZ4750 +#ifdef CONFIG_SOC_JZ4750 + __gpio_as_msc0_8bit(); // for jz4750 +#else + __gpio_as_msc0_4bit(); // for jz4750d +#endif + __msc0_init_io(); + __msc0_enable_power(); +#else + __gpio_as_msc1_4bit(); + __msc1_init_io(); + __msc1_enable_power(); +#endif + __msc_reset(MSC_ID); + REG_MSC_LPM(MSC_ID) = 0x1; + + MMC_IRQ_MASK(); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!r || irq < 0) + return -ENXIO; + + r = request_mem_region(r->start, SZ_4K, DRIVER_NAME); + if (!r) + return -EBUSY; + + mmc = mmc_alloc_host(sizeof(struct jz_mmc_host), &pdev->dev); + if (!mmc) { + retval = -ENOMEM; + goto out; + } + mmc->ops = &jz_mmc_ops; + mmc->f_min = MMC_CLOCK_SLOW; + mmc->f_max = SD_CLOCK_HIGH; + /* + * We can do SG-DMA, but we don't because we never know how much + * data we successfully wrote to the card. + */ + mmc->max_phys_segs = NR_SG; + + mmc->max_seg_size = PAGE_SIZE * 16; + mmc->max_req_size = mmc->max_seg_size; + mmc->max_blk_size = 4095; + /* + * Block count register is 16 bits. + */ + mmc->max_blk_count = 65535; + host = mmc_priv(mmc); + host->mmc = mmc; + host->pdata = pdev->dev.platform_data; + mmc->ocr_avail = host->pdata ? + host->pdata->ocr_mask : MMC_VDD_32_33 | MMC_VDD_33_34; + host->mmc->caps = + MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_SD_HIGHSPEED + | MMC_CAP_MMC_HIGHSPEED; + /* + *MMC_CAP_4_BIT_DATA (1 << 0) The host can do 4 bit transfers + * + */ + host->sg_cpu = + dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, + GFP_KERNEL); + if (!host->sg_cpu) { + retval = -ENOMEM; + goto out; + } + spin_lock_init(&host->lock); + host->irq = IRQ_MSC; /* is it useful ?*/ + host->imask = 0xffff; + /* + * Ensure that the host controller is shut down, and setup + * with our defaults. + */ + retval = request_irq(IRQ_MSC, jz_mmc_irq, 0, "MMC/SD", host); + if (retval) { + printk(KERN_ERR "MMC/SD: can't request MMC/SD IRQ\n"); + return retval; + } + + jz_mmc_slot_is_empty(0); + /* Request card detect interrupt */ + + retval = request_irq(MSC_HOTPLUG_IRQ, jz_mmc_detect_irq, 0, //SA_INTERRUPT, + "MMC card detect", host); + if (retval) { + printk(KERN_ERR "MMC/SD: can't request card detect IRQ\n"); + goto err1; + } +#ifdef USE_DMA + /* Request MMC Rx DMA channel */ + rxdmachan = + jz_request_dma(DMA_ID_MSC_RX, "MMC Rx", jz_mmc_dma_rx_callback, + 0, host); + if (rxdmachan < 0) { + printk(KERN_ERR "jz_request_dma failed for MMC Rx\n"); + goto err2; + } + + if (rxdmachan < HALF_DMA_NUM) + REG_DMAC_DMACR(0) |= DMAC_DMACR_FMSC; + else + REG_DMAC_DMACR(1) |= DMAC_DMACR_FMSC; + + /* Request MMC Tx DMA channel */ + txdmachan = + jz_request_dma(DMA_ID_MSC_TX, "MMC Tx", jz_mmc_dma_tx_callback, + 0, host); + if (txdmachan < 0) { + printk(KERN_ERR "jz_request_dma failed for MMC Tx\n"); + goto err3; + } + + if (txdmachan < HALF_DMA_NUM) + REG_DMAC_DMACR(0) |= DMAC_DMACR_FMSC; + else + REG_DMAC_DMACR(1) |= DMAC_DMACR_FMSC; + +#endif + platform_set_drvdata(pdev, mmc); + mmc_add_host(mmc); +#ifdef CONFIG_PM + /* Register MMC slot as as power-managed device */ + pm_register(PM_UNKNOWN_DEV, PM_SYS_UNKNOWN, jz_mmc_pm_callback); +#endif + printk("JZ SD/MMC card driver registered\n"); + + /* Detect card during initialization */ +#if defined(CONFIG_SOC_JZ4750) || defined(CONFIG_SOC_JZ4750D) + if (!jz_mmc_slot_is_empty(0)) { + mmc_slot_enable = 1; + mmc_detect_change(host->mmc, 0); + } +#endif + return 0; + +err1:free_irq(IRQ_MSC, &host); +#ifdef USE_DMA + err2:jz_free_dma(rxdmachan); + err3:jz_free_dma(txdmachan); +#endif +out: + if (host) { + if (host->sg_cpu) + dma_free_coherent(&pdev->dev, PAGE_SIZE, + host->sg_cpu, host->sg_dma); + } + if (mmc) + mmc_free_host(mmc); + return -1; +} + +static int jz_mmc_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + long flags; + + platform_set_drvdata(pdev, NULL); + + if (mmc) { + struct jz_mmc_host *host = mmc_priv(mmc); + + if (host->pdata && host->pdata->exit) + host->pdata->exit(&pdev->dev, mmc); + + mmc_remove_host(mmc); + + local_irq_save(flags); + __msc0_disable_power(); + jz_free_dma(rxdmachan); + jz_free_dma(txdmachan); + free_irq(IRQ_MSC, host); + local_irq_restore(flags); + mmc_free_host(mmc); + } + return 0; +} + +#ifdef CONFIG_PM +pm_message_t state; +static int jz_mmc_suspend(struct platform_device *dev, pm_message_t state) +{ + struct mmc_host *mmc = platform_get_drvdata(dev); + int ret = 0; + + if (mmc) + ret = mmc_suspend_host(mmc, state); + + return ret; +} + +static int jz_mmc_resume(struct platform_device *dev) +{ + struct mmc_host *mmc = platform_get_drvdata(dev); + int ret = 0; + + if (mmc) + ret = mmc_resume_host(mmc); + + return ret; +} +static int jz_mmc_pm_callback(struct pm_dev *pm_dev, + pm_request_t req, void *data) +{ + struct platform_device *pdev = (struct platform_device *)pm_dev; + + switch(req) { + case PM_RESUME: + jz_mmc_resume(pdev); + break; + case PM_SUSPEND: + /* state has no use */ + jz_mmc_suspend(pdev,state); + break; + default: + printk("MMC/SD: invalid PM request %d\n", req); + break; + } + return 0; +} +#else +#define jz_mmc_suspend NULL +#define jz_mmc_resume NULL +#endif + +static struct platform_driver jz_mmc_driver = { + .probe = jz_mmc_probe, + .remove = jz_mmc_remove, + .suspend = jz_mmc_suspend, + .resume = jz_mmc_resume, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init jz_mmc_init(void) +{ + return platform_driver_register(&jz_mmc_driver); +} + +static void __exit jz_mmc_exit(void) +{ + platform_driver_unregister(&jz_mmc_driver); +} + +module_init(jz_mmc_init); +module_exit(jz_mmc_exit); + +MODULE_DESCRIPTION("JZ47XX SD/Multimedia Card Interface Driver"); +MODULE_LICENSE("GPL"); --- /dev/null +++ b/drivers/mmc/host/jz4750_mmc.h @@ -0,0 +1,88 @@ +#ifndef __JZ4750_MMC_H__ +#define __JZ4750_MMC_H__ + +#define MMC_CLOCK_SLOW 400000 /* 400 kHz for initial setup */ +#define MMC_CLOCK_FAST 20000000 /* 20 MHz for maximum for normal operation */ +#define SD_CLOCK_FAST 24000000 /* 24 MHz for SD Cards */ +#define SD_CLOCK_HIGH 24000000 /* 24 MHz for SD Cards */ +#define MMC_NO_ERROR 0 + +#define NR_SG 1 + +#ifdef CONFIG_MSC0_JZ4750 +#define MSC_ID 0 +#define MSC_HOTPLUG_IRQ MSC0_HOTPLUG_IRQ +#define IRQ_MSC IRQ_MSC0 +#define DMA_ID_MSC_RX DMA_ID_MSC0_RX +#define DMA_ID_MSC_TX DMA_ID_MSC0_TX +#define MSC_HOTPLUG_PIN MSC0_HOTPLUG_PIN +#else +#define MSC_ID 1 +#define MSC_HOTPLUG_IRQ MSC1_HOTPLUG_IRQ +#define IRQ_MSC IRQ_MSC1 +#define DMA_ID_MSC_RX DMA_ID_MSC1_RX +#define DMA_ID_MSC_TX DMA_ID_MSC1_TX +#define MSC_HOTPLUG_PIN MSC1_HOTPLUG_PIN +#endif + +#define MSC_1BIT_BUS 0 +#define MSC_4BIT_BUS 1 +#define MSC_8BIT_BUS 2 + +#define SZ_4K 0x00001000 + +struct jz_mmc_host { + struct mmc_host *mmc; + spinlock_t lock; + struct { + int len; + int dir; + } dma; + struct { + int index; + int offset; + int len; + } pio; + int irq; + unsigned int clkrt; + unsigned int cmdat; + unsigned int imask; + unsigned int power_mode; + struct jz_mmc_platform_data *pdata; + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + dma_addr_t sg_dma; + struct jzsoc_dma_desc *sg_cpu; + unsigned int dma_len; + unsigned int dma_dir; +}; + +#define MMC_IRQ_MASK() \ +do { \ + REG_MSC_IMASK(MSC_ID) = 0xffff; \ + REG_MSC_IREG(MSC_ID) = 0xffff; \ +} while (0) + +typedef struct jzsoc_dma_desc { + volatile u32 ddadr; /* Points to the next descriptor + flags */ + volatile u32 dsadr; /* DSADR value for the current transfer */ + volatile u32 dtadr; /* DTADR value for the current transfer */ + volatile u32 dcmd; /* DCMD value for the current transfer */ +} jzsoc_dma_desc; + +#include + +struct device; +struct mmc_host; + +struct jz_mmc_platform_data { + unsigned int ocr_mask; /* available voltages */ + unsigned long detect_delay; /* delay in jiffies before detecting cards after interrupt */ + int (*init)(struct device *, irq_handler_t , void *); + int (*get_ro)(struct device *); + void (*setpower)(struct device *, unsigned int); + void (*exit)(struct device *, void *); +}; + +#endif /* __JZ4750_MMC_H__ */ --- /dev/null +++ b/drivers/mmc/host/jz_mmc.c @@ -0,0 +1,1026 @@ +/* + * linux/drivers/mmc/jz_mmc.c - JZ SD/MMC driver + * + * Copyright (C) 2005 - 2008 Ingenic Semiconductor Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "jz_mmc.h" + +#define DRIVER_NAME "jz-mmc" + +#define NR_SG 1 + +#if defined(CONFIG_SOC_JZ4725) || defined(CONFIG_SOC_JZ4720) +#undef USE_DMA +#else +#define USE_DMA +#endif + +struct jz_mmc_host { + struct mmc_host *mmc; + spinlock_t lock; + struct { + int len; + int dir; + } dma; + struct { + int index; + int offset; + int len; + } pio; + int irq; + unsigned int clkrt; + unsigned int cmdat; + unsigned int imask; + unsigned int power_mode; + struct jz_mmc_platform_data *pdata; + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + dma_addr_t sg_dma; + struct jzsoc_dma_desc *sg_cpu; + unsigned int dma_len; + unsigned int dma_dir; + struct pm_dev *pmdev; +}; + +static int r_type = 0; + +#define MMC_IRQ_MASK() \ +do { \ + REG_MSC_IMASK = 0xff; \ + REG_MSC_IREG = 0xff; \ +} while (0) + +static int rxdmachan = 0; +static int txdmachan = 0; +static int mmc_slot_enable = 0; + +/* Stop the MMC clock and wait while it happens */ +static inline int jz_mmc_stop_clock(void) +{ + int timeout = 1000; + + REG_MSC_STRPCL = MSC_STRPCL_CLOCK_CONTROL_STOP; + while (timeout && (REG_MSC_STAT & MSC_STAT_CLK_EN)) { + timeout--; + if (timeout == 0) + return 0; + udelay(1); + } + return MMC_NO_ERROR; +} + +/* Start the MMC clock and operation */ +static inline int jz_mmc_start_clock(void) +{ + REG_MSC_STRPCL = + MSC_STRPCL_CLOCK_CONTROL_START | MSC_STRPCL_START_OP; + return MMC_NO_ERROR; +} + +static inline u32 jz_mmc_calc_clkrt(int is_sd, u32 rate) +{ + u32 clkrt; + u32 clk_src = is_sd ? 24000000 : 20000000; + + clkrt = 0; + while (rate < clk_src) { + clkrt++; + clk_src >>= 1; + } + return clkrt; +} + +/* Select the MMC clock frequency */ +static int jz_mmc_set_clock(u32 rate) +{ + int clkrt; + + jz_mmc_stop_clock(); + __cpm_select_msc_clk(1); /* select clock source from CPM */ + clkrt = jz_mmc_calc_clkrt(1, rate); + REG_MSC_CLKRT = clkrt; + return MMC_NO_ERROR; +} + +static void jz_mmc_enable_irq(struct jz_mmc_host *host, unsigned int mask) +{ + unsigned long flags; + spin_lock_irqsave(&host->lock, flags); + host->imask &= ~mask; + REG_MSC_IMASK = host->imask; + spin_unlock_irqrestore(&host->lock, flags); +} + +static void jz_mmc_disable_irq(struct jz_mmc_host *host, unsigned int mask) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + host->imask |= mask; + REG_MSC_IMASK = host->imask; + spin_unlock_irqrestore(&host->lock, flags); +} + +void jz_set_dma_block_size(int dmanr, int nbyte); + +#ifdef USE_DMA +static inline void +jz_mmc_start_dma(int chan, unsigned long phyaddr, int count, int mode) +{ + unsigned long flags; + + flags = claim_dma_lock(); + disable_dma(chan); + clear_dma_ff(chan); + jz_set_dma_block_size(chan, 32); + set_dma_mode(chan, mode); + set_dma_addr(chan, phyaddr); + set_dma_count(chan, count + 31); + enable_dma(chan); + release_dma_lock(flags); +} + +static irqreturn_t jz_mmc_dma_rx_callback(int irq, void *devid) +{ + int chan = rxdmachan; + + disable_dma(chan); + if (__dmac_channel_address_error_detected(chan)) { + printk(KERN_DEBUG "%s: DMAC address error.\n", + __FUNCTION__); + __dmac_channel_clear_address_error(chan); + } + if (__dmac_channel_transmit_end_detected(chan)) { + __dmac_channel_clear_transmit_end(chan); + } + return IRQ_HANDLED; +} +static irqreturn_t jz_mmc_dma_tx_callback(int irq, void *devid) +{ + int chan = txdmachan; + + disable_dma(chan); + if (__dmac_channel_address_error_detected(chan)) { + printk(KERN_DEBUG "%s: DMAC address error.\n", + __FUNCTION__); + __dmac_channel_clear_address_error(chan); + } + if (__dmac_channel_transmit_end_detected(chan)) { + __dmac_channel_clear_transmit_end(chan); + } + return IRQ_HANDLED; +} + +/* Prepare DMA to start data transfer from the MMC card */ +static void jz_mmc_rx_setup_data(struct jz_mmc_host *host, + struct mmc_data *data) +{ + unsigned int nob = data->blocks; + int channelrx = rxdmachan; + int i; + u32 size; + + if (data->flags & MMC_DATA_STREAM) + nob = 0xffff; + + REG_MSC_NOB = nob; + REG_MSC_BLKLEN = data->blksz; + size = nob * data->blksz; + + if (data->flags & MMC_DATA_READ) { + host->dma.dir = DMA_FROM_DEVICE; + } else { + host->dma.dir = DMA_TO_DEVICE; + } + + host->dma.len = + dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + host->dma.dir); + + for (i = 0; i < host->dma.len; i++) { + host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]); + host->sg_cpu[i].dcmd = sg_dma_len(&data->sg[i]); + dma_cache_wback_inv((unsigned long) + CKSEG0ADDR(sg_dma_address(data->sg)) + + data->sg->offset, + host->sg_cpu[i].dcmd); + jz_mmc_start_dma(channelrx, host->sg_cpu[i].dtadr, + host->sg_cpu[i].dcmd, DMA_MODE_READ); + } +} + +/* Prepare DMA to start data transfer from the MMC card */ +static void jz_mmc_tx_setup_data(struct jz_mmc_host *host, + struct mmc_data *data) +{ + unsigned int nob = data->blocks; + int channeltx = txdmachan; + int i; + u32 size; + + if (data->flags & MMC_DATA_STREAM) + nob = 0xffff; + + REG_MSC_NOB = nob; + REG_MSC_BLKLEN = data->blksz; + size = nob * data->blksz; + + if (data->flags & MMC_DATA_READ) { + host->dma.dir = DMA_FROM_DEVICE; + } else { + host->dma.dir = DMA_TO_DEVICE; + } + + host->dma.len = + dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + host->dma.dir); + + for (i = 0; i < host->dma.len; i++) { + host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]); + host->sg_cpu[i].dcmd = sg_dma_len(&data->sg[i]); + dma_cache_wback_inv((unsigned long) + CKSEG0ADDR(sg_dma_address(data->sg)) + + data->sg->offset, + host->sg_cpu[i].dcmd); + jz_mmc_start_dma(channeltx, host->sg_cpu[i].dtadr, + host->sg_cpu[i].dcmd, DMA_MODE_WRITE); + } +} +#else +static void jz_mmc_receive_pio(struct jz_mmc_host *host) +{ + + struct mmc_data *data = 0; + int sg_len = 0, max = 0, count = 0; + u32 *buf = 0; + struct scatterlist *sg; + unsigned int nob; + + data = host->mrq->data; + nob = data->blocks; + REG_MSC_NOB = nob; + REG_MSC_BLKLEN = data->blksz; + + max = host->pio.len; + if (host->pio.index < host->dma.len) { + sg = &data->sg[host->pio.index]; + buf = sg_virt(sg) + host->pio.offset; + + /* This is the space left inside the buffer */ + sg_len = sg_dma_len(&data->sg[host->pio.index]) - host->pio.offset; + /* Check to if we need less then the size of the sg_buffer */ + if (sg_len < max) max = sg_len; + } + max = max / 4; + for(count = 0; count < max; count++) { + while (REG_MSC_STAT & MSC_STAT_DATA_FIFO_EMPTY) + ; + *buf++ = REG_MSC_RXFIFO; + } + host->pio.len -= count; + host->pio.offset += count; + + if (sg_len && count == sg_len) { + host->pio.index++; + host->pio.offset = 0; + } +} + +static void jz_mmc_send_pio(struct jz_mmc_host *host) +{ + + struct mmc_data *data = 0; + int sg_len, max, count = 0; + u32 *wbuf = 0; + struct scatterlist *sg; + unsigned int nob; + + data = host->mrq->data; + nob = data->blocks; + + REG_MSC_NOB = nob; + REG_MSC_BLKLEN = data->blksz; + + /* This is the pointer to the data buffer */ + sg = &data->sg[host->pio.index]; + wbuf = sg_virt(sg) + host->pio.offset; + + /* This is the space left inside the buffer */ + sg_len = data->sg[host->pio.index].length - host->pio.offset; + + /* Check to if we need less then the size of the sg_buffer */ + max = (sg_len > host->pio.len) ? host->pio.len : sg_len; + max = max / 4; + for(count = 0; count < max; count++ ) { + while (REG_MSC_STAT & MSC_STAT_DATA_FIFO_FULL) + ; + REG_MSC_TXFIFO = *wbuf++; + } + + host->pio.len -= count; + host->pio.offset += count; + + if (count == sg_len) { + host->pio.index++; + host->pio.offset = 0; + } +} + +static int +jz_mmc_prepare_data(struct jz_mmc_host *host, struct mmc_data *data) +{ + int datalen = data->blocks * data->blksz; + + host->dma.dir = DMA_BIDIRECTIONAL; + host->dma.len = dma_map_sg(mmc_dev(host->mmc), data->sg, + data->sg_len, host->dma.dir); + if (host->dma.len == 0) + return -ETIMEDOUT; + + host->pio.index = 0; + host->pio.offset = 0; + host->pio.len = datalen; + return 0; +} +#endif + +static int jz_mmc_cmd_done(struct jz_mmc_host *host, unsigned int stat); + +static void jz_mmc_finish_request(struct jz_mmc_host *host, struct mmc_request *mrq) +{ + jz_mmc_stop_clock(); + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + mmc_request_done(host->mmc, mrq); +} + +static void jz_mmc_start_cmd(struct jz_mmc_host *host, + struct mmc_command *cmd, unsigned int cmdat) +{ + u32 timeout = 0x3fffff; + unsigned int stat; + struct jz_mmc_host *hst = host; + WARN_ON(host->cmd != NULL); + host->cmd = cmd; + + /* stop MMC clock */ + jz_mmc_stop_clock(); + + /* mask interrupts */ + REG_MSC_IMASK = 0xff; + + /* clear status */ + REG_MSC_IREG = 0xff; + + if (cmd->flags & MMC_RSP_BUSY) + cmdat |= MSC_CMDAT_BUSY; + +#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE)) + switch (RSP_TYPE(mmc_resp_type(cmd))) { + case RSP_TYPE(MMC_RSP_R1): /* r1,r1b, r6, r7 */ + cmdat |= MSC_CMDAT_RESPONSE_R1; + r_type = 1; + break; + case RSP_TYPE(MMC_RSP_R3): + cmdat |= MSC_CMDAT_RESPONSE_R3; + r_type = 1; + break; + case RSP_TYPE(MMC_RSP_R2): + cmdat |= MSC_CMDAT_RESPONSE_R2; + r_type = 2; + break; + default: + break; + } + REG_MSC_CMD = cmd->opcode; + + /* Set argument */ +#ifdef CONFIG_JZ_MMC_BUS_1 + if (cmd->opcode == 6) { + /* set 1 bit sd card bus*/ + if (cmd->arg ==2) + REG_MSC_ARG = 0; + + /* set 1 bit mmc card bus*/ + if (cmd->arg == 0x3b70101) + REG_MSC_ARG = 0x3b70001; + } else + REG_MSC_ARG = cmd->arg; +#else + REG_MSC_ARG = cmd->arg; +#endif + + /* Set command */ + REG_MSC_CMDAT = cmdat; + + /* Send command */ + jz_mmc_start_clock(); + + while (timeout-- && !(REG_MSC_STAT & MSC_STAT_END_CMD_RES)) + ; + + REG_MSC_IREG = MSC_IREG_END_CMD_RES; /* clear irq flag */ + if (cmd->opcode == 12) { + while (timeout-- && !(REG_MSC_IREG & MSC_IREG_PRG_DONE)) + ; + REG_MSC_IREG = MSC_IREG_PRG_DONE; /* clear status */ + } + if (!mmc_slot_enable) { + /* It seems that MSC can't report the MSC_STAT_TIME_OUT_RES when + * card was removed. We force to return here. + */ + cmd->error = -ETIMEDOUT; + jz_mmc_finish_request(hst, hst->mrq); + return; + } + + if (SD_IO_SEND_OP_COND == cmd->opcode) { + /* + * Don't support SDIO card currently. + */ + cmd->error = -ETIMEDOUT; + jz_mmc_finish_request(hst, hst->mrq); + return; + } + + /* Check for status */ + stat = REG_MSC_STAT; + jz_mmc_cmd_done(hst, stat); + if (host->data) { + if (cmd->opcode == MMC_WRITE_BLOCK || cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) +#ifdef USE_DMA + jz_mmc_tx_setup_data(host, host->data); +#else + jz_mmc_send_pio(host); + else + jz_mmc_receive_pio(host); +#endif + } +} + +static int jz_mmc_cmd_done(struct jz_mmc_host *host, unsigned int stat) +{ + struct mmc_command *cmd = host->cmd; + int i, temp[16]; + u8 *buf; + u32 data, v, w1, w2; + + if (!cmd) + return 0; + + host->cmd = NULL; + buf = (u8 *) temp; + switch (r_type) { + case 1: + { + data = REG_MSC_RES; + buf[0] = (data >> 8) & 0xff; + buf[1] = data & 0xff; + data = REG_MSC_RES; + buf[2] = (data >> 8) & 0xff; + buf[3] = data & 0xff; + data = REG_MSC_RES; + buf[4] = data & 0xff; + cmd->resp[0] = + buf[1] << 24 | buf[2] << 16 | buf[3] << 8 | + buf[4]; + break; + } + case 2: + { + data = REG_MSC_RES; + v = data & 0xffff; + for (i = 0; i < 4; i++) { + data = REG_MSC_RES; + w1 = data & 0xffff; + data = REG_MSC_RES; + w2 = data & 0xffff; + cmd->resp[i] = v << 24 | w1 << 8 | w2 >> 8; + v = w2; + } + break; + } + case 0: + break; + } + if (stat & MSC_STAT_TIME_OUT_RES) { + printk("MSC_STAT_TIME_OUT_RES\n"); + cmd->error = -ETIMEDOUT; + } else if (stat & MSC_STAT_CRC_RES_ERR && cmd->flags & MMC_RSP_CRC) { + printk("MSC_STAT_CRC\n"); + if (cmd->opcode == MMC_ALL_SEND_CID || + cmd->opcode == MMC_SEND_CSD || + cmd->opcode == MMC_SEND_CID) { + /* a bogus CRC error can appear if the msb of + the 15 byte response is a one */ + if ((cmd->resp[0] & 0x80000000) == 0) + cmd->error = -EILSEQ; + } + } + /* + * Did I mention this is Sick. We always need to + * discard the upper 8 bits of the first 16-bit word. + */ + if (host->data && cmd->error == 0) + jz_mmc_enable_irq(host, MSC_IMASK_DATA_TRAN_DONE); + else + jz_mmc_finish_request(host, host->mrq); + + return 1; +} + +static int jz_mmc_data_done(struct jz_mmc_host *host, unsigned int stat) +{ + struct mmc_data *data = host->data; + + if (!data) + return 0; + REG_MSC_IREG = MSC_IREG_DATA_TRAN_DONE; /* clear status */ + jz_mmc_stop_clock(); + dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len, + host->dma_dir); + if (stat & MSC_STAT_TIME_OUT_READ) { + printk("MMC/SD timeout, MMC_STAT 0x%x\n", stat); + data->error = -ETIMEDOUT; + } else if (REG_MSC_STAT & + (MSC_STAT_CRC_READ_ERROR | MSC_STAT_CRC_WRITE_ERROR)) { + printk("MMC/SD CRC error, MMC_STAT 0x%x\n", stat); + data->error = -EILSEQ; + } + /* + * There appears to be a hardware design bug here. There seems to + * be no way to find out how much data was transferred to the card. + * This means that if there was an error on any block, we mark all + * data blocks as being in error. + */ + if (data->error == 0) + data->bytes_xfered = data->blocks * data->blksz; + else + data->bytes_xfered = 0; + + jz_mmc_disable_irq(host, MSC_IMASK_DATA_TRAN_DONE); + host->data = NULL; + if (host->mrq->stop) { + jz_mmc_stop_clock(); + jz_mmc_start_cmd(host, host->mrq->stop, 0); + } else { + jz_mmc_finish_request(host, host->mrq); + } + return 1; +} + +static void jz_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct jz_mmc_host *host = mmc_priv(mmc); + unsigned int cmdat; + + /* stop MMC clock */ + jz_mmc_stop_clock(); + + /* Save current request for the future processing */ + host->mrq = mrq; + host->data = mrq->data; + cmdat = host->cmdat; + host->cmdat &= ~MSC_CMDAT_INIT; + + if (mrq->data) { + cmdat &= ~MSC_CMDAT_BUSY; +#ifdef USE_DMA + if ((mrq->cmd->opcode == 51) | (mrq->cmd->opcode == 8) | (mrq->cmd->opcode == 6)) + + cmdat |= + MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN | + MSC_CMDAT_DMA_EN; + else { +#ifdef CONFIG_JZ_MMC_BUS_1 + cmdat &= ~MSC_CMDAT_BUS_WIDTH_4BIT; + cmdat |= MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN | + MSC_CMDAT_DMA_EN; +#else + cmdat |= MSC_CMDAT_DATA_EN | MSC_CMDAT_DMA_EN; +#endif + } + if (mrq->data->flags & MMC_DATA_WRITE) + cmdat |= MSC_CMDAT_WRITE; + + if (mrq->data->flags & MMC_DATA_STREAM) + cmdat |= MSC_CMDAT_STREAM_BLOCK; + if (mrq->cmd->opcode != MMC_WRITE_BLOCK + && mrq->cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK) + jz_mmc_rx_setup_data(host, mrq->data); +#else /*USE_DMA*/ + + if ((mrq->cmd->opcode == 51) | (mrq->cmd->opcode == 8) | (mrq->cmd->opcode == 6)) + cmdat |= MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN; + else { +#ifdef CONFIG_JZ_MMC_BUS_1 + cmdat &= ~MSC_CMDAT_BUS_WIDTH_4BIT; + cmdat |= MSC_CMDAT_BUS_WIDTH_1BIT | MSC_CMDAT_DATA_EN; +#else + cmdat |= MSC_CMDAT_DATA_EN; +#endif + } + if (mrq->data->flags & MMC_DATA_WRITE) + cmdat |= MSC_CMDAT_WRITE; + + if (mrq->data->flags & MMC_DATA_STREAM) + cmdat |= MSC_CMDAT_STREAM_BLOCK; + jz_mmc_prepare_data(host, host->data); +#endif /*USE_DMA*/ + } + jz_mmc_start_cmd(host, mrq->cmd, cmdat); +} + +static irqreturn_t jz_mmc_irq(int irq, void *devid) +{ + struct jz_mmc_host *host = devid; + unsigned int ireg; + int handled = 0; + + ireg = REG_MSC_IREG; + + if (ireg) { + unsigned stat = REG_MSC_STAT; + if (ireg & MSC_IREG_DATA_TRAN_DONE) + handled |= jz_mmc_data_done(host, stat); + } + return IRQ_RETVAL(handled); +} + +/* Returns true if MMC slot is empty */ +static int jz_mmc_slot_is_empty(int slot) +{ + int empty; + + empty = (__msc_card_detected(slot) == 0) ? 1 : 0; + + if (empty) { + /* wait for card insertion */ +#ifdef CONFIG_MIPS_JZ4740_LYRA + __gpio_as_irq_rise_edge(MSC_HOTPLUG_PIN); +#else + __gpio_as_irq_fall_edge(MSC_HOTPLUG_PIN); +#endif + } else { + /* wait for card removal */ +#ifdef CONFIG_MIPS_JZ4740_LYRA + __gpio_as_irq_fall_edge(MSC_HOTPLUG_PIN); +#else + __gpio_as_irq_rise_edge(MSC_HOTPLUG_PIN); +#endif + } + + return empty; +} + +static irqreturn_t jz_mmc_detect_irq(int irq, void *devid) +{ + struct jz_mmc_host *host = (struct jz_mmc_host *) devid; + + if (jz_mmc_slot_is_empty(0)) { + mmc_slot_enable = 0; + mmc_detect_change(host->mmc, 50); + } else { + mmc_slot_enable = 1; + mmc_detect_change(host->mmc, 50); + } + return IRQ_HANDLED; +} + +static int jz_mmc_get_ro(struct mmc_host *mmc) +{ + struct jz_mmc_host *host = mmc_priv(mmc); + + if (host->pdata && host->pdata->get_ro) + return host->pdata->get_ro(mmc_dev(mmc)); + /* Host doesn't support read only detection so assume writeable */ + return 0; +} + +/* set clock and power */ +static void jz_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct jz_mmc_host *host = mmc_priv(mmc); + + if (ios->clock) + jz_mmc_set_clock(ios->clock); + else + jz_mmc_stop_clock(); + + if (host->power_mode != ios->power_mode) { + host->power_mode = ios->power_mode; + + if (ios->power_mode == MMC_POWER_ON) + host->cmdat |= CMDAT_INIT; + } + + if ((ios->bus_width == MMC_BUS_WIDTH_4) || (ios->bus_width == MMC_BUS_WIDTH_8)) + host->cmdat |= MSC_CMDAT_BUS_WIDTH_4BIT; + else + host->cmdat &= ~MSC_CMDAT_BUS_WIDTH_4BIT; +} + +static const struct mmc_host_ops jz_mmc_ops = { + .request = jz_mmc_request, + .get_ro = jz_mmc_get_ro, + .set_ios = jz_mmc_set_ios, +}; +static int jz_mmc_pm_callback(struct pm_dev *pm_dev, + pm_request_t req, void *data); + +static int jz_mmc_probe(struct platform_device *pdev) +{ + int retval; + struct mmc_host *mmc; + struct jz_mmc_host *host = NULL; + int irq; + struct resource *r; + + __gpio_as_msc(); + __msc_init_io(); + __msc_enable_power(); + + __msc_reset(); + + /* On reset, stop MMC clock */ + jz_mmc_stop_clock(); + + MMC_IRQ_MASK(); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!r || irq < 0) + return -ENXIO; + + r = request_mem_region(r->start, SZ_4K, DRIVER_NAME); + if (!r) + return -EBUSY; + + mmc = mmc_alloc_host(sizeof(struct jz_mmc_host), &pdev->dev); + if (!mmc) { + retval = -ENOMEM; + goto out; + } + mmc->ops = &jz_mmc_ops; + mmc->f_min = MMC_CLOCK_SLOW; + mmc->f_max = SD_CLOCK_FAST; + /* + * We can do SG-DMA, but we don't because we never know how much + * data we successfully wrote to the card. + */ + mmc->max_phys_segs = NR_SG; + /* + * Our hardware DMA can handle a maximum of one page per SG entry. + */ + mmc->max_seg_size = PAGE_SIZE; + /* + * Block length register is 10 bits. + */ + mmc->max_blk_size = 1023; + /* + * Block count register is 16 bits. + */ + mmc->max_blk_count = 65535; + host = mmc_priv(mmc); + host->mmc = mmc; + host->pdata = pdev->dev.platform_data; + mmc->ocr_avail = host->pdata ? + host->pdata->ocr_mask : MMC_VDD_32_33 | MMC_VDD_33_34; + host->mmc->caps = + MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_SD_HIGHSPEED + | MMC_CAP_MMC_HIGHSPEED; + /* + *MMC_CAP_4_BIT_DATA (1 << 0) The host can do 4 bit transfers + * + */ + host->sg_cpu = + dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, + GFP_KERNEL); + if (!host->sg_cpu) { + retval = -ENOMEM; + goto out; + } + spin_lock_init(&host->lock); + host->irq = IRQ_MSC; + host->imask = 0xff; + /* + * Ensure that the host controller is shut down, and setup + * with our defaults. + */ + retval = request_irq(IRQ_MSC, jz_mmc_irq, 0, "MMC/SD", host); + if (retval) { + printk(KERN_ERR "MMC/SD: can't request MMC/SD IRQ\n"); + return retval; + } + jz_mmc_slot_is_empty(0); + /* Request card detect interrupt */ + + retval = request_irq(MSC_HOTPLUG_IRQ, jz_mmc_detect_irq, 0, //SA_INTERRUPT, + "MMC card detect", host); + if (retval) { + printk(KERN_ERR "MMC/SD: can't request card detect IRQ\n"); + goto err1; + } +#ifdef USE_DMA + /* Request MMC Rx DMA channel */ + rxdmachan = + jz_request_dma(DMA_ID_MSC_RX, "MMC Rx", jz_mmc_dma_rx_callback, + 0, host); + if (rxdmachan < 0) { + printk(KERN_ERR "jz_request_dma failed for MMC Rx\n"); + goto err2; + } + + /* Request MMC Tx DMA channel */ + txdmachan = + jz_request_dma(DMA_ID_MSC_TX, "MMC Tx", jz_mmc_dma_tx_callback, + 0, host); + if (txdmachan < 0) { + printk(KERN_ERR "jz_request_dma failed for MMC Tx\n"); + goto err3; + } +#endif + platform_set_drvdata(pdev, mmc); + mmc_add_host(mmc); +#ifdef CONFIG_PM + /* Register MMC slot as as power-managed device */ + host->pmdev = pm_register(PM_UNKNOWN_DEV, PM_SYS_UNKNOWN, jz_mmc_pm_callback); + if (host->pmdev) + host->pmdev->data = pdev; +#endif + printk("JZ SD/MMC card driver registered\n"); + + /* Detect card during initialization */ +#ifdef CONFIG_SOC_JZ4740 + if (!jz_mmc_slot_is_empty(0)) { + mmc_slot_enable = 1; + mmc_detect_change(host->mmc, 0); + } +#endif + return 0; + +err1:free_irq(IRQ_MSC, &host); +#ifdef USE_DMA + err2:jz_free_dma(rxdmachan); + err3:jz_free_dma(txdmachan); +#endif +out: + if (host) { + if (host->sg_cpu) + dma_free_coherent(&pdev->dev, PAGE_SIZE, + host->sg_cpu, host->sg_dma); + } + if (mmc) + mmc_free_host(mmc); + return -1; +} + +static int jz_mmc_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + long flags; + + platform_set_drvdata(pdev, NULL); + + if (mmc) { + struct jz_mmc_host *host = mmc_priv(mmc); + + if (host->pdata && host->pdata->exit) + host->pdata->exit(&pdev->dev, mmc); + + mmc_remove_host(mmc); + + local_irq_save(flags); + jz_mmc_stop_clock(); + __msc_disable_power(); + jz_free_dma(rxdmachan); + jz_free_dma(txdmachan); + free_irq(IRQ_MSC, host); + local_irq_restore(flags); + mmc_free_host(mmc); + } + return 0; +} + +#ifdef CONFIG_PM +pm_message_t state; +static int jz_mmc_suspend(struct platform_device *dev, pm_message_t state) +{ + struct mmc_host *mmc = platform_get_drvdata(dev); + int ret = 0; + + __msc_disable_power(); + if (mmc) + ret = mmc_suspend_host(mmc, state); + + return ret; +} + +static int jz_mmc_resume(struct platform_device *dev) +{ + struct mmc_host *mmc = platform_get_drvdata(dev); + int ret = 0; +#if 0 + /*for sandisk BB0807011816D and other strange cards*/ + int i; + + for(i = 104; i < 110; i++) + __gpio_as_input(i); + + /* perhaps you should mdelay more */ + mdelay(1000); + __gpio_as_msc(); +#endif + __msc_init_io(); + __msc_enable_power(); + __msc_reset(); + + if (!jz_mmc_slot_is_empty(0)) { + mmc_slot_enable = 1; + mmc_detect_change(mmc, 10); + } + + if (mmc) + ret = mmc_resume_host(mmc); + + return ret; +} +static int jz_mmc_pm_callback(struct pm_dev *pm_dev, + pm_request_t req, void *data) +{ + struct platform_device *pdev = (struct platform_device *)pm_dev->data; + + switch(req) { + case PM_RESUME: + jz_mmc_resume(pdev); + break; + case PM_SUSPEND: + /* state has no use */ + jz_mmc_suspend(pdev, state); + break; + default: + printk("MMC/SD: invalid PM request %d\n", req); + break; + } + return 0; +} +#else +#define jz_mmc_suspend NULL +#define jz_mmc_resume NULL +#endif + +static struct platform_driver jz_mmc_driver = { + .probe = jz_mmc_probe, + .remove = jz_mmc_remove, + .suspend = jz_mmc_suspend, + .resume = jz_mmc_resume, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init jz_mmc_init(void) +{ + return platform_driver_register(&jz_mmc_driver); +} + +static void __exit jz_mmc_exit(void) +{ + platform_driver_unregister(&jz_mmc_driver); +} + +module_init(jz_mmc_init); +module_exit(jz_mmc_exit); + +MODULE_DESCRIPTION("JZ47XX SD/Multimedia Card Interface Driver"); +MODULE_LICENSE("GPL"); --- /dev/null +++ b/drivers/mmc/host/jz_mmc.h @@ -0,0 +1,65 @@ +#ifndef __JZ_MMC_H__ +#define __JZ_MMC_H__ + +#define MMC_CLOCK_SLOW 400000 /* 400 kHz for initial setup */ +#define MMC_CLOCK_FAST 20000000 /* 20 MHz for maximum for normal operation */ +#define SD_CLOCK_FAST 24000000 /* 24 MHz for SD Cards */ +#define MMC_NO_ERROR 0 +/* Extra MMC commands for state control */ +/* Use negative numbers to disambiguate */ +#define MMC_CIM_RESET -1 +#define MMC_SET_CLOCK 100 + +typedef struct jzsoc_dma_desc { + volatile u32 ddadr; /* Points to the next descriptor + flags */ + volatile u32 dsadr; /* DSADR value for the current transfer */ + volatile u32 dtadr; /* DTADR value for the current transfer */ + volatile u32 dcmd; /* DCMD value for the current transfer */ +} jzsoc_dma_desc; + + + + +#include + +struct device; +struct mmc_host; + +struct jz_mmc_platform_data { + unsigned int ocr_mask; /* available voltages */ + unsigned long detect_delay; /* delay in jiffies before detecting cards after interrupt */ + int (*init)(struct device *, irq_handler_t , void *); + int (*get_ro)(struct device *); + void (*setpower)(struct device *, unsigned int); + void (*exit)(struct device *, void *); +}; + +//extern void pxa_set_mci_info(struct pxamci_platform_data *info); + + + +#define SZ_1K 0x00000400 +#define SZ_4K 0x00001000 +#define SZ_8K 0x00002000 +#define SZ_16K 0x00004000 +#define SZ_64K 0x00010000 +#define SZ_128K 0x00020000 +#define SZ_256K 0x00040000 +#define SZ_512K 0x00080000 + +#define SZ_1M 0x00100000 +#define SZ_2M 0x00200000 +#define SZ_4M 0x00400000 +#define SZ_8M 0x00800000 +#define SZ_16M 0x01000000 +#define SZ_32M 0x02000000 +#define SZ_64M 0x04000000 +#define SZ_128M 0x08000000 +#define SZ_256M 0x10000000 +#define SZ_512M 0x20000000 + +#define SZ_1G 0x40000000 +#define SZ_2G 0x80000000 + + +#endif /* __JZ_MMC_H__ */ --- /dev/null +++ b/drivers/mtd/mtdblock-jz.uu @@ -0,0 +1,535 @@ +begin 644 mtdblock-jz.o +M?T5,1@$!`0````````````$`"``!```````````````T-0```1``4#0````` +M`"@`(P`@`````````````````#P`@XR`*`4`'`!BC.K_!"0A**(```"CC/]_ +M`CS__T(T`P!A!"008@```,*L(2````@`X`,A$(````"#C```HHP``(*L"`#@ +M`P``HZS8_[TG(3B@`"``M*\AH(``)`"(CAP`LZ\AF,``0#`'`,`0!P"`(`<` +M*AAF`B0`OZ\8`+*O%`"QKQ``L*\@`(F.`0#*)"$02`!$`&`4(2"(````@XP` +M`$*,`"D#```A`@"`&`,`@!`"`",@@@`C**,`(2")`"$HJ0```(*,``"CC"L0 +M8@`U`$`0(8C``(`0!P"`(`H`(9`"`8`8$0`J$&H"(2"(``\`0!0A&&@```"" +MC```8XP`(0(``"D#`(`0`@"`&`,`(R""`",HHP`A((D`(2BI````@HP``*., +M*Q!B``N(0@$?`"<2@(`1`"&`$`$``$*.```#C@`A`@``*0,`@!`"`(`8`P`C +M(((`(RBC`"$H)0$A("0!"`"$)`X```P(`*4D(2!``@X```PA*``"(3@@`D`P +M!P`D`(B.P!`'`(`@!P`J&&8"(`")C@$`RB0A$$@`OO]@$"$@B``V```((8C@ +M`"0`OX\@`+2/'`"SCQ@`LH\4`+&/$`"PCP@`X`,H`+TGR/^])RP`MZ\T`+^O +M,`"^KR@`MJ\D`+6O(`"TKQP`LZ\8`+*O%`"QKQ``L*\\`)6,`!$%`"``M(Z` +M*`4`(Q!%`"$05``(`%*,``"GCB0`OHXJ$$<"*0!`$"&X0`(`$1(`@"@2`",P +M10`A&(8"#`!DC```8HPK$((`(`!`$`P`TR2=```(([#R`"``HHXA$&("#`!$ +MC```0XPK&(,`%P!@$`P`/*`"VCR0`M8\@`+2/'`"SCQ@` +MLH\4`+&/$`"PCP@`X`,X`+TG```#/`@`X`/8`&*,```#/`@`X`/<`&*,Z/^] +M)Q``L*\4`+^O````#"&`@``A(``"%`"_CQ``L(\````(&`"])^#_O2<0`+"O +M(8"@`!0`L:\A*```(8B```@`!B08`+^O````#"$@``($``,D$``")`$``J(` +M``.B(``CCB$0``""&0,``@`#IA@`OX\4`+&/$`"PCP@`X`,@`+TGX/^])QP` +MOZ\4`+&O&`"RKQ``L*\``(*,(8B``*@`0XP4`%*,4`!DC%0`<(P!``@D(X`$ +M`@2`"`+__Q`R)``DCD*2$@`(`":N!``EKB$P``(,`">N+``HKO__4C(````, +M(2@```(P$G(H`"2.'`"_CQ@`LH\4`+&/$`"PCR$H```````((`"])XC_O2=P +M`+^O;`"WKV@`MJ]D`+6O8`"TKUP`LZ]8`+*O5`"QKU``L*\\`(.,``"3C``` +M:(RH`'*.`0`")0(`0B@@`&>,5`!&CE``1(X9`$`4)`!CC",0Q``!`!0D!!!4 +M`/__0C`A,&``__]5)"$@```8`+8G```7/`0`T(P!`(0D@!`0```9$``C&&(` +M(8CC``0`(I(!`$,P`@!",`,`0!0$`,8D$`!@%`````#R_X@4`````/3_`R1P +M`+^/;`"WCV@`MH]D`+6/8`"TCUP`LX]8`+*/5`"QCU``L(\A$&``"`#@`W@` +MO2<``+"L(2#``B$H```````,.``&),P`Y28,``(D(1@```(`!"0P`**O-`"C +MKT@`I:\8`+2O0`"DKU0`2([#'Q``)S`(`$(X$`!0`$2.!CC'``08`P$@``8Q +M!!`0`248XP`+&$8`!""5``L0!@`A,$0`PR\$`!``MJ\K0,(`(3AE`$@`8HXA +M.`O>`"VKW0`M:]P`+2O;`"SKV@`LJ]D`+&O8`"PKP```CP,`)&,)`!"C!P` +MD(Q7`$`0J``SCAT``A(```(\*`!")(8"``@!``0D&``#$@```````$.,`0"$ +M)/O_8!0$`$(D```#/```8HP?`$`0```2/```8B0$`$*,#`!`$``````7``(2 +M```"/`@`0B29`@`(`0`$)!0``Q(``!(\``!#C`$`A"3[_V`4!`!")"$0``"$ +M`+^/@`"^CWP`MX]X`+:/=`"UCW``M(]L`+./:`"RCV0`L8]@`+"/"`#@`X@` +MO2.)R`&`$`8"0`$&(,`!B#'`"4@9``@`,@P!BC)``L@J``A$(("```$K@`` +M0XPC$(,`"``#K@\`0!P$``*N```$/`````P``(0D```%C@@`!HX```0\```` +M#```A"0```0\````#```A"0```2.__^")`0``JX$``*.#``$KM"`!32`(`(` +M````#!```JX```2.'``"K@`1!`"`(`0`(R!$``````S0``4D```$CB```JZ` +M(`0`!`"$)`````S0``4D(1A``!P``HY;_T`0)``#KB```HX5_T`0]/\")!/_ +M8!`AD```4`"@KR&8```\`+"N```1CB&P0`(J$%$"5`!`$%@`LJ]"*!(`(8@` +M`%P`I:]4`**/7`"ECU0`1(P8`*,G$`"CKR<8!``&&&4`!#B3`"``A3`E.&<` +M!#"2`$@`XHX+.,4`(2#@`@GX0``+,`4````"/,P`1"3,`$.,!`""C`8`8A`! +M`#$F````#``````J$"("Y_]`%%0`HH\@``*.4`"ECP$``R0A$*(`!`!#H``` +M`CS,`$(D"`!#C/__`B1F`&(04`"DCR```HXA$((```!#K"0``XZ`$!(`(1!# +M``0`5JQ0`*6/(``#C@$`PB8A&*,`"`!BK%0`HH]<`*6/5`!$C(``XHXG&`0` +M!AAE``0XDP`@`(4P!#"2`"4X9P`+.,4````#/`LP!0`A(.`""?A``,P`<8Q# +M`$`06`"FCU@`I8\K`@`,(2"@`@$`0B8K(%(`(1B3`%``I(\AD$``#`"$)"&8 +M8`"/`P`(4`"DK\(7$0`\`+*.(1!1`$.``@`@`%:."```&B0`4XXA*``"(2!` +M`O__$"83```,(3`@`OO_`!8A*``"`@`B*AL`0!2`$!$`(8!3``$`%R0$`',F +M```#C@``8HX`*0,``"$"`(`8`P"`$`(`(R""`",HHP`A(,0"(2C%`@@`A"0. +M```,"`"E)/__,28A*``"#@``#"$@8`(A($`"`0`%)!,```PA,"`"ZO\W%OS_ +M$";__P,D3`#")P0`HZXA$(("```#/```5:S8`'6L``"DC@```CR=`@`(W`!$ +MK"$H(`+N`0`,(2"@`M\#``@!`$(F(``"CB$0H@#``P`(``!`K'C_O2=\`+.O +M>`"RKW``L*\<`*"O(`"@KX``OZ]T`+&O&`"@KR0`H*\H`*"O``"#CP```CP0 +M`+,G``!")"&`@``<`*.O(`"BKR$@8`(AD*``````#*@`$8X```(\*!9")&`` +MHJ\P`+"O5``GCL,?$@`G*`<`0B`2``8@I``$,.,`)3#$`#P`IJ]4`"2.$``% +MC@00D@`@`.`"RCW0`L8]P`+"/"`#@`X@`O2<````,```` +M`"$@8`(````,(2@@`B$0``*``+^/?`"SCW@`LH]T`+&/<`"PCP@`X`.(`+TG +MX/^])QP`OZ\8`+*O%`"QKQ``L*\D`*6O``"0C`0`A8PAD(``OP$`#*@`$8XD +M`*6/*P(`#"$@0`(D`*6/)@0`#"$@``(```4\```$/```I20F`$`4``"$)%0` +M)(XD`**/(`"%,"$P0`##/P(`0A@"`"<0!``&&$,`!#B'`"4X9P`$,(8`A``" +MC@LXQ0`A(``""?A```LP!0`A($`""`$`#"0`I2<```0\```%/```A"0#`$`0 +M``"E)`````P`````!`!%CB0`IH_N`0`,(2!``B0`HH\<`+^/&`"RCQ0`L8\0 +M`+"/"`#@`R``O2<````,)`"FCYD$``A4`"2.@/^])W``MJ]\`+^O>`"^KW0` +MMZ]L`+6O:`"TKV0`LZ]@`+*O7`"QKU@`L*^``*2O``"3C#P`@HRH`'6.$`!D +MCH0`I:\@`$*,T``%)%``HJ]4`+&.````#%``L(ZD`$`0(;!``!@`I"/<`"VCVP`M8]H`+2/9`"SCV``LH]<`+&/6`"PCP@` +MX`.``+TG9`4`"```A"1\`+^/>`"^CW0`MX]P`+:/;`"UCV@`M(]D`+./8`"R +MCUP`L8]8`+"/]/\")`@`X`.``+TG-`"$C`,`!20!``8D````""$X```@_[TG +MT`"VK[P`L:_<`+^OV`"^K]0`MZ_,`+6OR`"TK\0`LZ_``+*ON`"PKP``@HP! +M`!$DK`"BKZ@`0XP\`(6,J`"CKU0`8HQ0`&.,(`"EC",00P`$$%$`__]","&P +M@``L`(2,I`"EKZ``HJ\X`,6.#@"`%)P`I:_<`+^/V`"^C]0`MX_0`+:/S`"U +MC\@`M(_$`+./P`"RC[P`L8^X`+"/(1````@`X`/@`+TG&`"D)R$H```````, +M.``&)*P`J(\8`+&O%``$C0P``B0``-6.,`"BKP```CP@`*2O(1@```(`!"3, +M`$(D-`"CKT``I*\D`*"O2`"BKZ@`HXX4`**.E`"CKS@`Q(Z4`*6/F`"DKT(2 +M`@#__UC@(0T'/__Q`FC`"BK[``L*^(`*"O +MB`"CCR0`PH[__W,P(1!3````0Y"B`&`0E`"DCQ$`X!("$'=RD`"BKY``HX\H +M`,*.(2!#````@Y`,`6`0E`"EC_\%``@AD`````!"D`@!0!"4`*6/`0!")O__ +M4C#Z__(6(1"2`+``HX^(`*2/__]B,`$`A"0!`$(DX?]$%(@`I*\(`->.(8@` +M`(`@%P``&1<`I`"ECR.09``A@+(`!``"D@0`0C!D`$`0`````/G_(`:D`*6/ +M``#4CCP`QXZH`)..`0`1)%``8XY4`&*.4`"D)R,00P`$$%$`(2@``#@`!B0@ +M`/",````#/__7C!0`+&O```(/!0`AXX```@E#``")```!(UH`**O```"/"$8 +M``#_``4D6`"GK\P`0B0"``O7`"@KP````R``**O!`## +MC@``!#S,`(.L!`#"CLP`A20A@%`"!`"BK````XZ9`,`;"`"CK"&`X`+#CQ<` +M(9```$D&``A"J!<`D@#2$P````!4`&>.4`!HCB_T`4__\1)*P`I(\F!``,(2C@`B&(0``` +M``*.(2#``@$`0B0```*N=```#"$HX`*4_R$&(2C@`H($``PA(,`"```$/``` +M!3PA,.`"``"$)`@`PJX``*4D````#"$X0``(`->.@"`7``X&``@`&1<`C`"E +MCU``AXPA$+,`(1@``$(H`@`G(`<`!BB%``2(XP`@`.8P!(#B`"6(L0`+B`8" +MIP8`"`N`!@!(`$*.`0!")`4`0R@F`&`02`!"KI0`J(]0``>-(`#"C@08\P`A +M$$,`?`"BKU``HB<0`**O2`"BCB$P``(A."`""?A``"$@H`(``!0\```$/``` +MA"0``(4F(3#``Q<`0!`A.&`"```#/$@`8HPAD&``````#!``HJ\,`,.."`#" +MC@``!#S>_V(4``"$)`````P`````2`!"C@$`0B0%`$,HW/]@%$@`0JX```0\ +M``"$)```A28````,(3#``P``!#RP`*./2`"`K(@`I(___V(P`0"$)`$`0B03 +M_T04B`"DKPL&``@(`->.````#`````"!`$`0H`"BCW\`0!@"$%=P(8!``*`` +MHH_F!@`((9`"`GD`$A(`````J`"CCYP`I8]0`&2,&`"H)Q``J*\G$`0`K`"H +MCT(8$``A.```1`"EKP880P`$.(<`(`"%,`0PD``E.&<`2``"C0LXQ0`A(``! +M"?A```LP!0`!`!`FZ/]`$"&(0``A*.`"@@0`#"$@P`(AN$``@"`"```9`@`. +M!@`("`#"KB&0``",`*B/4`"FC"$0:`(A&```0B@"`"<@!@`&*(4`!(C#`"`` +MQS`$@,(`)8BQ``N(!P(9!P`("X`'`$0`@HX!`$(D!0!#*"(`8!!$`(*NF`"B +MCU``HR=\`**O$`"CKT@`HHXA,``"(3@@`@GX0``A(*`"```%/```"#P``*0D +M(3#``P``!248`$`0(3A@`@```SQ$`&*,(:!@``````P0`**O#`##C@@`PHX` +M``4\XO]B%```I"0````,`````$0`@HX!`$(D!0!#*.#_8!1$`(*N```$/``` +M"#P``(0D```%)0````PA,,`#```#/"L05P((`$`41`!@K`0&``BP`*./`0!" +M)O__4C`K&%<"NOY@$+``HX\H`,*.D`"DCY@`J(\A$((`(1!2````0Y!`.A(` +M(2@'`?+_8!0``@8DE`"BCU``1(P@`,*.!""3`"$@AP`````,(2""`$<'``@! +M`$(F#`#0C@@`PHX8``(24`"D)P``T8X\`,*.(2@``#@`!B0@`%*,````#*@` +M,XX,``(D`0`&)&@`HJ\```(\4`"FKR$8```"``8DS`!")"$@(`(A*``";`"C +MKW@`IJ\F!``,@`"BKR(`0!````,\``#"C@$`$"2H`$.,%`!1C%``9(Q4`&*, +M0HH1`",01``$@%``__\0,B0`Q(XA,``"__\Q,BP`P*X````,(2@```(P$7(H +M`,2.````#"$H``#<`+^/V`"^C]0`MX_0`+:/S`"UC\@`M(_$`+./P`"RC[P` +ML8^X`+"/(1````@`X`/@`+TG``!C)```9(S_``4D````#`P`!B2`&!```!$0 +M`",00P`A$$("``!#C```!#P!`&,DS`"$)```0ZP(`(.L5`!DCE``I2O +M=`"UKW``M*]L`+.O:`"RKV``L*^$`+^O@`"^KW@`MJ]D`+&O``"1C"&@@``4 +M`"*.J``VCD(2`@#__T0P&P"D`/0!@``!`!OD`"FKQ*H``!)`*,2$(`` +M`!@`OB>Q"``((`"W)P````P`````&P!R`O0!0`(!``(D(`"BKQ0`(XX8`**/ +M,`"'CC0`A8XH`*.O(2`@`BP`H*\0,````AA"/>`"VCW0`M8]P`+2/;`"SCV@`LH]D +M`+&/8`"PCR$0```(`.`#B`"])P(8D'`H`(*.6`"DCR$08@`A$$0```!#D"H` +M8!`@`+,G0!($`"``@XX$*+``(2BB`"$HHP`````,(2#@`(0`OX^``+Z/?`"W +MCW@`MH]T`+6/<`"TCVP`LX]H`+*/9`"QCV``L(\A$```"`#@`X@`O2. +M-`"0KE@`I(^0`*:/0"H$`)0`I(\````,(2BG`(0`OX^``+Z/?`"WCW@`MH]T +M`+6/<`"TCVP`LX]H`+*/9`"QCV``L(\A$```"`#@`X@`O2"5X("T^ +M($Y53$P*`&1R:79EF]N95]P +M='(@:7,@;G5L;`H```!Z;VYE7W!T"`*`````"5S.B!W"`*`````"5S.B!F:6QL +M7V)L;V-K,2!P:'ES7V)L;V-K.B5D+'!A9V4Z)60L7-?8FQO +M8VL*````)7,Z(')E861I;F<@97)R;W(@=VAE;B!M;V1I9GEI;F<@<&AY"5X(&5R87-I +M;F<@9F%I;&5D+"!M87)K960@8F%D+"!A;F0@9FEN9"!N97<@8FQO8VL@,'@E +M>`H`````)7,Z('!H>7-?8FQO8VL@,'@E>"!P````!T````?````]`4`````#X#X____```````` +M```H````'0```!\```#\!@``````@/C___\``````````!@````=````'P`` +M`+@'``````>`_/___P``````````(````!T````?````K`@``````X#X____ +M```````````@````'0```!\```"X"0````#_P/S___\``````````(@````= +M````'P```)@0``````^`^/___P``````````B````!T````?````"!(````` +M!X#\____```````````@````'0```!\````$$P````#_P/S___\````````` +M`(`````=````'P```"@6`````````````````````````````!T````?```` +M````````````````````````````````'0```!\```````````#_P/S___\` +M`````````.`````=````'P```/@>``````.`^/___P``````````(````!T` +M```?````;!\`````#X#X____```````````H````'0```!\````````````# +M@/C___\``````````"`````=````'P```'@A`````/_`_/___P`````````` +MB````!T````?`````````````8#\____```````````8````'0```!\```!4 +M)0`````````````````````````````=````'P```'@E`````/^`^/___P`` +M````````0````!T````?`````````````(#X____```````````8````'0`` +M`!\```"D)P`````````````````````````````=````'P````!'0T,Z("A' +M3E4I(#0N,2XR```N6UT86)?9W!L +M`"YR96Q?7VMC"\``"0```````````````0````````` +MVP````D```````````````Q)```P````(0```!<````$````"````.L````! +M`````P````````"<+P``&P```````````````0````````#V````"`````,` +M````````P"\``/```````````````!``````````^P````@````#```0```` +M`,`O```````````````````$``````````4!```!``````````````#`+P`` +M0`0`````````````!``````````!`0``"0``````````````/$D``!`!```A +M````'`````0````(````"@$```$````````````````T```````````````` +M```!`````````!@!```!````````````````-```$@```````````````0`` +M```````1`````P``````````````$C0``"$!``````````````$````````` +M`0````(``````````````$Q*``"0"@``(@```(8````$````$`````D````# +M``````````````#<5```@`@``````````````0````````!H`0``!`$``'0! +M```$`0``J`$```0!``!0`@``!`$``*@"```$`0``M`(```0!``#\`@``!1H` +M``0#```&&@``"`,```4:```0`P``!AH``"`#```$J```-`,```2+``!<`P`` +M!)D``/0#```$F0``&`0```29```4!0``!)D``!P%```&&@``F`4```8:``#L +M!0``!`$```P&```%HP``%`8```:C``````0!0```9X``!$%```!)<``+`2```%:```2!0```9H``!P%``` +M!`$``(`4```%>0``A!0```9Y```\%```!6@``(P4```&:```D!0```27``"H +M%```!`$``+@4```$`0``Z!0```0!```H%0``!`$``(@5```%>@``C!4```9Z +M``"4%0``!)<``'P4```%:```F!4```9H``"@%0``!`$``*@5```$`0``L!4` +M``2+``#L%0``!`$``/04```%>P``\!4```9[```T%@``!(X``/`6```$F0`` +M$!<```4:```@%P``!AH``'07```$F0``Z!<```0!``",&```!)D``)@8```% +M?```H!@```9\``"P&```!1H``,`8```&&@``V!@```29``#D&```!1H``.@8 +M```&&@``E`0```4:``#P&```!AH``!09```$`0``D!D```0!``"8&0``!7T` +M`*09```&?0``K!0```5H``"H&0``!F@``+`9```$EP``Y!D```0!````&@`` +M!`$``!`:```$`0``&!H```5^```D&@``!GX``)P9```%:```+!H```9H```P +M&@``!)<``$`:```$`0``>!H```0!``"`&@``!AH``)`:```&&@``S!H```5_ +M``#0&@``!G\``!P:```%:```U!H```9H``#D&@``!1H``.@:```&&@``\!H` +M``27````&P``!8````@;```&@```#!L```27```4&P``!AH``"0;```&&@`` +M*!L```6!```L&P``!H$``,@:```%:```,!L```9H```T&P``!)<``#P;```% +M&@``1!L```8:``!@&P``!`$``&@;```$E0``B!L```0!``#X&P``!`$```P< +M```$`0``2!P```0!``!0'```!AH``&`<```&&@``B!P```6"``"0'```!H(` +M`(P<```%:```F!P```9H``"D'```!1H``*@<```&&@``L!P```27``#`'``` +M!8```,@<```&@```S!P```27``#4'```!AH``.0<```&&@``Z!P```6!``#P +M'```!H$``.P<```%:```]!P```9H``#X'```!)<````=```%&@``#!T```8: +M```0'0``!`$``&@=```$B@`````$F0``+!X```29``#@'0``!7P``&@>```&?``` +M=!X```29``"0'@``!1H``)@>```&&@``Z!X```0!``#P'@``!`$```P?```% +M&@``$!\```8:```D'P``!)8``"P?```$G@``-!\```2=``"$'P``!1H``(@? +M```&&@``G!\```0!``"8'P``!1H``*`?```&&@``N!\```4:``"\'P``!AH` +M`,0?```&&@``W!\```0!``#8'P``!1H``.`?```&&@``&"````4:```@(``` +M!AH``#0@```$E@``/"````2>``!$(```!)T``'0@```$BP``?"````2+``"$ +M(```!(L``(P@```$BP``E"````2+``"<(```!(L``*0@```$BP``K"````2+ +M``"T(```!(L``+P@```$BP``Z"````4:``#L(```!AH``/@@```%@P``_"`` +M``:#````(0``!)<``/0@```%#P``!"$```8/```((0``!`$``"@A```$E@`` +M,"$```2>```X(0``!)T```PB```$`0``%"(```29``"4(@``!6@``*`B```& +M:```F"(```6$``"D(@``!H0``)PB```%#P``K"(```8/``"P(@``!)<``+PB +M```$`0``S"(```0!``#L(@``!)D``%PC```$B@``L",```2*``#T(P``!)D` +M`(@D```$B@``S"0```0!``#4)```!`$``-PD```$E@``Y"0```2>``#L)``` +M!)T``/0D```%:````"4```9H``#X)```!80```0E```&A```""4```27``#\ +M)```!0\```PE```&#P``%"4```0!```<)0``!`$``#@E```$`0``6"4```4: +M``!@)0``!AH``'`E```$`0``^"4```0!```4)@``!`$``"`F```$`0``/"8` +M``0!``!,)@``!`$``)0F```$B@``V"8```26``#@)@``!)X``.@F```$G0`` +M]"8```0!````)P``!`$```PG```%A0``+"8```6%```4)P``!H4``!@G```$ +MEP``$"<```5H```<)P``!F@``"`G```$`0``+"<```26```T)P``!)X``#PG +M```$G0``2"<```0!``!@)P``!`$``&@G```%A0``;"<```:%``!P)P``!)<` +M`%0G```%:```="<```9H``!X)P``!`$``(PG```$`0``J"<```4:``"P)P`` +M!AH``,`G```$`0``$`````4:```4````!AH``"`````%&@``*`````8:```P +M````!*```%@````%&@``8`````8:``!H````!*```)0````$FP``D`````41 +M``"8````!A$```0````$D0````````41```(````!A$````````"&@`````` +M``*4```$`````A````@````"H0``#`````(0```0`````HD``!0````"$``` +M&`````*0```<`````A```"`````"B```)`````(0`````````J4```0````" +MI@``"`````*?```,`````HT``!`````"AP````````((```4`````@$``!@` +M```"`0``'`````(!```@`````@$``"0````"`0``*`````(!```L`````@$` +M`#`````"`0````````(%`````````@,````````"&0``!`````(#```,```` +M`AD``!`````"`P``&`````(9```<`````@,````````"`0``(`````(!``!` +M`````@$``&`````"`0``@`````*)``"@`````J$``,`````"`P``X`````(# +M`````0```@,``"`!```"`P``0`$```(!``!@`0```@$``(`!```"`0``H`$` +M``(!``#``0```@$``.`!```"`0````(```(!```@`@```@$``$`"```"`0`` +M8`(```(!``"``@```@$``*`"```"`0``P`(```(!``#@`@```@4````#```" +MG@``(`,```(!``!``P```@$``&`#```"E```@`,```(!``"@`P```H@``,`# +M```"`0``X`,```(!````!````I```"`$```"`0`````````````````````` +M``````````````````,``0`````````````````#``(````````````````` +M`P`#``````````````````,`!``````````````````#``4````````````` +M`````P`&``````````````````,`!P`````````````````#``@````````` +M`````````P`)``````````````````,`"@`````````````````#``L````` +M`````````````P`,``````````````````,`#0`````````````````#``X` +M`````````````````P`/``````````````````,`$``````````````````# +M`!$``````````````````P`2``````````````````,`$P`````````````` +M```#`!0``````````````````P`5``````````````````,`%@`````````` +M```````#`!<``````````````````P`8``````````````````,`&0`````` +M```````````#`!H``````````````````P`;``````````````````,`'``` +M```````````````#`!T``````````````````P`>``````````````````,` +M'P`````````````````#`"```````````````````P`A```````````````` +M``,`(@`!```````````````$`/'_#P`````````X`````@`!`"H````X```` +M%`````(``0`X````3````(0!```"``$`3````-`!```L`0```@`!`&4```#8 +M````!`````$`&@!R````W`````0````!`!H`?``````````@`````@`#`(P` +M``!`````!`````$`&@"6````(````#@````"``,`I````"`````@`````0`: +M`*T```!8````.`````(``P"Z`````````"`````!`!H`P@```)`````,```` +M`@`#`-``````````3`````$`$0#<````%`,``"@````"``$`\````#P#``!< +M`````@`!```!``"8`P``B`````(``0`;`0``(`0``-0!```"``$`-`$``,P` +M```,`````0`:`#T!``#T!0``"`$```(``0!.`0``_`8``+P````"``$`<`$` +M`+@'``#T`````@`!`)`!```D````(`````$`#P"?`0``K`@```P!```"``$` +MP0$``+@)``#@!@```@`!`,\!``#@````!`````$`&@#:`0``3````(`````! +M`!H`X@$``!0````.`````0`/`/$!``````````````$`&P#]`0``F!```'`! +M```"``$`"0(``"@6```4`````@`!`!@"```($@``_`````(``0`X`@``!!,` +M`"0#```"``$`5P(````````,`````@`%`&@"``!(````!`````$`&@!Z`@`` +M1`````0````!`!H`C`(``/@>``!T`````@`!`)L"``!L'P``I`$```(``0"L +M`@```````!$````!``\`NP(``'@A``"L`P```@`!`,H"``!$````#P````$` +M#P#9`@``5"4``"0````"``$`ZP(``'@E```(`@```@`!`/L"``"D)P``)``` +M``(``0`.`P````````0````!`!,`*@,````````$`````0`5`$0#```````` +M#`````$`%P!<`P````````H````!`!D`>`,```P````,`````0`7`(X#```* +M````"0````$`&0"H`P``&`````P````!`!<`O0,``!,````(`````0`9`-8# +M````````"`````$`"P#P`P```````!`````!`!``"@0````````$`````0`- +M`"0$```(````"`````$`"P`Z!```$`````P````!`!``4`0```0````$```` +M`0`-`&8$```0````"`````$`"P!_!```'`````\````!`!``F`0```@````$ +M`````0`-`+$$```8````"`````$`"P#2!```+````!<````!`!``\P0```P` +M```$`````0`-`!0%```@````"`````$`"P`T!0``1````!8````!`!``5`4` +M`!`````$`````0`-`'0%````````````````"`!Y!0``+`````````````@` +M?@4``$@````````````(`(,%``!@````````````"`"(!0``=``````````` +M``@`C04``)0````````````(`)(%``#4````````````"`"7!0``,`$````` +M``````@`G`4```P!```````````(`*$%``!(`0``````````"`"F!0``8`$` +M``````````@`K`4``'@!```````````(`+(%``"D`0``````````"`"X!0`` +M[`$```````````@`O@4``#`"```````````(`,0%``!,`@``````````"`#* +M!0``>`(```````````@`T`4``)P"```````````(`-8%``#,`@`````````` +M"`#BYC`&UT9&)L;V-K7V%D9')E0!M=&1B;&]C:U]L:69E=&EM95]R97-O0!?7V9U;F-?7RXQ-3,T.0!M +M=&1B;&]C:U]B;&]C:U]I;F9O7VUA<%]B861?8FQO8VL`;71D8FQO8VM?;W!E +M;@!M=&1B;&M?:61X`&UT9&)L:W,`7U]F=6YC7U\N,38Q.#,`7U]K97DN,38P +M-3,`97)A&ET8V%L;%]C;&5A;G5P7VUT9&)L;V-K`%]?:6YI=&-A;&Q? +M:6YI=%]M=&1B;&]C:S8`7U]S971U<%]M=&1B;&ML;V=?6UT86)?=61C7V9L=7-H +M7V-A8VAE`%]?:W-T6UT86)?=61C7V=E=%]M=&0`7U]K6UT86)? +M=61C7V=E=%]M=&1B;&L`7U]K6UT86)?=61C7VUT9&)L;V-K7W=R +M:71E6UT86)?=61C7VUT +M9&)L;V-K7W)E861S96-T`%]?:W-T%]L;V-K`'!R:6YT:P!?7VUU=&5X7VEN:70` +M;65M #include #include - +#include #include #include #include @@ -359,12 +359,27 @@ static void mtdblock_remove_dev(struct m kfree(dev); } + +static int mtdblock_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo) +{ + struct gendisk *gd = dev->blkcore_priv; + memset(geo, 0, sizeof(*geo)); + geo->heads = 4; + geo->sectors = 16; + geo->cylinders = dev->size/(4*16); + + printk("cylinders: %x \n", geo->cylinders); + printk("sects: %x\n", dev->size); + return 0; +} + static struct mtd_blktrans_ops mtdblock_tr = { .name = "mtdblock", .major = 31, .part_bits = 0, .blksize = 512, .open = mtdblock_open, + .getgeo = mtdblock_getgeo, .flush = mtdblock_flush, .release = mtdblock_release, .readsect = mtdblock_readsect, --- a/drivers/mtd/mtdchar.c +++ b/drivers/mtd/mtdchar.c @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -164,7 +163,7 @@ static ssize_t mtd_read(struct file *fil { struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; - size_t retlen=0; + size_mtd_t retlen=0; size_t total_retlen=0; int ret=0; int len; @@ -258,7 +257,7 @@ static ssize_t mtd_write(struct file *fi struct mtd_file_info *mfi = file->private_data; struct mtd_info *mtd = mfi->mtd; char *kbuf; - size_t retlen; + size_mtd_t retlen; size_t total_retlen=0; int ret=0; int len; @@ -591,6 +590,73 @@ static int mtd_ioctl(struct inode *inode break; } + case MEMWRITEPAGE: + { + struct mtd_page_buf buf; + struct mtd_oob_ops ops; + + memset(&ops, 0, sizeof(ops)); +#if 1 + if(!(file->f_mode & 2)) + return -EPERM; +#endif + + if (copy_from_user(&buf, argp, sizeof(struct mtd_page_buf))) + return -EFAULT; + + if (buf.ooblength > mtd->oobsize) + return -EINVAL; + + if (!mtd->write_oob) + ret = -EOPNOTSUPP; + else + ret = access_ok(VERIFY_READ, buf.oobptr, + buf.ooblength) ? 0 : EFAULT; + + if (ret) + return ret; + + ops.len = mtd->writesize; + ops.ooblen = buf.ooblength; + ops.ooboffs = buf.start & (mtd->oobsize - 1); + ops.mode = MTD_OOB_PLACE; + + if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs)) + return -EINVAL; + + /* alloc memory and copy oob data from user mode to kernel mode */ + ops.oobbuf = kmalloc(buf.ooblength, GFP_KERNEL); + if (!ops.oobbuf) + return -ENOMEM; + + if (copy_from_user(ops.oobbuf, buf.oobptr, buf.ooblength)) { + kfree(ops.oobbuf); + return -EFAULT; + } + + /* alloc memory and copy page data from user mode to kernel mode */ + ops.datbuf = kmalloc(mtd->writesize, GFP_KERNEL); + if (!ops.datbuf) + return -ENOMEM; + + if (copy_from_user(ops.datbuf, buf.datptr, mtd->writesize)) { + kfree(ops.datbuf); + return -EFAULT; + } + + buf.start &= ~(mtd->oobsize - 1); + ret = mtd->write_oob(mtd, buf.start, &ops); + + if (copy_to_user(argp + 2*sizeof(uint32_t), &ops.retlen, + sizeof(uint32_t))) + ret = -EFAULT; + + kfree(ops.oobbuf); + kfree(ops.datbuf); + break; + } + + case MEMLOCK: { struct erase_info_user einfo; @@ -642,9 +708,9 @@ static int mtd_ioctl(struct inode *inode case MEMGETBADBLOCK: { - loff_t offs; + loff_mtd_t offs; - if (copy_from_user(&offs, argp, sizeof(loff_t))) + if (copy_from_user(&offs, argp, sizeof(loff_mtd_t))) return -EFAULT; if (!mtd->block_isbad) ret = -EOPNOTSUPP; @@ -655,9 +721,9 @@ static int mtd_ioctl(struct inode *inode case MEMSETBADBLOCK: { - loff_t offs; + loff_mtd_t offs; - if (copy_from_user(&offs, argp, sizeof(loff_t))) + if (copy_from_user(&offs, argp, sizeof(loff_mtd_t))) return -EFAULT; if (!mtd->block_markbad) ret = -EOPNOTSUPP; @@ -779,9 +845,9 @@ static int mtd_ioctl(struct inode *inode #endif default: + printk("line : %d\n", __LINE__); ret = -ENOTTY; } - return ret; } /* memory_ioctl */ --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -420,4 +420,127 @@ config MTD_NAND_SH_FLCTL Several Renesas SuperH CPU has FLCTL. This option enables support for NAND Flash using FLCTL. This driver support SH7723. +config MTD_NAND_JZ4730 + tristate "Support NAND Flash device on Jz4730 board" + depends on SOC_JZ4730 + help + Support NAND Flash device on Jz4730 board + +config MTD_NAND_JZ4740 + tristate "Support NAND Flash device on Jz4740 board" + depends on SOC_JZ4740 + help + Support NAND Flash device on Jz4740 board + +config MTD_NAND_JZ4750 + tristate "Support NAND Flash device on Jz4750 board" + depends on SOC_JZ4750 || SOC_JZ4750D + help + Support NAND Flash device on Jz4750 board + +config MTD_NAND_CS2 + depends on MTD_NAND_JZ4740 || MTD_NAND_JZ4750 + bool 'Use NAND on CS2_N of JZSOC' + default n + +config MTD_NAND_CS3 + depends on MTD_NAND_JZ4740 || MTD_NAND_JZ4750 + bool 'Use NAND on CS3_N of JZSOC' + default n + +config MTD_NAND_CS4 + depends on MTD_NAND_JZ4740 || MTD_NAND_JZ4750 + bool 'Use NAND on CS4_N of JZSOC' + default n + +config MTD_NAND_MULTI_PLANE + depends on MTD_NAND_JZ4730 || MTD_NAND_JZ4740 || MTD_NAND_JZ4750 + bool 'Use multiple planes if the NAND supports' + default y + help + It is just supported on jz4740 now. + +if MTD_NAND_JZ4740 || MTD_NAND_JZ4730 || MTD_NAND_JZ4750 +choice + prompt "ECC type" + default CONFIG_MTD_SW_HM_ECC + +config MTD_HW_HM_ECC + depends on MTD_NAND_JZ4740 || MTD_NAND_JZ4730 + bool 'Select hardware HM ECC' + +config MTD_SW_HM_ECC + bool 'Select software HM ECC' + +config MTD_HW_RS_ECC + depends on MTD_NAND_JZ4740 + bool 'Select hardware RS ECC' + +config MTD_HW_BCH_ECC + depends on MTD_NAND_JZ4750 + bool 'Select hardware BCH ECC' +endchoice + +choice + prompt "4 bit or 8 bit BCH ecc" + depends on MTD_HW_BCH_ECC + default CONFIG_MTD_HW_BCH_4BIT + +config MTD_HW_BCH_4BIT + bool '4 bit' + +config MTD_HW_BCH_8BIT + bool '8 bit' + +endchoice + +config MTD_NAND_DMA + depends on MTD_HW_BCH_ECC + bool 'Use DMA mode' + help + This enables using DMA for reading and writing NAND flash, if not selected, + then CPU mode is used. + +config MTD_NAND_DMABUF + depends on MTD_NAND_DMA + bool 'use DMA buffer in NAND driver' + help + It's better to say NO. If saying yes, DMA buffers will be allocated for + NAND reading and writing in NAND driver instead of upper layer. It's + slower. Just usable on CS1_N now. By saying NO, upper buffers will be + used as DMA buffer. It's faster, but kmalloc instead of vmalloc is required. +endif + +config MTD_MTDBLOCK_WRITE_VERIFY_ENABLE + bool "MTDBLOCK write verify enable" + default n + help + This will enable the write verification, which will read back data to check + after doing a write opetaion. + + It will be used by the JZ mtdblock driver (mtdblock-jz.c). + +config MTD_OOB_COPIES + int "how many copies of the fs info in the oob area" + default 3 + range 0 128 + depends on MTD + help + This defines the copies of the fs info in the oob area inside a block. + Value ranges from 0 to (pages_per_block - 1). + + It will be used by the JZ mtdblock driver (mtdblock-jz.c). + +config MTD_BADBLOCK_FLAG_PAGE + int "which page inside a block will store the badblock mark" + default 0 + range 0 127 + depends on MTD + help + This defines which page of a block will store the badblock mark. + Value ranges from 0 to (pages_per_block - 1). + + Old SLC NANDs store the badblock mark in the first page of a block, but + most modern MLC NANDs store it in the last page of a block. + endif # MTD_NAND --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -36,5 +36,8 @@ obj-$(CONFIG_MTD_NAND_FSL_ELBC) += fsl_ obj-$(CONFIG_MTD_NAND_FSL_UPM) += fsl_upm.o obj-$(CONFIG_MTD_NAND_SH_FLCTL) += sh_flctl.o obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o +obj-$(CONFIG_MTD_NAND_JZ4730) += jz4730_nand.o +obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o +obj-$(CONFIG_MTD_NAND_JZ4750) += jz4750_nand.o nand-objs := nand_base.o nand_bbt.o --- /dev/null +++ b/drivers/mtd/nand/jz4730_nand.c @@ -0,0 +1,367 @@ +/* + * linux/drivers/mtd/nand/jz4730_nand.c + * + * Copyright (c) 2005 - 2007 Ingenic Semiconductor Inc. + * + * Ingenic JZ4730 NAND driver + * + * This program 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define NAND_DATA_PORT 0xB4000000 /* read-write area */ +#define __nand_ecc() (REG_EMC_NFECC & 0x00ffffff) +#define __nand_ecc_enable() (REG_EMC_NFCSR |= EMC_NFCSR_ECCE | EMC_NFCSR_ERST) +#define __nand_ecc_disable() (REG_EMC_NFCSR &= ~EMC_NFCSR_ECCE) + +/* + * MTD structure for JzSOC board + */ +static struct mtd_info *jz_mtd = NULL; + +/* + * Define partitions for flash devices + */ +#ifdef CONFIG_JZ4730_PMP +static struct mtd_partition partition_info[] = { + { name: "NAND BOOT partition", + offset: 0 * 0x100000, + size: 4 * 0x100000 }, + { name: "NAND KERNEL partition", + offset: 4 * 0x100000, + size: 4 * 0x100000 }, + { name: "NAND ROOTFS partition", + offset: 8 * 0x100000, + size: 56 * 0x100000 }, + { name: "NAND SSFDC partition", + offset: 64 * 0x100000, + size: 64 * 0x100000 }, +}; + +/* Define max reserved bad blocks for each partition. + * This is used by the mtdblock-jz.c NAND FTL driver only. + * + * The NAND FTL driver reserves some good blocks which can't be + * seen by the upper layer. When the bad block number of a partition + * exceeds the max reserved blocks, then there is no more reserved + * good blocks to be used by the NAND FTL driver when another bad + * block generated. + */ +static int partition_reserved_badblocks[]= { + 2, /* reserved blocks of mtd0 */ + 2, /* reserved blocks of mtd1 */ + 10, /* reserved blocks of mtd2 */ + 10}; /* reserved blocks of mtd3 */ +#elif CONFIG_JZ4730_PMPV1 +static struct mtd_partition partition_info[] = { + { name: "NAND ROOTFS partition", + offset: 3 * 0x100000, + size: (32-3) * 0x100000 }, + { name: "NAND DATAFS partition", + offset: 32 * 0x100000, + size: 32 * 0x100000 }, +}; +static int partition_reserved_badblocks[]={ + 10, + 10}; +#else +static struct mtd_partition partition_info[] = { + { name: "NAND ROOTFS partition", + offset: 3 * 0x100000, + size: (128-3) * 0x100000 }, +}; +static int partition_reserved_badblocks[]={ + 20}; +#endif + +/*------------------------------------------------------------------------- + * Following three functions are exported and used by the mtdblock-jz.c + * NAND FTL driver only. + */ + +unsigned short get_mtdblock_write_verify_enable(void) +{ +#ifdef CONFIG_MTD_MTDBLOCK_WRITE_VERIFY_ENABLE + return 1; +#endif + return 0; +} +EXPORT_SYMBOL(get_mtdblock_write_verify_enable); + +unsigned short get_mtdblock_oob_copies(void) +{ + return CONFIG_MTD_OOB_COPIES; +} +EXPORT_SYMBOL(get_mtdblock_oob_copies); + +int *get_jz_badblock_table(void) +{ + return partition_reserved_badblocks; +} +EXPORT_SYMBOL(get_jz_badblock_table); + +/*-------------------------------------------------------------------------*/ + +static void jz_hwcontrol(struct mtd_info *mtd, int dat, + unsigned int ctrl) +{ + struct nand_chip *this = (struct nand_chip *)(mtd->priv); + unsigned int nandaddr = (unsigned int)this->IO_ADDR_W; + + if (ctrl & NAND_CTRL_CHANGE) { + if ( ctrl & NAND_ALE ) + nandaddr = (unsigned int)((unsigned long)(this->IO_ADDR_W) | 0x00080000); + else + nandaddr = (unsigned int)((unsigned long)(this->IO_ADDR_W) & ~0x00080000); + + if ( ctrl & NAND_CLE ) + nandaddr = nandaddr | 0x00040000; + else + nandaddr = nandaddr & ~0x00040000; + if ( ctrl & NAND_NCE ) + REG_EMC_NFCSR |= EMC_NFCSR_FCE; + else + REG_EMC_NFCSR &= ~EMC_NFCSR_FCE; + } + + this->IO_ADDR_W = (void __iomem *)nandaddr; + if (dat != NAND_CMD_NONE) + writeb(dat , this->IO_ADDR_W); + +} + +static int jz_device_ready(struct mtd_info *mtd) +{ + int ready; + ready = (REG_EMC_NFCSR & EMC_NFCSR_RB) ? 1 : 0; + return ready; +} + +/* + * EMC setup + */ +static void jz_device_setup(void) +{ + /* Set NFE bit */ + REG_EMC_NFCSR |= EMC_NFCSR_NFE; +} + +static void jzsoc_nand_enable_hwecc(struct mtd_info* mtd, int mode) +{ + __nand_ecc_enable(); +} + +static int jzsoc_nand_calculate_ecc(struct mtd_info* mtd, const u_char* dat, + u_char* ecc_code) +{ + unsigned int calc_ecc; + unsigned char *tmp; + + __nand_ecc_disable(); + + calc_ecc = ~(__nand_ecc()) | 0x00030000; + + tmp = (unsigned char *)&calc_ecc; + + ecc_code[0] = tmp[1]; + ecc_code[1] = tmp[0]; + ecc_code[2] = tmp[2]; + + return 0; +} + +/* ECC handling functions */ + +static int jzsoc_nand_correct_data(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + u_char a, b, c, d1, d2, d3, add, bit, i; + + /* Do error detection */ + d1 = calc_ecc[0] ^ read_ecc[0]; + d2 = calc_ecc[1] ^ read_ecc[1]; + d3 = calc_ecc[2] ^ read_ecc[2]; + + if ((d1 | d2 | d3) == 0) { + /* No errors */ + return 0; + } + else { + a = (d1 ^ (d1 >> 1)) & 0x55; + b = (d2 ^ (d2 >> 1)) & 0x55; + c = (d3 ^ (d3 >> 1)) & 0x54; + + /* Found and will correct single bit error in the data */ + if ((a == 0x55) && (b == 0x55) && (c == 0x54)) { + c = 0x80; + add = 0; + a = 0x80; + for (i=0; i<4; i++) { + if (d1 & c) + add |= a; + c >>= 2; + a >>= 1; + } + c = 0x80; + for (i=0; i<4; i++) { + if (d2 & c) + add |= a; + c >>= 2; + a >>= 1; + } + bit = 0; + b = 0x04; + c = 0x80; + for (i=0; i<3; i++) { + if (d3 & c) + bit |= b; + c >>= 2; + b >>= 1; + } + b = 0x01; + a = dat[add]; + a ^= (b << bit); + dat[add] = a; + return 0; + } + else { + i = 0; + while (d1) { + if (d1 & 0x01) + ++i; + d1 >>= 1; + } + while (d2) { + if (d2 & 0x01) + ++i; + d2 >>= 1; + } + while (d3) { + if (d3 & 0x01) + ++i; + d3 >>= 1; + } + if (i == 1) { + /* ECC Code Error Correction */ + read_ecc[0] = calc_ecc[0]; + read_ecc[1] = calc_ecc[1]; + read_ecc[2] = calc_ecc[2]; + return 0; + } + else { + /* Uncorrectable Error */ + printk("uncorrectable ECC error\n"); + return -1; + } + } + } + + /* Should never happen */ + return -1; +} + + +/* + * Main initialization routine + */ +int __init jznand_init(void) +{ + struct nand_chip *this; + int nr_partitions; + + /* Allocate memory for MTD device structure and private data */ + jz_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip), + GFP_KERNEL); + if (!jz_mtd) { + printk ("Unable to allocate JzSOC NAND MTD device structure.\n"); + return -ENOMEM; + } + + /* Get pointer to private data */ + this = (struct nand_chip *) (&jz_mtd[1]); + + /* Initialize structures */ + memset((char *) jz_mtd, 0, sizeof(struct mtd_info)); + memset((char *) this, 0, sizeof(struct nand_chip)); + + /* Link the private data with the MTD structure */ + jz_mtd->priv = this; + + /* Set & initialize NAND Flash controller */ + jz_device_setup(); + + /* Set address of NAND IO lines */ + this->IO_ADDR_R = (void __iomem *) NAND_DATA_PORT; + this->IO_ADDR_W = (void __iomem *) NAND_DATA_PORT; + this->cmd_ctrl = jz_hwcontrol; + this->dev_ready = jz_device_ready; + +#ifdef CONFIG_MTD_HW_HM_ECC + this->ecc.calculate = jzsoc_nand_calculate_ecc; + this->ecc.correct = jzsoc_nand_correct_data; + this->ecc.hwctl = jzsoc_nand_enable_hwecc; + this->ecc.mode = NAND_ECC_HW; + this->ecc.size = 256; + this->ecc.bytes = 3; + +#endif + +#ifdef CONFIG_MTD_SW_HM_ECC + this->eccmode = NAND_ECC_SOFT; +#endif + + /* 20 us command delay time */ + this->chip_delay = 20; + + /* Scan to find existance of the device */ + if (nand_scan(jz_mtd, 1)) { + kfree (jz_mtd); + return -ENXIO; + } + + /* Register the partitions */ + nr_partitions = sizeof(partition_info) / sizeof(struct mtd_partition); + printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nr_partitions, jz_mtd->name); + add_mtd_partitions(jz_mtd, partition_info, nr_partitions); + + return 0; +} +module_init(jznand_init); + +/* + * Clean up routine + */ +#ifdef MODULE +static void __exit jznand_cleanup(void) +{ + struct nand_chip *this = (struct nand_chip *) &jz_mtd[1]; + + /* Unregister partitions */ + del_mtd_partitions(jz_mtd); + + /* Unregister the device */ + del_mtd_device (jz_mtd); + + /* Free internal data buffers */ + kfree (this->data_buf); + + /* Free the MTD device structure */ + kfree (jz_mtd); +} +module_exit(jznand_cleanup); +#endif --- /dev/null +++ b/drivers/mtd/nand/jz4740_nand.c @@ -0,0 +1,1037 @@ +/* + * linux/drivers/mtd/nand/jz4740_nand.c + * + * Copyright (c) 2005 - 2007 Ingenic Semiconductor Inc. + * + * Ingenic JZ4740 NAND driver + * + * This program 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#define NAND_DATA_PORT1 0xB8000000 /* read-write area in static bank 1 */ +#define NAND_DATA_PORT2 0xB4000000 /* read-write area in static bank 2 */ +#define NAND_DATA_PORT3 0xAC000000 /* read-write area in static bank 3 */ +#define NAND_DATA_PORT4 0xA8000000 /* read-write area in static bank 4 */ + +#define PAR_SIZE 9 + +#define __nand_enable() (REG_EMC_NFCSR |= EMC_NFCSR_NFE1 | EMC_NFCSR_NFCE1) +#define __nand_disable() (REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1) + +#define __nand_ecc_enable() (REG_EMC_NFECR = EMC_NFECR_ECCE | EMC_NFECR_ERST ) +#define __nand_ecc_disable() (REG_EMC_NFECR &= ~EMC_NFECR_ECCE) + +#define __nand_select_hm_ecc() (REG_EMC_NFECR &= ~EMC_NFECR_RS ) +#define __nand_select_rs_ecc() (REG_EMC_NFECR |= EMC_NFECR_RS) + +#define __nand_read_hm_ecc() (REG_EMC_NFECC & 0x00ffffff) + +#define __nand_rs_ecc_encoding() (REG_EMC_NFECR |= EMC_NFECR_RS_ENCODING) +#define __nand_rs_ecc_decoding() (REG_EMC_NFECR &= ~EMC_NFECR_RS_ENCODING) +#define __nand_ecc_encode_sync() while (!(REG_EMC_NFINTS & EMC_NFINTS_ENCF)) +#define __nand_ecc_decode_sync() while (!(REG_EMC_NFINTS & EMC_NFINTS_DECF)) + +/* + * MTD structure for JzSOC board + */ +static struct mtd_info *jz_mtd = NULL; +extern struct mtd_info *jz_mtd1; +extern char all_use_planes; +extern int global_page; /* for two-plane operations */ + +/* + * Define partitions for flash devices + */ +#ifdef CONFIG_JZ4740_PAVO +static struct mtd_partition partition_info[] = { + { name: "NAND BOOT partition", + offset: 0 * 0x100000, + size: 4 * 0x100000, + use_planes: 0 }, + { name: "NAND KERNEL partition", + offset: 4 * 0x100000, + size: 4 * 0x100000, + use_planes: 0 }, + { name: "NAND ROOTFS partition", + offset: 8 * 0x100000, + size: 120 * 0x100000, + use_planes: 0 }, + { name: "NAND DATA1 partition", + offset: 128 * 0x100000, + size: 128 * 0x100000, + use_planes: 1 }, + { name: "NAND DATA2 partition", + offset: 256 * 0x100000, + size: 256 * 0x100000, + use_planes: 1 }, + { name: "NAND VFAT partition", + offset: 512 * 0x100000, + size: 512 * 0x100000, + use_planes: 1 }, +}; + + +/* Define max reserved bad blocks for each partition. + * This is used by the mtdblock-jz.c NAND FTL driver only. + * + * The NAND FTL driver reserves some good blocks which can't be + * seen by the upper layer. When the bad block number of a partition + * exceeds the max reserved blocks, then there is no more reserved + * good blocks to be used by the NAND FTL driver when another bad + * block generated. + */ +static int partition_reserved_badblocks[] = { + 2, /* reserved blocks of mtd0 */ + 2, /* reserved blocks of mtd1 */ + 10, /* reserved blocks of mtd2 */ + 10, /* reserved blocks of mtd3 */ + 20, /* reserved blocks of mtd4 */ + 20}; /* reserved blocks of mtd5 */ +#endif /* CONFIG_JZ4740_PAVO */ + +#ifdef CONFIG_JZ4740_LEO +static struct mtd_partition partition_info[] = { + { name: "NAND BOOT partition", + offset: 0 * 0x100000, + size: 4 * 0x100000 }, + { name: "NAND KERNEL partition", + offset: 4 * 0x100000, + size: 4 * 0x100000 }, + { name: "NAND ROOTFS partition", + offset: 8 * 0x100000, + size: 56 * 0x100000 }, + { name: "NAND VFAT partition", + offset: 64 * 0x100000, + size: 64 * 0x100000 }, +}; +static int partition_reserved_badblocks[] = { + 2, /* reserved blocks of mtd0 */ + 2, /* reserved blocks of mtd1 */ + 10, /* reserved blocks of mtd2 */ + 10}; /* reserved blocks of mtd3 */ +#endif /* CONFIG_JZ4740_LEO */ + +#ifdef CONFIG_JZ4740_LYRA +static struct mtd_partition partition_info[] = { + { name: "NAND BOOT partition", + offset: 0 * 0x100000, + size: 4 * 0x100000 }, + { name: "NAND KERNEL partition", + offset: 4 * 0x100000, + size: 4 * 0x100000 }, + { name: "NAND ROOTFS partition", + offset: 8 * 0x100000, + size: 120 * 0x100000 }, + { name: "NAND DATA1 partition", + offset: 128 * 0x100000, + size: 128 * 0x100000 }, + { name: "NAND DATA2 partition", + offset: 256 * 0x100000, + size: 256 * 0x100000 }, + { name: "NAND VFAT partition", + offset: 512 * 0x100000, + size: 512 * 0x100000 }, +}; + +/* Define max reserved bad blocks for each partition. + * This is used by the mtdblock-jz.c NAND FTL driver only. + * + * The NAND FTL driver reserves some good blocks which can't be + * seen by the upper layer. When the bad block number of a partition + * exceeds the max reserved blocks, then there is no more reserved + * good blocks to be used by the NAND FTL driver when another bad + * block generated. + */ +static int partition_reserved_badblocks[] = { + 2, /* reserved blocks of mtd0 */ + 2, /* reserved blocks of mtd1 */ + 10, /* reserved blocks of mtd2 */ + 10, /* reserved blocks of mtd3 */ + 20, /* reserved blocks of mtd4 */ + 20}; /* reserved blocks of mtd5 */ +#endif /* CONFIG_JZ4740_LYRA */ + +#ifdef CONFIG_JZ4725_DIPPER +static struct mtd_partition partition_info[] = { + { name: "NAND BOOT partition", + offset: 0 * 0x100000, + size: 4 * 0x100000 }, + { name: "NAND KERNEL partition", + offset: 4 * 0x100000, + size: 4 * 0x100000 }, + { name: "NAND ROOTFS partition", + offset: 8 * 0x100000, + size: 56 * 0x100000 }, + { name: "NAND VFAT partition", + offset: 64 * 0x100000, + size: 64 * 0x100000 }, +}; + +/* Define max reserved bad blocks for each partition. + * This is used by the mtdblock-jz.c NAND FTL driver only. + * + * The NAND FTL driver reserves some good blocks which can't be + * seen by the upper layer. When the bad block number of a partition + * exceeds the max reserved blocks, then there is no more reserved + * good blocks to be used by the NAND FTL driver when another bad + * block generated. + */ +static int partition_reserved_badblocks[] = { + 2, /* reserved blocks of mtd0 */ + 2, /* reserved blocks of mtd1 */ + 10, /* reserved blocks of mtd2 */ + 10}; /* reserved blocks of mtd3 */ +#endif /* CONFIG_JZ4740_DIPPER */ + +#ifdef CONFIG_JZ4720_VIRGO +static struct mtd_partition partition_info[] = { + { name: "NAND BOOT partition", + offset: 0 * 0x100000, + size: 4 * 0x100000 }, + { name: "NAND KERNEL partition", + offset: 4 * 0x100000, + size: 4 * 0x100000 }, + { name: "NAND ROOTFS partition", + offset: 8 * 0x100000, + size: 120 * 0x100000 }, + { name: "NAND DATA1 partition", + offset: 128 * 0x100000, + size: 128 * 0x100000 }, + { name: "NAND DATA2 partition", + offset: 256 * 0x100000, + size: 256 * 0x100000 }, + { name: "NAND VFAT partition", + offset: 512 * 0x100000, + size: 512 * 0x100000 }, +}; + + +/* Define max reserved bad blocks for each partition. + * This is used by the mtdblock-jz.c NAND FTL driver only. + * + * The NAND FTL driver reserves some good blocks which can't be + * seen by the upper layer. When the bad block number of a partition + * exceeds the max reserved blocks, then there is no more reserved + * good blocks to be used by the NAND FTL driver when another bad + * block generated. + */ +static int partition_reserved_badblocks[] = { + 2, /* reserved blocks of mtd0 */ + 2, /* reserved blocks of mtd1 */ + 10, /* reserved blocks of mtd2 */ + 10, /* reserved blocks of mtd3 */ + 20, /* reserved blocks of mtd4 */ + 20}; /* reserved blocks of mtd5 */ +#endif /* CONFIG_JZ4720_VIRGO */ +/*------------------------------------------------------------------------- + * Following three functions are exported and used by the mtdblock-jz.c + * NAND FTL driver only. + */ + +unsigned short get_mtdblock_write_verify_enable(void) +{ +#ifdef CONFIG_MTD_MTDBLOCK_WRITE_VERIFY_ENABLE + return 1; +#endif + return 0; +} +EXPORT_SYMBOL(get_mtdblock_write_verify_enable); + +unsigned short get_mtdblock_oob_copies(void) +{ + return CONFIG_MTD_OOB_COPIES; +} +EXPORT_SYMBOL(get_mtdblock_oob_copies); + +int *get_jz_badblock_table(void) +{ + return partition_reserved_badblocks; +} +EXPORT_SYMBOL(get_jz_badblock_table); + +/*-------------------------------------------------------------------------*/ + +static void jz_hwcontrol(struct mtd_info *mtd, int dat, + unsigned int ctrl) +{ + struct nand_chip *this = (struct nand_chip *)(mtd->priv); + unsigned int nandaddr = (unsigned int)this->IO_ADDR_W; + extern u8 nand_nce; /* in nand_base.c, indicates which chip select is used for current nand chip */ + + if (ctrl & NAND_CTRL_CHANGE) { + if (ctrl & NAND_NCE) { + switch (nand_nce) { + case NAND_NCE1: + this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT1; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4; + REG_EMC_NFCSR |= EMC_NFCSR_NFCE1; + break; + case NAND_NCE2: + this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT2; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4; + REG_EMC_NFCSR |= EMC_NFCSR_NFCE2; + break; + case NAND_NCE3: + this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT3; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4; + REG_EMC_NFCSR |= EMC_NFCSR_NFCE3; + break; + case NAND_NCE4: + this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT4; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3; + REG_EMC_NFCSR |= EMC_NFCSR_NFCE4; + break; + default: + printk("error: no nand_nce 0x%x\n",nand_nce); + break; + } + } else { + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4; + } + + if ( ctrl & NAND_ALE ) + nandaddr = (unsigned int)((unsigned long)(this->IO_ADDR_W) | 0x00010000); + else + nandaddr = (unsigned int)((unsigned long)(this->IO_ADDR_W) & ~0x00010000); + + if ( ctrl & NAND_CLE ) + nandaddr = nandaddr | 0x00008000; + else + nandaddr = nandaddr & ~0x00008000; + } + + this->IO_ADDR_W = (void __iomem *)nandaddr; + if (dat != NAND_CMD_NONE) + writeb(dat, this->IO_ADDR_W); +} + +static int jz_device_ready(struct mtd_info *mtd) +{ + int ready, wait = 10; + while (wait--); + ready = __gpio_get_pin(94); + return ready; +} + +/* + * EMC setup + */ +static void jz_device_setup(void) +{ +// PORT 0: +// ... +// PORT 1: +// PIN/BIT N FUNC0 FUNC1 +// 25 CS1# - +// 26 CS2# - +// 27 CS3# - +// 28 CS4# - +#define GPIO_CS2_N (32+26) +#define GPIO_CS3_N (32+27) +#define GPIO_CS4_N (32+28) +#define SMCR_VAL 0x0d221200 + + /* Set NFE bit */ + REG_EMC_NFCSR |= EMC_NFCSR_NFE1; + /* Read/Write timings */ + REG_EMC_SMCR1 = SMCR_VAL; + +#if defined(CONFIG_MTD_NAND_CS2) + /* Set CS2# pin as function 0 */ + __gpio_as_func0(GPIO_CS2_N); + REG_EMC_NFCSR |= EMC_NFCSR_NFE2; + REG_EMC_SMCR2 = SMCR_VAL; +#endif + +#if defined(CONFIG_MTD_NAND_CS3) + __gpio_as_func0(GPIO_CS3_N); + REG_EMC_NFCSR |= EMC_NFCSR_NFE3; + REG_EMC_SMCR3 = SMCR_VAL; +#endif + +#if defined(CONFIG_MTD_NAND_CS4) + __gpio_as_func0(GPIO_CS4_N); + REG_EMC_NFCSR |= EMC_NFCSR_NFE4; + REG_EMC_SMCR4 = SMCR_VAL; +#endif +} + +#ifdef CONFIG_MTD_HW_HM_ECC + +static int jzsoc_nand_calculate_hm_ecc(struct mtd_info* mtd, + const u_char* dat, u_char* ecc_code) +{ + unsigned int calc_ecc; + unsigned char *tmp; + + __nand_ecc_disable(); + + calc_ecc = ~(__nand_read_hm_ecc()) | 0x00030000; + + tmp = (unsigned char *)&calc_ecc; + //adjust eccbytes order for compatible with software ecc + ecc_code[0] = tmp[1]; + ecc_code[1] = tmp[0]; + ecc_code[2] = tmp[2]; + + return 0; +} + +static void jzsoc_nand_enable_hm_hwecc(struct mtd_info* mtd, int mode) +{ + __nand_ecc_enable(); + __nand_select_hm_ecc(); +} + +static int jzsoc_nand_hm_correct_data(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + u_char a, b, c, d1, d2, d3, add, bit, i; + + /* Do error detection */ + d1 = calc_ecc[0] ^ read_ecc[0]; + d2 = calc_ecc[1] ^ read_ecc[1]; + d3 = calc_ecc[2] ^ read_ecc[2]; + + if ((d1 | d2 | d3) == 0) { + /* No errors */ + return 0; + } + else { + a = (d1 ^ (d1 >> 1)) & 0x55; + b = (d2 ^ (d2 >> 1)) & 0x55; + c = (d3 ^ (d3 >> 1)) & 0x54; + + /* Found and will correct single bit error in the data */ + if ((a == 0x55) && (b == 0x55) && (c == 0x54)) { + c = 0x80; + add = 0; + a = 0x80; + for (i=0; i<4; i++) { + if (d1 & c) + add |= a; + c >>= 2; + a >>= 1; + } + c = 0x80; + for (i=0; i<4; i++) { + if (d2 & c) + add |= a; + c >>= 2; + a >>= 1; + } + bit = 0; + b = 0x04; + c = 0x80; + for (i=0; i<3; i++) { + if (d3 & c) + bit |= b; + c >>= 2; + b >>= 1; + } + b = 0x01; + a = dat[add]; + a ^= (b << bit); + dat[add] = a; + return 0; + } + else { + i = 0; + while (d1) { + if (d1 & 0x01) + ++i; + d1 >>= 1; + } + while (d2) { + if (d2 & 0x01) + ++i; + d2 >>= 1; + } + while (d3) { + if (d3 & 0x01) + ++i; + d3 >>= 1; + } + if (i == 1) { + /* ECC Code Error Correction */ + read_ecc[0] = calc_ecc[0]; + read_ecc[1] = calc_ecc[1]; + read_ecc[2] = calc_ecc[2]; + return 0; + } + else { + /* Uncorrectable Error */ + printk("NAND: uncorrectable ECC error\n"); + return -1; + } + } + } + + /* Should never happen */ + return -1; +} + +#endif /* CONFIG_MTD_HW_HM_ECC */ + +#ifdef CONFIG_MTD_HW_RS_ECC + +static void jzsoc_nand_enable_rs_hwecc(struct mtd_info* mtd, int mode) +{ + REG_EMC_NFINTS = 0x0; + __nand_ecc_enable(); + __nand_select_rs_ecc(); + + if (mode == NAND_ECC_READ) + __nand_rs_ecc_decoding(); + + if (mode == NAND_ECC_WRITE) + __nand_rs_ecc_encoding(); +} + +static void jzsoc_rs_correct(unsigned char *dat, int idx, int mask) +{ + int i; + + idx--; + + i = idx + (idx >> 3); + if (i >= 512) + return; + + mask <<= (idx & 0x7); + + dat[i] ^= mask & 0xff; + if (i < 511) + dat[i+1] ^= (mask >> 8) & 0xff; +} + +/* + * calc_ecc points to oob_buf for us + */ +static int jzsoc_nand_rs_correct_data(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + volatile u8 *paraddr = (volatile u8 *)EMC_NFPAR0; + short k; + u32 stat; + + /* Set PAR values */ + for (k = 0; k < PAR_SIZE; k++) { + *paraddr++ = read_ecc[k]; + } + + /* Set PRDY */ + REG_EMC_NFECR |= EMC_NFECR_PRDY; + + /* Wait for completion */ + __nand_ecc_decode_sync(); + __nand_ecc_disable(); + + /* Check decoding */ + stat = REG_EMC_NFINTS; + + if (stat & EMC_NFINTS_ERR) { + /* Error occurred */ + if (stat & EMC_NFINTS_UNCOR) { + printk("NAND: Uncorrectable ECC error\n"); + return -1; + } else { + u32 errcnt = (stat & EMC_NFINTS_ERRCNT_MASK) >> EMC_NFINTS_ERRCNT_BIT; + switch (errcnt) { + case 4: + jzsoc_rs_correct(dat, (REG_EMC_NFERR3 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT, (REG_EMC_NFERR3 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT); + /* FALL-THROUGH */ + case 3: + jzsoc_rs_correct(dat, (REG_EMC_NFERR2 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT, (REG_EMC_NFERR2 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT); + /* FALL-THROUGH */ + case 2: + jzsoc_rs_correct(dat, (REG_EMC_NFERR1 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT, (REG_EMC_NFERR1 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT); + /* FALL-THROUGH */ + case 1: + jzsoc_rs_correct(dat, (REG_EMC_NFERR0 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT, (REG_EMC_NFERR0 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT); + return 0; + default: + break; + } + } + } + + return 0; +} + +static int jzsoc_nand_calculate_rs_ecc(struct mtd_info* mtd, const u_char* dat, + u_char* ecc_code) +{ + volatile u8 *paraddr = (volatile u8 *)EMC_NFPAR0; + short i; + + __nand_ecc_encode_sync(); + __nand_ecc_disable(); + + for(i = 0; i < PAR_SIZE; i++) { + ecc_code[i] = *paraddr++; + } + + return 0; +} + +#endif /* CONFIG_MTD_HW_RS_ECC */ + +/* Nand optimized functions */ +static int dma_chan; +static unsigned int dma_src_phys_addr, dma_dst_phys_addr; +extern int jz_request_dma(int dev_id, const char *dev_str, + irqreturn_t (*irqhandler)(int, void *), + unsigned long irqflags, void *irq_dev_id); + +static void dma_setup(void) +{ + /* Request DMA channel and setup irq handler */ + dma_chan = jz_request_dma(DMA_ID_AUTO, "auto", NULL, IRQF_DISABLED, NULL); + if (dma_chan < 0) { + printk("Setup irq for nand failed!\n"); + return; + } else + printk("Nand DMA request channel %d.\n",dma_chan); +} + +static void jz4740_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + int i; + struct nand_chip *chip = mtd->priv; + + if ((len <= 32) || (len & 0xf) || ((u32)buf >= (u32)high_memory)) + { + for (i = 0; i < len; i++) + buf[i] = readb(chip->IO_ADDR_R); + } else { + REG_DMAC_DRSR(dma_chan) = DMAC_DRSR_RS_AUTO; + dma_src_phys_addr = CPHYSADDR(chip->IO_ADDR_R); + dma_dst_phys_addr = CPHYSADDR(buf); + dma_cache_inv((u32)buf, len); + REG_DMAC_DSAR(dma_chan) = dma_src_phys_addr; + REG_DMAC_DTAR(dma_chan) = dma_dst_phys_addr; + REG_DMAC_DTCR(dma_chan) = len / 16; + REG_DMAC_DCMD(dma_chan) = DMAC_DCMD_DAI | DMAC_DCMD_SWDH_8 | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_16BYTE; + REG_DMAC_DCCSR(dma_chan) = DMAC_DCCSR_NDES | DMAC_DCCSR_EN; + REG_DMAC_DMACR = DMAC_DMACR_DMAE; /* global DMA enable bit */ + + while(!(REG_DMAC_DCCSR(dma_chan) & DMAC_DCCSR_TT)); + REG_DMAC_DCCSR(dma_chan) &= ~DMAC_DCCSR_EN; /* disable DMA */ + __dmac_channel_clear_transmit_end(dma_chan); + } +} + +static void jz4740_nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + int i; + struct nand_chip *chip = mtd->priv; + + if ((len <= 32) || (len & 0xf) || ((u32)buf >= (u32)high_memory)) + { + for (i = 0; i < len; i++) + writeb(buf[i], chip->IO_ADDR_W); + } else { + REG_DMAC_DRSR(dma_chan) = DMAC_DRSR_RS_AUTO; + dma_dst_phys_addr = CPHYSADDR(chip->IO_ADDR_R); + dma_src_phys_addr = CPHYSADDR(buf); + dma_cache_wback((unsigned long)buf, len); + REG_DMAC_DSAR(dma_chan) = dma_src_phys_addr; + REG_DMAC_DTAR(dma_chan) = dma_dst_phys_addr; + REG_DMAC_DTCR(dma_chan) = len / 16; + REG_DMAC_DCMD(dma_chan) = DMAC_DCMD_SAI | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_8 | DMAC_DCMD_DS_16BYTE ; + REG_DMAC_DCCSR(dma_chan) = DMAC_DCCSR_NDES | DMAC_DCCSR_EN; + REG_DMAC_DMACR = DMAC_DMACR_DMAE; /* global DMA enable bit */ + + while(!(REG_DMAC_DCCSR(dma_chan) & DMAC_DCCSR_TT)); + REG_DMAC_DCCSR(dma_chan) &= ~DMAC_DCCSR_EN; /* disable DMA */ + __dmac_channel_clear_transmit_end(dma_chan); + } +} + +static int nand_read_page_hwecc_rs_planes(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf) +{ + int i, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps >> 1; + uint8_t *p; + uint8_t *ecc_calc = chip->buffers->ecccalc; + uint8_t *ecc_code = chip->buffers->ecccode; + uint32_t *eccpos = chip->ecc.layout->eccpos; + uint32_t page; + uint8_t flag = 0; + int oobsize = mtd->oobsize >> 1; + int ppb = mtd->erasesize / mtd->writesize; + int ecctotal = chip->ecc.total >> 1; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* Read first page */ + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + chip->read_buf(mtd, chip->oob_poi, oobsize); + for (i = 0; i < ecctotal; i++) { + ecc_code[i] = chip->oob_poi[eccpos[i]]; + if (ecc_code[i] != 0xff) flag = 1; + } + + p = buf; + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0x00, -1); + for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + int stat; + if (flag) { + chip->ecc.hwctl(mtd, NAND_ECC_READ); + chip->read_buf(mtd, p, eccsize); + stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]); + if (stat < 0) + mtd->ecc_stats.failed++; + else + mtd->ecc_stats.corrected += stat; + } + else { + chip->ecc.hwctl(mtd, NAND_ECC_READ); + chip->read_buf(mtd, p, eccsize); + } + } + /* Read second page */ + page += ppb; + flag = 0; + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + chip->read_buf(mtd, chip->oob_poi + oobsize, oobsize); + for (i = 0; i < ecctotal; i++) { + ecc_code[i] = chip->oob_poi[oobsize + eccpos[i]]; + if (ecc_code[i] != 0xff) flag = 1; + } + + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0x00, -1); + eccsteps = chip->ecc.steps >> 1; + for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + int stat; + if (flag) { + chip->ecc.hwctl(mtd, NAND_ECC_READ); + chip->read_buf(mtd, p, eccsize); + stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]); + if (stat < 0) + mtd->ecc_stats.failed++; + else + mtd->ecc_stats.corrected += stat; + } + else { + chip->ecc.hwctl(mtd, NAND_ECC_READ); + chip->read_buf(mtd, p, eccsize); + } + } + + return 0; +} + +static int nand_read_oob_std_planes(struct mtd_info *mtd, struct nand_chip *chip, + int global_page, int sndcmd) +{ + int page; + int oobsize = mtd->oobsize >> 1; + int ppb = mtd->erasesize / mtd->writesize; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* Read first page OOB */ + if (sndcmd) { + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + } + chip->read_buf(mtd, chip->oob_poi, oobsize); + /* Read second page OOB */ + page += ppb; + if (sndcmd) { + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + sndcmd = 0; + } + chip->read_buf(mtd, chip->oob_poi+oobsize, oobsize); + return 0; +} + +static int nand_write_oob_std_planes(struct mtd_info *mtd, struct nand_chip *chip, + int global_page) +{ + int status = 0,page; + int pagesize = mtd->writesize >> 1; + int oobsize = mtd->oobsize >> 1; + int ppb = mtd->erasesize / mtd->writesize; + const uint8_t *buf = chip->oob_poi; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* send cmd 0x80, the MSB should be valid if realplane is 4 */ + if (chip->realplanenum == 2) + chip->cmdfunc(mtd, 0x80, pagesize, 0x00); + else + chip->cmdfunc(mtd, 0x80, pagesize, page & (1 << (chip->chip_shift - chip->page_shift))); + + chip->write_buf(mtd, buf, oobsize); + /* Send first command to program the OOB data */ + chip->cmdfunc(mtd, 0x11, -1, -1); + ndelay(100); + status = chip->waitfunc(mtd, chip); + + page += ppb; + buf += oobsize; + chip->cmdfunc(mtd, 0x81, pagesize, page); + chip->write_buf(mtd, buf, oobsize); + /* Send command to program the OOB data */ + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + /* Wait long R/B */ + ndelay(100); + status = chip->waitfunc(mtd, chip); + + return status & NAND_STATUS_FAIL ? -EIO : 0; +} + +static void nand_write_page_hwecc_planes(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf) +{ + int i, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps >> 1; + uint8_t *ecc_calc = chip->buffers->ecccalc; + uint8_t *p = (uint8_t *)buf; + uint32_t *eccpos = chip->ecc.layout->eccpos; + int oobsize = mtd->oobsize >> 1; + int ppb = mtd->erasesize / mtd->writesize; + int ecctotal = chip->ecc.total >> 1; + int page; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* send cmd 0x80, the MSB should be valid if realplane is 4 */ + if (chip->realplanenum == 2) + chip->cmdfunc(mtd, 0x80, 0x00, 0x00); + else + chip->cmdfunc(mtd, 0x80, 0x00, page & (1 << (chip->chip_shift - chip->page_shift))); + + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + chip->ecc.hwctl(mtd, NAND_ECC_WRITE); + chip->write_buf(mtd, p, eccsize); + chip->ecc.calculate(mtd, p, &ecc_calc[i]); + } + for (i = 0; i < ecctotal; i++) + chip->oob_poi[eccpos[i]] = ecc_calc[i]; + + chip->write_buf(mtd, chip->oob_poi, oobsize); + + chip->cmdfunc(mtd, 0x11, -1, -1); /* send cmd 0x11 */ + ndelay(100); + while(!chip->dev_ready(mtd)); + + page += ppb; + chip->cmdfunc(mtd, 0x81, 0x00, page); /* send cmd 0x81 */ + eccsteps = chip->ecc.steps >> 1; + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + chip->ecc.hwctl(mtd, NAND_ECC_WRITE); + chip->write_buf(mtd, p, eccsize); + chip->ecc.calculate(mtd, p, &ecc_calc[i]); + } + + for (i = 0; i < ecctotal; i++) + chip->oob_poi[eccpos[i]] = ecc_calc[i]; + + chip->write_buf(mtd, chip->oob_poi, oobsize); +} + +static void single_erase_cmd_planes(struct mtd_info *mtd, int global_page) +{ + struct nand_chip *chip = mtd->priv; + + /* Send commands to erase a block */ + int page; + int ppb = mtd->erasesize / mtd->writesize; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* send cmd 0x60, the MSB should be valid if realplane is 4 */ + if (chip->realplanenum == 2) + chip->cmdfunc(mtd, 0x60, -1, 0x00); + else + chip->cmdfunc(mtd, 0x60, -1, page & (1 << (chip->chip_shift - chip->page_shift))); + + page += ppb; + chip->cmdfunc(mtd, 0x60, -1, page & (~(ppb-1))); /* send cmd 0x60 */ + + chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); /* send cmd 0xd0 */ + /* Do not need wait R/B or check status */ +} + +/* + * Main initialization routine + */ +int __init jznand_init(void) +{ + struct nand_chip *this; + int nr_partitions, ret, i; + + /* Allocate memory for MTD device structure and private data */ + jz_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip), + GFP_KERNEL); + if (!jz_mtd) { + printk ("Unable to allocate JzSOC NAND MTD device structure.\n"); + return -ENOMEM; + } + + jz_mtd1 = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip), + GFP_KERNEL); + if (!jz_mtd1) { + printk ("Unable to allocate JzSOC NAND MTD device structure 1.\n"); + kfree(jz_mtd); + return -ENOMEM; + } + + /* Get pointer to private data */ + this = (struct nand_chip *) (&jz_mtd[1]); + + /* Initialize structures */ + memset((char *) jz_mtd, 0, sizeof(struct mtd_info)); + memset((char *) this, 0, sizeof(struct nand_chip)); + + /* Link the private data with the MTD structure */ + jz_mtd->priv = this; + + /* Set & initialize NAND Flash controller */ + jz_device_setup(); + + /* Set address of NAND IO lines */ + this->IO_ADDR_R = (void __iomem *) NAND_DATA_PORT1; + this->IO_ADDR_W = (void __iomem *) NAND_DATA_PORT1; + this->cmd_ctrl = jz_hwcontrol; + this->dev_ready = jz_device_ready; + +#ifdef CONFIG_MTD_HW_HM_ECC + this->ecc.calculate = jzsoc_nand_calculate_hm_ecc; + this->ecc.correct = jzsoc_nand_hm_correct_data; + this->ecc.hwctl = jzsoc_nand_enable_hm_hwecc; + this->ecc.mode = NAND_ECC_HW; + this->ecc.size = 256; + this->ecc.bytes = 3; + +#endif + +#ifdef CONFIG_MTD_HW_RS_ECC + this->ecc.calculate = jzsoc_nand_calculate_rs_ecc; + this->ecc.correct = jzsoc_nand_rs_correct_data; + this->ecc.hwctl = jzsoc_nand_enable_rs_hwecc; + this->ecc.mode = NAND_ECC_HW; + this->ecc.size = 512; + this->ecc.bytes = 9; +#endif + +#ifdef CONFIG_MTD_SW_HM_ECC + this->ecc.mode = NAND_ECC_SOFT; +#endif + /* 20 us command delay time */ + this->chip_delay = 20; + + dma_setup(); + + /* Scan to find existance of the device */ + ret = nand_scan_ident(jz_mtd, NAND_MAX_CHIPS); + if (!ret) { + if (this->planenum == 2) { + /* reset nand functions */ + this->erase_cmd = single_erase_cmd_planes; + this->ecc.read_page = nand_read_page_hwecc_rs_planes; //Muti planes read + this->ecc.write_page = nand_write_page_hwecc_planes; + this->ecc.read_oob = nand_read_oob_std_planes; + this->ecc.write_oob = nand_write_oob_std_planes; + this->write_buf = jz4740_nand_write_buf; + this->read_buf = jz4740_nand_read_buf; + + printk(KERN_INFO "Nand using two-plane mode, " + "and resized to writesize:%d oobsize:%d blocksize:0x%x \n", + jz_mtd->writesize, jz_mtd->oobsize, jz_mtd->erasesize); + } else + return -ENXIO; + } + + /* Determine whether all the partitions will use multiple planes if supported */ + nr_partitions = sizeof(partition_info) / sizeof(struct mtd_partition); + all_use_planes = 1; + for (i = 0; i < nr_partitions; i++) { + all_use_planes &= partition_info[i].use_planes; + } + + if (!ret) + ret = nand_scan_tail(jz_mtd); + + if (ret){ + kfree (jz_mtd1); + kfree (jz_mtd); + return -ENXIO; + } + + /* Register the partitions */ + printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nr_partitions, jz_mtd->name); + + if ((this->planenum == 2) && !all_use_planes) { + for (i = 0; i < nr_partitions; i++) { + if (partition_info[i].use_planes) + add_mtd_partitions(jz_mtd, &partition_info[i], 1); + else + add_mtd_partitions(jz_mtd1, &partition_info[i], 1); + } + } else { + kfree(jz_mtd1); + add_mtd_partitions(jz_mtd, partition_info, nr_partitions); + } + return 0; +} +module_init(jznand_init); + +/* + * Clean up routine + */ +#ifdef MODULE +static void __exit jznand_cleanup(void) +{ + struct nand_chip *this = (struct nand_chip *) &jz_mtd[1]; + + /* Unregister partitions */ + del_mtd_partitions(jz_mtd); + + /* Unregister the device */ + del_mtd_device (jz_mtd); + + /* Free internal data buffers */ + kfree (this->data_buf); + + /* Free the MTD device structure */ + if ((this->planenum == 2) && !all_use_planes) + kfree (jz_mtd1); + kfree (jz_mtd); +} +module_exit(jznand_cleanup); +#endif --- /dev/null +++ b/drivers/mtd/nand/jz4750_nand.c @@ -0,0 +1,1746 @@ +/* + * linux/drivers/mtd/nand/jz4750_nand.c + * + * JZ4750 NAND driver + * + * Copyright (c) 2005 - 2007 Ingenic Semiconductor Inc. + * Author: + * + * This program 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +/* 32bit instead of 16byte burst is used by DMA to read or + write NAND and BCH avoiding grabbing bus for too long */ +#define DMAC_DCMD_DS_NAND DMAC_DCMD_DS_32BIT +#define DIV_DS_NAND 4 + +#define DMAC_DCMD_DS_BCH DMAC_DCMD_DS_32BIT +#define DIV_DS_BCH 4 + +#define DEBUG1 0 +#if DEBUG1 +#define dprintk(n,x...) printk(n,##x) +#else +#define dprintk(n,x...) +#endif + +#if defined(CONFIG_MTD_HW_BCH_8BIT) +#define __ECC_ENCODING __ecc_encoding_8bit +#define __ECC_DECODING __ecc_decoding_8bit +#define ERRS_SIZE 5 /* 5 words */ +#else +#define __ECC_ENCODING __ecc_encoding_4bit +#define __ECC_DECODING __ecc_decoding_4bit +#define ERRS_SIZE 3 /* 3 words */ +#endif + +#define NAND_DATA_PORT1 0xB8000000 /* read-write area in static bank 1 */ +#define NAND_DATA_PORT2 0xB4000000 /* read-write area in static bank 2 */ +#define NAND_DATA_PORT3 0xAC000000 /* read-write area in static bank 3 */ +#define NAND_DATA_PORT4 0xA8000000 /* read-write area in static bank 4 */ + +#define NAND_ADDR_OFFSET0 0x00010000 /* address port offset for share mode */ +#define NAND_CMD_OFFSET0 0x00008000 /* command port offset for share mode */ +#define NAND_ADDR_OFFSET1 0x00000010 /* address port offset for unshare mode */ +#define NAND_CMD_OFFSET1 0x00000008 /* command port offset for unshare mode */ + +#if defined(CONFIG_MTD_NAND_DMA) +#define USE_IRQ 1 +enum { + NAND_NONE, + NAND_PROG, + NAND_READ +}; +static volatile u8 nand_status; +static volatile int dma_ack = 0; +static volatile int dma_ack1 = 0; +static char nand_dma_chan; /* automatically select a free channel */ +static char bch_dma_chan = 0; /* fixed to channel 0 */ +static u32 *errs; +static jz_dma_desc_8word *dma_desc_enc, *dma_desc_enc1, *dma_desc_dec, *dma_desc_dec1, *dma_desc_dec2, + *dma_desc_nand_prog, *dma_desc_nand_read; +static u32 *pval_nand_ddr; +static u8 *pval_nand_cmd_pgprog; /* for sending 0x11 or 0x10 when programing*/ +#if defined(CONFIG_MTD_NAND_DMABUF) +u8 *prog_buf, *read_buf; +#endif +DECLARE_WAIT_QUEUE_HEAD(nand_prog_wait_queue); +DECLARE_WAIT_QUEUE_HEAD(nand_read_wait_queue); +#endif + +struct buf_be_corrected { + u8 *data; + u8 *oob; +}; + +static u32 addr_offset; +static u32 cmd_offset; + +extern int global_page; /* for two-plane operations */ + +/* + * MTD structure for JzSOC board + */ +static struct mtd_info *jz_mtd = NULL; +extern struct mtd_info *jz_mtd1; +extern char all_use_planes; + +/* + * Define partitions for flash devices + */ +#if defined(CONFIG_JZ4750_FUWA) || defined(CONFIG_JZ4750D_FUWA1) +static struct mtd_partition partition_info[] = { + {name:"NAND BOOT partition", + offset:0 * 0x100000, + size:4 * 0x100000, + use_planes: 0}, + {name:"NAND KERNEL partition", + offset:4 * 0x100000, + size:4 * 0x100000, + use_planes: 0}, + {name:"NAND ROOTFS partition", + offset:8 * 0x100000, + size:120 * 0x100000, + use_planes: 1}, + {name:"NAND DATA1 partition", + offset:128 * 0x100000, + size:128 * 0x100000, + use_planes: 1}, + {name:"NAND DATA2 partition", + offset:256 * 0x100000, + size:256 * 0x100000, + use_planes: 1}, + {name:"NAND VFAT partition", + offset:512 * 0x100000, + size:512 * 0x100000, + use_planes: 1}, +}; + +/* Define max reserved bad blocks for each partition. + * This is used by the mtdblock-jz.c NAND FTL driver only. + * + * The NAND FTL driver reserves some good blocks which can't be + * seen by the upper layer. When the bad block number of a partition + * exceeds the max reserved blocks, then there is no more reserved + * good blocks to be used by the NAND FTL driver when another bad + * block generated. + */ +static int partition_reserved_badblocks[] = { + 2, /* reserved blocks of mtd0 */ + 2, /* reserved blocks of mtd1 */ + 10, /* reserved blocks of mtd2 */ + 10, /* reserved blocks of mtd3 */ + 20, /* reserved blocks of mtd4 */ + 20 +}; /* reserved blocks of mtd5 */ +#endif /* CONFIG_JZ4750_FUWA or CONFIG_JZ4750_APUS */ + +#if defined(CONFIG_JZ4750_APUS) +static struct mtd_partition partition_info[] = { + {name:"NAND BOOT partition", + offset:0 * 0x100000, + size:4 * 0x100000, + use_planes: 0}, + {name:"NAND KERNEL partition", + offset:4 * 0x100000, + size:4 * 0x100000, + use_planes: 0}, + {name:"NAND ROOTFS partition", + offset:8 * 0x100000, + size:504 * 0x100000, + use_planes: 0}, + {name:"NAND VFAT partition", + offset:512 * 0x100000, + size:512 * 0x100000, + use_planes: 1}, +}; + + +/* Define max reserved bad blocks for each partition. + * This is used by the mtdblock-jz.c NAND FTL driver only. + * + * The NAND FTL driver reserves some good blocks which can't be + * seen by the upper layer. When the bad block number of a partition + * exceeds the max reserved blocks, then there is no more reserved + * good blocks to be used by the NAND FTL driver when another bad + * block generated. + */ +static int partition_reserved_badblocks[] = { + 2, /* reserved blocks of mtd0 */ + 2, /* reserved blocks of mtd1 */ + 10, /* reserved blocks of mtd2 */ + 10, /* reserved blocks of mtd3 */ +}; +#endif /* CONFIG_JZ4750_FUWA or CONFIG_JZ4750_APUS */ + +/*------------------------------------------------------------------------- + * Following three functions are exported and used by the mtdblock-jz.c + * NAND FTL driver only. + */ + +unsigned short get_mtdblock_write_verify_enable(void) +{ +#ifdef CONFIG_MTD_MTDBLOCK_WRITE_VERIFY_ENABLE + return 1; +#endif + return 0; +} + +EXPORT_SYMBOL(get_mtdblock_write_verify_enable); + +unsigned short get_mtdblock_oob_copies(void) +{ + return CONFIG_MTD_OOB_COPIES; +} + +EXPORT_SYMBOL(get_mtdblock_oob_copies); + +int *get_jz_badblock_table(void) +{ + return partition_reserved_badblocks; +} + +EXPORT_SYMBOL(get_jz_badblock_table); + +/*-------------------------------------------------------------------------*/ + +static void jz_hwcontrol(struct mtd_info *mtd, int dat, u32 ctrl) +{ + struct nand_chip *this = (struct nand_chip *)(mtd->priv); + u32 nandaddr = (u32)this->IO_ADDR_W; + extern u8 nand_nce; /* defined in nand_base.c, indicates which chip select is used for current nand chip */ + + if (ctrl & NAND_CTRL_CHANGE) { + if (ctrl & NAND_NCE) { + switch (nand_nce) { + case NAND_NCE1: + this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT1; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4; + REG_EMC_NFCSR |= EMC_NFCSR_NFCE1; + break; + case NAND_NCE2: + this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT2; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4; + REG_EMC_NFCSR |= EMC_NFCSR_NFCE2; + break; + case NAND_NCE3: + this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT3; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4; + REG_EMC_NFCSR |= EMC_NFCSR_NFCE3; + break; + case NAND_NCE4: + this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT4; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3; + REG_EMC_NFCSR |= EMC_NFCSR_NFCE4; + break; + default: + printk("error: no nand_nce 0x%x\n",nand_nce); + break; + } + } else { + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3; + REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4; + } + + if (ctrl & NAND_ALE) + nandaddr = (u32)((u32)(this->IO_ADDR_W) | addr_offset); + else + nandaddr = (u32)((u32)(this->IO_ADDR_W) & ~addr_offset); + if (ctrl & NAND_CLE) + nandaddr = (u32)(nandaddr | cmd_offset); + else + nandaddr = (u32)(nandaddr & ~cmd_offset); + } + + this->IO_ADDR_W = (void __iomem *)nandaddr; + if (dat != NAND_CMD_NONE) { + writeb(dat, this->IO_ADDR_W); + /* printk("write cmd:0x%x to 0x%x\n",dat,(u32)this->IO_ADDR_W); */ + } +} + +static int jz_device_ready(struct mtd_info *mtd) +{ + int ready, wait = 10; + while (wait--); + ready = __gpio_get_pin(91); + return ready; +} + +/* + * EMC setup + */ +static void jz_device_setup(void) +{ +// PORT 0: +// PORT 1: +// PORT 2: +// PIN/BIT N FUNC0 FUNC1 +// 21 CS1# - +// 22 CS2# - +// 23 CS3# - +// 24 CS4# - +#define GPIO_CS2_N (32*2+22) +#define GPIO_CS3_N (32*2+23) +#define GPIO_CS4_N (32*2+24) +#define SMCR_VAL 0x0d444400 + + __gpio_as_nand_8bit(1); + /* Set NFE bit */ + REG_EMC_NFCSR |= EMC_NFCSR_NFE1; + /* Read/Write timings */ + REG_EMC_SMCR1 = SMCR_VAL; + +#if defined(CONFIG_MTD_NAND_CS2) + __gpio_as_func0(GPIO_CS2_N); + /* Set NFE bit */ + REG_EMC_NFCSR |= EMC_NFCSR_NFE2; + /* Read/Write timings */ + REG_EMC_SMCR2 = SMCR_VAL; +#endif + +#if defined(CONFIG_MTD_NAND_CS3) + __gpio_as_func0(GPIO_CS3_N); + /* Set NFE bit */ + REG_EMC_NFCSR |= EMC_NFCSR_NFE3; + /* Read/Write timings */ + REG_EMC_SMCR3 = SMCR_VAL; +#endif + +#if defined(CONFIG_MTD_NAND_CS4) + __gpio_as_func0(GPIO_CS4_N); + /* Set NFE bit */ + REG_EMC_NFCSR |= EMC_NFCSR_NFE4; + /* Read/Write timings */ + REG_EMC_SMCR4 = SMCR_VAL; +#endif +} + +#ifdef CONFIG_MTD_HW_BCH_ECC + +static void jzsoc_nand_enable_bch_hwecc(struct mtd_info *mtd, int mode) +{ + struct nand_chip *this = (struct nand_chip *)(mtd->priv); + int eccsize = this->ecc.size; + int eccbytes = this->ecc.bytes; + int eccsteps = this->ecc.steps / this->planenum; + int oob_per_eccsize = this->ecc.layout->eccpos[0] / eccsteps; + + REG_BCH_INTS = 0xffffffff; + if (mode == NAND_ECC_READ) { + __ECC_DECODING(); + __ecc_cnt_dec(eccsize + oob_per_eccsize + eccbytes); +#if defined(CONFIG_MTD_NAND_DMA) + __ecc_dma_enable(); +#endif + } + + if (mode == NAND_ECC_WRITE) { + __ECC_ENCODING(); + __ecc_cnt_enc(eccsize + oob_per_eccsize); +#if defined(CONFIG_MTD_NAND_DMA) + __ecc_dma_enable(); +#endif + } +} + +/** + * bch_correct + * @dat: data to be corrected + * @idx: the index of error bit in an eccsize + */ +static void bch_correct(struct mtd_info *mtd, u8 * dat, int idx) +{ + struct nand_chip *this = (struct nand_chip *)(mtd->priv); + int eccsize = this->ecc.size; + int eccsteps = this->ecc.steps / this->planenum; + int ecc_pos = this->ecc.layout->eccpos[0]; + int oob_per_eccsize = ecc_pos / eccsteps; + int i, bit; /* the 'bit' of i byte is error */ + + i = (idx - 1) >> 3; + bit = (idx - 1) & 0x7; + + dprintk("error:i=%d, bit=%d\n",i,bit); + + if (i < eccsize){ + ((struct buf_be_corrected *)dat)->data[i] ^= (1 << bit); + } else if (i < eccsize + oob_per_eccsize) { + ((struct buf_be_corrected *)dat)->oob[i-eccsize] ^= (1 << bit); + } +} + +#if defined(CONFIG_MTD_NAND_DMA) + +/** + * jzsoc_nand_bch_correct_data + * @mtd: mtd info structure + * @dat: data to be corrected + * @errs0: pointer to the dma target buffer of bch decoding which stores BHINTS and + * BHERR0~3(8-bit BCH) or BHERR0~1(4-bit BCH) + * @calc_ecc: no used + */ +static int jzsoc_nand_bch_correct_data(struct mtd_info *mtd, u_char * dat, u_char * errs0, u_char * calc_ecc) +{ + u32 stat; + u32 *errs = (u32 *)errs0; + + if (REG_DMAC_DCCSR(0) & DMAC_DCCSR_BERR) { + stat = errs[0]; + dprintk("stat=%x err0:%x err1:%x \n", stat, errs[1], errs[2]); + + if (stat & BCH_INTS_ERR) { + if (stat & BCH_INTS_UNCOR) { + printk("NAND: Uncorrectable ECC error\n"); + return -1; + } else { + u32 errcnt = (stat & BCH_INTS_ERRC_MASK) >> BCH_INTS_ERRC_BIT; + switch (errcnt) { +#if defined(CONFIG_MTD_HW_BCH_8BIT) + case 8: + bch_correct(mtd, dat, (errs[4] & BCH_ERR_INDEX_ODD_MASK) >> BCH_ERR_INDEX_ODD_BIT); + case 7: + bch_correct(mtd, dat, (errs[4] & BCH_ERR_INDEX_EVEN_MASK) >> BCH_ERR_INDEX_EVEN_BIT); + case 6: + bch_correct(mtd, dat, (errs[3] & BCH_ERR_INDEX_ODD_MASK) >> BCH_ERR_INDEX_ODD_BIT); + case 5: + bch_correct(mtd, dat, (errs[3] & BCH_ERR_INDEX_EVEN_MASK) >> BCH_ERR_INDEX_EVEN_BIT); +#endif + case 4: + bch_correct(mtd, dat, (errs[2] & BCH_ERR_INDEX_ODD_MASK) >> BCH_ERR_INDEX_ODD_BIT); + case 3: + bch_correct(mtd, dat, (errs[2] & BCH_ERR_INDEX_EVEN_MASK) >> BCH_ERR_INDEX_EVEN_BIT); + case 2: + bch_correct(mtd, dat, (errs[1] & BCH_ERR_INDEX_ODD_MASK) >> BCH_ERR_INDEX_ODD_BIT); + case 1: + bch_correct(mtd, dat, (errs[1] & BCH_ERR_INDEX_EVEN_MASK) >> BCH_ERR_INDEX_EVEN_BIT); + default: + break; + } + } + } + } + + return 0; +} + +#else /* cpu mode */ + +/** + * jzsoc_nand_bch_correct_data + * @mtd: mtd info structure + * @dat: data to be corrected + * @read_ecc: pointer to ecc buffer calculated when nand writing + * @calc_ecc: no used + */ +static int jzsoc_nand_bch_correct_data(struct mtd_info *mtd, u_char * dat, u_char * read_ecc, u_char * calc_ecc) +{ + struct nand_chip *this = (struct nand_chip *)(mtd->priv); + int eccsize = this->ecc.size; + int eccbytes = this->ecc.bytes; + int eccsteps = this->ecc.steps / this->planenum; + int ecc_pos = this->ecc.layout->eccpos[0]; + int oob_per_eccsize = ecc_pos / eccsteps; + short k; + u32 stat; + + /* Write data to REG_BCH_DR */ + for (k = 0; k < eccsize; k++) { + REG_BCH_DR = ((struct buf_be_corrected *)dat)->data[k]; + } + /* Write oob to REG_BCH_DR */ + for (k = 0; k < oob_per_eccsize; k++) { + REG_BCH_DR = ((struct buf_be_corrected *)dat)->oob[k]; + } + /* Write parities to REG_BCH_DR */ + for (k = 0; k < eccbytes; k++) { + REG_BCH_DR = read_ecc[k]; + } + + /* Wait for completion */ + __ecc_decode_sync(); + __ecc_disable(); + + /* Check decoding */ + stat = REG_BCH_INTS; + + if (stat & BCH_INTS_ERR) { + /* Error occurred */ + if (stat & BCH_INTS_UNCOR) { + printk("NAND: Uncorrectable ECC error--\n"); + return -1; + } else { + u32 errcnt = (stat & BCH_INTS_ERRC_MASK) >> BCH_INTS_ERRC_BIT; + switch (errcnt) { +#if defined(CONFIG_MTD_HW_BCH_8BIT) + case 8: + bch_correct(mtd, dat, (REG_BCH_ERR3 & BCH_ERR_INDEX_ODD_MASK) >> BCH_ERR_INDEX_ODD_BIT); + /* FALL-THROUGH */ + case 7: + bch_correct(mtd, dat, (REG_BCH_ERR3 & BCH_ERR_INDEX_EVEN_MASK) >> BCH_ERR_INDEX_EVEN_BIT); + /* FALL-THROUGH */ + case 6: + bch_correct(mtd, dat, (REG_BCH_ERR2 & BCH_ERR_INDEX_ODD_MASK) >> BCH_ERR_INDEX_ODD_BIT); + /* FALL-THROUGH */ + case 5: + bch_correct(mtd, dat, (REG_BCH_ERR2 & BCH_ERR_INDEX_EVEN_MASK) >> BCH_ERR_INDEX_EVEN_BIT); + /* FALL-THROUGH */ +#endif + case 4: + bch_correct(mtd, dat, (REG_BCH_ERR1 & BCH_ERR_INDEX_ODD_MASK) >> BCH_ERR_INDEX_ODD_BIT); + /* FALL-THROUGH */ + case 3: + bch_correct(mtd, dat, (REG_BCH_ERR1 & BCH_ERR_INDEX_EVEN_MASK) >> BCH_ERR_INDEX_EVEN_BIT); + /* FALL-THROUGH */ + case 2: + bch_correct(mtd, dat, (REG_BCH_ERR0 & BCH_ERR_INDEX_ODD_MASK) >> BCH_ERR_INDEX_ODD_BIT); + /* FALL-THROUGH */ + case 1: + bch_correct(mtd, dat, (REG_BCH_ERR0 & BCH_ERR_INDEX_EVEN_MASK) >> BCH_ERR_INDEX_EVEN_BIT); + return 0; + default: + break; + } + } + } + + return 0; +} +#endif /* CONFIG_MTD_NAND_DMA */ + +static int jzsoc_nand_calculate_bch_ecc(struct mtd_info *mtd, const u_char * dat, u_char * ecc_code) +{ + struct nand_chip *this = (struct nand_chip *)(mtd->priv); + int eccsize = this->ecc.size; + int eccbytes = this->ecc.bytes; + int eccsteps = this->ecc.steps / this->planenum; + int ecc_pos = this->ecc.layout->eccpos[0]; + int oob_per_eccsize = ecc_pos / eccsteps; + volatile u8 *paraddr = (volatile u8 *)BCH_PAR0; + short i; + + /* Write data to REG_BCH_DR */ + for (i = 0; i < eccsize; i++) { + REG_BCH_DR = ((struct buf_be_corrected *)dat)->data[i]; + } + /* Write oob to REG_BCH_DR */ + for (i = 0; i < oob_per_eccsize; i++) { + REG_BCH_DR = ((struct buf_be_corrected *)dat)->oob[i]; + } + __ecc_encode_sync(); + __ecc_disable(); + + for (i = 0; i < eccbytes; i++) { + ecc_code[i] = *paraddr++; + } + + return 0; +} + +#if defined(CONFIG_MTD_NAND_DMA) + +/** + * nand_write_page_hwecc_bch - [REPLACABLE] hardware ecc based page write function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: data buffer + */ +static void nand_write_page_hwecc_bch0(struct mtd_info *mtd, struct nand_chip *chip, const uint8_t * buf, u8 cmd_pgprog) +{ + int eccsize = chip->ecc.size; + int eccsteps = chip->ecc.steps / chip->planenum; + int eccbytes = chip->ecc.bytes; + int ecc_pos = chip->ecc.layout->eccpos[0]; + int oob_per_eccsize = ecc_pos / eccsteps; + int pagesize = mtd->writesize / chip->planenum; + int oobsize = mtd->oobsize / chip->planenum; + int i, err, timeout; + const u8 *databuf; + u8 *oobbuf; + jz_dma_desc_8word *desc; + +#if defined(CONFIG_MTD_NAND_DMABUF) + memcpy(prog_buf, buf, pagesize); + memcpy(prog_buf + pagesize, chip->oob_poi, oobsize); + dma_cache_wback_inv((u32)prog_buf, pagesize + oobsize); +#else + databuf = buf; + oobbuf = chip->oob_poi; + + /* descriptors for encoding data blocks */ + desc = dma_desc_enc; + for (i = 0; i < eccsteps; i++) { + desc->dsadr = CPHYSADDR((u32)databuf) + i * eccsize; /* DMA source address */ + desc->dtadr = CPHYSADDR((u32)oobbuf) + ecc_pos + i * eccbytes; /* DMA target address */ + dprintk("dma_desc_enc:desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + desc++; + } + + /* descriptors for encoding oob blocks */ + desc = dma_desc_enc1; + for (i = 0; i < eccsteps; i++) { + desc->dsadr = CPHYSADDR((u32)oobbuf) + oob_per_eccsize * i; /* DMA source address, 28/4 = 7bytes */ + desc->dtadr = CPHYSADDR((u32)oobbuf) + ecc_pos + i * eccbytes; /* DMA target address */ + dprintk("dma_desc_enc1:desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + desc++; + } + + /* descriptor for nand programing data block */ + desc = dma_desc_nand_prog; + desc->dsadr = CPHYSADDR((u32)databuf); /* DMA source address */ + desc->dtadr = CPHYSADDR((u32)chip->IO_ADDR_W); /* It will be changed when using multiply chip select */ + dprintk("dma_desc_nand_prog:desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + + /* descriptor for nand programing oob block */ + desc++; + desc->dsadr = CPHYSADDR((u32)oobbuf); /* DMA source address */ + desc->dtadr = CPHYSADDR((u32)chip->IO_ADDR_W); /* It will be changed when using multiply chip select */ + dprintk("dma_desc_oob_prog:desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + + /* descriptor for __nand_cmd(CMD_PGPROG) */ + desc++; + *pval_nand_cmd_pgprog = cmd_pgprog; + desc->dsadr = CPHYSADDR((u32)pval_nand_cmd_pgprog); + desc->dtadr = CPHYSADDR((u32)chip->IO_ADDR_R | cmd_offset); /* DMA target address: cmdport */ + if (cmd_pgprog == 0x10) + desc->dcmd |= DMAC_DCMD_LINK; /* __nand_sync() by a DMA descriptor */ + else if (cmd_pgprog == 0x11) + desc->dcmd &= ~DMAC_DCMD_LINK; /* __nand_sync() by polling */ + + dma_cache_wback_inv((u32)dma_desc_enc, (eccsteps * 2 + 2 + 1) * (sizeof(jz_dma_desc_8word))); + dma_cache_wback_inv((u32)databuf, pagesize); + dma_cache_wback_inv((u32)oobbuf, oobsize); + /* 4*6: pval_nand_ddr, pval_nand_dcs, pval_bch_ddr, pval_bch_dcs, dummy, pval_nand_cmd_pgprog */ + dma_cache_wback_inv((u32)pval_nand_ddr, 4 * 8); /* 8 words, a cache line */ +#endif + + REG_DMAC_DCCSR(bch_dma_chan) = 0; + REG_DMAC_DCCSR(nand_dma_chan) = 0; + + /* Setup DMA descriptor address */ + REG_DMAC_DDA(bch_dma_chan) = CPHYSADDR((u32)dma_desc_enc); + REG_DMAC_DDA(nand_dma_chan) = CPHYSADDR((u32)dma_desc_nand_prog); + + /* Setup request source */ + REG_DMAC_DRSR(bch_dma_chan) = DMAC_DRSR_RS_BCH_ENC; + REG_DMAC_DRSR(nand_dma_chan) = DMAC_DRSR_RS_AUTO; + + /* Setup DMA channel control/status register */ + REG_DMAC_DCCSR(bch_dma_chan) = DMAC_DCCSR_DES8 | DMAC_DCCSR_EN; /* descriptor transfer, clear status, start channel */ + + /* Enable DMA */ + REG_DMAC_DMACR(0) |= DMAC_DMACR_DMAE; + REG_DMAC_DMACR(nand_dma_chan/HALF_DMA_NUM) |= DMAC_DMACR_DMAE; + + /* Enable BCH encoding */ + chip->ecc.hwctl(mtd, NAND_ECC_WRITE); + + dma_ack1 = 0; + nand_status = NAND_PROG; + + /* DMA doorbell set -- start DMA now ... */ + __dmac_channel_set_doorbell(bch_dma_chan); + +#if USE_IRQ + if (cmd_pgprog == 0x10) { + dprintk("nand prog before wake up\n"); + err = wait_event_interruptible_timeout(nand_prog_wait_queue, dma_ack1, 3 * HZ); + nand_status = NAND_NONE; + dprintk("nand prog after wake up\n"); + if (!err) { + printk("*** NAND WRITE, Warning, wait event 3s timeout!\n"); + dump_jz_dma_channel(0); + dump_jz_dma_channel(nand_dma_chan); + printk("REG_BCH_CR=%x REG_BCH_CNT=0x%x REG_BCH_INTS=%x\n", REG_BCH_CR, REG_BCH_CNT, REG_BCH_INTS); + } + dprintk("timeout remain = %d\n", err); + } else if (cmd_pgprog == 0x11) { + timeout = 100000; + while ((!__dmac_channel_transmit_end_detected(nand_dma_chan)) && (timeout--)); + if (timeout <= 0) + printk("two-plane prog 0x11 timeout!\n"); + } +#else + timeout = 100000; + while ((!__dmac_channel_transmit_end_detected(nand_dma_chan)) && (timeout--)); + while(!chip->dev_ready(mtd)); + if (timeout <= 0) + printk("not use irq, prog timeout!\n"); +#endif +} + +static void nand_write_page_hwecc_bch(struct mtd_info *mtd, struct nand_chip *chip, const uint8_t * buf) +{ + nand_write_page_hwecc_bch0(mtd, chip, buf, 0x10); +} + +static void nand_write_page_hwecc_bch_planes(struct mtd_info *mtd, struct nand_chip *chip, const uint8_t * buf) +{ + int page; + int pagesize = mtd->writesize >> 1; + int ppb = mtd->erasesize / mtd->writesize; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* send cmd 0x80, the MSB should be valid if realplane is 4 */ + if (chip->realplanenum == 2) + chip->cmdfunc(mtd, 0x80, 0x00, 0x00); + else + chip->cmdfunc(mtd, 0x80, 0x00, page & (1 << (chip->chip_shift - chip->page_shift))); + + nand_write_page_hwecc_bch0(mtd, chip, buf, 0x11); + chip->cmdfunc(mtd, 0x81, 0x00, page + ppb); + nand_write_page_hwecc_bch0(mtd, chip, buf + pagesize, 0x10); +} + +#else /* nand write in cpu mode */ + +static void nand_write_page_hwecc_bch(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf) +{ + int i, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps / chip->planenum; + int oob_per_eccsize = chip->ecc.layout->eccpos[0] / eccsteps; + int oobsize = mtd->oobsize / chip->planenum; + int ecctotal = chip->ecc.total / chip->planenum; + uint8_t *p = (uint8_t *)buf; + uint8_t *ecc_calc = chip->buffers->ecccalc; + uint32_t *eccpos = chip->ecc.layout->eccpos; + static struct buf_be_corrected buf_calc0; + struct buf_be_corrected *buf_calc = &buf_calc0; + + for (i = 0; i < eccsteps; i++, p += eccsize) { + buf_calc->data = (u8 *)buf + eccsize * i; + buf_calc->oob = chip->oob_poi + oob_per_eccsize * i; + chip->ecc.hwctl(mtd, NAND_ECC_WRITE); + chip->ecc.calculate(mtd, (u8 *)buf_calc, &ecc_calc[eccbytes*i]); + chip->write_buf(mtd, p, eccsize); + } + + for (i = 0; i < ecctotal; i++) + chip->oob_poi[eccpos[i]] = ecc_calc[i]; + + chip->write_buf(mtd, chip->oob_poi, oobsize); +} + +/* nand write using two-plane mode */ +static void nand_write_page_hwecc_bch_planes(struct mtd_info *mtd, struct nand_chip *chip, + const uint8_t *buf) +{ + int pagesize = mtd->writesize >> 1; + int ppb = mtd->erasesize / mtd->writesize; + int page; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* send cmd 0x80, the MSB should be valid if realplane is 4 */ + if (chip->realplanenum == 2) + chip->cmdfunc(mtd, 0x80, 0x00, 0x00); + else + chip->cmdfunc(mtd, 0x80, 0x00, page & (1 << (chip->chip_shift - chip->page_shift))); + + nand_write_page_hwecc_bch(mtd, chip, buf); + + chip->cmdfunc(mtd, 0x11, -1, -1); /* send cmd 0x11 */ + ndelay(100); + while(!chip->dev_ready(mtd)); + + chip->cmdfunc(mtd, 0x81, 0x00, page + ppb); /* send cmd 0x81 */ + nand_write_page_hwecc_bch(mtd, chip, buf + pagesize); +} +#endif /* CONFIG_MTD_NAND_DMA */ + +/** + * nand_read_page_hwecc_bch - [REPLACABLE] hardware ecc based page read function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * + * Not for syndrome calculating ecc controllers which need a special oob layout + */ +#if defined(CONFIG_MTD_NAND_DMA) +static int nand_read_page_hwecc_bch0(struct mtd_info *mtd, struct nand_chip *chip, uint8_t * buf, u32 page) +{ + int i, eccsize = chip->ecc.size; + int eccsteps = chip->ecc.steps / chip->planenum; + int eccbytes = chip->ecc.bytes; + int ecc_pos = chip->ecc.layout->eccpos[0]; + int oob_per_eccsize = ecc_pos / eccsteps; + int pagesize = mtd->writesize / chip->planenum; + int oobsize = mtd->oobsize / chip->planenum; + u8 *databuf, *oobbuf; + jz_dma_desc_8word *desc; + int err; + u32 addrport, cmdport; + static struct buf_be_corrected buf_correct0; + + addrport = (u32)(chip->IO_ADDR_R) | addr_offset; + cmdport = (u32)(chip->IO_ADDR_R) | cmd_offset; + +#if defined(CONFIG_MTD_NAND_DMABUF) + databuf = read_buf; + oobbuf = read_buf + pagesize; + + dma_cache_inv((u32)read_buf, pagesize + oobsize); // databuf should be invalidated. + memset(errs, 0, eccsteps * ERRS_SIZE * 4); + dma_cache_wback_inv((u32)errs, eccsteps * ERRS_SIZE * 4); +#else + + databuf = buf; + oobbuf = chip->oob_poi; + + /* descriptor for nand reading data block */ + desc = dma_desc_nand_read; + desc->dsadr = CPHYSADDR((u32)chip->IO_ADDR_R); /* It will be changed when using multiply chip select */ + desc->dtadr = CPHYSADDR((u32)databuf); /* DMA target address */ + + dprintk("desc_nand_read:desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + + /* descriptor for nand reading oob block */ + desc++; + desc->dsadr = CPHYSADDR((u32)chip->IO_ADDR_R); /* It will be changed when using multiply chip select */ + desc->dtadr = CPHYSADDR((u32)oobbuf); /* DMA target address */ + dprintk("desc_oob_read:desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + + /* descriptors for data to be written to bch */ + desc = dma_desc_dec; + for (i = 0; i < eccsteps; i++) { + desc->dsadr = CPHYSADDR((u32)databuf) + i * eccsize; /* DMA source address */ + dprintk("dma_desc_dec:desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + desc++; + } + + /* descriptors for oob to be written to bch */ + desc = dma_desc_dec1; + for (i = 0; i < eccsteps; i++) { + desc->dsadr = CPHYSADDR((u32)oobbuf) + oob_per_eccsize * i; /* DMA source address */ + dprintk("dma_desc_dec1:desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + desc++; + } + + /* descriptors for parities to be written to bch */ + desc = dma_desc_dec2; + for (i = 0; i < eccsteps; i++) { + desc->dsadr = CPHYSADDR((u32)oobbuf) + ecc_pos + i * eccbytes; /* DMA source address */ + dprintk("dma_desc_dec2:desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + desc++; + } + + dma_cache_wback_inv((u32)dma_desc_nand_read, (2 + eccsteps * 3) * (sizeof(jz_dma_desc_8word))); + + memset(errs, 0, eccsteps * ERRS_SIZE * 4); + dma_cache_inv((u32)databuf, pagesize); // databuf should be invalidated. + dma_cache_inv((u32)oobbuf, oobsize); // oobbuf should be invalidated too + dma_cache_wback_inv((u32)errs, eccsteps * ERRS_SIZE * 4); +#endif + REG_DMAC_DCCSR(bch_dma_chan) = 0; + REG_DMAC_DCCSR(nand_dma_chan) = 0; + + /* Setup DMA descriptor address */ + REG_DMAC_DDA(nand_dma_chan) = CPHYSADDR((u32)dma_desc_nand_read); + REG_DMAC_DDA(bch_dma_chan) = CPHYSADDR((u32)dma_desc_dec); + + /* Setup request source */ + REG_DMAC_DRSR(nand_dma_chan) = DMAC_DRSR_RS_NAND; + REG_DMAC_DRSR(bch_dma_chan) = DMAC_DRSR_RS_BCH_DEC; + + /* Enable DMA */ + REG_DMAC_DMACR(0) |= DMAC_DMACR_DMAE; + REG_DMAC_DMACR(nand_dma_chan/HALF_DMA_NUM) |= DMAC_DMACR_DMAE; + + /* Enable BCH decoding */ + chip->ecc.hwctl(mtd, NAND_ECC_READ); + + dma_ack = 0; + nand_status = NAND_READ; + /* DMA doorbell set -- start nand DMA now ... */ + __dmac_channel_set_doorbell(nand_dma_chan); + + /* Setup DMA channel control/status register */ + REG_DMAC_DCCSR(nand_dma_chan) = DMAC_DCCSR_DES8 | DMAC_DCCSR_EN; + +#define __nand_cmd(n) (REG8(cmdport) = (n)) +#define __nand_addr(n) (REG8(addrport) = (n)) + + __nand_cmd(NAND_CMD_READ0); + + __nand_addr(0); + if (pagesize != 512) + __nand_addr(0); + + __nand_addr(page & 0xff); + __nand_addr((page >> 8) & 0xff); + + /* One more address cycle for the devices whose number of page address bits > 16 */ + if (((chip->chipsize >> chip->page_shift) >> 16) - 1 > 0) + __nand_addr((page >> 16) & 0xff); + + if (pagesize != 512) + __nand_cmd(NAND_CMD_READSTART); + +#if USE_IRQ + do { + err = wait_event_interruptible_timeout(nand_read_wait_queue, dma_ack, 3 * HZ); + }while(err == -ERESTARTSYS); + nand_status = NAND_NONE; + + if (!err) { + printk("*** NAND READ, Warning, wait event 3s timeout!\n"); + dump_jz_dma_channel(0); + dump_jz_dma_channel(nand_dma_chan); + printk("REG_BCH_CR=%x REG_BCH_CNT=0x%x REG_BCH_INTS=%x\n", REG_BCH_CR, REG_BCH_CNT, REG_BCH_INTS); + printk("databuf[0]=%x\n", databuf[0]); + } + dprintk("timeout remain = %d\n", err); +#else + int timeout; + timeout = 100000; + while ((!__dmac_channel_transmit_end_detected(bch_dma_chan)) && (timeout--)); + if (timeout <= 0) { + printk("not use irq, NAND READ timeout!\n"); + } +#endif + + for (i = 0; i < eccsteps; i++) { + int stat; + struct buf_be_corrected *buf_correct = &buf_correct0; + + buf_correct->data = databuf + eccsize * i; + buf_correct->oob = oobbuf + oob_per_eccsize * i; + + stat = chip->ecc.correct(mtd, (u8 *)buf_correct, (u8 *)&errs[i * ERRS_SIZE], NULL); + if (stat < 0) + mtd->ecc_stats.failed++; + else + mtd->ecc_stats.corrected += stat; + } + +#if defined(CONFIG_MTD_NAND_DMABUF) + memcpy(buf, read_buf, pagesize); + memcpy(chip->oob_poi, read_buf + pagesize, oobsize); +#endif + return 0; +} + +static int nand_read_page_hwecc_bch(struct mtd_info *mtd, struct nand_chip *chip, uint8_t * buf) +{ + u32 page = global_page; + + nand_read_page_hwecc_bch0(mtd, chip, buf, page); + return 0; +} + +static int nand_read_page_hwecc_bch_planes(struct mtd_info *mtd, struct nand_chip *chip, uint8_t * buf) +{ + u32 page; + int pagesize = mtd->writesize >> 1; + int ppb = mtd->erasesize / mtd->writesize; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* read 1st page */ + nand_read_page_hwecc_bch0(mtd, chip, buf, page); + + /* read 2nd page */ + nand_read_page_hwecc_bch0(mtd, chip, buf + pagesize, page + ppb); + return 0; +} + +#else /* nand read in cpu mode */ + +static int nand_read_page_hwecc_bch(struct mtd_info *mtd, struct nand_chip *chip, uint8_t * buf) +{ + int i, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps / chip->planenum; + int ecc_pos = chip->ecc.layout->eccpos[0]; + int oob_per_eccsize = ecc_pos / eccsteps; + uint8_t *ecc_calc = chip->buffers->ecccalc; + uint8_t *ecc_code = chip->buffers->ecccode; + uint32_t *eccpos = chip->ecc.layout->eccpos; + int pagesize = mtd->writesize / chip->planenum; + int oobsize = mtd->oobsize / chip->planenum; + int ecctotal = chip->ecc.total / chip->planenum; + static struct buf_be_corrected buf_correct0; + + chip->read_buf(mtd, buf, pagesize); + chip->read_buf(mtd, chip->oob_poi, oobsize); + + for (i = 0; i < ecctotal; i++) { + ecc_code[i] = chip->oob_poi[eccpos[i]]; + } + + for (i = 0; i < eccsteps; i++) { + int stat; + struct buf_be_corrected *buf_correct = &buf_correct0; + + buf_correct->data = buf + eccsize * i; + buf_correct->oob = chip->oob_poi + oob_per_eccsize * i; + + chip->ecc.hwctl(mtd, NAND_ECC_READ); + stat = chip->ecc.correct(mtd, (u8 *)buf_correct, &ecc_code[eccbytes*i], &ecc_calc[eccbytes*i]); + if (stat < 0) + mtd->ecc_stats.failed++; + else + mtd->ecc_stats.corrected += stat; + } + + return 0; +} + +static int nand_read_page_hwecc_bch_planes(struct mtd_info *mtd, struct nand_chip *chip, uint8_t * buf) +{ + int pagesize = mtd->writesize >> 1; + int ppb = mtd->erasesize / mtd->writesize; + uint32_t page; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* Read first page */ + chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page); + nand_read_page_hwecc_bch(mtd, chip, buf); + + /* Read 2nd page */ + chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page + ppb); + nand_read_page_hwecc_bch(mtd, chip, buf+pagesize); + return 0; +} +#endif /* CONFIG_MTD_NAND_DMA */ + +#endif /* CONFIG_MTD_HW_BCH_ECC */ + +/* read oob using two-plane mode */ +static int nand_read_oob_std_planes(struct mtd_info *mtd, struct nand_chip *chip, + int global_page, int sndcmd) +{ + int page; + int oobsize = mtd->oobsize >> 1; + int ppb = mtd->erasesize / mtd->writesize; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* Read first page OOB */ + if (sndcmd) { + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + } + chip->read_buf(mtd, chip->oob_poi, oobsize); + /* Read second page OOB */ + page += ppb; + if (sndcmd) { + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + sndcmd = 0; + } + chip->read_buf(mtd, chip->oob_poi+oobsize, oobsize); + return 0; +} + +/* write oob using two-plane mode */ +static int nand_write_oob_std_planes(struct mtd_info *mtd, struct nand_chip *chip, + int global_page) +{ + int status = 0, page; + const uint8_t *buf = chip->oob_poi; + int pagesize = mtd->writesize >> 1; + int oobsize = mtd->oobsize >> 1; + int ppb = mtd->erasesize / mtd->writesize; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* send cmd 0x80, the MSB should be valid if realplane is 4 */ + if (chip->realplanenum == 2) + chip->cmdfunc(mtd, 0x80, pagesize, 0x00); + else + chip->cmdfunc(mtd, 0x80, pagesize, page & (1 << (chip->chip_shift - chip->page_shift))); + + chip->write_buf(mtd, buf, oobsize); + /* Send first command to program the OOB data */ + chip->cmdfunc(mtd, 0x11, -1, -1); + ndelay(100); + status = chip->waitfunc(mtd, chip); + + page += ppb; + buf += oobsize; + chip->cmdfunc(mtd, 0x81, pagesize, page); + chip->write_buf(mtd, buf, oobsize); + /* Send command to program the OOB data */ + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + /* Wait long R/B */ + ndelay(100); + status = chip->waitfunc(mtd, chip); + + return status & NAND_STATUS_FAIL ? -EIO : 0; +} + +/* nand erase using two-plane mode */ +static void single_erase_cmd_planes(struct mtd_info *mtd, int global_page) +{ + struct nand_chip *chip = mtd->priv; + int page, ppb = mtd->erasesize / mtd->writesize; + + page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */ + + /* send cmd 0x60, the MSB should be valid if realplane is 4 */ + if (chip->realplanenum == 2) + chip->cmdfunc(mtd, 0x60, -1, 0x00); + else + chip->cmdfunc(mtd, 0x60, -1, page & (1 << (chip->chip_shift - chip->page_shift))); + + page += ppb; + chip->cmdfunc(mtd, 0x60, -1, page & (~(ppb-1))); /* send cmd 0x60 */ + + chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); /* send cmd 0xd0 */ + /* Do not need wait R/B or check status */ +} + +#if defined(CONFIG_MTD_NAND_DMA) + +#if USE_IRQ +static irqreturn_t nand_dma_irq(int irq, void *dev_id) +{ + u8 dma_chan; + volatile int wakeup = 0; + + dma_chan = irq - IRQ_DMA_0; + + dprintk("jz4750_dma_irq %d, channel %d\n", irq, dma_chan); + + if (__dmac_channel_transmit_halt_detected(dma_chan)) { + __dmac_channel_clear_transmit_halt(dma_chan); + wakeup = 1; + printk("DMA HALT\n"); + } + + if (__dmac_channel_address_error_detected(dma_chan)) { + + REG_DMAC_DCCSR(dma_chan) &= ~DMAC_DCCSR_EN; /* disable DMA */ + __dmac_channel_clear_address_error(dma_chan); + + REG_DMAC_DSAR(dma_chan) = 0; /* reset source address register */ + REG_DMAC_DTAR(dma_chan) = 0; /* reset destination address register */ + + /* clear address error in DMACR */ + REG_DMAC_DMACR((dma_chan / HALF_DMA_NUM)) &= ~(1 << 2); + wakeup = 1; + printk("DMA address error!\n"); + } + + if (__dmac_channel_descriptor_invalid_detected(dma_chan)) { + __dmac_channel_clear_descriptor_invalid(dma_chan); + wakeup = 1; + printk("DMA DESC INVALID\n"); + } +#if 1 + + while (!__dmac_channel_transmit_end_detected(dma_chan)); + + if (__dmac_channel_count_terminated_detected(dma_chan)) { + dprintk("DMA CT\n"); + __dmac_channel_clear_count_terminated(dma_chan); + wakeup = 0; + } +#endif + + if (__dmac_channel_transmit_end_detected(dma_chan)) { + dprintk("DMA TT\n"); + REG_DMAC_DCCSR(dma_chan) &= ~DMAC_DCCSR_EN; /* disable DMA */ + __dmac_channel_clear_transmit_end(dma_chan); + wakeup = 1; + } + + if (wakeup) { + dprintk("ack %d irq , wake up dma_chan %d nand_status %d\n", dma_ack, dma_chan, nand_status); + /* wakeup wait event */ + if ((dma_chan == nand_dma_chan) && (nand_status == NAND_PROG)) { + dprintk("nand prog dma irq, wake up----\n"); + dma_ack1 = 1; + wake_up_interruptible(&nand_prog_wait_queue); + } + + if ((dma_chan == bch_dma_chan) && (nand_status == NAND_READ)) { + dprintk("nand read irq, wake up----\n"); + dma_ack = 1; + wake_up_interruptible(&nand_read_wait_queue); + } + wakeup = 0; + } + + return IRQ_HANDLED; +} +#endif /* USE_IRQ */ + +static int jz4750_nand_dma_init(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd->priv; + int eccsize = chip->ecc.size; + int eccsteps = chip->ecc.steps / chip->planenum; + int eccbytes = chip->ecc.bytes; + int ecc_pos = chip->ecc.layout->eccpos[0]; + int oob_per_eccsize = ecc_pos / eccsteps; + int pagesize = mtd->writesize / chip->planenum; + int oobsize = mtd->oobsize / chip->planenum; + int i, err; + jz_dma_desc_8word *desc, *dma_desc_bch_ddr, *dma_desc_nand_ddr, *dma_desc_nand_cmd_pgprog; + u32 *pval_nand_dcs, *pval_bch_ddr, *pval_bch_dcs, *dummy; + u32 next; +#if defined(CONFIG_MTD_NAND_DMABUF) + u8 *oobbuf; +#endif + +#if USE_IRQ + if ((nand_dma_chan = jz_request_dma(DMA_ID_NAND, "nand read or write", nand_dma_irq, IRQF_DISABLED, NULL)) < 0) { + printk("can't reqeust DMA nand channel.\n"); + return 0; + } + dprintk("nand dma channel:%d----\n", nand_dma_chan); + + if ((err = request_irq(IRQ_DMA_0 + bch_dma_chan, nand_dma_irq, IRQF_DISABLED, "bch_dma", NULL))) { + printk("bch_dma irq request err\n"); + return 0; + } +#else + if ((nand_dma_chan = jz_request_dma(DMA_ID_NAND, "nand read or write", NULL, IRQF_DISABLED, NULL)) < 0) { + printk("can't reqeust DMA nand channel.\n"); + return 0; + } + dprintk("nand dma channel:%d----\n", nand_dma_chan); +#endif + + __dmac_channel_enable_clk(nand_dma_chan); + __dmac_channel_enable_clk(bch_dma_chan); + +#if defined(CONFIG_MTD_NAND_DMABUF) + if (pagesize < 4096) { + read_buf = prog_buf = (u8 *) __get_free_page(GFP_KERNEL); + } else { + read_buf = prog_buf = (u8 *) __get_free_pages(GFP_KERNEL, 1); + } + if (!read_buf) + return -ENOMEM; +#endif + /* space for the error reports of bch decoding((4 * 5 * eccsteps) bytes), and the space for the value + * of ddr and dcs of channel 0 and channel nand_dma_chan (4 * (2 + 2) bytes) */ + errs = (u32 *)kmalloc(4 * (2 + 2 + 5 * eccsteps), GFP_KERNEL); + if (!errs) + return -ENOMEM; + + pval_nand_ddr = errs + 5 * eccsteps; + pval_nand_dcs = pval_nand_ddr + 1; + pval_bch_ddr = pval_nand_dcs + 1; + pval_bch_dcs = pval_bch_ddr + 1; + /* space for nand prog waiting target, the content is useless */ + dummy = pval_bch_dcs + 1; + /* space to store CMD_PGPROG(0x10) or 0x11 */ + pval_nand_cmd_pgprog = (u8 *)(dummy + 1); + + /* desc can't across 4KB boundary, as desc base address is fixed */ + /* space of descriptors for nand reading data and oob blocks */ + dma_desc_nand_read = (jz_dma_desc_8word *) __get_free_page(GFP_KERNEL); + if (!dma_desc_nand_read) + return -ENOMEM; + + /* space of descriptors for bch decoding */ + dma_desc_dec = dma_desc_nand_read + 2; + dma_desc_dec1 = dma_desc_dec + eccsteps; + dma_desc_dec2 = dma_desc_dec + eccsteps * 2; + + /* space of descriptors for notifying bch channel */ + dma_desc_bch_ddr = dma_desc_dec2 + eccsteps; + + /* space of descriptors for bch encoding */ + dma_desc_enc = dma_desc_bch_ddr + 2; + dma_desc_enc1 = dma_desc_enc + eccsteps; + + /* space of descriptors for nand programing data and oob blocks */ + dma_desc_nand_prog = dma_desc_enc1 + eccsteps; + + /* space of descriptors for nand prog waiting, including pgprog and sync */ + dma_desc_nand_cmd_pgprog = dma_desc_nand_prog + 2; + + /* space of descriptors for notifying nand channel, including ddr and dcsr */ + dma_desc_nand_ddr = dma_desc_nand_cmd_pgprog + 2; + +/************************************* + * Setup of nand programing descriptors + *************************************/ +#if defined(CONFIG_MTD_NAND_DMABUF) + oobbuf = prog_buf + pagesize; +#endif + /* set descriptor for encoding data blocks */ + desc = dma_desc_enc; + for (i = 0; i < eccsteps; i++) { + next = (CPHYSADDR((u32)dma_desc_enc1) + i * (sizeof(jz_dma_desc_8word))) >> 4; + + desc->dcmd = + DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_8 | + DMAC_DCMD_DS_BCH | DMAC_DCMD_LINK; +#if defined(CONFIG_MTD_NAND_DMABUF) + desc->dsadr = CPHYSADDR((u32)prog_buf) + i * eccsize; /* DMA source address */ + desc->dtadr = CPHYSADDR((u32)oobbuf) + ecc_pos + i * eccbytes; /* DMA target address */ +#endif + desc->ddadr = (next << 24) + eccsize / DIV_DS_BCH; /* size: eccsize bytes */ + desc->dreqt = DMAC_DRSR_RS_BCH_ENC; + dprintk("cmd:%x sadr:%x tadr:%x dadr:%x\n", desc->dcmd, desc->dsadr, desc->dtadr, desc->ddadr); + desc++; + } + + /* set descriptor for encoding oob blocks */ + desc = dma_desc_enc1; + for (i = 0; i < eccsteps; i++) { + next = (CPHYSADDR((u32)dma_desc_enc) + (i + 1) * (sizeof(jz_dma_desc_8word))) >> 4; + + desc->dcmd = + DMAC_DCMD_BLAST | DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_8 | + DMAC_DCMD_DWDH_8 | DMAC_DCMD_DS_32BIT | DMAC_DCMD_LINK; +#if defined(CONFIG_MTD_NAND_DMABUF) + desc->dsadr = CPHYSADDR((u32)oobbuf) + oob_per_eccsize * i; /* DMA source address, 28/4 = 7bytes */ + desc->dtadr = CPHYSADDR((u32)oobbuf) + ecc_pos + i * eccbytes; /* DMA target address */ +#endif + desc->ddadr = (next << 24) + (oob_per_eccsize + 3) / 4; /* size: 7 bytes -> 2 words */ + desc->dreqt = DMAC_DRSR_RS_BCH_ENC; + dprintk("cmd:%x sadr:%x tadr:%x dadr:%x\n", desc->dcmd, desc->dsadr, desc->dtadr, desc->ddadr); + desc++; + } + + next = (CPHYSADDR((u32)dma_desc_nand_ddr)) >> 4; + desc--; + desc->ddadr = (next << 24) + (oob_per_eccsize + 3) / 4; + + /* set the descriptor to set door bell of nand_dma_chan for programing nand */ + desc = dma_desc_nand_ddr; + *pval_nand_ddr = 1 << (nand_dma_chan - nand_dma_chan / HALF_DMA_NUM * HALF_DMA_NUM); + next = (CPHYSADDR((u32)dma_desc_nand_ddr) + sizeof(jz_dma_desc_8word)) >> 4; + desc->dcmd = DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_32BIT | DMAC_DCMD_LINK; + desc->dsadr = CPHYSADDR((u32)pval_nand_ddr); /* DMA source address */ + desc->dtadr = CPHYSADDR(DMAC_DMADBSR(nand_dma_chan / HALF_DMA_NUM)); /* nand_dma_chan's descriptor addres register */ + desc->ddadr = (next << 24) + 1; /* size: 1 word */ + desc->dreqt = DMAC_DRSR_RS_AUTO; + dprintk("*pval_nand_ddr=0x%x\n", *pval_nand_ddr); + + /* set the descriptor to write dccsr of nand_dma_chan for programing nand, dccsr should be set at last */ + desc++; + *pval_nand_dcs = DMAC_DCCSR_DES8 | DMAC_DCCSR_EN; /* set value for writing ddr to enable channel nand_dma_chan */ + desc->dcmd = DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_32BIT; + desc->dsadr = CPHYSADDR((u32)pval_nand_dcs); /* DMA source address */ + desc->dtadr = CPHYSADDR(DMAC_DCCSR(nand_dma_chan)); /* address of dma door bell set register */ + desc->ddadr = (0 << 24) + 1; /* size: 1 word */ + desc->dreqt = DMAC_DRSR_RS_AUTO; + dprintk("*pval_nand_dcs=0x%x\n", *pval_nand_dcs); + + /* set descriptor for nand programing data block */ + desc = dma_desc_nand_prog; + next = (CPHYSADDR((u32)dma_desc_nand_prog) + sizeof(jz_dma_desc_8word)) >> 4; + desc->dcmd = + DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_8 | + DMAC_DCMD_DS_NAND | DMAC_DCMD_LINK; +#if defined(CONFIG_MTD_NAND_DMABUF) + desc->dsadr = CPHYSADDR((u32)prog_buf); /* DMA source address */ +#endif + desc->dtadr = CPHYSADDR((u32)(chip->IO_ADDR_W)); /* DMA target address */ + desc->ddadr = (next << 24) + pagesize / DIV_DS_NAND; /* size: eccsize bytes */ + desc->dreqt = DMAC_DRSR_RS_AUTO; + dprintk("cmd:%x sadr:%x tadr:%x dadr:%x\n", desc->dcmd, desc->dsadr, desc->dtadr, desc->ddadr); + + /* set descriptor for nand programing oob block */ + desc++; + next = (CPHYSADDR((u32)dma_desc_nand_cmd_pgprog)) >> 4; + desc->dcmd = + DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_8 | + DMAC_DCMD_DS_NAND | DMAC_DCMD_LINK; +#if defined(CONFIG_MTD_NAND_DMABUF) + desc->dsadr = CPHYSADDR((u32)oobbuf); /* DMA source address */ +#endif + desc->dtadr = CPHYSADDR((u32)(chip->IO_ADDR_W)); /* DMA target address: dataport */ + desc->ddadr = (next << 24) + oobsize / DIV_DS_NAND; /* size: eccsize bytes */ + desc->dreqt = DMAC_DRSR_RS_AUTO; + dprintk("cmd:%x sadr:%x tadr:%x dadr:%x\n", desc->dcmd, desc->dsadr, desc->dtadr, desc->ddadr); + + /* set descriptor for __nand_cmd(CMD_PGPROG) */ + desc = dma_desc_nand_cmd_pgprog; + *pval_nand_cmd_pgprog = NAND_CMD_PAGEPROG; + next = (CPHYSADDR((u32)dma_desc_nand_cmd_pgprog) + sizeof(jz_dma_desc_8word)) >> 4; + desc->dcmd = + DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_8 | DMAC_DCMD_DWDH_8 | DMAC_DCMD_DS_8BIT | DMAC_DCMD_LINK; + desc->dsadr = CPHYSADDR((u32)pval_nand_cmd_pgprog); /* DMA source address */ + desc->dtadr = CPHYSADDR((u32)chip->IO_ADDR_R | cmd_offset); /* DMA target address: cmdport */ + desc->ddadr = (next << 24) + 1; /* size: 1 byte */ + desc->dreqt = DMAC_DRSR_RS_AUTO; + dprintk("cmd:%x sadr:%x tadr:%x dadr:%x\n", desc->dcmd, desc->dsadr, desc->dtadr, desc->ddadr); + + /* set descriptor for __nand_sync() */ + desc++; +#if USE_IRQ + desc->dcmd = + DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_32BIT | DMAC_DCMD_TIE; +#else + desc->dcmd = + DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_32BIT; +#endif + desc->dsadr = CPHYSADDR((u32)pval_nand_ddr); /* DMA source address */ + desc->dtadr = CPHYSADDR((u32)dummy); /* DMA target address, the content is useless */ + desc->ddadr = (0 << 24) + 1; /* size: 1 word */ + desc->dreqt = DMAC_DRSR_RS_NAND; + dprintk("1cmd:%x sadr:%x tadr:%x dadr:%x\n", desc->dcmd, desc->dsadr, desc->dtadr, desc->ddadr); + + /* eccsteps*2 + 2 + 2 + 2: + dma_desc_enc + dma_desc_enc1 + dma_desc_nand_prog(oob) + dma_desc_nand_ddr(csr) + + dma_desc_nand_cmd_pgprog(sync) */ + dma_cache_wback_inv((u32)dma_desc_enc, (eccsteps * 2 + 2 + 2 + 2) * (sizeof(jz_dma_desc_8word))); + /* 4*6: pval_nand_ddr, pval_nand_dcs, pval_bch_ddr, pval_bch_dcs, dummy, pval_nand_cmd_pgprog */ + dma_cache_wback_inv((u32)pval_nand_ddr, 4 * 8); /* 8 words, a cache line */ + +/************************************* + * Setup of nand reading descriptors + *************************************/ +#if defined(CONFIG_MTD_NAND_DMABUF) + oobbuf = read_buf + pagesize; +#endif + /* set descriptor for nand reading data block */ + desc = dma_desc_nand_read; + next = (CPHYSADDR((u32)dma_desc_nand_read) + sizeof(jz_dma_desc_8word)) >> 4; + desc->dcmd = + DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_8 | DMAC_DCMD_DWDH_32 | + DMAC_DCMD_DS_NAND | DMAC_DCMD_LINK; + desc->dsadr = CPHYSADDR((u32)(chip->IO_ADDR_R)); /* DMA source address */ +#if defined(CONFIG_MTD_NAND_DMABUF) + desc->dtadr = CPHYSADDR((u32)read_buf); /* DMA target address */ +#endif + desc->ddadr = (next << 24) + pagesize / DIV_DS_NAND; /* size: eccsize bytes */ + desc->dreqt = DMAC_DRSR_RS_NAND; + dprintk("cmd:%x sadr:%x tadr:%x dadr:%x\n", desc->dcmd, desc->dsadr, desc->dtadr, desc->ddadr); + + /* set descriptor for nand reading oob block */ + desc++; + next = (CPHYSADDR((u32)dma_desc_bch_ddr)) >> 4; + desc->dcmd = + DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_8 | DMAC_DCMD_DWDH_32 | + DMAC_DCMD_DS_NAND | DMAC_DCMD_LINK; + desc->dsadr = CPHYSADDR((u32)(chip->IO_ADDR_R)); /* DMA source address */ +#if defined(CONFIG_MTD_NAND_DMABUF) + desc->dtadr = CPHYSADDR((u32)oobbuf); /* DMA target address */ +#endif + desc->ddadr = (next << 24) + oobsize / DIV_DS_NAND; /* size: eccsize bytes */ + desc->dreqt = DMAC_DRSR_RS_AUTO; + dprintk("cmd:%x sadr:%x tadr:%x dadr:%x\n", desc->dcmd, desc->dsadr, desc->dtadr, desc->ddadr); + + /* set the descriptor to set door bell for bch */ + desc = dma_desc_bch_ddr; + *pval_bch_ddr = DMAC_DMADBSR_DBS0; // set value for writing ddr to enable channel 0 + next = (CPHYSADDR((u32)dma_desc_bch_ddr) + sizeof(jz_dma_desc_8word)) >> 4; + desc->dcmd = DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_32BIT | DMAC_DCMD_LINK; + desc->dsadr = CPHYSADDR((u32)pval_bch_ddr); /* DMA source address */ + desc->dtadr = CPHYSADDR(DMAC_DMADBSR(0)); /* channel 1's descriptor addres register */ + desc->ddadr = (next << 24) + 1; /* size: 1 word */ + desc->dreqt = DMAC_DRSR_RS_AUTO; + + /* set descriptor for writing dcsr */ + desc++; + *pval_bch_dcs = DMAC_DCCSR_DES8 | DMAC_DCCSR_EN; // set value for writing ddr to enable channel 1 + desc->dcmd = DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_32BIT; + desc->dsadr = CPHYSADDR((u32)pval_bch_dcs); /* DMA source address */ + desc->dtadr = CPHYSADDR(DMAC_DCCSR(bch_dma_chan)); /* address of dma door bell set register */ + desc->ddadr = (0 << 24) + 1; /* size: 1 word */ + desc->dreqt = DMAC_DRSR_RS_AUTO; + + /* descriptors for data to be written to bch */ + desc = dma_desc_dec; + for (i = 0; i < eccsteps; i++) { + next = CPHYSADDR((u32)dma_desc_dec1 + i * (sizeof(jz_dma_desc_8word))) >> 4; + + desc->dcmd = + DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 | + DMAC_DCMD_DS_BCH | DMAC_DCMD_LINK; +#if defined(CONFIG_MTD_NAND_DMABUF) + desc->dsadr = CPHYSADDR((u32)read_buf) + i * eccsize; /* DMA source address */ +#endif + desc->dtadr = CPHYSADDR((u32)errs) + i * 4 * ERRS_SIZE; /* DMA target address */ + desc->ddadr = (next << 24) + eccsize / DIV_DS_BCH; /* size: eccsize bytes */ + desc->dreqt = DMAC_DRSR_RS_BCH_DEC; + dprintk("desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + desc++; + } + + /* descriptors for oob to be written to bch */ + desc = dma_desc_dec1; + for (i = 0; i < eccsteps; i++) { + next = CPHYSADDR((u32)dma_desc_dec2 + i * (sizeof(jz_dma_desc_8word))) >> 4; + + desc->dcmd = + DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_8 | DMAC_DCMD_DWDH_32 | + DMAC_DCMD_DS_8BIT | DMAC_DCMD_LINK; +#if defined(CONFIG_MTD_NAND_DMABUF) + desc->dsadr = CPHYSADDR((u32)oobbuf) + oob_per_eccsize * i; /* DMA source address */ +#endif + desc->dtadr = CPHYSADDR((u32)errs) + i * 4 * ERRS_SIZE; /* DMA target address */ + desc->ddadr = (next << 24) + oob_per_eccsize; /* size: 7 bytes */ + desc->dreqt = DMAC_DRSR_RS_BCH_DEC; + dprintk("desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + desc++; + } + + /* descriptors for parities to be written to bch */ + desc = dma_desc_dec2; + for (i = 0; i < eccsteps; i++) { + next = (CPHYSADDR((u32)dma_desc_dec) + (i + 1) * (sizeof(jz_dma_desc_8word))) >> 4; + + desc->dcmd = + DMAC_DCMD_BLAST | DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_8 | + DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_BCH | DMAC_DCMD_LINK; +#if defined(CONFIG_MTD_NAND_DMABUF) + desc->dsadr = CPHYSADDR((u32)oobbuf) + ecc_pos + i * eccbytes; /* DMA source address */ +#endif + desc->dtadr = CPHYSADDR((u32)errs) + i * 4 * ERRS_SIZE; /* DMA target address */ + desc->ddadr = (next << 24) + (eccbytes + 15) / DIV_DS_BCH; /* size: eccbytes bytes */ + desc->dreqt = DMAC_DRSR_RS_BCH_DEC; + dprintk("desc:%x cmd:%x sadr:%x tadr:%x dadr:%x\n", (u32)desc, desc->dcmd, desc->dsadr, desc->dtadr, + desc->ddadr); + desc++; + } + desc--; + desc->dcmd &= ~DMAC_DCMD_LINK; +#if USE_IRQ + desc->dcmd |= DMAC_DCMD_TIE; +#endif + + dma_cache_wback_inv((u32)dma_desc_nand_read, (2 + 2 + eccsteps * 3) * (sizeof(jz_dma_desc_8word))); + dma_cache_wback_inv((u32)pval_bch_ddr, 4 * 2); /* two words */ + + return 0; +} + +#endif /* CONFIG_MTD_NAND_DMA */ +/* + * Main initialization routine + */ +int __init jznand_init(void) +{ + struct nand_chip *this; + int nr_partitions, ret, i; + + printk(KERN_INFO "JZ NAND init"); +#if defined(CONFIG_MTD_NAND_DMA) +#if defined(CONFIG_MTD_NAND_DMABUF) + printk(KERN_INFO " DMA mode, using DMA buffer in NAND driver.\n"); +#else + printk(KERN_INFO " DMA mode, using DMA buffer in upper layer.\n"); +#endif +#else + printk(KERN_INFO " CPU mode.\n"); +#endif + + + /* Allocate memory for MTD device structure and private data */ + jz_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); + if (!jz_mtd) { + printk("Unable to allocate JzSOC NAND MTD device structure.\n"); + return -ENOMEM; + } + + /* Allocate memory for NAND when using only one plane */ + jz_mtd1 = kmalloc(sizeof(struct mtd_info) + sizeof (struct nand_chip), GFP_KERNEL); + if (!jz_mtd1) { + printk ("Unable to allocate JzSOC NAND MTD device structure 1.\n"); + kfree(jz_mtd); + return -ENOMEM; + } + + /* Get pointer to private data */ + this = (struct nand_chip *)(&jz_mtd[1]); + + /* Initialize structures */ + memset((char *)jz_mtd, 0, sizeof(struct mtd_info)); + memset((char *)this, 0, sizeof(struct nand_chip)); + + /* Link the private data with the MTD structure */ + jz_mtd->priv = this; + + if (is_share_mode()) { + addr_offset = NAND_ADDR_OFFSET0; + cmd_offset = NAND_CMD_OFFSET0; + } else { + addr_offset = NAND_ADDR_OFFSET1; + cmd_offset = NAND_CMD_OFFSET1; + } + + /* Set & initialize NAND Flash controller */ + jz_device_setup(); + + /* Set address of NAND IO lines to static bank1 by default */ + this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT1; + this->IO_ADDR_W = (void __iomem *)NAND_DATA_PORT1; + this->cmd_ctrl = jz_hwcontrol; + this->dev_ready = jz_device_ready; + +#ifdef CONFIG_MTD_HW_BCH_ECC + this->ecc.calculate = jzsoc_nand_calculate_bch_ecc; + this->ecc.correct = jzsoc_nand_bch_correct_data; + this->ecc.hwctl = jzsoc_nand_enable_bch_hwecc; + this->ecc.mode = NAND_ECC_HW; + this->ecc.size = 512; + this->ecc.read_page = nand_read_page_hwecc_bch; + this->ecc.write_page = nand_write_page_hwecc_bch; +#if defined(CONFIG_MTD_HW_BCH_8BIT) + this->ecc.bytes = 13; +#else + this->ecc.bytes = 7; +#endif +#endif + +#ifdef CONFIG_MTD_SW_HM_ECC + this->ecc.mode = NAND_ECC_SOFT; +#endif + /* 20 us command delay time */ + this->chip_delay = 20; + /* Scan to find existance of the device */ + ret = nand_scan_ident(jz_mtd, NAND_MAX_CHIPS); + + if (!ret) { + if (this->planenum == 2) { + /* reset nand functions */ + this->erase_cmd = single_erase_cmd_planes; + this->ecc.read_page = nand_read_page_hwecc_bch_planes; + this->ecc.write_page = nand_write_page_hwecc_bch_planes; + this->ecc.read_oob = nand_read_oob_std_planes; + this->ecc.write_oob = nand_write_oob_std_planes; + + printk(KERN_INFO "Nand using two-plane mode, " + "and resized to writesize:%d oobsize:%d blocksize:0x%x \n", + jz_mtd->writesize, jz_mtd->oobsize, jz_mtd->erasesize); + } + } + + /* Determine whether all the partitions will use multiple planes if supported */ + nr_partitions = sizeof(partition_info) / sizeof(struct mtd_partition); + all_use_planes = 1; + for (i = 0; i < nr_partitions; i++) { + all_use_planes &= partition_info[i].use_planes; + } + + if (!ret) + ret = nand_scan_tail(jz_mtd); + + if (ret){ + kfree (jz_mtd1); + kfree (jz_mtd); + return -ENXIO; + } + +#if defined(CONFIG_MTD_NAND_DMA) + jz4750_nand_dma_init(jz_mtd); +#endif + + ((struct nand_chip *) (&jz_mtd1[1]))->ecc.read_page = nand_read_page_hwecc_bch; + ((struct nand_chip *) (&jz_mtd1[1]))->ecc.write_page = nand_write_page_hwecc_bch; + + /* Register the partitions */ + printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nr_partitions, jz_mtd->name); + + if ((this->planenum == 2) && !all_use_planes) { + for (i = 0; i < nr_partitions; i++) { + if (partition_info[i].use_planes) + add_mtd_partitions(jz_mtd, &partition_info[i], 1); + else + add_mtd_partitions(jz_mtd1, &partition_info[i], 1); + } + } else { + kfree(jz_mtd1); + add_mtd_partitions(jz_mtd, partition_info, nr_partitions); + } + return 0; +} + +module_init(jznand_init); + +/* + * Clean up routine + */ +#ifdef MODULE + +#if defined(CONFIG_MTD_NAND_DMA) +static int jz4750_nand_dma_exit(struct mtd_info *mtd) +{ + int pagesize = mtd->writesize / chip->planenum; + +#if USE_IRQ + free_irq(IRQ_DMA_0 + nand_dma_chan, NULL); + free_irq(IRQ_DMA_0 + bch_dma_chan, NULL); +#endif + + /* space for the error reports of bch decoding((4 * 5 * eccsteps) bytes), + * and the space for the value of ddr and dcs of channel 0 and channel + * nand_dma_chan (4 * (2 + 2) bytes) */ + kfree(errs); + + /* space for dma_desc_nand_read contains dma_desc_nand_prog, + * dma_desc_enc and dma_desc_dec */ + free_page((u32)dma_desc_nand_read); + +#if defined(CONFIG_MTD_NAND_DMABUF) + if (pagesize < 4096) { + free_page((u32)prog_buf); + } else { + free_pages((u32)prog_buf, 1); + } +#endif + + return 0; +} +#endif + +static void __exit jznand_cleanup(void) +{ +#if defined(CONFIG_MTD_NAND_DMA) + jz4750_nand_dma_exit(jz_mtd); +#endif + + /* Unregister partitions */ + del_mtd_partitions(jz_mtd); + + /* Unregister the device */ + del_mtd_device(jz_mtd); + + /* Free the MTD device structure */ + if ((this->planenum == 2) && !all_use_planes) + kfree (jz_mtd1); + kfree(jz_mtd); +} + +module_exit(jznand_cleanup); +#endif --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -52,6 +52,16 @@ #include #endif +#include + +u8 nand_nce; /* indicates which chip select on JZSOC is used for + current nand chip */ +int global_page; /* page index of large page used for nand with multiple planes */ + +struct mtd_info *jz_mtd1 = NULL; /* for 1 plane operation */ +char all_use_planes = 1; /* indicates whether multiple planes operation is used + by all partitions if multiple planes is supported by NAND */ + /* Define default oob placement schemes for large and small page devices */ static struct nand_ecclayout nand_oob_8 = { .eccbytes = 3, @@ -72,6 +82,20 @@ static struct nand_ecclayout nand_oob_16 }; static struct nand_ecclayout nand_oob_64 = { +#if defined(CONFIG_MTD_HW_RS_ECC) || defined(CONFIG_MTD_HW_BCH_ECC) +/* Reed-Solomon ECC or BCH ECC */ + .eccbytes = 36, + .eccpos = { + 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63}, + .oobfree = { + {.offset = 2, + . length = 26}} +#else +/* HW&SW Hamming ECC */ .eccbytes = 24, .eccpos = { 40, 41, 42, 43, 44, 45, 46, 47, @@ -80,12 +104,85 @@ static struct nand_ecclayout nand_oob_64 .oobfree = { {.offset = 2, .length = 38}} +#endif +}; + +static struct nand_ecclayout nand_oob_128 = { +#if defined(CONFIG_MTD_HW_RS_ECC) +/* Reed-Solomon ECC */ + .eccbytes = 72, + .eccpos = { + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127}, + .oobfree = { + {.offset = 2, + . length = 54}} + +#elif defined(CONFIG_MTD_HW_BCH_ECC) +#if !defined(CONFIG_MTD_HW_BCH_8BIT) +/* 4-bit BCH ECC */ + .eccbytes = 56, + .eccpos = { + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127}, + .oobfree = { + {.offset = 2, + . length = 70}} + +#else +/* 8-bit BCH ECC */ + .eccbytes = 104, + .eccpos = { + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103 + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127}, + .oobfree = { + {.offset = 2, + . length = 22}} + +#endif +#else +/* HW&SW Hamming ECC */ + .eccbytes = 48, + .eccpos = { + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127}, + .oobfree = { + {.offset = 2, + .length = 78}} +#endif }; static int nand_get_device(struct nand_chip *chip, struct mtd_info *mtd, int new_state); -static int nand_do_write_oob(struct mtd_info *mtd, loff_t to, +static int nand_do_write_oob(struct mtd_info *mtd, loff_mtd_t to, struct mtd_oob_ops *ops); /* @@ -95,6 +192,35 @@ static int nand_do_write_oob(struct mtd_ DEFINE_LED_TRIGGER(nand_led_trigger); /** + * ffs_ll - find first bit set in a 64bit word. + * @word: The word to search + */ +static inline int ffs_ll(u64 word) +{ + u32 low = word & 0xffffffff; + u32 high = word >> 32; + int i; + + for(i = 0; i < 32; i++) { + if (low & 0x1) + break; + low >>= 1; + } + if (i == 32) { + for(i = 0; i < 32; i++) { + if (high & 0x1) + break; + high >>= 1; + } + i += 32; + } + if (i == 64) + return 0; + else + return (i+1); +} + +/** * nand_release_device - [GENERIC] release chip * @mtd: MTD device structure * @@ -169,6 +295,20 @@ static void nand_select_chip(struct mtd_ chip->cmd_ctrl(mtd, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE); break; case 0: + nand_nce = NAND_NCE1; + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); + break; + case 1: + nand_nce = NAND_NCE2; + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); + break; + case 2: + nand_nce = NAND_NCE3; + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); + break; + case 3: + nand_nce = NAND_NCE4; + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); break; default: @@ -298,13 +438,19 @@ static int nand_verify_buf16(struct mtd_ * * Check, if the block is bad. */ -static int nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) +static int nand_block_bad(struct mtd_info *mtd, loff_mtd_t ofs, int getchip) { - int page, chipnr, res = 0; + int page, page1 = 0, chipnr, res = 0; struct nand_chip *chip = mtd->priv; u16 bad; - page = (int)(ofs >> chip->page_shift) & chip->pagemask; + if (chip->planenum > 1) { + page = ((int)(ofs >> chip->page_shift) * chip->planenum + CONFIG_MTD_BADBLOCK_FLAG_PAGE); + page1 = page + mtd->erasesize / mtd->writesize; + page &= chip->pagemask; + page1 &= chip->pagemask; + } else + page = ((int)(ofs >> chip->page_shift) + CONFIG_MTD_BADBLOCK_FLAG_PAGE) & chip->pagemask; if (getchip) { chipnr = (int)(ofs >> chip->chip_shift); @@ -327,6 +473,11 @@ static int nand_block_bad(struct mtd_inf chip->cmdfunc(mtd, NAND_CMD_READOOB, chip->badblockpos, page); if (chip->read_byte(mtd) != 0xff) res = 1; + if (chip->planenum > 1) { + chip->cmdfunc(mtd, NAND_CMD_READOOB, chip->badblockpos, page1); + if (chip->read_byte(mtd) != 0xff) + res = 1; + } } if (getchip) @@ -343,7 +494,7 @@ static int nand_block_bad(struct mtd_inf * This is the default implementation, which can be overridden by * a hardware specific driver. */ -static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs) +static int nand_default_block_markbad(struct mtd_info *mtd, loff_mtd_t ofs) { struct nand_chip *chip = mtd->priv; uint8_t buf[2] = { 0, 0 }; @@ -363,6 +514,7 @@ static int nand_default_block_markbad(st */ nand_get_device(chip, mtd, FL_WRITING); ofs += mtd->oobsize; + ofs += (CONFIG_MTD_BADBLOCK_FLAG_PAGE << chip->page_shift); chip->ops.len = chip->ops.ooblen = 2; chip->ops.datbuf = NULL; chip->ops.oobbuf = buf; @@ -402,7 +554,7 @@ static int nand_check_wp(struct mtd_info * Check, if the block is bad. Either by reading the bad block table or * calling of the scan function. */ -static int nand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip, +static int nand_block_checkbad(struct mtd_info *mtd, loff_mtd_t ofs, int getchip, int allowbbt) { struct nand_chip *chip = mtd->priv; @@ -554,7 +706,10 @@ static void nand_command_lp(struct mtd_i /* Emulate NAND_CMD_READOOB */ if (command == NAND_CMD_READOOB) { - column += mtd->writesize; + if (chip->planenum > 1) + column += (mtd->writesize / chip->planenum); + else + column += mtd->writesize; command = NAND_CMD_READ0; } @@ -600,6 +755,8 @@ static void nand_command_lp(struct mtd_i case NAND_CMD_RNDIN: case NAND_CMD_STATUS: case NAND_CMD_DEPLETE1: + case 0x81: /* for two-plane page program */ + case 0x11: /* for two-plane page program */ return; /* @@ -675,7 +832,6 @@ nand_get_device(struct nand_chip *chip, spin_lock(lock); /* Hardware controller shared among independend devices */ - /* Hardware controller shared among independend devices */ if (!chip->controller->active) chip->controller->active = chip; @@ -878,6 +1034,7 @@ static int nand_read_subpage(struct mtd_ return 0; } +#ifndef CONFIG_MTD_HW_RS_ECC /* HW&SW Hamming ECC */ /** * nand_read_page_hwecc - [REPLACABLE] hardware ecc based page read function * @mtd: mtd info structure @@ -922,6 +1079,63 @@ static int nand_read_page_hwecc(struct m return 0; } +#else /* CONFIG_MTD_HW_RS_ECC */ + +/** + * nand_read_page_hwecc_rs - [REPLACABLE] hardware rs ecc based page read function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @buf: buffer to store read data + * + * Not for syndrome calculating ecc controllers which need a special oob layout + */ +static int nand_read_page_hwecc_rs(struct mtd_info *mtd, struct nand_chip *chip, + uint8_t *buf) +{ + int i, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + uint8_t *p = buf; + uint8_t *ecc_calc = chip->buffers->ecccalc; + uint8_t *ecc_code = chip->buffers->ecccode; + uint32_t *eccpos = chip->ecc.layout->eccpos; + uint32_t page; + uint8_t flag = 0; + + page = (buf[3]<<24) + (buf[2]<<16) + (buf[1]<<8) + buf[0]; + + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + for (i = 0; i < chip->ecc.total; i++) { + ecc_code[i] = chip->oob_poi[eccpos[i]]; + if (ecc_code[i] != 0xff) flag = 1; + } + + eccsteps = chip->ecc.steps; + p = buf; + + chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0x00, -1); + for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + int stat; + if (flag) { + chip->ecc.hwctl(mtd, NAND_ECC_READ); + chip->read_buf(mtd, p, eccsize); + stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]); + if (stat < 0) + mtd->ecc_stats.failed++; + else + mtd->ecc_stats.corrected += stat; + } + else { + chip->ecc.hwctl(mtd, NAND_ECC_READ); + chip->read_buf(mtd, p, eccsize); + } + } + return 0; +} + +#endif /* CONFIG_MTD_HW_RS_ECC */ + /** * nand_read_page_syndrome - [REPLACABLE] hardware ecc syndrom based page read * @mtd: mtd info structure @@ -984,7 +1198,7 @@ static int nand_read_page_syndrome(struc * @len: size of oob to transfer */ static uint8_t *nand_transfer_oob(struct nand_chip *chip, uint8_t *oob, - struct mtd_oob_ops *ops, size_t len) + struct mtd_oob_ops *ops, size_mtd_t len) { switch(ops->mode) { @@ -996,7 +1210,7 @@ static uint8_t *nand_transfer_oob(struct case MTD_OOB_AUTO: { struct nand_oobfree *free = chip->ecc.layout->oobfree; uint32_t boffs = 0, roffs = ops->ooboffs; - size_t bytes = 0; + size_mtd_t bytes = 0; for(; free->length && len; free++, len -= bytes) { /* Read request not from offset 0 ? */ @@ -1006,11 +1220,11 @@ static uint8_t *nand_transfer_oob(struct continue; } boffs = free->offset + roffs; - bytes = min_t(size_t, len, + bytes = min_t(size_mtd_t, len, (free->length - roffs)); roffs = 0; } else { - bytes = min_t(size_t, len, free->length); + bytes = min_t(size_mtd_t, len, free->length); boffs = free->offset; } memcpy(oob, chip->oob_poi + boffs, bytes); @@ -1033,7 +1247,7 @@ static uint8_t *nand_transfer_oob(struct * * Internal function. Called with chip held. */ -static int nand_do_read_ops(struct mtd_info *mtd, loff_t from, +static int nand_do_read_ops(struct mtd_info *mtd, loff_mtd_t from, struct mtd_oob_ops *ops) { int chipnr, page, realpage, col, bytes, aligned; @@ -1067,10 +1281,18 @@ static int nand_do_read_ops(struct mtd_i if (realpage != chip->pagebuf || oob) { bufpoi = aligned ? buf : chip->buffers->databuf; + global_page = page; +#if defined(CONFIG_MTD_HW_RS_ECC) || defined(CONFIG_MTD_NAND_DMA) + bufpoi[0] = (uint8_t)page; + bufpoi[1] = (uint8_t)(page >> 8); + bufpoi[2] = (uint8_t)(page >> 16); + bufpoi[3] = (uint8_t)(page >> 24); +#else if (likely(sndcmd)) { chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); sndcmd = 0; } +#endif /* Now read the page into the buffer */ if (unlikely(ops->mode == MTD_OOB_RAW)) @@ -1149,7 +1371,7 @@ static int nand_do_read_ops(struct mtd_i sndcmd = 1; } - ops->retlen = ops->len - (size_t) readlen; + ops->retlen = ops->len - (size_mtd_t) readlen; if (oob) ops->oobretlen = ops->ooblen - oobreadlen; @@ -1172,8 +1394,8 @@ static int nand_do_read_ops(struct mtd_i * * Get hold of the chip and call nand_do_read */ -static int nand_read(struct mtd_info *mtd, loff_t from, size_t len, - size_t *retlen, uint8_t *buf) +static int nand_read(struct mtd_info *mtd, loff_mtd_t from, size_mtd_t len, + size_mtd_t *retlen, uint8_t *buf) { struct nand_chip *chip = mtd->priv; int ret; @@ -1346,7 +1568,7 @@ static int nand_write_oob_syndrome(struc * * NAND read out-of-band data from the spare area */ -static int nand_do_read_oob(struct mtd_info *mtd, loff_t from, +static int nand_do_read_oob(struct mtd_info *mtd, loff_mtd_t from, struct mtd_oob_ops *ops) { int page, realpage, chipnr, sndcmd = 1; @@ -1439,7 +1661,7 @@ static int nand_do_read_oob(struct mtd_i * * NAND read data and/or out-of-band data */ -static int nand_read_oob(struct mtd_info *mtd, loff_t from, +static int nand_read_oob(struct mtd_info *mtd, loff_mtd_t from, struct mtd_oob_ops *ops) { struct nand_chip *chip = mtd->priv; @@ -1602,12 +1824,17 @@ static int nand_write_page(struct mtd_in { int status; - chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); - - if (unlikely(raw)) - chip->ecc.write_page_raw(mtd, chip, buf); - else + global_page = page; + if (chip->planenum > 1) chip->ecc.write_page(mtd, chip, buf); + else { + chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); + + if (unlikely(raw)) + chip->ecc.write_page_raw(mtd, chip, buf); + else + chip->ecc.write_page(mtd, chip, buf); + } /* * Cached progamming disabled for now, Not sure if its worth the @@ -1616,9 +1843,17 @@ static int nand_write_page(struct mtd_in cached = 0; if (!cached || !(chip->options & NAND_CACHEPRG)) { - - chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); +/* +* __nand_cmd(CMD_PAGEPROG) and __nand_sync() have been done by DMA for jz4750 and +* later chip, status should still be read by "status = chip->waitfunc(mtd, chip)" +*/ +#if defined(CONFIG_SOC_JZ4730) || defined(CONFIG_SOC_JZ4740) || \ + (!defined(CONFIG_SOC_JZ4730) && !defined(CONFIG_SOC_JZ4740) \ + && !defined(CONFIG_MTD_NAND_DMA)) + chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); +#endif status = chip->waitfunc(mtd, chip); + /* * See if operation failed and additional status checks are * available @@ -1653,7 +1888,7 @@ static int nand_write_page(struct mtd_in static uint8_t *nand_fill_oob(struct nand_chip *chip, uint8_t *oob, struct mtd_oob_ops *ops) { - size_t len = ops->ooblen; + size_mtd_t len = ops->ooblen; switch(ops->mode) { @@ -1665,7 +1900,7 @@ static uint8_t *nand_fill_oob(struct nan case MTD_OOB_AUTO: { struct nand_oobfree *free = chip->ecc.layout->oobfree; uint32_t boffs = 0, woffs = ops->ooboffs; - size_t bytes = 0; + size_mtd_t bytes = 0; for(; free->length && len; free++, len -= bytes) { /* Write request not from offset 0 ? */ @@ -1675,11 +1910,11 @@ static uint8_t *nand_fill_oob(struct nan continue; } boffs = free->offset + woffs; - bytes = min_t(size_t, len, + bytes = min_t(size_mtd_t, len, (free->length - woffs)); woffs = 0; } else { - bytes = min_t(size_t, len, free->length); + bytes = min_t(size_mtd_t, len, free->length); boffs = free->offset; } memcpy(chip->oob_poi + boffs, oob, bytes); @@ -1703,7 +1938,7 @@ static uint8_t *nand_fill_oob(struct nan * * NAND write with ECC */ -static int nand_do_write_ops(struct mtd_info *mtd, loff_t to, +static int nand_do_write_ops(struct mtd_info *mtd, loff_mtd_t to, struct mtd_oob_ops *ops) { int chipnr, realpage, page, blockmask, column; @@ -1806,8 +2041,8 @@ static int nand_do_write_ops(struct mtd_ * * NAND write with ECC */ -static int nand_write(struct mtd_info *mtd, loff_t to, size_t len, - size_t *retlen, const uint8_t *buf) +static int nand_write(struct mtd_info *mtd, loff_mtd_t to, size_mtd_t len, + size_mtd_t *retlen, const uint8_t *buf) { struct nand_chip *chip = mtd->priv; int ret; @@ -1841,7 +2076,7 @@ static int nand_write(struct mtd_info *m * * NAND write out-of-band */ -static int nand_do_write_oob(struct mtd_info *mtd, loff_t to, +static int nand_do_write_oob(struct mtd_info *mtd, loff_mtd_t to, struct mtd_oob_ops *ops) { int chipnr, page, status, len; @@ -1919,7 +2154,7 @@ static int nand_do_write_oob(struct mtd_ * @to: offset to write to * @ops: oob operation description structure */ -static int nand_write_oob(struct mtd_info *mtd, loff_t to, +static int nand_write_oob(struct mtd_info *mtd, loff_mtd_t to, struct mtd_oob_ops *ops) { struct nand_chip *chip = mtd->priv; @@ -2083,7 +2318,7 @@ int nand_erase_nand(struct mtd_info *mtd /* * heck if we have a bad block, we do not erase bad blocks ! */ - if (nand_block_checkbad(mtd, ((loff_t) page) << + if (nand_block_checkbad(mtd, ((loff_mtd_t) page) << chip->page_shift, 0, allowbbt)) { printk(KERN_WARNING "nand_erase: attempt to erase a " "bad block at page 0x%08x\n", page); @@ -2205,7 +2440,7 @@ static void nand_sync(struct mtd_info *m * @mtd: MTD device structure * @offs: offset relative to mtd start */ -static int nand_block_isbad(struct mtd_info *mtd, loff_t offs) +static int nand_block_isbad(struct mtd_info *mtd, loff_mtd_t offs) { /* Check for invalid offset */ if (offs > mtd->size) @@ -2219,7 +2454,7 @@ static int nand_block_isbad(struct mtd_i * @mtd: MTD device structure * @ofs: offset relative to mtd start */ -static int nand_block_markbad(struct mtd_info *mtd, loff_t ofs) +static int nand_block_markbad(struct mtd_info *mtd, loff_mtd_t ofs) { struct nand_chip *chip = mtd->priv; int ret; @@ -2385,7 +2620,21 @@ static struct nand_flash_dev *nand_get_f extid >>= 2; /* Get buswidth information */ busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0; + /* The 5th id byte */ +#if defined(CONFIG_MTD_NAND_MULTI_PLANE) + extid = chip->read_byte(mtd); + chip->realplanenum = 1 << ((extid & 0x0c) >> 2); +#else + chip->realplanenum = 1; +#endif + if (chip->realplanenum > 1) { /* use muti planes mode */ + chip->planenum = 2; + mtd->writesize *= 2; /* two pages as one page */ + mtd->oobsize *= 2; + mtd->erasesize *= 2; /* two blocks as one block */ + } else + chip->planenum = 1; } else { /* * Old devices have chip data hardcoded in the device id table @@ -2423,7 +2672,7 @@ static struct nand_flash_dev *nand_get_f chip->bbt_erase_shift = chip->phys_erase_shift = ffs(mtd->erasesize) - 1; - chip->chip_shift = ffs(chip->chipsize) - 1; + chip->chip_shift = ffs_ll(chip->chipsize) - 1; /* Set the bad block position */ chip->badblockpos = mtd->writesize > 512 ? @@ -2455,8 +2704,8 @@ static struct nand_flash_dev *nand_get_f chip->cmdfunc = nand_command_lp; printk(KERN_INFO "NAND device: Manufacturer ID:" - " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id, dev_id, - nand_manuf_ids[maf_idx].name, type->name); + " 0x%02x, Chip ID: 0x%02x (%s %s) planenum:%d\n", *maf_id, dev_id, + nand_manuf_ids[maf_idx].name, type->name, chip->realplanenum); return type; } @@ -2525,7 +2774,7 @@ int nand_scan_ident(struct mtd_info *mtd */ int nand_scan_tail(struct mtd_info *mtd) { - int i; + int i, res; struct nand_chip *chip = mtd->priv; if (!(chip->options & NAND_OWN_BUFFERS)) @@ -2550,6 +2799,16 @@ int nand_scan_tail(struct mtd_info *mtd) case 64: chip->ecc.layout = &nand_oob_64; break; + case 128: + if (chip->planenum > 1) + chip->ecc.layout = &nand_oob_64; + else + chip->ecc.layout = &nand_oob_128; + break; + case 256: + if (chip->planenum > 1) + chip->ecc.layout = &nand_oob_128; + break; default: printk(KERN_WARNING "No oob scheme defined for " "oobsize %d\n", mtd->oobsize); @@ -2572,8 +2831,13 @@ int nand_scan_tail(struct mtd_info *mtd) switch (chip->ecc.mode) { case NAND_ECC_HW: /* Use standard hwecc read page function ? */ - if (!chip->ecc.read_page) + if (!chip->ecc.read_page) { +#ifndef CONFIG_MTD_HW_RS_ECC chip->ecc.read_page = nand_read_page_hwecc; +#else + chip->ecc.read_page = nand_read_page_hwecc_rs; +#endif + } if (!chip->ecc.write_page) chip->ecc.write_page = nand_write_page_hwecc; if (!chip->ecc.read_oob) @@ -2711,8 +2975,58 @@ int nand_scan_tail(struct mtd_info *mtd) if (chip->options & NAND_SKIP_BBTSCAN) return 0; - /* Build bad block table */ - return chip->scan_bbt(mtd); + /* Create jz_mtd1 for one plane operation if the NAND support multiple + planes operation, because some partitions will only use one plane. */ + if ((chip->planenum == 2) && !all_use_planes) { + int i, len, numblocks; + struct nand_chip *this = (struct nand_chip *) (&jz_mtd1[1]); + + memcpy(jz_mtd1, mtd, sizeof(*mtd)); + jz_mtd1->priv = this; + memcpy(this, chip, sizeof(*chip)); + + this->planenum = 1; + jz_mtd1->writesize >>= 1; + jz_mtd1->oobsize >>= 1; + jz_mtd1->erasesize >>= 1; + this->page_shift = chip->page_shift - 1; + this->pagemask = (this->chipsize >> this->page_shift) - 1; + this->bbt_erase_shift = this->phys_erase_shift = + chip->phys_erase_shift - 1; + this->ecc.steps >>= 1; + this->ecc.total = this->ecc.steps * this->ecc.bytes; + this->subpagesize = jz_mtd1->writesize; + + this->erase_cmd = single_erase_cmd; +#if defined(CONFIG_MTD_HW_RS_ECC) + this->ecc.read_page = nand_read_page_hwecc_rs; + this->ecc.write_page = nand_write_page_hwecc; +#endif + this->ecc.read_oob = nand_read_oob_std; + this->ecc.write_oob = nand_write_oob_std; + this->write_buf = nand_write_buf; + this->read_buf = nand_read_buf; + + /* Firstly, build bad block table as one plane */ + res = this->scan_bbt(jz_mtd1); + + /* Secondly, build bad block table as 2 plane based on bbt of jz_mtd1 */ + numblocks = chip->chipsize >> (chip->bbt_erase_shift - 1); /* = (real numblocks * 2) */ + len = mtd->size >> (chip->bbt_erase_shift + 2); + chip->bbt = kzalloc(len, GFP_KERNEL); + +#define isbad_2plane(block) (((this->bbt[(block) >> 3] >> ((block) & 0x06)) \ + | (this->bbt[((block)+2) >> 3] >> (((block)+2) & 0x06))) & 0x03) + + for (i = 0; i < numblocks; i += 2) { + if (isbad_2plane(2*i)) + chip->bbt[i >> 3] |= 0x03 << (i & 0x6); + } + } else { + res = chip->scan_bbt(mtd); + } + + return res; } /* module_text_address() isn't exported, and it's mostly a pointless --- a/drivers/mtd/nand/nand_bbt.c +++ b/drivers/mtd/nand/nand_bbt.c @@ -145,15 +145,15 @@ static int read_bbt(struct mtd_info *mtd { int res, i, j, act = 0; struct nand_chip *this = mtd->priv; - size_t retlen, len, totlen; - loff_t from; + size_mtd_t retlen, len, totlen; + loff_mtd_t from; uint8_t msk = (uint8_t) ((1 << bits) - 1); totlen = (num * bits) >> 3; - from = ((loff_t) page) << this->page_shift; + from = ((loff_mtd_t) page) << this->page_shift; while (totlen) { - len = min(totlen, (size_t) (1 << this->bbt_erase_shift)); + len = min(totlen, (size_mtd_t) (1 << this->bbt_erase_shift)); res = mtd->read(mtd, from, len, &retlen, buf); if (res < 0) { if (retlen != len) { @@ -233,8 +233,8 @@ static int read_abs_bbt(struct mtd_info /* * Scan read raw data from flash */ -static int scan_read_raw(struct mtd_info *mtd, uint8_t *buf, loff_t offs, - size_t len) +static int scan_read_raw(struct mtd_info *mtd, uint8_t *buf, loff_mtd_t offs, + size_mtd_t len) { struct mtd_oob_ops ops; @@ -251,7 +251,7 @@ static int scan_read_raw(struct mtd_info /* * Scan write data with oob to flash */ -static int scan_write_bbt(struct mtd_info *mtd, loff_t offs, size_t len, +static int scan_write_bbt(struct mtd_info *mtd, loff_mtd_t offs, size_mtd_t len, uint8_t *buf, uint8_t *oob) { struct mtd_oob_ops ops; @@ -306,7 +306,7 @@ static int read_abs_bbts(struct mtd_info * Scan a given block full */ static int scan_block_full(struct mtd_info *mtd, struct nand_bbt_descr *bd, - loff_t offs, uint8_t *buf, size_t readlen, + loff_mtd_t offs, uint8_t *buf, size_mtd_t readlen, int scanlen, int len) { int ret, j; @@ -326,7 +326,7 @@ static int scan_block_full(struct mtd_in * Scan a given block partially */ static int scan_block_fast(struct mtd_info *mtd, struct nand_bbt_descr *bd, - loff_t offs, uint8_t *buf, int len) + loff_mtd_t offs, uint8_t *buf, int len) { struct mtd_oob_ops ops; int j, ret; @@ -372,8 +372,8 @@ static int create_bbt(struct mtd_info *m struct nand_chip *this = mtd->priv; int i, numblocks, len, scanlen; int startblock; - loff_t from; - size_t readlen; + loff_mtd_t from; + size_mtd_t readlen; printk(KERN_INFO "Scanning device for bad blocks\n"); @@ -401,7 +401,7 @@ static int create_bbt(struct mtd_info *m * below as it makes shifting and masking less painful */ numblocks = mtd->size >> (this->bbt_erase_shift - 1); startblock = 0; - from = 0; + from = (CONFIG_MTD_BADBLOCK_FLAG_PAGE << this->page_shift); //from = 0; } else { if (chip >= this->numchips) { printk(KERN_WARNING "create_bbt(): chipnr (%d) > available chips (%d)\n", @@ -411,7 +411,7 @@ static int create_bbt(struct mtd_info *m numblocks = this->chipsize >> (this->bbt_erase_shift - 1); startblock = chip * numblocks; numblocks += startblock; - from = startblock << (this->bbt_erase_shift - 1); + from = (startblock << (this->bbt_erase_shift - 1)) + (CONFIG_MTD_BADBLOCK_FLAG_PAGE << this->page_shift); //from = startblock << (this->bbt_erase_shift - 1); } for (i = startblock; i < numblocks;) { @@ -428,8 +428,8 @@ static int create_bbt(struct mtd_info *m if (ret) { this->bbt[i >> 3] |= 0x03 << (i & 0x6); - printk(KERN_WARNING "Bad eraseblock %d at 0x%08x\n", - i >> 1, (unsigned int)from); + printk(KERN_WARNING "Bad eraseblock %d at 0x%09llx\n", + i >> 1, (unsigned long long)from); mtd->ecc_stats.badblocks++; } @@ -495,7 +495,7 @@ static int search_bbt(struct mtd_info *m for (block = 0; block < td->maxblocks; block++) { int actblock = startblock + dir * block; - loff_t offs = actblock << this->bbt_erase_shift; + loff_mtd_t offs = actblock << this->bbt_erase_shift; /* Read first page */ scan_read_raw(mtd, buf, offs, mtd->writesize); @@ -565,8 +565,8 @@ static int write_bbt(struct mtd_info *mt int nrchips, bbtoffs, pageoffs, ooboffs; uint8_t msk[4]; uint8_t rcode = td->reserved_block_code; - size_t retlen, len = 0; - loff_t to; + size_mtd_t retlen, len = 0; + loff_mtd_t to; struct mtd_oob_ops ops; ops.ooblen = mtd->oobsize; @@ -653,12 +653,12 @@ static int write_bbt(struct mtd_info *mt bbtoffs = chip * (numblocks >> 2); - to = ((loff_t) page) << this->page_shift; + to = ((loff_mtd_t) page) << this->page_shift; /* Must we save the block contents ? */ if (td->options & NAND_BBT_SAVECONTENT) { /* Make it block aligned */ - to &= ~((loff_t) ((1 << this->bbt_erase_shift) - 1)); + to &= ~((loff_mtd_t) ((1 << this->bbt_erase_shift) - 1)); len = 1 << this->bbt_erase_shift; res = mtd->read(mtd, to, len, &retlen, buf); if (res < 0) { @@ -683,12 +683,12 @@ static int write_bbt(struct mtd_info *mt pageoffs = page - (int)(to >> this->page_shift); offs = pageoffs << this->page_shift; /* Preset the bbt area with 0xff */ - memset(&buf[offs], 0xff, (size_t) (numblocks >> sft)); + memset(&buf[offs], 0xff, (size_mtd_t) (numblocks >> sft)); ooboffs = len + (pageoffs * mtd->oobsize); } else { /* Calc length */ - len = (size_t) (numblocks >> sft); + len = (size_mtd_t) (numblocks >> sft); /* Make it page aligned ! */ len = (len + (mtd->writesize - 1)) & ~(mtd->writesize - 1); @@ -1015,7 +1015,7 @@ int nand_scan_bbt(struct mtd_info *mtd, * * The function updates the bad block table(s) */ -int nand_update_bbt(struct mtd_info *mtd, loff_t offs) +int nand_update_bbt(struct mtd_info *mtd, loff_mtd_t offs) { struct nand_chip *this = mtd->priv; int len, res = 0, writeops = 0; @@ -1191,7 +1191,7 @@ int nand_default_bbt(struct mtd_info *mt * @allowbbt: allow access to bad block table region * */ -int nand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt) +int nand_isbad_bbt(struct mtd_info *mtd, loff_mtd_t offs, int allowbbt) { struct nand_chip *this = mtd->priv; int block; --- a/drivers/mtd/nand/nand_ids.c +++ b/drivers/mtd/nand/nand_ids.c @@ -109,6 +109,9 @@ struct nand_flash_dev nand_flash_ids[] = {"NAND 2GiB 1,8V 16-bit", 0xB5, 0, 2048, 0, LP_OPTIONS16}, {"NAND 2GiB 3,3V 16-bit", 0xC5, 0, 2048, 0, LP_OPTIONS16}, + /* 32 Gigabit */ + {"NAND 4GiB 3,3V 8-bit", 0xD7, 0, 4096, 0, LP_OPTIONS}, + /* * Renesas AND 1 Gigabit. Those chips do not support extended id and * have a strange page/block layout ! The chosen minimum erasesize is --- /dev/null +++ b/drivers/mtd/udc_cache.c @@ -0,0 +1,531 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CACHE_MAX_NUM 256 +#define SECTOR_SIZE 512 + +//#define UDC_CACHE_DEBUG + +#ifdef UDC_CACHE_DEBUG +#define dprintk(a...) printk(a) +#else +#define dprintk(a...) while(0){} +#endif + +typedef struct { + unsigned short CacheState; + unsigned short UseCount; + unsigned short CacheChange; + unsigned short CacheReserve; + unsigned int BlockId; + unsigned char *aBlockData; +} SSFDC__LB_CACHE; + +#define FREE_CACHE 0 +#define PREWRITE_CACHE 2 +#define OFTEN_USE_CACHE 3 +#define SECTOR_SHIFT 9 + +#define CACHE_TO_UNCATCH(x) ((unsigned int)x | 0xa0000000) +static unsigned int __aBlockData[SECTOR_SIZE * CACHE_MAX_NUM / 4] __attribute__ ((aligned (32))); +static SSFDC__LB_CACHE ssfdc_cache[CACHE_MAX_NUM]; +static unsigned short Cur_CacheCount = 0; +int FlushDataState = 0; +static struct mtdblk_dev *g_udc_mtdblk; +static struct mtd_info *g_udc_mtd; + +extern int udc_mtdblock_readsect(struct mtdblk_dev *, unsigned long, char *, int); +extern int udc_mtdblock_writesect(struct mtdblk_dev *, unsigned long, char *); +extern struct mtdblk_dev *udc_get_mtdblk(void); +extern struct mtd_info *udc_get_mtd(void); +extern void udc_flush_cache(struct mtdblk_dev *mtdblk); + +#define _NAND_LB_Write(pCache) udc_mtdblock_writesect(g_udc_mtdblk, pCache->BlockId,pCache->aBlockData) +#define _NAND_LB_Read(Sector,pBuffer) udc_mtdblock_readsect(g_udc_mtdblk, Sector, pBuffer, SECTOR_SIZE); + +#define DMA_ENABLE 0 + +#if DMA_ENABLE +#define DMA_CHANNEL 5 +#define PHYSADDR(x) virt_to_phys((void *)x) +#else +#define lb_memcpy memcpy +#endif + +#if DMA_ENABLE +static void lb_memcpy(void *target,void* source,unsigned int len) +{ + int ch = DMA_CHANNEL; + if(((unsigned int)source < 0xa0000000) && len) + dma_cache_wback_inv((unsigned long)source, len); + if(((unsigned int)target < 0xa0000000) && len) + dma_cache_wback_inv((unsigned long)target, len); + + REG_DMAC_DSAR(ch) = PHYSADDR((unsigned long)source); + REG_DMAC_DTAR(ch) = PHYSADDR((unsigned long)target); + REG_DMAC_DTCR(ch) = len / 32; + REG_DMAC_DRSR(ch) = DMAC_DRSR_RS_AUTO; + REG_DMAC_DCMD(ch) = DMAC_DCMD_SAI| DMAC_DCMD_DAI | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32|DMAC_DCMD_DS_32BYTE; + REG_DMAC_DCCSR(ch) = DMAC_DCCSR_EN | DMAC_DCCSR_NDES; + while ( REG_DMAC_DTCR(ch) ); +} +#endif + +static void _NAND_LB_InitCache(void) +{ + int i; + SSFDC__LB_CACHE *pCache = ssfdc_cache; +#if DMA_ENABLE + unsigned char * ptr = (unsigned char *)CACHE_TO_UNCATCH(__aBlockData); +#else + unsigned char * ptr = (unsigned char *)(__aBlockData); +#endif + for(i = 0;i < CACHE_MAX_NUM;i++) + { + pCache->CacheState = FREE_CACHE; + pCache->UseCount = 0; + pCache->CacheChange = 0; + pCache->aBlockData = ptr; + ptr+=SECTOR_SIZE; + pCache++; + } + Cur_CacheCount = 0; +} + +static SSFDC__LB_CACHE * _NAND_LB_GetFreeCache(void) +{ + int ret = 0; + SSFDC__LB_CACHE *pCacheInfo = &ssfdc_cache[Cur_CacheCount]; + while(1) + { + if(ret >= CACHE_MAX_NUM) + return 0; + if(pCacheInfo >= &ssfdc_cache[CACHE_MAX_NUM]) + { + pCacheInfo = ssfdc_cache; + Cur_CacheCount = 0; + } + + if(pCacheInfo->CacheState == FREE_CACHE) + { + return pCacheInfo; + } + pCacheInfo++; + Cur_CacheCount++; + ret++; + } + return 0; +} + +static void _NAND_LB_CloseCACHES(unsigned int sectorstart,unsigned int sectorend) +{ + unsigned int i; + SSFDC__LB_CACHE *pCache = ssfdc_cache; + for( i = 0;i < CACHE_MAX_NUM;i++){ + if((pCache->CacheState != FREE_CACHE) && (pCache->BlockId >= sectorstart) && (pCache->BlockId < sectorend)){ + pCache->CacheChange = 0; + pCache->CacheState = FREE_CACHE; + pCache->UseCount = 0; + } + pCache++; + } +} + +static void _NAND_LB_FLUSHCACHES(unsigned int sectorstart,unsigned int sectorend) +{ + unsigned int i; + SSFDC__LB_CACHE *pCache = ssfdc_cache; + for( i = 0;i < CACHE_MAX_NUM;i++){ + if((pCache->CacheState != FREE_CACHE) && (pCache->BlockId >= sectorstart) && (pCache->BlockId < sectorend)){ + if(pCache->CacheChange) + _NAND_LB_Write(pCache); + pCache->CacheChange = 0; + pCache->CacheState = FREE_CACHE; + pCache->UseCount = 0; + } + pCache++; + + } +} + +inline static int Get_NAND_CacheFreeCount(void) +{ + SSFDC__LB_CACHE *pCache = ssfdc_cache; + SSFDC__LB_CACHE *pEndCache = &ssfdc_cache[CACHE_MAX_NUM]; + unsigned int count = 0; + while(pCache < pEndCache) + { + if(pCache->CacheState == FREE_CACHE) + count++; + pCache++; + } + return count; + +} + +static unsigned int _NAND_LB_PreWiteToNand(SSFDC__LB_CACHE *pCache,unsigned short *count,unsigned int update) +{ + SSFDC__LB_CACHE *pWriteCache; + SSFDC__LB_CACHE *pEndCache = &ssfdc_cache[CACHE_MAX_NUM]; + unsigned int sector = -1; + unsigned int flag; + while(1) + { + sector = -1; + flag = 0; + pWriteCache = ssfdc_cache; + while(pWriteCache < pEndCache) + { + if(pWriteCache->CacheState == update) //PREWRITE_CACHE + { + if(pWriteCache->BlockId < sector) + { + sector = pWriteCache->BlockId; + pCache = pWriteCache; + } + }else + flag++; + pWriteCache++; + } + + if(flag < CACHE_MAX_NUM) + { + if(pCache->CacheChange) + { + _NAND_LB_Write(pCache); + pCache->CacheChange = 0; + } + pCache->CacheState = FREE_CACHE; + pCache->UseCount = 0; + (*count)++; + }else + break; + } + return 0; +} + +static void _NAND_LB_OftenToNand(SSFDC__LB_CACHE *pCache,unsigned short *count,unsigned int update) +{ + SSFDC__LB_CACHE *pWriteCache = pCache; + SSFDC__LB_CACHE *pOldCache = pCache; + SSFDC__LB_CACHE *pEndCache = &ssfdc_cache[CACHE_MAX_NUM]; + + dprintk("%s!\n",__FUNCTION__); + while(pCache) + { + if(pCache->CacheState == OFTEN_USE_CACHE) + { + if(pWriteCache->CacheState != OFTEN_USE_CACHE) + pWriteCache = pCache; + else if(pWriteCache->UseCount > pCache->UseCount) + { + pWriteCache = pCache; + } + } + pCache++; + if(pCache >= pEndCache) + break; + } + if(pWriteCache->CacheState == OFTEN_USE_CACHE) + { + (*count)++; + if(pWriteCache->CacheChange) + _NAND_LB_Write(pWriteCache); + pWriteCache->CacheState = FREE_CACHE; + + pWriteCache->UseCount = 0; + pWriteCache->CacheChange = 0; + if(update != -1) + update--; + if(update != 0) + _NAND_LB_OftenToNand(pOldCache,count,update); + } +} + +static int _NAND_LB_FreeCache(unsigned int update) +{ + unsigned short freecount = 0,totalfree = 0; + + freecount = 0; + _NAND_LB_PreWiteToNand(ssfdc_cache,&freecount,PREWRITE_CACHE); + + totalfree += freecount; + dprintk("free count = %d\n",freecount); + if(freecount == 0) + { + freecount = 0; + _NAND_LB_PreWiteToNand(ssfdc_cache,&freecount,OFTEN_USE_CACHE); + totalfree += freecount; + update = 0; + } + if(update) + { + if(Get_NAND_CacheFreeCount() < CACHE_MAX_NUM * 1 / 4) // because fat is 4 sector + { + freecount = 0; + _NAND_LB_PreWiteToNand(ssfdc_cache,&freecount,OFTEN_USE_CACHE); + totalfree += freecount; + } + } + + dprintk("Free = %d\r\n",totalfree); + return totalfree; +} + +static int _NAND_LB_GetFromCache(unsigned int Sector, void *pBuffer) { + + SSFDC__LB_CACHE *pCache = &ssfdc_cache[Cur_CacheCount]; + SSFDC__LB_CACHE *pUseCache = 0; + unsigned short i; + dprintk("sector = %x pBuffer = %x\n",Sector,pBuffer); + if(pCache >= &ssfdc_cache[CACHE_MAX_NUM]) + pCache = ssfdc_cache; + + i = 0; + while (1) { + if(pCache->CacheState != FREE_CACHE) + { + if (Sector == pCache->BlockId) { + dprintk("Cache is use = %d\r\n",pCache->BlockId); + pUseCache = pCache; + pCache->UseCount++; + if(pCache->UseCount == 0) + pCache->UseCount = -1; + pCache->CacheState = OFTEN_USE_CACHE; + } + } + pCache--; + if(pCache < ssfdc_cache) + pCache = &ssfdc_cache[CACHE_MAX_NUM - 1]; + + i++; + if (i >= CACHE_MAX_NUM) { + break; /* Sector not in cache */ + } + } + if (pUseCache) { + dprintk("From Cache %d\r\n",Sector); + lb_memcpy(pBuffer, pUseCache->aBlockData, SECTOR_SIZE); + return 0; + } + return -1; +} + +static void _NAND_LB_ClearCache(void) { + + unsigned short freecount = 0; + dprintk("Clear Cache\r\n"); + + _NAND_LB_PreWiteToNand(ssfdc_cache,&freecount,PREWRITE_CACHE); + _NAND_LB_PreWiteToNand(ssfdc_cache,&freecount,OFTEN_USE_CACHE); +} + +static void _NAND_LB_CopyToCache(unsigned int Sector, void *pBuffer,unsigned short rw) +{ + SSFDC__LB_CACHE *pCache = _NAND_LB_GetFreeCache(); + dprintk("Copy to Cache = 0x%08x 0x%08x\r\n",pCache,ssfdc_cache); + + if(!pCache) + { + _NAND_LB_FreeCache(rw); + + pCache = _NAND_LB_GetFreeCache(); + } + pCache->BlockId = Sector; + pCache->CacheState = PREWRITE_CACHE; + pCache->UseCount = 0; + pCache->CacheChange = rw; + + lb_memcpy(pCache->aBlockData,pBuffer,SECTOR_SIZE); +} + + +static int _NAND_LB_UpdateInCache(unsigned int Sector, void *pBuffer) { + short i,ret = 0; + i = Cur_CacheCount; + if(Cur_CacheCount > CACHE_MAX_NUM) + i = 0; + while(1) + { + if(ret >= CACHE_MAX_NUM) + return -1; + if(ssfdc_cache[i].CacheState != FREE_CACHE) + { + + if(ssfdc_cache[i].BlockId == Sector) + { + dprintk("UpdateInCache = %d\r\n",Sector); + ssfdc_cache[i].CacheState = OFTEN_USE_CACHE; + ssfdc_cache[i].UseCount++; + ssfdc_cache[i].CacheChange = 1; + lb_memcpy(ssfdc_cache[i].aBlockData,pBuffer,SECTOR_SIZE); + return 0; + } + } + i--; + if(i < 0) + i = CACHE_MAX_NUM - 1; + ret++; + } + return -1; +} + +static int NAND_LB_MultiRead(unsigned int Sector, void *pBuffer,unsigned int SectorCount) +{ + int i,ret,end; + void *p; + + dprintk("NAND_LB_MultiRead = %d %d \n",Sector,SectorCount); + end = Sector + SectorCount; + _NAND_LB_FLUSHCACHES(Sector,end); + + p = pBuffer; + for (i = Sector; i < end; i ++) + { + ret = udc_mtdblock_readsect(g_udc_mtdblk, i, p, SECTOR_SIZE); + p += SECTOR_SIZE; + } + return ret; +} + +static int NAND_LB_Read(unsigned int Sector, void *pBuffer) +{ + int x; +#if DMA_ENABLE + unsigned char *ptr = (unsigned char *)CACHE_TO_UNCATCH(pBuffer); + dma_cache_wback_inv(pBuffer,SECTOR_SIZE); +#else + unsigned char *ptr = (unsigned char *)pBuffer; +#endif + dprintk("LB_Read = %d \n",Sector); + if(_NAND_LB_GetFromCache(Sector,ptr)) + { + x = _NAND_LB_Read(Sector,ptr); + _NAND_LB_CopyToCache(Sector,ptr,0); + } + return 512; +} + +static int NAND_LB_MultiWrite(unsigned int Sector, void *pBuffer,unsigned int SectorCount) +{ + int i,ret; + unsigned char *p; + + _NAND_LB_CloseCACHES(Sector,Sector + SectorCount); + p = (unsigned char *)pBuffer; + for (i = Sector; i < Sector + SectorCount; i ++) + { + ret = udc_mtdblock_writesect(g_udc_mtdblk, i, p); + p += 512; + } + return ret; +} + +static int NAND_LB_Write(unsigned int Sector, void *pBuffer) +{ +#if DMA_ENABLE + unsigned char *ptr = (unsigned char *)CACHE_TO_UNCATCH(pBuffer); + dma_cache_wback_inv(pBuffer,SECTOR_SIZE); +#else + unsigned char *ptr = (unsigned char *)pBuffer; +#endif + dprintk("LB_Write = %x %x\r\n",Sector,pBuffer); + if(_NAND_LB_UpdateInCache(Sector,ptr)) + { + _NAND_LB_CopyToCache(Sector,ptr,1); + } + return 512; +} +/********************************************************************* +* +* Global functions +* +***********************************************************************/ + +int NAND_LB_Init(void) +{ + dprintk("UDC CACHE Init \n"); + _NAND_LB_InitCache(); + g_udc_mtdblk = udc_get_mtdblk(); + g_udc_mtd = udc_get_mtd(); + return 0; +} + +int NAND_LB_FLASHCACHE(void) +{ + dprintk("Flush lb cache !\n"); + _NAND_LB_ClearCache(); +// dprintk("Flush mtd cache !\n"); +// udc_flush_cache(g_udc_mtdblk); + return 0; +} + +int NAND_MTD_FLASHCACHE(void) +{ + dprintk("Flush mtd cache !\n"); + udc_flush_cache(g_udc_mtdblk); + return 0; +} + +int udc_read(unsigned int offset, unsigned int len, unsigned char *buf) +{ + unsigned long block,sector,i; + + block = offset >> SECTOR_SHIFT; + sector = len >> SECTOR_SHIFT; + dprintk("read dev = ia:%x, s:%d c:%d\r\n",buf,block,sector); + + if (sector <= 8) + { + for(i = 0;i < sector; i++) + { + NAND_LB_Read(block + i,(void *)(buf)); + buf += 512; + } + } + else + NAND_LB_MultiRead(block, buf, sector); + + return len; +} + +int udc_write(unsigned int offset, unsigned int len, unsigned char *buf) +{ + unsigned long block,sector,i; + + block = offset >> SECTOR_SHIFT; + sector = len >> SECTOR_SHIFT; + dprintk("write dev s:%d c:%d\r\n",block,sector); + + if(sector <= 8) + { + for(i = 0;i < sector; i++) + { + NAND_LB_Write(block + i,(void *)(buf)); + buf += 512; + FlushDataState = 1; + } + }else + NAND_LB_MultiWrite(block,(void *)(buf),sector); + + return len; +} + +EXPORT_SYMBOL_GPL(udc_write); +EXPORT_SYMBOL_GPL(udc_read); +EXPORT_SYMBOL_GPL(NAND_LB_Init); +EXPORT_SYMBOL_GPL(NAND_LB_FLASHCACHE); +EXPORT_SYMBOL_GPL(FlushDataState); +EXPORT_SYMBOL_GPL(NAND_MTD_FLASHCACHE); --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -330,6 +330,24 @@ config MII or internal device. It is safe to say Y or M here even if your ethernet card lack MII. +config JZ_ETH + tristate "JZ4730/JZ5730 On-Chip Ethernet support" + depends on NET_ETHERNET && (SOC_JZ4730 || SOC_JZ5730 || JZ_FPGA) + help + Say Y for support of JZ4730/JZ5730 On-Chip Ethernet interface. + + To compile this driver as a module, choose M here: the module + will be called jz_eth. + +config JZCS8900 + tristate "JZ CS8900A Ethernet support" + depends on NET_ETHERNET && (SOC_JZ4740 || SOC_JZ4750) + help + Say Y for support of JZ CS8900A Ethernet interface. + + To compile this driver as a module, choose M here: the module + will be called jzcs8900a. + config MACB tristate "Atmel MACB support" depends on AVR32 || ARCH_AT91SAM9260 || ARCH_AT91SAM9263 || ARCH_AT91SAM9G20 || ARCH_AT91CAP9 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -93,6 +93,8 @@ obj-$(CONFIG_SH_ETH) += sh_eth.o obj-$(CONFIG_MII) += mii.o obj-$(CONFIG_PHYLIB) += phy/ +obj-$(CONFIG_JZ_ETH) += jz_eth.o +obj-$(CONFIG_JZCS8900) += jzcs8900a.o obj-$(CONFIG_SUNDANCE) += sundance.o obj-$(CONFIG_HAMACHI) += hamachi.o obj-$(CONFIG_NET) += Space.o loopback.o --- /dev/null +++ b/drivers/net/jz_eth.c @@ -0,0 +1,1290 @@ +/* + * linux/drivers/net/jz_eth.c + * + * Jz4730/Jz5730 On-Chip ethernet driver. + * + * Copyright (C) 2005 - 2007 Ingenic Semiconductor Inc. + * + * This program 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "jz_eth.h" + +#define P2ADDR(a) (((unsigned long)(a) & 0x1fffffff) | 0xa0000000) +#define P1ADDR(a) (((unsigned long)(a) & 0x1fffffff) | 0x80000000) + +//#define DEBUG +#ifdef DEBUG +# define DBPRINTK(fmt,args...) printk(KERN_DEBUG fmt,##args) +#else +# define DBPRINTK(fmt,args...) do {} while(0) +#endif + +#define errprintk(fmt,args...) printk(KERN_ERR fmt,##args); +#define infoprintk(fmt,args...) printk(KERN_INFO fmt,##args); + +#define DRV_NAME "jz_eth" +#define DRV_VERSION "1.2" +#define DRV_AUTHOR "Peter Wei " +#define DRV_DESC "JzSOC On-chip Ethernet driver" + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESC); +MODULE_LICENSE("GPL"); + +/* + * Local variables + */ +static struct net_device *netdev; +static char * hwaddr = NULL; +static int debug = -1; +static struct mii_if_info mii_info; + +MODULE_PARM_DESC(debug, "i"); +MODULE_PARM_DESC(hwaddr,"s"); + +/* + * Local routines + */ +static irqreturn_t jz_eth_interrupt(int irq, void *dev_id); + +static int link_check_thread (void *data); + +/* + * Get MAC address + */ + +#define I2C_DEVICE 0x57 +#define MAC_OFFSET 64 + +extern void i2c_open(void); +extern void i2c_close(void); +extern int i2c_read(unsigned char device, unsigned char *buf, + unsigned char address, int count); + +static inline unsigned char str2hexnum(unsigned char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return 0; /* foo */ +} + +static inline void str2eaddr(unsigned char *ea, unsigned char *str) +{ + int i; + + for (i = 0; i < 6; i++) { + unsigned char num; + + if((*str == '.') || (*str == ':')) + str++; + num = str2hexnum(*str++) << 4; + num |= (str2hexnum(*str++)); + ea[i] = num; + } +} + +static int ethaddr_cmd = 0; +static unsigned char ethaddr_hex[6]; + +static int __init ethernet_addr_setup(char *str) +{ + if (!str) { + printk("ethaddr not set in command line\n"); + return -1; + } + ethaddr_cmd = 1; + str2eaddr(ethaddr_hex, str); + + return 0; +} + +__setup("ethaddr=", ethernet_addr_setup); + +static int get_mac_address(struct net_device *dev) +{ + int i; + unsigned char flag0=0; + unsigned char flag1=0xff; + + dev->dev_addr[0] = 0xff; + if (hwaddr != NULL) { + /* insmod jz-ethc.o hwaddr=00:ef:a3:c1:00:10 */ + str2eaddr(dev->dev_addr, hwaddr); + } else if (ethaddr_cmd) { + /* linux command line: ethaddr=00:ef:a3:c1:00:10 */ + for (i=0; i<6; i++) + dev->dev_addr[i] = ethaddr_hex[i]; + } else { +#if 0 + /* mac address in eeprom: byte 0x40-0x45 */ + i2c_open(); + i2c_read(I2C_DEVICE, dev->dev_addr, MAC_OFFSET, 6); + i2c_close(); +#endif + } + + /* check whether valid MAC address */ + for (i=0; i<6; i++) { + flag0 |= dev->dev_addr[i]; + flag1 &= dev->dev_addr[i]; + } + if ((dev->dev_addr[0] & 0xC0) || (flag0 == 0) || (flag1 == 0xff)) { + printk("WARNING: There is not MAC address, use default ..\n"); + dev->dev_addr[0] = 0x00; + dev->dev_addr[1] = 0xef; + dev->dev_addr[2] = 0xa3; + dev->dev_addr[3] = 0xc1; + dev->dev_addr[4] = 0x00; + dev->dev_addr[5] = 0x10; + dev->dev_addr[5] = 0x03; + } + return 0; +} + +/*---------------------------------------------------------------------*/ + +static u32 jz_eth_curr_mode(struct net_device *dev); + +/* + * Ethernet START/STOP routines + */ +#define START_ETH { \ + s32 val; \ + val = readl(DMA_OMR); \ + val |= OMR_ST | OMR_SR; \ + writel(val, DMA_OMR); \ +} + +#define STOP_ETH { \ + s32 val; \ + val = readl(DMA_OMR); \ + val &= ~(OMR_ST|OMR_SR); \ + writel(val, DMA_OMR); \ +} + +/* + * Link check routines + */ +static void start_check(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + + np->thread_die = 0; + init_waitqueue_head(&np->thr_wait); + init_completion (&np->thr_exited); + np->thr_pid = kernel_thread (link_check_thread,(void *)dev, + CLONE_FS | CLONE_FILES); + if (np->thr_pid < 0) + errprintk("%s: unable to start kernel thread\n",dev->name); +} + +static int close_check(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + int ret = 0; + + if (np->thr_pid >= 0) { + np->thread_die = 1; + wmb(); + ret = kill_proc (np->thr_pid, SIGTERM, 1); + if (ret) { + errprintk("%s: unable to signal thread\n", dev->name); + return 1; + } + wait_for_completion (&np->thr_exited); + } + return 0; +} + +static int link_check_thread(void *data) +{ + struct net_device *dev=(struct net_device *)data; + struct jz_eth_private *np = (struct jz_eth_private *)netdev->priv; + unsigned char current_link; + unsigned long timeout; + + daemonize("%s", dev->name); + spin_lock_irq(¤t->sighand->siglock); + sigemptyset(¤t->blocked); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + strncpy (current->comm, dev->name, sizeof(current->comm) - 1); + current->comm[sizeof(current->comm) - 1] = '\0'; + + while (1) { + timeout = 3*HZ; + do { + timeout = interruptible_sleep_on_timeout (&np->thr_wait, timeout); + /* make swsusp happy with our thread */ +// if (current->flags & PF_FREEZE) +// refrigerator(PF_FREEZE); + } while (!signal_pending (current) && (timeout > 0)); + + if (signal_pending (current)) { + spin_lock_irq(¤t->sighand->siglock); + flush_signals(current); + spin_unlock_irq(¤t->sighand->siglock); + } + + if (np->thread_die) + break; + + current_link=mii_link_ok(&mii_info); + if (np->link_state!=current_link) { + if (current_link) { + infoprintk("%s: Ethernet Link OK!\n",dev->name); + jz_eth_curr_mode(dev); + netif_carrier_on(dev); + } + else { + errprintk("%s: Ethernet Link offline!\n",dev->name); + netif_carrier_off(dev); + } + } + np->link_state=current_link; + + } + complete_and_exit (&np->thr_exited, 0); +} + +#ifdef DEBUG +/* + * Display ethernet packet header + * This routine is used for test function + */ +static void eth_dbg_rx(struct sk_buff *skb, int len) +{ + + int i, j; + + printk("R: %02x:%02x:%02x:%02x:%02x:%02x <- %02x:%02x:%02x:%02x:%02x:%02x len/SAP:%02x%02x [%d]\n", + (u8)skb->data[0], + (u8)skb->data[1], + (u8)skb->data[2], + (u8)skb->data[3], + (u8)skb->data[4], + (u8)skb->data[5], + (u8)skb->data[6], + (u8)skb->data[7], + (u8)skb->data[8], + (u8)skb->data[9], + (u8)skb->data[10], + (u8)skb->data[11], + (u8)skb->data[12], + (u8)skb->data[13], + len); + for (j=0; len>0; j+=16, len-=16) { + printk(" %03x: ",j); + for (i=0; i<16 && idata[i+j]); + } + printk("\n"); + } + return; + } +#endif + +/* + * Reset ethernet device + */ +static inline void jz_eth_reset(void) +{ + u32 i; + i = readl(DMA_BMR); + writel(i | BMR_SWR, DMA_BMR); + for(i = 0; i < 1000; i++) { + if(!(readl(DMA_BMR) & BMR_SWR)) break; + mdelay(1); + } +} + +/* + * MII operation routines + */ +static inline void mii_wait(void) +{ + int i; + for(i = 0; i < 10000; i++) { + if(!(readl(MAC_MIIA) & 0x1)) + break; + mdelay(1); + } + if (i >= 10000) + printk("MII wait timeout : %d.\n", i); +} + +static int mdio_read(struct net_device *dev,int phy_id, int location) +{ + u32 mii_cmd = (phy_id << 11) | (location << 6) | 1; + int retval = 0; + + writel(mii_cmd, MAC_MIIA); + mii_wait(); + retval = readl(MAC_MIID) & 0x0000ffff; + + return retval; + +} + +static void mdio_write(struct net_device *dev,int phy_id, int location, int data) +{ + u32 mii_cmd = (phy_id << 11) | (location << 6) | 0x2 | 1; + + writel(mii_cmd, MAC_MIIA); + writel(data & 0x0000ffff, MAC_MIID); + mii_wait(); +} + + +/* + * Search MII phy + */ +static int jz_search_mii_phy(struct net_device *dev) +{ + + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + int phy, phy_idx = 0; + + np->valid_phy = 0xff; + for (phy = 0; phy < 32; phy++) { + int mii_status = mdio_read(dev,phy, 1); + if (mii_status != 0xffff && mii_status != 0x0000) { + np->phys[phy_idx] = phy; + np->ecmds[phy_idx].speed=SPEED_100; + np->ecmds[phy_idx].duplex=DUPLEX_FULL; + np->ecmds[phy_idx].port=PORT_MII; + np->ecmds[phy_idx].transceiver=XCVR_INTERNAL; + np->ecmds[phy_idx].phy_address=np->phys[phy_idx]; + np->ecmds[phy_idx].autoneg=AUTONEG_ENABLE; + np->ecmds[phy_idx].advertising=(ADVERTISED_10baseT_Half | + ADVERTISED_10baseT_Full | + ADVERTISED_100baseT_Half | + ADVERTISED_100baseT_Full); + phy_idx++; + } + } + if (phy_idx == 1) { + np->valid_phy = np->phys[0]; + np->phy_type = 0; + } + if (phy_idx != 0) { + phy = np->valid_phy; + np->advertising = mdio_read(dev,phy, 4); + } + return phy_idx; +} + +/* + * CRC calc for Destination Address for gets hashtable index + */ + +#define POLYNOMIAL 0x04c11db7UL +static u16 jz_hashtable_index(u8 *addr) +{ +#if 1 + u32 crc = 0xffffffff, msb; + int i, j; + u32 byte; + for (i = 0; i < 6; i++) { + byte = *addr++; + for (j = 0; j < 8; j++) { + msb = crc >> 31; + crc <<= 1; + if (msb ^ (byte & 1)) crc ^= POLYNOMIAL; + byte >>= 1; + } + } + return ((int)(crc >> 26)); +#endif +#if 0 + int crc = -1; + int length=6; + int bit; + unsigned char current_octet; + while (--length >= 0) { + current_octet = *addr++; + for (bit = 0; bit < 8; bit++, current_octet >>= 1) + crc = (crc << 1) ^ ((crc < 0) ^ (current_octet & 1) ? + POLYNOMIAL : 0); + } + return ((int)(crc >> 26)); +#endif +} + +/* + * Multicast filter and config multicast hash table + */ +#define MULTICAST_FILTER_LIMIT 64 + +static void jz_set_multicast_list(struct net_device *dev) +{ + int i, hash_index; + u32 mcr, hash_h, hash_l, hash_bit; + + mcr = readl(MAC_MCR); + mcr &= ~(MCR_PR | MCR_PM | MCR_HP); + + if (dev->flags & IFF_PROMISC) { + /* Accept any kinds of packets */ + mcr |= MCR_PR; + hash_h = 0xffffffff; + hash_l = 0xffffffff; + DBPRINTK("%s: enter promisc mode!\n",dev->name); + } + else if ((dev->flags & IFF_ALLMULTI) || (dev->mc_count > MULTICAST_FILTER_LIMIT)){ + /* Accept all multicast packets */ + mcr |= MCR_PM; + hash_h = 0xffffffff; + hash_l = 0xffffffff; + DBPRINTK("%s: enter allmulticast mode! %d \n",dev->name,dev->mc_count); + } + else if (dev->flags & IFF_MULTICAST) + { + /* Update multicast hash table */ + struct dev_mc_list *mclist; + hash_h = readl(MAC_HTH); + hash_l = readl(MAC_HTL); + for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; + i++, mclist = mclist->next) + { + hash_index = jz_hashtable_index(mclist->dmi_addr); + hash_bit=0x00000001; + hash_bit <<= (hash_index & 0x1f); + if (hash_index > 0x1f) + hash_h |= hash_bit; + else + hash_l |= hash_bit; + DBPRINTK("----------------------------\n"); +#ifdef DEBUG + int j; + for (j=0;jdmi_addrlen;j++) + printk("%2.2x:",mclist->dmi_addr[j]); + printk("\n"); +#endif + DBPRINTK("dmi.addrlen => %d\n",mclist->dmi_addrlen); + DBPRINTK("dmi.users => %d\n",mclist->dmi_users); + DBPRINTK("dmi.gusers => %d\n",mclist->dmi_users); + } + writel(hash_h,MAC_HTH); + writel(hash_l,MAC_HTL); + mcr |= MCR_HP; + DBPRINTK("This is multicast hash table high bits [%4.4x]\n",readl(MAC_HTH)); + DBPRINTK("This is multicast hash table low bits [%4.4x]\n",readl(MAC_HTL)); + DBPRINTK("%s: enter multicast mode!\n",dev->name); + } + writel(mcr,MAC_MCR); +} + +static inline int jz_phy_reset(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + unsigned int mii_reg0; + unsigned int count; + + mii_reg0 = mdio_read(dev,np->valid_phy,MII_BMCR); + mii_reg0 |=MII_CR_RST; + mdio_write(dev,np->valid_phy,MII_BMCR,mii_reg0); //reset phy + for ( count = 0; count < 1000; count++) { + mdelay(1); + mii_reg0 = mdio_read(dev,np->valid_phy,MII_BMCR); + if (!(mii_reg0 & MII_CR_RST)) break; //reset completed + } + if (count>=100) + return 1; //phy error + else + return 0; +} + +/* + * Show all mii registers - this routine is used for test + */ +#ifdef DEBUG +static void mii_db_out(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + unsigned int mii_test; + + mii_test = mdio_read(dev,np->valid_phy,MII_BMCR); + DBPRINTK("BMCR ====> 0x%4.4x \n",mii_test); + + mii_test = mdio_read(dev,np->valid_phy,MII_BMSR); + DBPRINTK("BMSR ====> 0x%4.4x \n",mii_test); + + mii_test = mdio_read(dev,np->valid_phy,MII_ANAR); + DBPRINTK("ANAR ====> 0x%4.4x \n",mii_test); + + mii_test = mdio_read(dev,np->valid_phy,MII_ANLPAR); + DBPRINTK("ANLPAR ====> 0x%4.4x \n",mii_test); + + mii_test = mdio_read(dev,np->valid_phy,16); + DBPRINTK("REG16 ====> 0x%4.4x \n",mii_test); + + mii_test = mdio_read(dev,np->valid_phy,17); + DBPRINTK("REG17 ====> 0x%4.4x \n",mii_test); +} +#endif + +/* + * Start Auto-Negotiation function for PHY + */ +static int jz_autonet_complete(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + int count; + u32 mii_reg1, timeout = 3000; + + for (count = 0; count < timeout; count++) { + mdelay(1); + mii_reg1 = mdio_read(dev,np->valid_phy,MII_BMSR); + if (mii_reg1 & 0x0020) break; + } + //mii_db_out(dev); //for debug to display all register of MII + if (count >= timeout) + return 1; //auto negotiation error + else + return 0; +} + +/* + * Get current mode of eth phy + */ +static u32 jz_eth_curr_mode(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + unsigned int mii_reg17; + u32 flag = 0; + + mii_reg17 = mdio_read(dev,np->valid_phy,MII_DSCSR); + np->media = mii_reg17>>12; + if (np->media==8) { + infoprintk("%s: Current Operation Mode is [100M Full Duplex]",dev->name); + flag = 0; + np->full_duplex=1; + } + if (np->media==4) { + infoprintk("%s: Current Operation Mode is [100M Half Duplex]",dev->name); + flag = 0; + np->full_duplex=0; + } + if (np->media==2) { + infoprintk("%s: Current Operation Mode is [10M Full Duplex]",dev->name); + flag = OMR_TTM; + np->full_duplex=1; + } + if (np->media==1) { + infoprintk("%s: Current Operation Mode is [10M Half Duplex]",dev->name); + flag = OMR_TTM; + np->full_duplex=0; + } + printk("\n"); + return flag; +} + +/* + * Ethernet device hardware init + * This routine initializes the ethernet device hardware and PHY + */ +static int jz_init_hw(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + struct ethtool_cmd ecmd; + u32 mcr, omr; + u32 sts, flag = 0; + int i; + + jz_eth_reset(); + STOP_ETH; +#if 0 + /* mii operation */ + if (jz_phy_reset(dev)) { + errprintk("PHY device do not reset!\n"); + return -EPERM; // return operation not permitted + } +#endif + /* Set MAC address */ + writel(le32_to_cpu(*(unsigned long *)&dev->dev_addr[0]), MAC_MAL); + writel(le32_to_cpu(*(unsigned long *)&dev->dev_addr[4]), MAC_MAH); + printk("%s: JZ On-Chip ethernet (MAC ", dev->name); + for (i = 0; i < 5; i++) { + printk("%2.2x:", dev->dev_addr[i]); + } + printk("%2.2x, IRQ %d)\n", dev->dev_addr[i], dev->irq); + + np->mii_phy_cnt = jz_search_mii_phy(dev); + printk("%s: Found %d PHY on JZ MAC\n", dev->name, np->mii_phy_cnt); + + mii_info.phy_id = np->valid_phy; + mii_info.dev = dev; + mii_info.mdio_read = &mdio_read; + mii_info.mdio_write = &mdio_write; + + ecmd.speed = SPEED_100; + ecmd.duplex = DUPLEX_FULL; + ecmd.port = PORT_MII; + ecmd.transceiver = XCVR_INTERNAL; + ecmd.phy_address = np->valid_phy; + ecmd.autoneg = AUTONEG_ENABLE; + + mii_ethtool_sset(&mii_info,&ecmd); + if (jz_autonet_complete(dev)) + errprintk("%s: Ethernet Module AutoNegotiation failed\n",dev->name); + mii_ethtool_gset(&mii_info,&ecmd); + + infoprintk("%s: Provide Modes: ",dev->name); + for (i = 0; i < 5;i++) + if (ecmd.advertising & (1<full_duplex) + mcr |= MCR_FDX; + mcr |= MCR_BFD | MCR_TE | MCR_RE | MCR_OWD|MCR_HBD; + writel(mcr, MAC_MCR); +// mcr &= (readl(MAC_MCR) & ~(MCR_PM | MCR_PR | MCR_IF | MCR_HO | MCR_HP)); +// mcr &= 0xffdf; +// mcr |= 0x0020; +// writel(mcr, MAC_MCR); + + /* Set base address of TX and RX descriptors */ + writel(np->dma_rx_ring, DMA_RRBA); + writel(np->dma_tx_ring, DMA_TRBA); + + START_ETH; + + /* set interrupt mask */ + writel(IMR_DEFAULT | IMR_ENABLE, DMA_IMR); + + /* Reset any pending (stale) interrupts */ + sts = readl(DMA_STS); + writel(sts, DMA_STS); + + return 0; +} + +static int jz_eth_open(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + int retval, i; + + retval = request_irq(dev->irq, jz_eth_interrupt, 0, dev->name, dev); + if (retval) { + errprintk("%s: unable to get IRQ %d .\n", dev->name, dev->irq); + return -EAGAIN; + } + + for (i = 0; i < NUM_RX_DESCS; i++) { + np->rx_ring[i].status = cpu_to_le32(R_OWN); + np->rx_ring[i].desc1 = cpu_to_le32(RX_BUF_SIZE | RD_RCH); + np->rx_ring[i].buf1_addr = cpu_to_le32(np->dma_rx_buf + i*RX_BUF_SIZE); + np->rx_ring[i].next_addr = cpu_to_le32(np->dma_rx_ring + (i+1) * sizeof (jz_desc_t)); + } + np->rx_ring[NUM_RX_DESCS - 1].next_addr = cpu_to_le32(np->dma_rx_ring); + + for (i = 0; i < NUM_TX_DESCS; i++) { + np->tx_ring[i].status = cpu_to_le32(0); + np->tx_ring[i].desc1 = cpu_to_le32(TD_TCH); + np->tx_ring[i].buf1_addr = 0; + np->tx_ring[i].next_addr = cpu_to_le32(np->dma_tx_ring + (i+1) * sizeof (jz_desc_t)); + } + np->tx_ring[NUM_TX_DESCS - 1].next_addr = cpu_to_le32(np->dma_tx_ring); + + np->rx_head = 0; + np->tx_head = np->tx_tail = 0; + + jz_init_hw(dev); + + dev->trans_start = jiffies; + netif_start_queue(dev); + start_check(dev); + + return 0; +} + +static int jz_eth_close(struct net_device *dev) +{ + netif_stop_queue(dev); + close_check(dev); + STOP_ETH; + free_irq(dev->irq, dev); + return 0; +} + +/* + * Get the current statistics. + * This may be called with the device open or closed. + */ +static struct net_device_stats * jz_eth_get_stats(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + int tmp; + + tmp = readl(DMA_MFC); // After read clear to zero + np->stats.rx_missed_errors += (tmp & MFC_CNT2) + ((tmp & MFC_CNT1) >> 16); + + return &np->stats; +} + +/* + * ethtool routines + */ +static int jz_ethtool_ioctl(struct net_device *dev, void *useraddr) +{ + struct jz_eth_private *np = dev->priv; + u32 ethcmd; + + /* dev_ioctl() in ../../net/core/dev.c has already checked + capable(CAP_NET_ADMIN), so don't bother with that here. */ + + if (get_user(ethcmd, (u32 *)useraddr)) + return -EFAULT; + + switch (ethcmd) { + + case ETHTOOL_GDRVINFO: { + struct ethtool_drvinfo info = { ETHTOOL_GDRVINFO }; + strcpy (info.driver, DRV_NAME); + strcpy (info.version, DRV_VERSION); + strcpy (info.bus_info, "OCS"); + if (copy_to_user (useraddr, &info, sizeof (info))) + return -EFAULT; + return 0; + } + + /* get settings */ + case ETHTOOL_GSET: { + struct ethtool_cmd ecmd = { ETHTOOL_GSET }; + spin_lock_irq(&np->lock); + mii_ethtool_gset(&mii_info, &ecmd); + spin_unlock_irq(&np->lock); + if (copy_to_user(useraddr, &ecmd, sizeof(ecmd))) + return -EFAULT; + return 0; + } + /* set settings */ + case ETHTOOL_SSET: { + int r; + struct ethtool_cmd ecmd; + if (copy_from_user(&ecmd, useraddr, sizeof(ecmd))) + return -EFAULT; + spin_lock_irq(&np->lock); + r = mii_ethtool_sset(&mii_info, &ecmd); + spin_unlock_irq(&np->lock); + return r; + } + /* restart autonegotiation */ + case ETHTOOL_NWAY_RST: { + return mii_nway_restart(&mii_info); + } + /* get link status */ + case ETHTOOL_GLINK: { + struct ethtool_value edata = {ETHTOOL_GLINK}; + edata.data = mii_link_ok(&mii_info); + if (copy_to_user(useraddr, &edata, sizeof(edata))) + return -EFAULT; + return 0; + } + + /* get message-level */ + case ETHTOOL_GMSGLVL: { + struct ethtool_value edata = {ETHTOOL_GMSGLVL}; + edata.data = debug; + if (copy_to_user(useraddr, &edata, sizeof(edata))) + return -EFAULT; + return 0; + } + /* set message-level */ + case ETHTOOL_SMSGLVL: { + struct ethtool_value edata; + if (copy_from_user(&edata, useraddr, sizeof(edata))) + return -EFAULT; + debug = edata.data; + return 0; + } + + + default: + break; + } + + return -EOPNOTSUPP; + +} + +/* + * Config device + */ +static int jz_eth_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct jz_eth_private *np =(struct jz_eth_private *)dev->priv; + struct mii_ioctl_data *data, rdata; + + switch (cmd) { + case SIOCETHTOOL: + return jz_ethtool_ioctl(dev, (void *) rq->ifr_data); + case SIOCGMIIPHY: + case SIOCDEVPRIVATE: + data = (struct mii_ioctl_data *)&rq->ifr_data; + data->phy_id = np->valid_phy; + case SIOCGMIIREG: + case SIOCDEVPRIVATE+1: + data = (struct mii_ioctl_data *)&rq->ifr_data; + data->val_out = mdio_read(dev,np->valid_phy, data->reg_num & 0x1f); + return 0; + case SIOCSMIIREG: + case SIOCDEVPRIVATE+2: + data = (struct mii_ioctl_data *)&rq->ifr_data; + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + mdio_write(dev,np->valid_phy, data->reg_num & 0x1f, data->val_in); + return 0; + case READ_COMMAND: + data = (struct mii_ioctl_data *)rq->ifr_data; + if (copy_from_user(&rdata,data,sizeof(rdata))) + return -EFAULT; + rdata.val_out = mdio_read(dev,rdata.phy_id, rdata.reg_num & 0x1f); + if (copy_to_user(data,&rdata,sizeof(rdata))) + return -EFAULT; + return 0; + case WRITE_COMMAND: + if (np->phy_type==1) { + data = (struct mii_ioctl_data *)rq->ifr_data; + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + if (copy_from_user(&rdata,data,sizeof(rdata))) + return -EFAULT; + mdio_write(dev,rdata.phy_id, rdata.reg_num & 0x1f, rdata.val_in); + } + return 0; + case GETDRIVERINFO: + if (np->phy_type==1) { + data = (struct mii_ioctl_data *)rq->ifr_data; + if (copy_from_user(&rdata,data,sizeof(rdata))) + return -EFAULT; + rdata.val_in = 0x1; + rdata.val_out = 0x00d0; + if (copy_to_user(data,&rdata,sizeof(rdata))) + return -EFAULT; + } + return 0; + default: + return -EOPNOTSUPP; + } + return 0; +} + +/* + * Received one packet + */ +static void eth_rxready(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private*)dev->priv; + struct sk_buff *skb; + unsigned char *pkt_ptr; + u32 pkt_len; + u32 status; + + status = le32_to_cpu(np->rx_ring[np->rx_head].status); + while (!(status & R_OWN)) { /* owner bit = 0 */ + if (status & RD_ES) { /* error summary */ + np->stats.rx_errors++; /* Update the error stats. */ + if (status & (RD_RF | RD_TL)) + np->stats.rx_frame_errors++; + if (status & RD_CE) + np->stats.rx_crc_errors++; + if (status & RD_TL) + np->stats.rx_length_errors++; + } else { + pkt_ptr = bus_to_virt(le32_to_cpu(np->rx_ring[np->rx_head].buf1_addr)); + pkt_len = ((status & RD_FL) >> 16) - 4; + + skb = dev_alloc_skb(pkt_len + 2); + if (skb == NULL) { + printk("%s: Memory squeeze, dropping.\n", + dev->name); + np->stats.rx_dropped++; + break; + } + skb->dev = dev; + skb_reserve(skb, 2); /* 16 byte align */ + + //pkt_ptr = P1ADDR(pkt_ptr); + //dma_cache_inv(pkt_ptr, pkt_len); + memcpy(skb->data, pkt_ptr, pkt_len); + skb_put(skb, pkt_len); + + //eth_dbg_rx(skb, pkt_len); + skb->protocol = eth_type_trans(skb,dev); + netif_rx(skb); /* pass the packet to upper layers */ + dev->last_rx = jiffies; + np->stats.rx_packets++; + np->stats.rx_bytes += pkt_len; + } + np->rx_ring[np->rx_head].status = cpu_to_le32(R_OWN); + + np->rx_head ++; + if (np->rx_head >= NUM_RX_DESCS) + np->rx_head = 0; + status = le32_to_cpu(np->rx_ring[np->rx_head].status); + } +} + +/* + * Tx timeout routine + */ +static void jz_eth_tx_timeout(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + + jz_init_hw(dev); + np->stats.tx_errors ++; + netif_wake_queue(dev); +} + +/* + * One packet was transmitted + */ +static void eth_txdone(struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private*)dev->priv; + int tx_tail = np->tx_tail; + + while (tx_tail != np->tx_head) { + int entry = tx_tail % NUM_TX_DESCS; + s32 status = le32_to_cpu(np->tx_ring[entry].status); + if(status < 0) break; + if (status & TD_ES ) { /* Error summary */ + np->stats.tx_errors++; + if (status & TD_NC) np->stats.tx_carrier_errors++; + if (status & TD_LC) np->stats.tx_window_errors++; + if (status & TD_UF) np->stats.tx_fifo_errors++; + if (status & TD_DE) np->stats.tx_aborted_errors++; + if (np->tx_head != np->tx_tail) + writel(1, DMA_TPD); /* Restart a stalled TX */ + } else + np->stats.tx_packets++; + /* Update the collision counter */ + np->stats.collisions += ((status & TD_EC) ? 16 : ((status & TD_CC) >> 3)); + /* Free the original skb */ + if (np->tx_skb[entry]) { + dev_kfree_skb_irq(np->tx_skb[entry]); + np->tx_skb[entry] = 0; + } + tx_tail++; + } + if (np->tx_full && (tx_tail + NUM_TX_DESCS > np->tx_head + 1)) { + /* The ring is no longer full */ + np->tx_full = 0; + netif_start_queue(dev); + } + np->tx_tail = tx_tail; +} + +/* + * Update the tx descriptor + */ +static void load_tx_packet(struct net_device *dev, char *buf, u32 flags, struct sk_buff *skb) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + int entry = np->tx_head % NUM_TX_DESCS; + + np->tx_ring[entry].buf1_addr = cpu_to_le32(virt_to_bus(buf)); + np->tx_ring[entry].desc1 &= cpu_to_le32((TD_TER | TD_TCH)); + np->tx_ring[entry].desc1 |= cpu_to_le32(flags); + np->tx_ring[entry].status = cpu_to_le32(T_OWN); + np->tx_skb[entry] = skb; +} + +/* + * Transmit one packet + */ +static int jz_eth_send_packet(struct sk_buff *skb, struct net_device *dev) +{ + struct jz_eth_private *np = (struct jz_eth_private *)dev->priv; + u32 length; + + if (np->tx_full) { + return 0; + } +#ifdef CONFIG_FPGA + mdelay(10); +#else + udelay(500); /* FIXME: can we remove this delay ? */ +#endif + length = (skb->len < ETH_ZLEN) ? ETH_ZLEN : skb->len; + dma_cache_wback((unsigned long)skb->data, length); + load_tx_packet(dev, (char *)skb->data, TD_IC | TD_LS | TD_FS | length, skb); + spin_lock_irq(&np->lock); + np->tx_head ++; + np->stats.tx_bytes += length; + writel(1, DMA_TPD); /* Start the TX */ + dev->trans_start = jiffies; /* for timeout */ + if (np->tx_tail + NUM_TX_DESCS > np->tx_head + 1) { + np->tx_full = 0; + } + else { + np->tx_full = 1; + netif_stop_queue(dev); + } + spin_unlock_irq(&np->lock); + + return 0; +} + +/* + * Interrupt service routine + */ +static irqreturn_t jz_eth_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct jz_eth_private *np = dev->priv; + u32 sts; + int i; + + spin_lock(&np->lock); + + writel((readl(DMA_IMR) & ~IMR_ENABLE), DMA_IMR); /* Disable interrupt */ + + for (i = 0; i < 100; i++) { + sts = readl(DMA_STS); + writel(sts, DMA_STS); /* clear status */ + + if (!(sts & IMR_DEFAULT)) break; + + if (sts & (DMA_INT_RI | DMA_INT_RU)) /* Rx IRQ */ + eth_rxready(dev); + if (sts & (DMA_INT_TI | DMA_INT_TU)) /* Tx IRQ */ + eth_txdone(dev); + + /* check error conditions */ + if (sts & DMA_INT_FB){ /* fatal bus error */ + STOP_ETH; + errprintk("%s: Fatal bus error occurred, sts=%#8x, device stopped.\n",dev->name, sts); + break; + } + + if (sts & DMA_INT_UN) { /* Transmit underrun */ + u32 omr; + omr = readl(DMA_OMR); + if (!(omr & OMR_SF)) { + omr &= ~(OMR_ST | OMR_SR); + writel(omr, DMA_OMR); + while (readl(DMA_STS) & STS_TS); /* wait for stop */ + if ((omr & OMR_TR) < OMR_TR) { /* ? */ + omr += TR_24; + } else { + omr |= OMR_SF; + } + writel(omr | OMR_ST | OMR_SR, DMA_OMR); + } + } + } + + writel(readl(DMA_IMR) | IMR_ENABLE, DMA_IMR); /* enable interrupt */ + + spin_unlock(&np->lock); + + return IRQ_HANDLED; +} + +#if 0 //def CONFIG_PM +/* + * Suspend the ETH interface. + */ +static int jz_eth_suspend(struct net_device *dev, int state) +{ + struct jz_eth_private *jep = (struct jz_eth_private *)dev->priv; + unsigned long flags, tmp; + + printk("ETH suspend.\n"); + + if (!netif_running(dev)) { + return 0; + } + + netif_device_detach(dev); + + spin_lock_irqsave(&jep->lock, flags); + + /* Disable interrupts, stop Tx and Rx. */ + REG32(DMA_IMR) = 0; + STOP_ETH; + + /* Update the error counts. */ + tmp = REG32(DMA_MFC); + jep->stats.rx_missed_errors += (tmp & 0x1ffff); + jep->stats.rx_fifo_errors += ((tmp >> 17) & 0x7ff); + + spin_unlock_irqrestore(&jep->lock, flags); + + return 0; +} + +/* + * Resume the ETH interface. + */ +static int jz_eth_resume(struct net_device *dev) +{ + printk("ETH resume.\n"); + + if (!netif_running(dev)) + return 0; + + jz_init_hw(dev); + + netif_device_attach(dev); + jz_eth_tx_timeout(dev); + netif_wake_queue(dev); + + return 0; +} + +static int jz_eth_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data) +{ + int ret; + + if (!dev->data) + return -EINVAL; + + switch (rqst) { + case PM_SUSPEND: + ret = jz_eth_suspend((struct net_device *)dev->data, + (int)data); + break; + + case PM_RESUME: + ret = jz_eth_resume((struct net_device *)dev->data); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +#endif /* CONFIG_PM */ + +static int __init jz_eth_init(void) +{ + struct net_device *dev; + struct jz_eth_private *np; + int err; + + dev = alloc_etherdev(sizeof(struct jz_eth_private)); + if (!dev) { + printk(KERN_ERR "%s: alloc_etherdev failed\n", DRV_NAME); + return -ENOMEM; + } + netdev = dev; + + np = (struct jz_eth_private *)P2ADDR(dev->priv); + dev->priv = np; + memset(np, 0, sizeof(struct jz_eth_private)); + + np->vaddr_rx_buf = (u32)dma_alloc_noncoherent(NULL, NUM_RX_DESCS*RX_BUF_SIZE, + &np->dma_rx_buf, 0); + + if (!np->vaddr_rx_buf) { + printk(KERN_ERR "%s: Cannot alloc dma buffers\n", DRV_NAME); + unregister_netdev(dev); + free_netdev(dev); + return -ENOMEM; + } + + np->dma_rx_ring = virt_to_bus(np->rx_ring); + np->dma_tx_ring = virt_to_bus(np->tx_ring); + np->full_duplex = 1; + np->link_state = 1; + + spin_lock_init(&np->lock); + + ether_setup(dev); + dev->irq = IRQ_ETH; + dev->open = jz_eth_open; + dev->stop = jz_eth_close; + dev->hard_start_xmit = jz_eth_send_packet; + dev->get_stats = jz_eth_get_stats; + dev->set_multicast_list = jz_set_multicast_list; + dev->do_ioctl = jz_eth_ioctl; + dev->tx_timeout = jz_eth_tx_timeout; + dev->watchdog_timeo = ETH_TX_TIMEOUT; + + /* configure MAC address */ + get_mac_address(dev); + + if ((err = register_netdev(dev)) != 0) { + printk(KERN_ERR "%s: Cannot register net device, error %d\n", + DRV_NAME, err); + free_netdev(dev); + return -ENOMEM; + } + +//#ifdef 0 //CONFIG_PM +// np->pmdev = pm_register(PM_SYS_DEV, PM_SYS_UNKNOWN, jz_eth_pm_callback); +// if (np->pmdev) +// np->pmdev->data = dev; +//#endif + + return 0; +} + +static void __exit jz_eth_exit(void) +{ + struct net_device *dev = netdev; + struct jz_eth_private *np = dev->priv; + + unregister_netdev(dev); + dma_free_noncoherent(NULL, NUM_RX_DESCS * RX_BUF_SIZE, + (void *)np->vaddr_rx_buf, np->dma_rx_buf); + free_netdev(dev); +} + +module_init(jz_eth_init); +module_exit(jz_eth_exit); --- /dev/null +++ b/drivers/net/jz_eth.h @@ -0,0 +1,403 @@ +/* + * linux/drivers/net/jz_eth.h + * + * Jz4730/Jz5730 On-Chip ethernet driver. + * + * Copyright (C) 2005 - 2007 Ingenic Semiconductor Inc. + * + * This program 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. + */ +#ifndef __JZ_ETH_H__ +#define __JZ_ETH_H__ + +/* DMA control and status registers */ +#define DMA_BMR (ETH_BASE + 0x1000) // Bus mode +#define DMA_TPD (ETH_BASE + 0x1004) // Transmit poll demand register +#define DMA_RPD (ETH_BASE + 0x1008) // Receieve poll demand register +#define DMA_RRBA (ETH_BASE + 0x100C) // Receieve descriptor base address +#define DMA_TRBA (ETH_BASE + 0x1010) // Transmit descriptor base address +#define DMA_STS (ETH_BASE + 0x1014) // Status register +#define DMA_OMR (ETH_BASE + 0x1018) // Command register +#define DMA_IMR (ETH_BASE + 0x101C) +#define DMA_MFC (ETH_BASE + 0x1020) + +/* DMA CSR8-CSR19 reserved */ +#define DMA_CTA (ETH_BASE + 0x1050) +#define DMA_CRA (ETH_BASE + 0x1054) + +/* Mac control and status registers */ +#define MAC_MCR (ETH_BASE + 0x0000) +#define MAC_MAH (ETH_BASE + 0x0004) +#define MAC_MAL (ETH_BASE + 0x0008) +#define MAC_HTH (ETH_BASE + 0x000C) +#define MAC_HTL (ETH_BASE + 0x0010) +#define MAC_MIIA (ETH_BASE + 0x0014) +#define MAC_MIID (ETH_BASE + 0x0018) +#define MAC_FCR (ETH_BASE + 0x001C) +#define MAC_VTR1 (ETH_BASE + 0x0020) +#define MAC_VTR2 (ETH_BASE + 0x0024) + +/* + * Bus Mode Register (DMA_BMR) + */ +#define BMR_PBL 0x00003f00 /* Programmable Burst Length */ +#define BMR_DSL 0x0000007c /* Descriptor Skip Length */ +#define BMR_BAR 0x00000002 /* Bus ARbitration */ +#define BMR_SWR 0x00000001 /* Software Reset */ + +#define PBL_0 0x00000000 /* DMA burst length = amount in RX FIFO */ +#define PBL_1 0x00000100 /* 1 longword DMA burst length */ +#define PBL_2 0x00000200 /* 2 longwords DMA burst length */ +#define PBL_4 0x00000400 /* 4 longwords DMA burst length */ +#define PBL_8 0x00000800 /* 8 longwords DMA burst length */ +#define PBL_16 0x00001000 /* 16 longwords DMA burst length */ +#define PBL_32 0x00002000 /* 32 longwords DMA burst length */ + +#define DSL_0 0x00000000 /* 0 longword / descriptor */ +#define DSL_1 0x00000004 /* 1 longword / descriptor */ +#define DSL_2 0x00000008 /* 2 longwords / descriptor */ +#define DSL_4 0x00000010 /* 4 longwords / descriptor */ +#define DSL_8 0x00000020 /* 8 longwords / descriptor */ +#define DSL_16 0x00000040 /* 16 longwords / descriptor */ +#define DSL_32 0x00000080 /* 32 longwords / descriptor */ + +/* + * Status Register (DMA_STS) + */ +#define STS_BE 0x03800000 /* Bus Error Bits */ +#define STS_TS 0x00700000 /* Transmit Process State */ +#define STS_RS 0x000e0000 /* Receive Process State */ + +#define TS_STOP 0x00000000 /* Stopped */ +#define TS_FTD 0x00100000 /* Running Fetch Transmit Descriptor */ +#define TS_WEOT 0x00200000 /* Running Wait for End Of Transmission */ +#define TS_QDAT 0x00300000 /* Running Queue skb data into TX FIFO */ +#define TS_RES 0x00400000 /* Reserved */ +#define TS_SPKT 0x00500000 /* Reserved */ +#define TS_SUSP 0x00600000 /* Suspended */ +#define TS_CLTD 0x00700000 /* Running Close Transmit Descriptor */ + +#define RS_STOP 0x00000000 /* Stopped */ +#define RS_FRD 0x00020000 /* Running Fetch Receive Descriptor */ +#define RS_CEOR 0x00040000 /* Running Check for End of Receive Packet */ +#define RS_WFRP 0x00060000 /* Running Wait for Receive Packet */ +#define RS_SUSP 0x00080000 /* Suspended */ +#define RS_CLRD 0x000a0000 /* Running Close Receive Descriptor */ +#define RS_FLUSH 0x000c0000 /* Running Flush RX FIFO */ +#define RS_QRFS 0x000e0000 /* Running Queue RX FIFO into RX Skb */ + +/* + * Operation Mode Register (DMA_OMR) + */ +#define OMR_TTM 0x00400000 /* Transmit Threshold Mode */ +#define OMR_SF 0x00200000 /* Store and Forward */ +#define OMR_TR 0x0000c000 /* Threshold Control Bits */ +#define OMR_ST 0x00002000 /* Start/Stop Transmission Command */ +#define OMR_OSF 0x00000004 /* Operate on Second Frame */ +#define OMR_SR 0x00000002 /* Start/Stop Receive */ + +#define TR_18 0x00000000 /* Threshold set to 18 (32) bytes */ +#define TR_24 0x00004000 /* Threshold set to 24 (64) bytes */ +#define TR_32 0x00008000 /* Threshold set to 32 (128) bytes */ +#define TR_40 0x0000c000 /* Threshold set to 40 (256) bytes */ + +/* + * Missed Frames Counters (DMA_MFC) + */ +//#define MFC_CNT1 0xffff0000 /* Missed Frames Counter Bits by application */ +#define MFC_CNT1 0x0ffe0000 /* Missed Frames Counter Bits by application */ +#define MFC_CNT2 0x0000ffff /* Missed Frames Counter Bits by controller */ + +/* + * Mac control Register (MAC_MCR) + */ +#define MCR_RA 0x80000000 /* Receive All */ +#define MCR_HBD 0x10000000 /* HeartBeat Disable */ +#define MCR_PS 0x08000000 /* Port Select */ +#define MCR_OWD 0x00800000 /* Receive own Disable */ +#define MCR_OM 0x00600000 /* Operating(loopback) Mode */ +#define MCR_FDX 0x00100000 /* Full Duplex Mode */ +#define MCR_PM 0x00080000 /* Pass All Multicast */ +#define MCR_PR 0x00040000 /* Promiscuous Mode */ +#define MCR_IF 0x00020000 /* Inverse Filtering */ +#define MCR_PB 0x00010000 /* Pass Bad Frames */ +#define MCR_HO 0x00008000 /* Hash Only Filtering Mode */ +#define MCR_HP 0x00002000 /* Hash/Perfect Receive Filtering Mode */ +#define MCR_FC 0x00001000 /* Late Collision control */ +#define MCR_BFD 0x00000800 /* Boardcast frame Disable */ +#define MCR_RED 0x00000400 /* Retry Disable */ +#define MCR_APS 0x00000100 /* Automatic pad stripping */ +#define MCR_BL 0x000000c0 /* Back off Limit */ +#define MCR_DC 0x00000020 /* Deferral check */ +#define MCR_TE 0x00000008 /* Transmitter enable */ +#define MCR_RE 0x00000004 /* Receiver enable */ + +#define MCR_MII_10 ( OMR_TTM | MCR_PS) +#define MCR_MII_100 ( MCR_HBD | MCR_PS) + +/* Flow control Register (MAC_FCR) */ +#define FCR_PT 0xffff0000 /* Pause time */ +#define FCR_PCF 0x00000004 /* Pass control frames */ +#define FCR_FCE 0x00000002 /* Flow control enable */ +#define FCR_FCB 0x00000001 /* Flow control busy */ + + +/* Constants for the interrupt mask and + * interrupt status registers. (DMA_SIS and DMA_IMR) + */ +#define DMA_INT_NI 0x00010000 // Normal interrupt summary +#define DMA_INT_AI 0x00008000 // Abnormal interrupt summary +#define DMA_INT_ER 0x00004000 // Early receive interrupt +#define DMA_INT_FB 0x00002000 // Fatal bus error +#define DMA_INT_ET 0x00000400 // Early transmit interrupt +#define DMA_INT_RW 0x00000200 // Receive watchdog timeout +#define DMA_INT_RS 0x00000100 // Receive stop +#define DMA_INT_RU 0x00000080 // Receive buffer unavailble +#define DMA_INT_RI 0x00000040 // Receive interrupt +#define DMA_INT_UN 0x00000020 // Underflow +#define DMA_INT_TJ 0x00000008 // Transmit jabber timeout +#define DMA_INT_TU 0x00000004 // Transmit buffer unavailble +#define DMA_INT_TS 0x00000002 // Transmit stop +#define DMA_INT_TI 0x00000001 // Transmit interrupt + +/* + * Receive Descriptor Bit Summary + */ +#define R_OWN 0x80000000 /* Own Bit */ +#define RD_FF 0x40000000 /* Filtering Fail */ +#define RD_FL 0x3fff0000 /* Frame Length */ +#define RD_ES 0x00008000 /* Error Summary */ +#define RD_DE 0x00004000 /* Descriptor Error */ +#define RD_LE 0x00001000 /* Length Error */ +#define RD_RF 0x00000800 /* Runt Frame */ +#define RD_MF 0x00000400 /* Multicast Frame */ +#define RD_FS 0x00000200 /* First Descriptor */ +#define RD_LS 0x00000100 /* Last Descriptor */ +#define RD_TL 0x00000080 /* Frame Too Long */ +#define RD_CS 0x00000040 /* Collision Seen */ +#define RD_FT 0x00000020 /* Frame Type */ +#define RD_RJ 0x00000010 /* Receive Watchdog timeout*/ +#define RD_RE 0x00000008 /* Report on MII Error */ +#define RD_DB 0x00000004 /* Dribbling Bit */ +#define RD_CE 0x00000002 /* CRC Error */ + +#define RD_RER 0x02000000 /* Receive End Of Ring */ +#define RD_RCH 0x01000000 /* Second Address Chained */ +#define RD_RBS2 0x003ff800 /* Buffer 2 Size */ +#define RD_RBS1 0x000007ff /* Buffer 1 Size */ + +/* + * Transmit Descriptor Bit Summary + */ +#define T_OWN 0x80000000 /* Own Bit */ +#define TD_ES 0x00008000 /* Frame Aborted (error summary)*/ +#define TD_LO 0x00000800 /* Loss Of Carrier */ +#define TD_NC 0x00000400 /* No Carrier */ +#define TD_LC 0x00000200 /* Late Collision */ +#define TD_EC 0x00000100 /* Excessive Collisions */ +#define TD_HF 0x00000080 /* Heartbeat Fail */ +#define TD_CC 0x0000003c /* Collision Counter */ +#define TD_UF 0x00000002 /* Underflow Error */ +#define TD_DE 0x00000001 /* Deferred */ + +#define TD_IC 0x80000000 /* Interrupt On Completion */ +#define TD_LS 0x40000000 /* Last Segment */ +#define TD_FS 0x20000000 /* First Segment */ +#define TD_FT1 0x10000000 /* Filtering Type */ +#define TD_SET 0x08000000 /* Setup Packet */ +#define TD_AC 0x04000000 /* Add CRC Disable */ +#define TD_TER 0x02000000 /* Transmit End Of Ring */ +#define TD_TCH 0x01000000 /* Second Address Chained */ +#define TD_DPD 0x00800000 /* Disabled Padding */ +#define TD_FT0 0x00400000 /* Filtering Type */ +#define TD_TBS2 0x003ff800 /* Buffer 2 Size */ +#define TD_TBS1 0x000007ff /* Buffer 1 Size */ + +#define PERFECT_F 0x00000000 +#define HASH_F TD_FT0 +#define INVERSE_F TD_FT1 +#define HASH_O_F (TD_FT1 | TD_F0) + +/* + * Constant setting + */ + +#define IMR_DEFAULT ( DMA_INT_TI | DMA_INT_RI | \ + DMA_INT_TS | DMA_INT_RS | \ + DMA_INT_TU | DMA_INT_RU | \ + DMA_INT_FB ) + +#define IMR_ENABLE (DMA_INT_NI | DMA_INT_AI) + +#define CRC_POLYNOMIAL_BE 0x04c11db7UL /* Ethernet CRC, big endian */ +#define CRC_POLYNOMIAL_LE 0xedb88320UL /* Ethernet CRC, little endian */ + +#define HASH_TABLE_LEN 512 /* Bits */ +#define HASH_BITS 0x01ff /* 9 LS bits */ + +#define SETUP_FRAME_LEN 192 /* Bytes */ +#define IMPERF_PA_OFFSET 156 /* Bytes */ + +/* + * Address Filtering Modes + */ +#define PERFECT 0 /* 16 perfect physical addresses */ +#define HASH_PERF 1 /* 1 perfect, 512 multicast addresses */ +#define PERFECT_REJ 2 /* Reject 16 perfect physical addresses */ +#define ALL_HASH 3 /* Hashes all physical & multicast addrs */ + +#define ALL 0 /* Clear out all the setup frame */ +#define PHYS_ADDR_ONLY 1 /* Update the physical address only */ + +/* MII register */ +#define MII_BMCR 0x00 /* MII Basic Mode Control Register */ +#define MII_BMSR 0x01 /* MII Basic Mode Status Register */ +#define MII_ID1 0x02 /* PHY Identifier Register 1 */ +#define MII_ID2 0x03 /* PHY Identifier Register 2 */ +#define MII_ANAR 0x04 /* Auto Negotiation Advertisement Register */ +#define MII_ANLPAR 0x05 /* Auto Negotiation Link Partner Ability */ +#define MII_ANER 0x06 /* Auto Negotiation Expansion */ +#define MII_DSCR 0x10 /* Davicom Specified Configration Register */ +#define MII_DSCSR 0x11 /* Davicom Specified Configration/Status Register */ +#define MII_10BTCSR 0x12 /* 10base-T Specified Configration/Status Register */ + + +#define MII_PREAMBLE 0xffffffff /* MII Management Preamble */ +#define MII_TEST 0xaaaaaaaa /* MII Test Signal */ +#define MII_STRD 0x06 /* Start of Frame+Op Code: use low nibble */ +#define MII_STWR 0x0a /* Start of Frame+Op Code: use low nibble */ + +/* + * MII Management Control Register + */ +#define MII_CR_RST 0x8000 /* RESET the PHY chip */ +#define MII_CR_LPBK 0x4000 /* Loopback enable */ +#define MII_CR_SPD 0x2000 /* 0: 10Mb/s; 1: 100Mb/s */ +#define MII_CR_ASSE 0x1000 /* Auto Speed Select Enable */ +#define MII_CR_PD 0x0800 /* Power Down */ +#define MII_CR_ISOL 0x0400 /* Isolate Mode */ +#define MII_CR_RAN 0x0200 /* Restart Auto Negotiation */ +#define MII_CR_FDM 0x0100 /* Full Duplex Mode */ +#define MII_CR_CTE 0x0080 /* Collision Test Enable */ + +/* + * MII Management Status Register + */ +#define MII_SR_T4C 0x8000 /* 100BASE-T4 capable */ +#define MII_SR_TXFD 0x4000 /* 100BASE-TX Full Duplex capable */ +#define MII_SR_TXHD 0x2000 /* 100BASE-TX Half Duplex capable */ +#define MII_SR_TFD 0x1000 /* 10BASE-T Full Duplex capable */ +#define MII_SR_THD 0x0800 /* 10BASE-T Half Duplex capable */ +#define MII_SR_ASSC 0x0020 /* Auto Speed Selection Complete*/ +#define MII_SR_RFD 0x0010 /* Remote Fault Detected */ +#define MII_SR_ANC 0x0008 /* Auto Negotiation capable */ +#define MII_SR_LKS 0x0004 /* Link Status */ +#define MII_SR_JABD 0x0002 /* Jabber Detect */ +#define MII_SR_XC 0x0001 /* Extended Capabilities */ + +/* + * MII Management Auto Negotiation Advertisement Register + */ +#define MII_ANA_TAF 0x03e0 /* Technology Ability Field */ +#define MII_ANA_T4AM 0x0200 /* T4 Technology Ability Mask */ +#define MII_ANA_TXAM 0x0180 /* TX Technology Ability Mask */ +#define MII_ANA_FDAM 0x0140 /* Full Duplex Technology Ability Mask */ +#define MII_ANA_HDAM 0x02a0 /* Half Duplex Technology Ability Mask */ +#define MII_ANA_100M 0x0380 /* 100Mb Technology Ability Mask */ +#define MII_ANA_10M 0x0060 /* 10Mb Technology Ability Mask */ +#define MII_ANA_CSMA 0x0001 /* CSMA-CD Capable */ + +/* + * MII Management Auto Negotiation Remote End Register + */ +#define MII_ANLPA_NP 0x8000 /* Next Page (Enable) */ +#define MII_ANLPA_ACK 0x4000 /* Remote Acknowledge */ +#define MII_ANLPA_RF 0x2000 /* Remote Fault */ +#define MII_ANLPA_TAF 0x03e0 /* Technology Ability Field */ +#define MII_ANLPA_T4AM 0x0200 /* T4 Technology Ability Mask */ +#define MII_ANLPA_TXAM 0x0180 /* TX Technology Ability Mask */ +#define MII_ANLPA_FDAM 0x0140 /* Full Duplex Technology Ability Mask */ +#define MII_ANLPA_HDAM 0x02a0 /* Half Duplex Technology Ability Mask */ +#define MII_ANLPA_100M 0x0380 /* 100Mb Technology Ability Mask */ +#define MII_ANLPA_10M 0x0060 /* 10Mb Technology Ability Mask */ +#define MII_ANLPA_CSMA 0x0001 /* CSMA-CD Capable */ + +/* + * MII Management DAVICOM Specified Configuration And Status Register + */ +#define MII_DSCSR_100FDX 0x8000 /* 100M Full Duplex Operation Mode */ +#define MII_DSCSR_100HDX 0x4000 /* 100M Half Duplex Operation Mode */ +#define MII_DSCSR_10FDX 0x2000 /* 10M Full Duplex Operation Mode */ +#define MII_DSCSR_10HDX 0x1000 /* 10M Half Duplex Operation Mode */ +#define MII_DSCSR_ANMB 0x000f /* Auto-Negotiation Monitor Bits */ + + +/* + * Used by IOCTL + */ +#define READ_COMMAND (SIOCDEVPRIVATE+4) +#define WRITE_COMMAND (SIOCDEVPRIVATE+5) +#define GETDRIVERINFO (SIOCDEVPRIVATE+6) + +/* + * Device data and structure + */ + +#define ETH_TX_TIMEOUT (6*HZ) + +#define RX_BUF_SIZE 1536 + +#define NUM_RX_DESCS 32 +#define NUM_TX_DESCS 16 + +static const char *media_types[] = { + "10BaseT-HD ", "10BaseT-FD ","100baseTx-HD ", + "100baseTx-FD", "100baseT4", 0 +}; + +typedef struct { + unsigned int status; + unsigned int desc1; + unsigned int buf1_addr; + unsigned int next_addr; +} jz_desc_t; + +struct jz_eth_private { + jz_desc_t tx_ring[NUM_TX_DESCS]; /* transmit descriptors */ + jz_desc_t rx_ring[NUM_RX_DESCS]; /* receive descriptors */ + dma_addr_t dma_tx_ring; /* bus address of tx ring */ + dma_addr_t dma_rx_ring; /* bus address of rx ring */ + dma_addr_t dma_rx_buf; /* DMA address of rx buffer */ + unsigned int vaddr_rx_buf; /* virtual address of rx buffer */ + + unsigned int rx_head; /* first rx descriptor */ + unsigned int tx_head; /* first tx descriptor */ + unsigned int tx_tail; /* last unacked transmit packet */ + unsigned int tx_full; /* transmit buffers are full */ + struct sk_buff *tx_skb[NUM_TX_DESCS]; /* skbuffs for packets to transmit */ + + struct net_device_stats stats; + spinlock_t lock; + + int media; /* Media (eg TP), mode (eg 100B)*/ + int full_duplex; /* Current duplex setting. */ + int link_state; + char phys[32]; /* List of attached PHY devices */ + char valid_phy; /* Current linked phy-id with MAC */ + int mii_phy_cnt; + int phy_type; /* 1-RTL8309,0-DVCOM */ + struct ethtool_cmd ecmds[32]; + u16 advertising; /* NWay media advertisement */ + + pid_t thr_pid; /* Link cheak thread ID */ + int thread_die; + struct completion thr_exited; + wait_queue_head_t thr_wait; + + struct pm_dev *pmdev; +}; + +#endif /* __JZ_ETH_H__ */ --- /dev/null +++ b/drivers/net/jzcs8900a.c @@ -0,0 +1,649 @@ + +/* + * linux/drivers/net/jzcs8900a.c + * + * Author: Lucifer + * + * A Cirrus Logic CS8900A driver for Linux + * based on the cs89x0 driver written by Russell Nelson, + * Donald Becker, and others. + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +/* + * At the moment the driver does not support memory mode operation. + * It is trivial to implement this, but not worth the effort. + */ + +/* + * TODO: + * + * 1. If !ready in send_start(), queue buffer and send it in interrupt handler + * when we receive a BufEvent with Rdy4Tx, send it again. dangerous! + * 2. how do we prevent interrupt handler destroying integrity of get_stats()? + * 3. Change reset code to check status. + * 4. Implement set_mac_address and remove fake mac address + * 5. Link status detection stuff + * 6. Write utility to write EEPROM, do self testing, etc. + * 7. Implement DMA routines (I need a board w/ DMA support for that) + * 8. Power management + * 9. Add support for multiple ethernet chips + * 10. Add support for other cs89xx chips (need hardware for that) + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "jzcs8900a.h" + +#define FULL_DUPLEX +#define INT_PIN 0 +#ifdef CONFIG_SOC_JZ4740 +#define CIRRUS_DEFAULT_IO 0xa8000000 +#define CIRRUS_DEFAULT_IRQ 107 + +#elif CONFIG_SOC_JZ4750 +#define CIRRUS_DEFAULT_IO 0xac000000 + +#ifdef CONFIG_JZ4750_FUWA +#define CIRRUS_DEFAULT_IRQ (32*4+20+48) +#else +#define CIRRUS_DEFAULT_IRQ (32*2 +6+48) +#endif + + +#endif + +typedef struct { + struct net_device_stats stats; + u16 txlen; +} cirrus_t; + +static int ethaddr_cmd = 0; +static unsigned char ethaddr_hex[6]; +static struct net_device *dev; + +/* + * I/O routines + */ +static void gpio_init_cs8900(void) +{ +#ifdef CONFIG_SOC_JZ4740 + __gpio_as_func0(60); //CS4# + __gpio_as_func0(61); //RD# + __gpio_as_func0(62); //WR# + __gpio_as_irq_high_level(59); //irq + __gpio_disable_pull(59); //disable pull + REG_EMC_SMCR4 |= (1 << 6); //16bit +#elif CONFIG_SOC_JZ4750 + __gpio_as_func0(32*2+23); //CS3# + __gpio_as_func0(32*2+25); //RD# + __gpio_as_func0(32*2+26); //WR# + +#ifdef CONFIG_JZ4750_FUWA + __gpio_as_irq_high_level(32*4+20); //irq + __gpio_disable_pull(32*4+20); //disable pull +#else + __gpio_as_irq_high_level(32*2 +6); //irq + __gpio_disable_pull(32*2 +6); //disable pull +#endif + + REG_EMC_SMCR3 |= (1 << 6); //16bit +#endif + udelay(1); +} + +static inline u16 cirrus_read (struct net_device *dev,u16 reg) +{ + outw (reg,dev->base_addr + PP_Address); + return (inw (dev->base_addr + PP_Data)); +} + +static inline void cirrus_write (struct net_device *dev,u16 reg,u16 value) +{ + outw (reg,dev->base_addr + PP_Address); + outw (value,dev->base_addr + PP_Data); +} + +static inline void cirrus_set (struct net_device *dev,u16 reg,u16 value) +{ + cirrus_write (dev,reg,cirrus_read (dev,reg) | value); +} + +static inline void cirrus_clear (struct net_device *dev,u16 reg,u16 value) +{ + cirrus_write (dev,reg,cirrus_read (dev,reg) & ~value); +} + +static inline void cirrus_frame_read (struct net_device *dev,struct sk_buff *skb,u16 length) +{ + insw (dev->base_addr,skb_put (skb,length),(length + 1) / 2); +} + +static inline void cirrus_frame_write (struct net_device *dev,struct sk_buff *skb) +{ + outsw (dev->base_addr,skb->data,(skb->len + 1) / 2); +} + +/* + * Debugging functions + */ + +#ifdef DEBUG +static inline int printable (int c) +{ + return ((c >= 32 && c <= 126) || + (c >= 174 && c <= 223) || + (c >= 242 && c <= 243) || + (c >= 252 && c <= 253)); +} + +static void dump16 (struct net_device *dev,const u8 *s,size_t len) +{ + int i; + char str[128]; + + if (!len) return; + + *str = '\0'; + + for (i = 0; i < len; i++) { + if (i && !(i % 4)) strcat (str," "); + sprintf (str,"%s%.2x ",str,s[i]); + } + + for ( ; i < 16; i++) { + if (i && !(i % 4)) strcat (str," "); + strcat (str," "); + } + + strcat (str," "); + for (i = 0; i < len; i++) sprintf (str,"%s%c",str,printable (s[i]) ? s[i] : '.'); + + printk (KERN_DEBUG "%s: %s\n",dev->name,str); +} + +static void hexdump (struct net_device *dev,const void *ptr,size_t size) +{ + const u8 *s = (u8 *) ptr; + int i; + for (i = 0; i < size / 16; i++, s += 16) dump16 (dev,s,16); + dump16 (dev,s,size % 16); +} + +static void dump_packet (struct net_device *dev,struct sk_buff *skb,const char *type) +{ + printk (KERN_INFO "%s: %s %d byte frame %.2x:%.2x:%.2x:%.2x:%.2x:%.2x to %.2x:%.2x:%.2x:%.2x:%.2x:%.2x type %.4x\n", + dev->name, + type, + skb->len, + skb->data[0],skb->data[1],skb->data[2],skb->data[3],skb->data[4],skb->data[5], + skb->data[6],skb->data[7],skb->data[8],skb->data[9],skb->data[10],skb->data[11], + (skb->data[12] << 8) | skb->data[13]); + if (skb->len < 0x100) hexdump (dev,skb->data,skb->len); +} +#endif /* #ifdef DEBUG */ + +/* + * Driver functions + */ + +static void cirrus_receive (struct net_device *dev) +{ + cirrus_t *priv = (cirrus_t *) dev->priv; + struct sk_buff *skb; + u16 status,length; + + status = cirrus_read (dev,PP_RxStatus); + length = cirrus_read (dev,PP_RxLength); + + if (!(status & RxOK)) { + priv->stats.rx_errors++; + if ((status & (Runt | Extradata))) priv->stats.rx_length_errors++; + if ((status & CRCerror)) priv->stats.rx_crc_errors++; + return; + } + + if ((skb = dev_alloc_skb (length + 4)) == NULL) { + priv->stats.rx_dropped++; + return; + } + + skb->dev = dev; + skb_reserve (skb,2); + + cirrus_frame_read (dev,skb,length); + skb->protocol = eth_type_trans (skb,dev); + + netif_rx (skb); + dev->last_rx = jiffies; + + priv->stats.rx_packets++; + priv->stats.rx_bytes += length; +} + +static int cirrus_send_start (struct sk_buff *skb,struct net_device *dev) +{ + cirrus_t *priv = (cirrus_t *) dev->priv; + u16 status; + + mdelay(10); + netif_stop_queue (dev); + + cirrus_write (dev,PP_TxCMD,TxStart (After5)); + cirrus_write (dev,PP_TxLength,skb->len); + + status = cirrus_read (dev,PP_BusST); + + if ((status & TxBidErr)) { + printk (KERN_WARNING "%s: Invalid frame size %d!\n",dev->name,skb->len); + priv->stats.tx_errors++; + priv->stats.tx_aborted_errors++; + priv->txlen = 0; + return (1); + } + + if (!(status & Rdy4TxNOW)) { + //printk (KERN_WARNING "%s: Transmit buffer not free!\n",dev->name); + priv->stats.tx_errors++; + priv->txlen = 0; + /* FIXME: store skb and send it in interrupt handler */ + return (1); + } + + cirrus_frame_write (dev,skb); + dev->trans_start = jiffies; + + dev_kfree_skb (skb); + + priv->txlen = skb->len; + + return (0); +} + +static irqreturn_t cirrus_interrupt(int irq, void *id) +{ + struct net_device *dev = (struct net_device *) id; + cirrus_t *priv; + u16 status; + + if (dev->priv == NULL) { + return IRQ_NONE; + } + + priv = (cirrus_t *) dev->priv; + + while ((status = cirrus_read (dev,PP_ISQ))) { + switch (RegNum (status)) { + case RxEvent: + cirrus_receive (dev); + break; + + case TxEvent: + priv->stats.collisions += ColCount (cirrus_read (dev,PP_TxCOL)); + if (!(RegContent (status) & TxOK)) { + priv->stats.tx_errors++; + if ((RegContent (status) & Out_of_window)) priv->stats.tx_window_errors++; + if ((RegContent (status) & Jabber)) priv->stats.tx_aborted_errors++; + break; + } else if (priv->txlen) { + priv->stats.tx_packets++; + priv->stats.tx_bytes += priv->txlen; + } + priv->txlen = 0; + netif_wake_queue (dev); + break; + + case BufEvent: + if ((RegContent (status) & RxMiss)) { + u16 missed = MissCount (cirrus_read (dev,PP_RxMISS)); + priv->stats.rx_errors += missed; + priv->stats.rx_missed_errors += missed; + } + if ((RegContent (status) & TxUnderrun)) { + priv->stats.tx_errors++; + priv->stats.tx_fifo_errors++; + } + /* FIXME: if Rdy4Tx, transmit last sent packet (if any) */ + priv->txlen = 0; + netif_wake_queue (dev); + break; + + case TxCOL: + priv->stats.collisions += ColCount (cirrus_read (dev,PP_TxCOL)); + break; + + case RxMISS: + status = MissCount (cirrus_read (dev,PP_RxMISS)); + priv->stats.rx_errors += status; + priv->stats.rx_missed_errors += status; + break; + default: + return IRQ_HANDLED; + } + } + + return IRQ_HANDLED; +} + +static void cirrus_transmit_timeout (struct net_device *dev) +{ + cirrus_t *priv = (cirrus_t *) dev->priv; + priv->stats.tx_errors++; + priv->stats.tx_heartbeat_errors++; + priv->txlen = 0; + netif_wake_queue (dev); +} + +static int cirrus_start (struct net_device *dev) +{ + int result; + + /* valid ethernet address? */ + if (!is_valid_ether_addr(dev->dev_addr)) { + printk(KERN_ERR "%s: invalid ethernet MAC address\n",dev->name); + return (-EINVAL); + } + + /* install interrupt handler */ + if ((result = request_irq (dev->irq, &cirrus_interrupt, IRQF_DISABLED, dev->name, dev)) < 0) { + printk (KERN_ERR "%s: could not register interrupt %d\n",dev->name,dev->irq); + return (result); + } + + /* enable the ethernet controller */ + cirrus_set (dev,PP_RxCFG,RxOKiE | BufferCRC | CRCerroriE | RuntiE | ExtradataiE); + cirrus_set (dev,PP_RxCTL,RxOKA | IndividualA | BroadcastA); + cirrus_set (dev,PP_TxCFG,TxOKiE | Out_of_windowiE | JabberiE); + cirrus_set (dev,PP_BufCFG,Rdy4TxiE | RxMissiE | TxUnderruniE | TxColOvfiE | MissOvfloiE); + cirrus_set (dev,PP_LineCTL,SerRxON | SerTxON); + cirrus_set (dev,PP_BusCTL,EnableRQ); + +#ifdef FULL_DUPLEX + cirrus_set (dev,PP_TestCTL,FDX); +#endif /* #ifdef FULL_DUPLEX */ + + /* start the queue */ + netif_start_queue (dev); + __gpio_unmask_irq(59); + + //MOD_INC_USE_COUNT; + return (0); +} + +static int cirrus_stop (struct net_device *dev) +{ + /* disable ethernet controller */ + cirrus_write (dev,PP_BusCTL,0); + cirrus_write (dev,PP_TestCTL,0); + cirrus_write (dev,PP_SelfCTL,0); + cirrus_write (dev,PP_LineCTL,0); + cirrus_write (dev,PP_BufCFG,0); + cirrus_write (dev,PP_TxCFG,0); + cirrus_write (dev,PP_RxCTL,0); + cirrus_write (dev,PP_RxCFG,0); + + /* uninstall interrupt handler */ + free_irq (dev->irq,dev); + + /* stop the queue */ + netif_stop_queue (dev); + + //MOD_DEC_USE_COUNT; + + return (0); +} + +static int cirrus_set_mac_address (struct net_device *dev, void *p) +{ + struct sockaddr *addr = (struct sockaddr *)p; + int i; + + if (netif_running(dev)) + return -EBUSY; + + memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); + + /* configure MAC address */ + for (i = 0; i < ETH_ALEN; i += 2) + cirrus_write (dev,PP_IA + i,dev->dev_addr[i] | (dev->dev_addr[i + 1] << 8)); + + return 0; +} + +static struct net_device_stats *cirrus_get_stats (struct net_device *dev) +{ + cirrus_t *priv = (cirrus_t *) dev->priv; + return (&priv->stats); +} + +static void cirrus_set_receive_mode (struct net_device *dev) +{ + if ((dev->flags & IFF_PROMISC)) + cirrus_set (dev,PP_RxCTL,PromiscuousA); + else + cirrus_clear (dev,PP_RxCTL,PromiscuousA); + + if ((dev->flags & IFF_ALLMULTI) && dev->mc_list) + cirrus_set (dev,PP_RxCTL,MulticastA); + else + cirrus_clear (dev,PP_RxCTL,MulticastA); +} + +/* + * Architecture dependant code + */ + +/* + * Driver initialization routines + */ + +int __init cirrus_probe(void) +{ + static cirrus_t priv; + int i; + u16 value; + + printk ("Jz CS8900A driver for Linux (V0.02)\n"); + + /* Init hardware for PAVO board */ + gpio_init_cs8900(); + + /* Allocate ethernet device */ + dev = alloc_etherdev(sizeof(struct net_device)); + + memset (&priv,0,sizeof (cirrus_t)); + + ether_setup (dev); + + dev->open = cirrus_start; + dev->stop = cirrus_stop; + dev->hard_start_xmit = cirrus_send_start; + dev->get_stats = cirrus_get_stats; + dev->set_multicast_list = cirrus_set_receive_mode; + dev->set_mac_address = cirrus_set_mac_address; + dev->tx_timeout = cirrus_transmit_timeout; + dev->watchdog_timeo = HZ; + + if (ethaddr_cmd==1) + { + dev->dev_addr[0] = ethaddr_hex[0]; + dev->dev_addr[1] = ethaddr_hex[1]; + dev->dev_addr[2] = ethaddr_hex[2]; + dev->dev_addr[3] = ethaddr_hex[3]; + dev->dev_addr[4] = ethaddr_hex[4]; + dev->dev_addr[5] = ethaddr_hex[5]; + } + else //default mac address 00:2a:cc:2a:af:fe + { + dev->dev_addr[0] = 0x00; + dev->dev_addr[1] = 0x62; + dev->dev_addr[2] = 0x9c; + dev->dev_addr[3] = 0x61; + dev->dev_addr[4] = 0xcf; + dev->dev_addr[5] = 0x16; + } + dev->if_port = IF_PORT_10BASET; + dev->priv = (void *) &priv; + + dev->base_addr = CIRRUS_DEFAULT_IO; + dev->irq = CIRRUS_DEFAULT_IRQ; + + + /* module parameters override everything */ + if (!dev->base_addr) { + printk (KERN_ERR + "%s: No default I/O base address defined. Use io=... or\n" + "%s: define CIRRUS_DEFAULT_IO for your platform\n", + dev->name,dev->name); + return (-EINVAL); + } + + if (!dev->irq) { + printk (KERN_ERR + "%s: No default IRQ number defined. Use irq=... or\n" + "%s: define CIRRUS_DEFAULT_IRQ for your platform\n", + dev->name,dev->name); + return (-EINVAL); + } +#if 0 + if ((result = check_region (dev->base_addr,16))) { + printk (KERN_ERR "%s: can't get I/O port address 0x%lx\n",dev->name,dev->base_addr); + return (result); + } +#endif + if (!request_region (dev->base_addr,16,dev->name)) + return -EBUSY; +#if 0 + /* verify EISA registration number for Cirrus Logic */ + if ((value = cirrus_read (dev,PP_ProductID)) != EISA_REG_CODE) { + printk (KERN_ERR "%s: incorrect signature 0x%.4x\n",dev->name,value); + return (-ENXIO); + } +#endif + + /* verify chip version */ + value = cirrus_read (dev,PP_ProductID + 2); + if (VERSION (value) != CS8900A) { + printk (KERN_ERR "%s: unknown chip version 0x%.8x\n",dev->name,VERSION (value)); + return (-ENXIO); + } + printk (KERN_INFO "%s: CS8900A rev %c detected\n",dev->name,'B' + REVISION (value) - REV_B); + + /* setup interrupt number */ + cirrus_write (dev,PP_IntNum,INT_PIN); + + /* configure MAC address */ + for (i = 0; i < ETH_ALEN; i += 2) + { + //printk(" %x",dev->dev_addr[i] | (dev->dev_addr[i + 1] << 8)); + cirrus_write (dev,PP_IA + i,dev->dev_addr[i] | (dev->dev_addr[i + 1] << 8)); + } + + if (register_netdev(dev) != 0) { + printk(KERN_ERR " Cannot register net device\n"); + free_netdev(dev); + return -ENOMEM; + } + + return (0); +} + +static inline unsigned char str2hexnum(unsigned char c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + if(c >= 'a' && c <= 'f') + return c - 'a' + 10; + if(c >= 'A' && c <= 'F') + return c - 'A' + 10; + return 0; /* foo */ +} + +static inline void str2eaddr(unsigned char *ea, unsigned char *str) +{ + int i; + + for(i = 0; i < 6; i++) { + unsigned char num; + + if((*str == '.') || (*str == ':')) + str++; + num = str2hexnum(*str++) << 4; + num |= (str2hexnum(*str++)); + ea[i] = num; + } +} + +static int __init ethernet_addr_setup(char *str) +{ + if (!str) { + printk("ethaddr not set in command line\n"); + return -1; + } + ethaddr_cmd = 1; + str2eaddr(ethaddr_hex, str); + return 0; +} + +__setup("ethaddr=", ethernet_addr_setup); + +//EXPORT_NO_SYMBOLS; + +MODULE_AUTHOR ("Lucifer "); +MODULE_DESCRIPTION ("Jz CS8900A driver for Linux (V0.02)"); +MODULE_LICENSE ("GPL"); + +//#ifdef MODULE + + +#if 0 +static int io = 0; +static int irq = 0; + +module_param(io, int, 0); +MODULE_PARM_DESC (io,"I/O Base Address"); +//MODULE_PARM (io,"i"); + +module_param(irq, int, 0); +MODULE_PARM_DESC (irq,"IRQ Number"); +//MODULE_PARM (irq,"i"); +#endif + +static int __init jzcs8900_init(void) +{ + if (cirrus_probe()) { + printk(KERN_WARNING "jzcs8900: No cs8900a found\n"); + } + + return 0; +} + +static void __exit jzcs8900_exit(void) +{ + release_region(dev->base_addr,16); + unregister_netdev(dev); + free_netdev(dev); +} + +module_init(jzcs8900_init); +module_exit(jzcs8900_exit); --- /dev/null +++ b/drivers/net/jzcs8900a.h @@ -0,0 +1,235 @@ +#ifndef JZCS8900A_H +#define JZCS8900A_H + +/* + * linux/drivers/net/jzcs8900a.h + * + * Author: Lucifer + * + * A Cirrus Logic CS8900A driver for Linux + * based on the cs89x0 driver written by Russell Nelson, + * Donald Becker, and others. + * + * This source code is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +/* + * Ports + */ + +#define PP_Address 0x0a /* PacketPage Pointer Port (Section 4.10.10) */ +#define PP_Data 0x0c /* PacketPage Data Port (Section 4.10.10) */ + +/* + * Registers + */ + +#define PP_ProductID 0x0000 /* Section 4.3.1 Product Identification Code */ +#define PP_MemBase 0x002c /* Section 4.9.2 Memory Base Address Register */ +#define PP_IntNum 0x0022 /* Section 3.2.3 Interrupt Number */ +#define PP_EEPROMCommand 0x0040 /* Section 4.3.11 EEPROM Command */ +#define PP_EEPROMData 0x0042 /* Section 4.3.12 EEPROM Data */ +#define PP_RxCFG 0x0102 /* Section 4.4.6 Receiver Configuration */ +#define PP_RxCTL 0x0104 /* Section 4.4.8 Receiver Control */ +#define PP_TxCFG 0x0106 /* Section 4.4.9 Transmit Configuration */ +#define PP_BufCFG 0x010a /* Section 4.4.12 Buffer Configuration */ +#define PP_LineCTL 0x0112 /* Section 4.4.16 Line Control */ +#define PP_SelfCTL 0x0114 /* Section 4.4.18 Self Control */ +#define PP_BusCTL 0x0116 /* Section 4.4.20 Bus Control */ +#define PP_TestCTL 0x0118 /* Section 4.4.22 Test Control */ +#define PP_ISQ 0x0120 /* Section 4.4.5 Interrupt Status Queue */ +#define PP_TxEvent 0x0128 /* Section 4.4.10 Transmitter Event */ +#define PP_BufEvent 0x012c /* Section 4.4.13 Buffer Event */ +#define PP_RxMISS 0x0130 /* Section 4.4.14 Receiver Miss Counter */ +#define PP_TxCOL 0x0132 /* Section 4.4.15 Transmit Collision Counter */ +#define PP_SelfST 0x0136 /* Section 4.4.19 Self Status */ +#define PP_BusST 0x0138 /* Section 4.4.21 Bus Status */ +#define PP_TxCMD 0x0144 /* Section 4.4.11 Transmit Command */ +#define PP_TxLength 0x0146 /* Section 4.5.2 Transmit Length */ +#define PP_IA 0x0158 /* Section 4.6.2 Individual Address (IEEE Address) */ +#define PP_RxStatus 0x0400 /* Section 4.7.1 Receive Status */ +#define PP_RxLength 0x0402 /* Section 4.7.1 Receive Length (in bytes) */ +#define PP_RxFrame 0x0404 /* Section 4.7.2 Receive Frame Location */ +#define PP_TxFrame 0x0a00 /* Section 4.7.2 Transmit Frame Location */ + +/* + * Values + */ + +/* PP_IntNum */ +#define INTRQ0 0x0000 +#define INTRQ1 0x0001 +#define INTRQ2 0x0002 +#define INTRQ3 0x0003 + +/* PP_ProductID */ +#define EISA_REG_CODE 0x630e +#define REVISION(x) (((x) & 0x1f00) >> 8) +#define VERSION(x) ((x) & ~0x1f00) + +#define CS8900A 0x0000 +#define REV_B 7 +#define REV_C 8 +#define REV_D 9 + +/* PP_RxCFG */ +#define Skip_1 0x0040 +#define StreamE 0x0080 +#define RxOKiE 0x0100 +#define RxDMAonly 0x0200 +#define AutoRxDMAE 0x0400 +#define BufferCRC 0x0800 +#define CRCerroriE 0x1000 +#define RuntiE 0x2000 +#define ExtradataiE 0x4000 + +/* PP_RxCTL */ +#define IAHashA 0x0040 +#define PromiscuousA 0x0080 +#define RxOKA 0x0100 +#define MulticastA 0x0200 +#define IndividualA 0x0400 +#define BroadcastA 0x0800 +#define CRCerrorA 0x1000 +#define RuntA 0x2000 +#define ExtradataA 0x4000 + +/* PP_TxCFG */ +#define Loss_of_CRSiE 0x0040 +#define SQErroriE 0x0080 +#define TxOKiE 0x0100 +#define Out_of_windowiE 0x0200 +#define JabberiE 0x0400 +#define AnycolliE 0x0800 +#define T16colliE 0x8000 + +/* PP_BufCFG */ +#define SWint_X 0x0040 +#define RxDMAiE 0x0080 +#define Rdy4TxiE 0x0100 +#define TxUnderruniE 0x0200 +#define RxMissiE 0x0400 +#define Rx128iE 0x0800 +#define TxColOvfiE 0x1000 +#define MissOvfloiE 0x2000 +#define RxDestiE 0x8000 + +/* PP_LineCTL */ +#define SerRxON 0x0040 +#define SerTxON 0x0080 +#define AUIonly 0x0100 +#define AutoAUI_10BT 0x0200 +#define ModBackoffE 0x0800 +#define PolarityDis 0x1000 +#define L2_partDefDis 0x2000 +#define LoRxSquelch 0x4000 + +/* PP_SelfCTL */ +#define RESET 0x0040 +#define SWSuspend 0x0100 +#define HWSleepE 0x0200 +#define HWStandbyE 0x0400 +#define HC0E 0x1000 +#define HC1E 0x2000 +#define HCB0 0x4000 +#define HCB1 0x8000 + +/* PP_BusCTL */ +#define ResetRxDMA 0x0040 +#define DMAextend 0x0100 +#define UseSA 0x0200 +#define MemoryE 0x0400 +#define DMABurst 0x0800 +#define IOCHRDYE 0x1000 +#define RxDMAsize 0x2000 +#define EnableRQ 0x8000 + +/* PP_TestCTL */ +#define DisableLT 0x0080 +#define ENDECloop 0x0200 +#define AUIloop 0x0400 +#define DisableBackoff 0x0800 +#define FDX 0x4000 + +/* PP_ISQ */ +#define RegNum(x) ((x) & 0x3f) +#define RegContent(x) ((x) & ~0x3d) + +#define RxEvent 0x0004 +#define TxEvent 0x0008 +#define BufEvent 0x000c +#define RxMISS 0x0010 +#define TxCOL 0x0012 + +/* PP_RxStatus */ +#define IAHash 0x0040 +#define Dribblebits 0x0080 +#define RxOK 0x0100 +#define Hashed 0x0200 +#define IndividualAdr 0x0400 +#define Broadcast 0x0800 +#define CRCerror 0x1000 +#define Runt 0x2000 +#define Extradata 0x4000 + +#define HashTableIndex(x) ((x) >> 0xa) + +/* PP_TxCMD */ +#define After5 0 +#define After381 1 +#define After1021 2 +#define AfterAll 3 +#define TxStart(x) ((x) << 6) + +#define Force 0x0100 +#define Onecoll 0x0200 +#define InhibitCRC 0x1000 +#define TxPadDis 0x2000 + +/* PP_BusST */ +#define TxBidErr 0x0080 +#define Rdy4TxNOW 0x0100 + +/* PP_TxEvent */ +#define Loss_of_CRS 0x0040 +#define SQEerror 0x0080 +#define TxOK 0x0100 +#define Out_of_window 0x0200 +#define Jabber 0x0400 +#define T16coll 0x8000 + +#define TX_collisions(x) (((x) >> 0xb) & ~0x8000) + +/* PP_BufEvent */ +#define SWint 0x0040 +#define RxDMAFrame 0x0080 +#define Rdy4Tx 0x0100 +#define TxUnderrun 0x0200 +#define RxMiss 0x0400 +#define Rx128 0x0800 +#define RxDest 0x8000 + +/* PP_RxMISS */ +#define MissCount(x) ((x) >> 6) + +/* PP_TxCOL */ +#define ColCount(x) ((x) >> 6) + +/* PP_SelfST */ +#define T3VActive 0x0040 +#define INITD 0x0080 +#define SIBUSY 0x0100 +#define EEPROMpresent 0x0200 +#define EEPROMOK 0x0400 +#define ELpresent 0x0800 +#define EEsize 0x1000 + +/* PP_EEPROMCommand */ +#define EEWriteRegister 0x0100 +#define EEReadRegister 0x0200 +#define EEEraseRegister 0x0300 +#define ELSEL 0x0400 + +#endif /* #ifndef CIRRUS_H */ --- a/drivers/pnp/Kconfig +++ b/drivers/pnp/Kconfig @@ -5,7 +5,7 @@ menuconfig PNP bool "Plug and Play support" depends on HAS_IOMEM - depends on ISA || ACPI +# depends on ISA || ACPI ---help--- Plug and Play (PnP) is a standard for peripherals which allows those peripherals to be configured by software, e.g. assign IRQ's or other --- a/drivers/pnp/core.c +++ b/drivers/pnp/core.c @@ -74,6 +74,8 @@ int pnp_register_protocol(struct pnp_pro return device_register(&protocol->dev); } +EXPORT_SYMBOL(pnp_register_protocol); + /** * pnp_protocol_unregister - removes a pnp protocol from the pnp layer * @protocol: pointer to the corresponding pnp_protocol structure @@ -86,6 +88,8 @@ void pnp_unregister_protocol(struct pnp_ device_unregister(&protocol->dev); } +EXPORT_SYMBOL(pnp_unregister_protocol); + static void pnp_free_ids(struct pnp_dev *dev) { struct pnp_id *id; @@ -197,6 +201,8 @@ int pnp_add_device(struct pnp_dev *dev) return 0; } +EXPORT_SYMBOL(pnp_add_device); + void __pnp_remove_device(struct pnp_dev *dev) { spin_lock(&pnp_lock); @@ -206,6 +212,21 @@ void __pnp_remove_device(struct pnp_dev device_unregister(&dev->dev); } +/** + * pnp_remove_device - removes a pnp device from the pnp layer + * @dev: pointer to dev to add + * + * this function will free all mem used by dev + */ +void pnp_remove_device(struct pnp_dev *dev) +{ + if (!dev || dev->card) + return; + __pnp_remove_device(dev); +} + +EXPORT_SYMBOL(pnp_remove_device); + static int __init pnp_init(void) { return bus_register(&pnp_bus_type); --- a/drivers/pnp/driver.c +++ b/drivers/pnp/driver.c @@ -259,3 +259,4 @@ EXPORT_SYMBOL(pnp_register_driver); EXPORT_SYMBOL(pnp_unregister_driver); EXPORT_SYMBOL(pnp_device_attach); EXPORT_SYMBOL(pnp_device_detach); +EXPORT_SYMBOL(pnp_add_id); --- a/drivers/pnp/resource.c +++ b/drivers/pnp/resource.c @@ -406,6 +406,10 @@ int pnp_check_irq(struct pnp_dev *dev, s int pnp_check_dma(struct pnp_dev *dev, struct resource *res) { + printk("*********** %s, %s, line[%d]: Fix me, this should update in the future *********\n", + __FILE__, __FUNCTION__, __LINE__); + return 0; /* should be update in the future. Wolfgang ???*/ + #ifndef CONFIG_IA64 int i; struct pnp_dev *tdev; --- a/drivers/serial/8250.c +++ b/drivers/serial/8250.c @@ -193,7 +193,7 @@ static const struct serial8250_config ua [PORT_16550A] = { .name = "16550A", .fifo_size = 16, - .tx_loadsz = 16, + .tx_loadsz = 8, .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10, .flags = UART_CAP_FIFO, }, @@ -412,6 +412,10 @@ serial_out(struct uart_8250_port *up, in break; case UPIO_MEM: +#if defined(CONFIG_JZSOC) + if (offset == (UART_FCR << up->port.regshift)) + value |= 0x10; /* set FCR.UUE */ +#endif writeb(value, up->port.membase + offset); break; @@ -2130,6 +2134,83 @@ static void serial8250_shutdown(struct u serial_unlink_irq_chain(up); } +#if defined(CONFIG_JZSOC) && !defined(CONFIG_SOC_JZ4730) +static unsigned short quot1[3] = {0}; /* quot[0]:baud_div, quot[1]:umr, quot[2]:uacr */ +static unsigned short * serial8250_get_divisor(struct uart_port *port, unsigned int baud) +{ + int err, sum, i, j; + int a[12], b[12]; + unsigned short div, umr, uacr; + unsigned short umr_best, div_best, uacr_best; + long long t0, t1, t2, t3; + + sum = 0; + umr_best = div_best = uacr_best = 0; + div = 1; + + if ((port->uartclk % (16 * baud)) == 0) { + quot1[0] = port->uartclk / (16 * baud); + quot1[1] = 16; + quot1[2] = 0; + return quot1; + } + + while (1) { + umr = port->uartclk / (baud * div); + if (umr > 32) { + div++; + continue; + } + if (umr < 4) { + break; + } + for (i = 0; i < 12; i++) { + a[i] = umr; + b[i] = 0; + sum = 0; + for (j = 0; j <= i; j++) { + sum += a[j]; + } + + /* the precision could be 1/2^(36) due to the value of t0 */ + t0 = 0x1000000000LL; + t1 = (i + 1) * t0; + t2 = (sum * div) * t0; + t3 = div * t0; + do_div(t1, baud); + do_div(t2, port->uartclk); + do_div(t3, (2 * port->uartclk)); + err = t1 - t2 - t3; + + if (err > 0) { + a[i] += 1; + b[i] = 1; + } + } + + uacr = 0; + for (i = 0; i < 12; i++) { + if (b[i] == 1) { + uacr |= 1 << i; + } + } + + /* the best value of umr should be near 16, and the value of uacr should better be smaller */ + if (abs(umr - 16) < abs(umr_best - 16) || (abs(umr - 16) == abs(umr_best - 16) && uacr_best > uacr)) { + div_best = div; + umr_best = umr; + uacr_best = uacr; + } + div++; + } + + quot1[0] = div_best; + quot1[1] = umr_best; + quot1[2] = uacr_best; + + return quot1; +} +#else static unsigned int serial8250_get_divisor(struct uart_port *port, unsigned int baud) { unsigned int quot; @@ -2149,6 +2230,7 @@ static unsigned int serial8250_get_divis return quot; } +#endif static void serial8250_set_termios(struct uart_port *port, struct ktermios *termios, @@ -2158,6 +2240,9 @@ serial8250_set_termios(struct uart_port unsigned char cval, fcr = 0; unsigned long flags; unsigned int baud, quot; +#if defined(CONFIG_JZSOC) && !defined(CONFIG_SOC_JZ4730) + unsigned short *quot1; +#endif switch (termios->c_cflag & CSIZE) { case CS5: @@ -2190,7 +2275,12 @@ serial8250_set_termios(struct uart_port * Ask the core to calculate the divisor for us. */ baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16); +#if defined(CONFIG_JZSOC) && !defined(CONFIG_SOC_JZ4730) + quot1 = serial8250_get_divisor(port, baud); + quot = quot1[0]; /* not usefull, just let gcc happy */ +#else quot = serial8250_get_divisor(port, baud); +#endif /* * Oxford Semi 952 rev B workaround @@ -2268,6 +2358,10 @@ serial8250_set_termios(struct uart_port if (up->capabilities & UART_CAP_UUE) up->ier |= UART_IER_UUE | UART_IER_RTOIE; +#ifdef CONFIG_JZSOC + up->ier |= UART_IER_RTOIE; /* Set this flag, or very slow */ +#endif + serial_out(up, UART_IER, up->ier); if (up->capabilities & UART_CAP_EFR) { @@ -2302,7 +2396,15 @@ serial8250_set_termios(struct uart_port serial_outp(up, UART_LCR, cval | UART_LCR_DLAB);/* set DLAB */ } +#if defined(CONFIG_JZSOC) && !defined(CONFIG_SOC_JZ4730) +#define UART_UMR 9 +#define UART_UACR 10 + serial_dl_write(up, quot1[0]); + serial_outp(up, UART_UMR, quot1[1]); + serial_outp(up, UART_UACR, quot1[2]); +#else serial_dl_write(up, quot); +#endif /* * LCR DLAB must be set to enable 64-byte FIFO mode. If the FCR --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -43,6 +43,7 @@ config USB_ARCH_HAS_OHCI default y if PPC_MPC52xx # MIPS: default y if SOC_AU1X00 + default y if JZSOC # SH: default y if CPU_SUBTYPE_SH7720 default y if CPU_SUBTYPE_SH7721 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2750,11 +2750,35 @@ static void hub_port_connect_change(stru le16_to_cpu(hub->descriptor->wHubCharacteristics); struct usb_device *udev; int status, i; +#ifdef CONFIG_JZSOC + static char jzhub = 1; /* the hub first to be initialized is jzsoc on-chip hub */ +#endif dev_dbg (hub_dev, "port %d, status %04x, change %04x, %s\n", port1, portstatus, portchange, portspeed (portstatus)); +#ifdef CONFIG_SOC_JZ4730 + /* + * On Jz4730, we assume that the first USB port was used as device. + * If not, please comment next lines. + */ + if ((port1 == 1) && (jzhub)) { + jzhub = 0; + return; + } +#endif + +#if defined(CONFIG_SOC_JZ4740) || defined(CONFIG_SOC_JZ4750) + /* + * On Jz4740 and Jz4750, the second USB port was used as device. + */ + if ((port1 == 2) && (jzhub)) { + jzhub = 0; + return; + } +#endif + if (hub->has_indicators) { set_port_led(hub, port1, HUB_LED_AUTO); hub->indicator[port1-1] = INDICATOR_AUTO; --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -337,6 +337,47 @@ config SUPERH_BUILT_IN_M66592 # Controllers available only in discrete form (and all PCI controllers) # +config USB_GADGET_JZ4740 + boolean "JZ4740 UDC" + depends on SOC_JZ4740 + select USB_GADGET_DUALSPEED + help + Select this to support the Ingenic JZ4740 processor + high speed USB device controller. + +config USB_JZ4740 + tristate + depends on USB_GADGET_JZ4740 + default USB_GADGET + select USB_GADGET_SELECTED + +config USB_GADGET_JZ4750 + boolean "JZ4750 UDC" + depends on SOC_JZ4750 + select USB_GADGET_DUALSPEED + help + Select this to support the Ingenic JZ4750 processor + high speed USB device controller. + +config USB_JZ4750 + tristate + depends on USB_GADGET_JZ4750 + default USB_GADGET + select USB_GADGET_SELECTED + +config USB_GADGET_JZ4730 + boolean "JZ4730 UDC" + depends on SOC_JZ4730 + help + Select this to support the Ingenic JZ4730 processor + full speed USB device controller. + +config USB_JZ4730 + tristate + depends on USB_GADGET_JZ4730 + default USB_GADGET + select USB_GADGET_SELECTED + config USB_GADGET_AMD5536UDC boolean "AMD5536 UDC" depends on PCI @@ -611,6 +652,14 @@ config USB_FILE_STORAGE_TEST behavior of USB Mass Storage hosts. Not needed for normal operation. +config UDC_USE_LB_CACHE + bool "enable lb cache" + depends on USB_FILE_STORAGE && (SOC_JZ4740 || SOC_JZ4750) + default y + help + say "y" to enable lb cache and UDC work in faster speed. + say "n" to disable lb cache and UDC work in normal speed. + config USB_G_SERIAL tristate "Serial Gadget (with CDC ACM and CDC OBEX support)" help --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -19,6 +19,9 @@ obj-$(CONFIG_USB_ATMEL_USBA) += atmel_us obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o +obj-$(CONFIG_USB_JZ4740) += jz4740_udc.o +obj-$(CONFIG_USB_JZ4730) += jz4730_udc.o +obj-$(CONFIG_USB_JZ4750) += jz4740_udc.o # # USB gadget drivers --- a/drivers/usb/gadget/file_storage.c +++ b/drivers/usb/gadget/file_storage.c @@ -286,6 +286,18 @@ MODULE_LICENSE("Dual BSD/GPL"); /*-------------------------------------------------------------------------*/ +#if defined(CONFIG_UDC_USE_LB_CACHE) +#define GHOST +#endif + +#ifdef GHOST +extern unsigned long udc_read(unsigned int offset, unsigned int len, unsigned char *); +extern unsigned long udc_write(unsigned int offset, unsigned int len, unsigned char *); +extern int NAND_LB_Init(void); +extern int NAND_LB_FLASHCACHE(void); +extern int NAND_MTD_FLASHCACHE(void); +extern int FlushDataState; +#endif #define LDBG(lun,fmt,args...) \ dev_dbg(&(lun)->dev , fmt , ## args) @@ -357,8 +369,8 @@ static struct { } mod_data = { // Default values .transport_parm = "BBB", .protocol_parm = "SCSI", - .removable = 0, - .can_stall = 1, + .removable = 1, + .can_stall = 0, .vendor = DRIVER_VENDOR_ID, .product = DRIVER_PRODUCT_ID, .release = 0xffff, // Use controller chip type @@ -818,6 +830,7 @@ static void put_be32(u8 *buf, u32 val) #define STRING_SERIAL 3 #define STRING_CONFIG 4 #define STRING_INTERFACE 5 +#define STRING_MS_OS 0xee /* There is only one configuration. */ #define CONFIG_VALUE 1 @@ -1005,6 +1018,7 @@ static struct usb_string strings[] = { {STRING_SERIAL, serial}, {STRING_CONFIG, "Self-powered"}, {STRING_INTERFACE, "Mass Storage"}, + {STRING_MS_OS, "Microsoft"}, {} }; @@ -1626,9 +1640,14 @@ static int do_read(struct fsg_dev *fsg) /* Perform the read */ file_offset_tmp = file_offset; +#ifdef GHOST + nread = udc_read(file_offset_tmp, amount, bh->buf); +#else nread = vfs_read(curlun->filp, (char __user *) bh->buf, amount, &file_offset_tmp); +#endif + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, (unsigned long long) file_offset, (int) nread); @@ -1807,9 +1826,13 @@ static int do_write(struct fsg_dev *fsg) /* Perform the write */ file_offset_tmp = file_offset; +#ifdef GHOST + nwritten = udc_write(file_offset_tmp, amount, bh->buf); +#else nwritten = vfs_write(curlun->filp, (char __user *) bh->buf, amount, &file_offset_tmp); +#endif VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, (unsigned long long) file_offset, (int) nwritten); @@ -1984,9 +2007,19 @@ static int do_verify(struct fsg_dev *fsg /* Perform the read */ file_offset_tmp = file_offset; +#ifdef GHOST + nread = udc_read(file_offset_tmp, amount, bh->buf); +#else nread = vfs_read(curlun->filp, (char __user *) bh->buf, amount, &file_offset_tmp); +#endif + +#if 0 + nread = vfs_read(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); +#endif VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, (unsigned long long) file_offset, (int) nread); @@ -2021,7 +2054,7 @@ static int do_inquiry(struct fsg_dev *fs { u8 *buf = (u8 *) bh->buf; - static char vendor_id[] = "Linux "; + static char vendor_id[] = "Ingenic "; static char product_id[] = "File-Stor Gadget"; if (!fsg->curlun) { // Unsupported LUNs are okay @@ -2901,6 +2934,15 @@ static int do_scsi_command(struct fsg_de reply = check_command(fsg, 6, DATA_DIR_NONE, 0, 1, "TEST UNIT READY"); +#ifdef GHOST + if( FlushDataState >= 1) + FlushDataState++; + if(FlushDataState > 6) + { + NAND_LB_FLASHCACHE(); + FlushDataState = 0; + } +#endif break; /* Although optional, this command is used by MS-Windows. We @@ -3450,6 +3492,13 @@ static int fsg_main_thread(void *fsg_) /* The main loop */ while (fsg->state != FSG_STATE_TERMINATED) { +#ifdef GHOST + if ((fsg->atomic_bitflags & SUSPENDED)) + { + NAND_LB_FLASHCACHE(); + NAND_MTD_FLASHCACHE(); + } +#endif if (exception_in_progress(fsg) || signal_pending(current)) { handle_exception(fsg); continue; @@ -3571,6 +3620,9 @@ static int open_backing_file(struct lun curlun->num_sectors = num_sectors; LDBG(curlun, "open backing file: %s\n", filename); rc = 0; +#ifdef GHOST + NAND_LB_Init(); +#endif out: filp_close(filp, current->files); --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -15,6 +15,18 @@ #ifndef __GADGET_CHIPS_H #define __GADGET_CHIPS_H +#ifdef CONFIG_USB_GADGET_JZ4740 +#define gadget_is_jz4740(g) !strcmp("jz4740_udc", (g)->name) +#else +#define gadget_is_jz4740(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_JZ4730 +#define gadget_is_jz4730(g) !strcmp("jz4730_udc", (g)->name) +#else +#define gadget_is_jz4730(g) 0 +#endif + #ifdef CONFIG_USB_GADGET_NET2280 #define gadget_is_net2280(g) !strcmp("net2280", (g)->name) #else @@ -225,6 +237,10 @@ static inline int usb_gadget_controller_ return 0x21; else if (gadget_is_fsl_qe(gadget)) return 0x22; + else if (gadget_is_jz4730(gadget)) + return 0x23; + else if (gadget_is_jz4740(gadget)) + return 0x24; return -ENOENT; } --- /dev/null +++ b/drivers/usb/gadget/jz4730_udc.c @@ -0,0 +1,1403 @@ +/* + * JZ4730 USB Device Controller driver + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +/* + * This device has ep0 and six bulk/interrupt/iso endpoints. + * + * - Endpoint numbering is fixed: ep0, ep1in-int, ep2in-bulk, ep3in-bulk, + * ep4in-iso, ep5out-bulk, ep6out-bulk, ep7out-iso. + * - Gadget drivers can choose ep maxpacket (8/16/32/64). + * - Just PIO mode currently. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "jz4730_udc.h" + +//#define DEBUG(fmt,args...) printk(KERN_DEBUG fmt , ## args) +//#define DEBUG_EP0(fmt,args...) printk(KERN_DEBUG fmt , ## args) + +#ifndef DEBUG +# define DEBUG(fmt,args...) do {} while(0) +#endif +#ifndef DEBUG_EP0 +# define DEBUG_EP0(fmt,args...) do {} while(0) +#endif + +#define DRIVER_DESC "JZ4730 USB Device Controller" +#define DRIVER_VERSION "20 Sep 2007" + +static const char driver_name [] = "jz4730_udc"; +static const char driver_desc [] = DRIVER_DESC; + +static unsigned int udc_debug = 0; /* 0: normal mode, 1: test udc cable type mode */ + +module_param(udc_debug, int, 0); +MODULE_PARM_DESC(udc_debug, "test udc cable type"); + +#ifdef CONFIG_JZ_UDC_HOTPLUG +extern int jz_udc_active; /* 0: No actions; 1: Have actions */ +#endif + +/* + * Local declarations. + */ +static void nuke(struct jz4730_ep *, int status); +static inline void pio_irq_enable(struct jz4730_ep *ep); +static inline void pio_irq_disable(struct jz4730_ep *ep); +static void jz4730_udc_release (struct device *dev) {} +/*-------------------------------------------------------------------------*/ + +static int jz4730_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct jz4730_udc *dev; + struct jz4730_ep *ep; + unsigned long flags; + u32 max; + + ep = container_of(_ep, struct jz4730_ep, ep); + if (!_ep || !desc || ep->desc + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + dev = ep->dev; + if (ep == &dev->ep[0]) + return -EINVAL; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + if (ep->index != (desc->bEndpointAddress & 0x0f)) + return -EINVAL; + + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + break; + default: + return -EINVAL; + } + +// max = le16_to_cpu(get_unaligned(&desc->wMaxPacketSize)); + max = 64; + ep->is_in = (USB_DIR_IN & desc->bEndpointAddress) != 0; + + spin_lock_irqsave(&ep->dev->lock, flags); + + ep->stopped = 0; + ep->desc = desc; + ep->ep.maxpacket = max; + + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DEBUG("enable %s %s maxpacket %u\n", ep->ep.name, + ep->is_in ? "IN" : "OUT", max); + + return 0; +} + +static int jz4730_ep_disable(struct usb_ep *_ep) +{ + struct jz4730_ep *ep; + struct jz4730_udc *dev; + unsigned long flags; + + ep = container_of(_ep, struct jz4730_ep, ep); + if (!_ep || !ep->desc) + return -ENODEV; + dev = ep->dev; + if (dev->ep0state == EP0_SUSPEND) + return -EBUSY; + + DEBUG("disable %s\n", _ep->name); + + spin_lock_irqsave(&dev->lock, flags); + + /* Nuke all pending requests */ + nuke(ep, -ESHUTDOWN); + + /* Disable ep IRQ */ + pio_irq_disable(ep); + + ep->desc = 0; + ep->stopped = 1; + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +static struct usb_request *jz4730_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct jz4730_request *req; + + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return 0; + + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + +static void jz4730_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct jz4730_request *req; + + req = container_of(_req, struct jz4730_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +static void *jz4730_alloc_buffer(struct usb_ep *_ep, unsigned bytes, + dma_addr_t *dma, gfp_t gfp_flags) +{ + void *retval; + + retval = kmalloc(bytes, gfp_flags & ~(__GFP_DMA | __GFP_HIGHMEM)); + if (retval) + *dma = virt_to_phys(retval); + return retval; +} + +static void jz4730_free_buffer(struct usb_ep *_ep, void *buf, + dma_addr_t dma, unsigned bytes) +{ + kfree(buf); +} + +/* + * done - retire a request; caller blocked irqs + */ +static void done(struct jz4730_ep *ep, struct jz4730_request *req, int status) +{ + struct jz4730_udc *dev; + unsigned stopped = ep->stopped; + + list_del_init(&req->queue); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + DEBUG("complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + dev = ep->dev; + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + spin_unlock(&dev->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&dev->lock); + ep->stopped = stopped; +} + +/*-------------------------------------------------------------------------*/ + +static __inline__ int write_packet(struct jz4730_ep *ep, + struct jz4730_request *req, int max) +{ + u8 *buf; + int length, nlong, nbyte; + volatile u32 *fifo = (volatile u32 *)ep->fifo; + + buf = req->req.buf + req->req.actual; + prefetch(buf); + + length = req->req.length - req->req.actual; + length = min(length, max); + req->req.actual += length; + + DEBUG("Write %d (max %d), fifo %p\n", length, max, fifo); + + if (!length) { + /* Send ZLP */ + writel(0, (unsigned int *)UDC_TXZLP); + writel(0x12345678, (unsigned int *)fifo); + } + else { + nlong = length >> 2; + nbyte = length & 0x3; + while (nlong--) { + *fifo = *((u32 *)buf); + buf += 4; + } + while (nbyte--) { + *((volatile u8 *)fifo) = *buf++; + } + } + + writel(0, (unsigned int *)UDC_TXCONFIRM); + + return length; +} + +static __inline__ int read_packet(struct jz4730_ep *ep, + struct jz4730_request *req, int count) +{ + u8 *buf; + int length, nlong, nbyte; + volatile u32 *fifo = (volatile u32 *)ep->fifo; + + buf = req->req.buf + req->req.actual; + prefetchw(buf); + + length = req->req.length - req->req.actual; + length = min(length, count); + req->req.actual += length; + + DEBUG("Read %d, fifo %p\n", length, fifo); + + nlong = length >> 2; + nbyte = length & 0x3; + while (nlong--) { + *((u32 *)buf) = *fifo; + buf += 4; + } + if (nbyte) { + u32 data = *fifo; + while (nbyte--) { + *buf++ = data & 0x0ff; + data >>= 8; + } + } + + REG32(UDC_RXCONFIRM); + + return length; +} + +/** Write request to FIFO (max write == maxp size) + * Return: 0 = still running, 1 = completed, negative = errno + */ +static int write_fifo(struct jz4730_ep *ep, struct jz4730_request *req) +{ + u32 max, count; + int is_last; + + max = ep->ep.maxpacket; + + count = write_packet(ep, req, max); + + /* last packet often short (sometimes a zlp, especially on ep0) */ + if (unlikely(count != max)) { + is_last = 1; + } else { + if (likely(req->req.length != req->req.actual) + || req->req.zero) + is_last = 0; + else + is_last = 1; + } + + DEBUG("write %s (%d)(IN) %d bytes%s req %p %d/%d is_last %d\n", + ep->ep.name, ep->index, count, + (count != ep->ep.maxpacket) ? " (short)" : "", + req, req->req.actual, req->req.length, is_last); + + /* requests complete when all IN data is in the FIFO, + * or sometimes later, if a zlp was needed. + */ + if (is_last) { + done(ep, req, 0); + return 1; + } + + return 0; +} + +/** Read to request from FIFO (max read == bytes in fifo) + * Return: 0 = still running, 1 = completed, negative = errno + */ +static int read_fifo(struct jz4730_ep *ep, struct jz4730_request *req, u32 count) +{ + int is_short; + + is_short = (count < ep->ep.maxpacket); + + count = read_packet(ep, req, count); + + DEBUG("read %s %u bytes%s OUT req %p %u/%u is_short %d\n", + ep->ep.name, count, (count < ep->ep.maxpacket) ? "(short)" : "", + req, req->req.actual, req->req.length, is_short); + + /* completion */ + if (is_short || req->req.actual == req->req.length) { + done(ep, req, 0); + return 1; + } + + /* finished that packet. the next one may be waiting... */ + return 0; +} + +static inline void pio_irq_enable(struct jz4730_ep *ep) +{ + switch (ep->index) { + case 0: + REG_UDC_EPIntMR &= ~0x1; + break; + case 1: + case 2: + case 3: + case 4: + REG_UDC_EPIntMR &= ~(1 << ep->index); + break; + case 5: + case 6: + case 7: + REG_UDC_EPIntMR &= ~(1 << (ep->index + 16)); + break; + } +} + +static inline void pio_irq_disable(struct jz4730_ep *ep) +{ + switch (ep->index) { + case 0: + REG_UDC_EPIntMR |= 0x1; + break; + case 1: + case 2: + case 3: + case 4: + REG_UDC_EPIntMR |= (1 << ep->index); + break; + case 5: + case 6: + case 7: + REG_UDC_EPIntMR |= (1 << (ep->index + 16)); + break; + } +} + +/*-------------------------------------------------------------------------*/ + +static int +jz4730_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct jz4730_request *req; + struct jz4730_ep *ep; + struct jz4730_udc *dev; + unsigned long flags; + int status; + + /* always require a cpu-view buffer so pio works */ + req = container_of(_req, struct jz4730_request, req); + if (unlikely(!_req || !_req->complete + || !_req->buf || !list_empty(&req->queue))) + return -EINVAL; + ep = container_of(_ep, struct jz4730_ep, ep); + if (unlikely(!_ep || (!ep->desc && ep->index != 0))) + return -EINVAL; + dev = ep->dev; + if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + + DEBUG("%s queue req %p, len %u buf %p\n", + _ep->name, _req, _req->length, _req->buf); + + spin_lock_irqsave(&dev->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* for ep0 IN without premature status, zlp is required and + * writing EOP starts the status stage (OUT). + */ + if (unlikely(ep->index == 0 && ep->is_in)) + _req->zero = 1; + + /* kickstart this i/o queue? */ + status = 0; + if (list_empty(&ep->queue) && likely(!ep->stopped)) { + if (unlikely(ep->index == 0)) { + pio_irq_enable(ep); + if (ep->irq_pending || + (REG_UDC_EPIntR & UDC_EPIntR_OUTEP0)) { + u32 stats, count; + + stats = REG_UDC_EP0OutSR; + if (stats & UDC_EPSR_OUT_RCVDATA) { + ep->irq_pending = 0; + REG_UDC_EP0OutSR &= ~UDC_EPSR_OUT_MASK; + if (REG_UDC_EPIntR & UDC_EPIntR_OUTEP0) + REG_UDC_EPIntR = UDC_EPIntR_OUTEP0; + + count = OUT_COUNT(stats); + if (read_fifo(ep, req, count) == 1) + req = 0; + } + } + + } else if (ep->is_in) { + /* EP1 ~ EP4 */ + if (ep->irq_pending || + (REG_UDC_EPIntR & UDC_EPIntR_INEP2)) { + if (REG_UDC_EP2InSR & UDC_EPSR_IN) { + ep->irq_pending = 0; + REG_UDC_EP2InSR &= ~UDC_EPSR_IN; + if (REG_UDC_EPIntR & UDC_EPIntR_INEP2) + REG_UDC_EPIntR = UDC_EPIntR_INEP2; + + if (write_fifo(ep, req) == 1) + req = 0; + } + } + pio_irq_enable(ep); + } else { + /* EP5 ~ EP7 */ + pio_irq_enable(ep); + + if (ep->irq_pending || + (REG_UDC_EPIntR & UDC_EPIntR_OUTEP5)) { + u32 stats, count; + + stats = REG_UDC_EP5OutSR; + if (stats & UDC_EPSR_OUT_RCVDATA) { + ep->irq_pending = 0; + REG_UDC_EP5OutSR &= ~UDC_EPSR_OUT_MASK; + if (REG_UDC_EPIntR & UDC_EPIntR_OUTEP5) + REG_UDC_EPIntR = UDC_EPIntR_OUTEP5; + + count = OUT_COUNT(stats); + if (read_fifo(ep, req, count) == 1) + req = 0; + } + } + } + } + + /* pio or dma irq handler advances the queue. */ + if (likely(req != 0)) { + list_add_tail(&req->queue, &ep->queue); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + return status; +} + +/* dequeue ALL requests */ +static void nuke(struct jz4730_ep *ep, int status) +{ + struct jz4730_request *req; + + if (list_empty(&ep->queue)) + return; + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct jz4730_request, queue); + done(ep, req, status); + } +} + +/* dequeue JUST ONE request */ +static int jz4730_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct jz4730_request *req; + struct jz4730_ep *ep; + struct jz4730_udc *dev; + unsigned long flags; + int stopped; + + ep = container_of(_ep, struct jz4730_ep, ep); + if (!_ep || !_req || (!ep->desc && ep->index != 0)) + return -EINVAL; + dev = ep->dev; + if (!dev->driver) + return -ESHUTDOWN; + + DEBUG("%s %s %p\n", __FUNCTION__, _ep->name,_req); + + spin_lock_irqsave(&dev->lock, flags); + stopped = ep->stopped; + ep->stopped = 1; + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry (req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore (&dev->lock, flags); + return -EINVAL; + } + + /* queue head may be partially complete. */ + if (ep->queue.next == &req->queue) { + done (ep, req, -ECONNRESET); + req = 0; + } + + if (req) + done (ep, req, -ECONNRESET); + ep->stopped = stopped; + + spin_unlock_irqrestore (&ep->dev->lock, flags); + return req ? 0 : -EOPNOTSUPP; +} + +/*-------------------------------------------------------------------------*/ + +static void jz4730_clear_halt(struct jz4730_ep *ep) +{ + if (ep->stopped) { + ep->stopped = 0; + } +} + +static int jz4730_set_halt(struct usb_ep *_ep, int value) +{ + struct jz4730_ep *ep; + unsigned long flags; + int retval = 0; + + if (!_ep) + return -ENODEV; + ep = container_of (_ep, struct jz4730_ep, ep); + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + if (ep->desc /* not ep0 */ && (ep->desc->bmAttributes & 0x03) + == USB_ENDPOINT_XFER_ISOC) + return -EINVAL; + + if (ep->index == 0) { + if (value) { + ep->dev->ep0state = EP0_STALL; + ep->dev->ep[0].stopped = 1; + } else + return -EINVAL; + + /* don't change EPxSTATUS_EP_INVALID to READY */ + } else if (!ep->desc) { + DEBUG("%s %s inactive?\n", __FUNCTION__, ep->ep.name); + return -EINVAL; + } + + spin_lock_irqsave(&ep->dev->lock, flags); + + if (!list_empty(&ep->queue)) + retval = -EAGAIN; + else if (!value) + jz4730_clear_halt(ep); + else { + ep->stopped = 1; + } + + spin_unlock_irqrestore(&ep->dev->lock, flags); + return retval; +} + +static int jz4730_fifo_status(struct usb_ep *_ep) +{ + struct jz4730_ep *ep; + u32 size = 0; + + if (!_ep) + return -ENODEV; + ep = container_of(_ep, struct jz4730_ep, ep); + + /* size is only reported sanely for OUT */ + if (ep->is_in) + return -EOPNOTSUPP; + + return size; +} + +static void jz4730_fifo_flush(struct usb_ep *_ep) +{ + struct jz4730_ep *ep; + + if (!_ep) + return; + ep = container_of(_ep, struct jz4730_ep, ep); + + /* don't change EPxSTATUS_EP_INVALID to READY */ + if (!ep->desc && ep->index != 0) { + return; + } +} + +static struct usb_ep_ops jz4730_ep_ops = { + .enable = jz4730_ep_enable, + .disable = jz4730_ep_disable, + + .alloc_request = jz4730_alloc_request, + .free_request = jz4730_free_request, +#if 0 + .alloc_buffer = jz4730_alloc_buffer, + .free_buffer = jz4730_free_buffer, +#endif + .queue = jz4730_queue, + .dequeue = jz4730_dequeue, + + .set_halt = jz4730_set_halt, + .fifo_status = jz4730_fifo_status, + .fifo_flush = jz4730_fifo_flush, +}; + +/*-------------------------------------------------------------------------*/ + +static int jz4730_get_frame(struct usb_gadget *_gadget) +{ + return -EOPNOTSUPP; +} + +static const struct usb_gadget_ops jz4730_ops = { + .get_frame = jz4730_get_frame, + // no remote wakeup + // not selfpowered +}; + +/*-------------------------------------------------------------------------*/ + +static void udc_reinit(struct jz4730_udc *dev) +{ + static char *names [] = { "ep0", "ep1in-int", "ep2in-bulk", "ep3in-bulk", + "ep4in-iso", "ep5out-bulk", "ep6out-bulk", + "ep7out-iso" }; + int i; + + INIT_LIST_HEAD (&dev->gadget.ep_list); + dev->gadget.ep0 = &dev->ep[0].ep; + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->ep0state = EP0_DISCONNECT; + + for (i = 0; i < MAX_EP_NUM; i++) { + struct jz4730_ep *ep = &dev->ep[i]; + + ep->index = i; + ep->ep.name = names[i]; + ep->fifo = ep_fifo[i]; + ep->ep.maxpacket = 64; + + ep->ep.ops = &jz4730_ep_ops; + list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list); + ep->dev = dev; + INIT_LIST_HEAD(&ep->queue); + + ep->desc = 0; + ep->stopped = 1; + ep->irq_pending = 0; + } + + dev->ep[0].ep.maxpacket = MAX_EP0_SIZE; + list_del_init(&dev->ep[0].ep.ep_list); +} + +/* Reset udc registers */ +static void udc_reset(struct jz4730_udc *dev) +{ + REG_UDC_DevIntMR = 0x32; /* Enable RESET and SC interrupts */ + REG_UDC_EPIntMR = 0x0; /* Enable all EP interrupts */ + REG_UDC_DevCFGR = 0x17; + REG_UDC_DevCR = 0x0; + + REG_UDC_EP0InCR = (0 << 4) | (1 << 1); + REG_UDC_EP0InCR = (0 << 4); + REG_UDC_EP1InCR = (3 << 4) | (1 << 1); + REG_UDC_EP1InCR = (3 << 4); + REG_UDC_EP2InCR = (2 << 4) | (1 << 1); + REG_UDC_EP2InCR = (2 << 4); + REG_UDC_EP3InCR = (2 << 4) | (1 << 1); + REG_UDC_EP3InCR = (2 << 4); + REG_UDC_EP4InCR = (1 << 4) | (1 << 1); + REG_UDC_EP4InCR = (1 << 4); + + REG_UDC_EP0OutCR = (0 << 4); + REG_UDC_EP5OutCR = (2 << 4); + REG_UDC_EP6OutCR = (2 << 4); + REG_UDC_EP7OutCR = (1 << 4); + + REG_UDC_EP0InSR = 0; + REG_UDC_EP1InSR = 0; + REG_UDC_EP2InSR = 0; + REG_UDC_EP3InSR = 0; + REG_UDC_EP4InSR = 0; + REG_UDC_EP5OutSR = 0; + REG_UDC_EP6OutSR = 0; + REG_UDC_EP7OutSR = 0; + + REG_UDC_EP0InBSR = MAX_EP0_SIZE/4; + REG_UDC_EP1InBSR = MAX_EP1_SIZE/4; + REG_UDC_EP2InBSR = MAX_EP2_SIZE/4; + REG_UDC_EP3InBSR = MAX_EP3_SIZE/4; + REG_UDC_EP4InBSR = MAX_EP4_SIZE/4; + + REG_UDC_EP0InMPSR = MAX_EP0_SIZE; + REG_UDC_EP1InMPSR = MAX_EP1_SIZE; + REG_UDC_EP2InMPSR = MAX_EP2_SIZE; + REG_UDC_EP3InMPSR = MAX_EP3_SIZE; + REG_UDC_EP4InMPSR = MAX_EP4_SIZE; + + REG_UDC_EP0OutMPSR = MAX_EP0_SIZE; + REG_UDC_EP5OutMPSR = MAX_EP5_SIZE; + REG_UDC_EP6OutMPSR = MAX_EP6_SIZE; + REG_UDC_EP7OutMPSR = MAX_EP7_SIZE; + + REG_UDC_EP0InfR = (MAX_EP0_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (0 << 5) | (0 << 4) | (0 << 0); + REG_UDC_EP1InfR = (MAX_EP1_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (3 << 5) | (1 << 4) | (1 << 0); + REG_UDC_EP2InfR = (MAX_EP2_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (2 << 5) | (1 << 4) | (2 << 0); + REG_UDC_EP3InfR = (MAX_EP3_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (2 << 5) | (1 << 4) | (3 << 0); + REG_UDC_EP4InfR = (MAX_EP4_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (1 << 5) | (1 << 4) | (4 << 0); + REG_UDC_EP5InfR = (MAX_EP5_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (2 << 5) | (0 << 4) | (5 << 0); + REG_UDC_EP6InfR = (MAX_EP6_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (2 << 5) | (0 << 4) | (6 << 0); + REG_UDC_EP7InfR = (MAX_EP7_SIZE << 19) | (0 << 15) | (0 << 11) | (0x1 << 7) | (1 << 5) | (0 << 4) | (7 << 0); + + REG_UDC_STCMAR = 0xffff; +} + +static void ep0_start(struct jz4730_udc *dev) +{ + udc_reset(dev); + udc_reinit(dev); + + /* expect ep0 requests when the host drops reset */ + dev->gadget.speed = USB_SPEED_FULL; + dev->ep0state = EP0_IDLE; +} + +static void udc_enable(struct jz4730_udc *dev) +{ + /* Enable udc and enable all interrupts */ + __intc_unmask_irq(IRQ_UDC); + __harb_usb0_udc(); + + /* start enumeration now, or after power detect irq */ + ep0_start(dev); +} + +/*-------------------------------------------------------------------------*/ + +/* keeping it simple: + * - one bus driver, initted first; + * - one function driver, initted second + */ + +static struct jz4730_udc *the_controller; + +/* when a driver is successfully registered, it will receive + * control requests including set_configuration(), which enables + * non-control requests. then usb traffic follows until a + * disconnect is reported. then a host may connect again, or + * the driver might get unbound. + */ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct jz4730_udc *dev = the_controller; + int retval; + + if (!driver +// || driver->speed != USB_SPEED_FULL + || !driver->bind + || !driver->unbind + || !driver->disconnect + || !driver->setup) + { + printk("\n -EINVAL"); + return -EINVAL; + } + if (!dev) + return -ENODEV; + + if (dev->driver) + return -EBUSY; + + /* hook up the driver */ + dev->driver = driver; + retval = driver->bind(&dev->gadget); + if (retval) { + DEBUG("bind to driver %s --> error %d\n", + driver->driver.name, retval); + dev->driver = 0; + return retval; + } + /* then enable host detection and ep0; and we're ready + * for set_configuration as well as eventual disconnect. + */ + udc_enable(dev); + + DEBUG("registered gadget driver '%s'\n", driver->driver.name); + return 0; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + +static void +stop_activity(struct jz4730_udc *dev, struct usb_gadget_driver *driver) +{ + unsigned i; + + DEBUG("%s\n", __FUNCTION__); + + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = 0; + + /* disconnect gadget driver after quiesceing hw and the driver */ + udc_reset (dev); + for (i = 0; i < MAX_EP_NUM; i++) + nuke(&dev->ep [i], -ESHUTDOWN); + if (driver) { + spin_unlock(&dev->lock); + driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + + if (dev->driver) + udc_enable(dev); +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct jz4730_udc *dev = the_controller; + unsigned long flags; + + /* disable UDC irq */ + __intc_mask_irq(IRQ_UDC); + __harb_usb0_uhc(); + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + dev->driver = 0; + stop_activity(dev, driver); + spin_unlock_irqrestore(&dev->lock, flags); + + driver->unbind(&dev->gadget); + + DEBUG("unregistered driver '%s'\n", driver->driver.name); + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +static void jz4730_epn_out(struct jz4730_udc *dev, int ep_idx, u32 count) +{ + struct jz4730_request *req; + struct jz4730_ep *ep = &dev->ep[ep_idx]; + + req = list_entry(ep->queue.next, struct jz4730_request, queue); + read_fifo(ep, req, count); +} + +static void jz4730_epn_in(struct jz4730_udc *dev, int ep_idx) +{ + struct jz4730_request *req; + struct jz4730_ep *ep = &dev->ep[ep_idx]; + + req = list_entry(ep->queue.next, struct jz4730_request, queue); + write_fifo(ep, req); +} + +/****************************************************************/ +/* End Point 0 related functions */ +/****************************************************************/ + +/* return: 0 = still running, 1 = completed, negative = errno */ +static int write_fifo_ep0(struct jz4730_ep *ep, struct jz4730_request *req) +{ + u32 max, count; + int is_last; + + max = ep->ep.maxpacket; + + count = write_packet(ep, req, max); + + /* last packet is usually short (or a zlp) */ + if (unlikely(count != max)) + is_last = 1; + else { + if (likely(req->req.length != req->req.actual) || req->req.zero) + is_last = 0; + else + is_last = 1; + } + + DEBUG_EP0("%s: wrote %s %d bytes%s %d left %p\n", __FUNCTION__, + ep->ep.name, count, + is_last ? "/L" : "", req->req.length - req->req.actual, req); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + done(ep, req, 0); + return 1; + } + + return 0; +} + +/* + * Simulate a USB_REQ_SET_CONFIGURATION to the function driver, + * this is required to enable the endpoints of the function driver. + * UDC should let software have the chance to handle this standard + * request, unfortunately UDC can't do that. + */ +static void psudo_set_config(void) +{ + struct jz4730_udc *dev = (struct jz4730_udc *) the_controller; + struct usb_ctrlrequest ctrl; + int tmp; + + /* SETUP packet */ + ctrl.bRequestType = 0x00; + ctrl.bRequest = USB_REQ_SET_CONFIGURATION; + ctrl.wValue = 1; + ctrl.wIndex = 0; + ctrl.wLength = 0; + + nuke(&dev->ep[0], 0); + dev->ep[0].stopped = 0; + + if (likely(ctrl.bRequestType & USB_DIR_IN)) { + dev->ep[0].is_in = 1; + dev->ep0state = EP0_IN; + } else { + dev->ep[0].is_in = 0; + dev->ep0state = EP0_OUT; + } + + /* delegate everything to the gadget driver. + * it may respond after this irq handler returns. + */ + spin_unlock (&dev->lock); + tmp = dev->driver->setup(&dev->gadget, &ctrl); + spin_lock (&dev->lock); + if (unlikely(tmp < 0)) { + DEBUG_EP0("req %02x.%02x protocol STALL; err %d\n", + ctrl.bRequestType, ctrl.bRequest, tmp); + dev->ep[0].stopped = 1; + dev->ep0state = EP0_STALL; + } +} + +/* + * Read 8 bytes setup packet from EP0 RX buffer + */ +static void read_setup_packet(u8 *buf) +{ + u32 *tmp = (u32 *)buf; + + *tmp++ = readl((unsigned int *)RXFIFO); + *tmp++ = readl((unsigned int *)RXFIFO); + + REG32(UDC_RXCONFIRM); +} + +static void jz4730_ep0_setup(struct jz4730_udc *dev) +{ + struct jz4730_ep *ep = &dev->ep[0]; + struct usb_ctrlrequest ctrl; + int tmp; + + /* read control req from fifo (8 bytes) */ + read_setup_packet((unsigned char *) &ctrl); + + DEBUG_EP0("SETUP %02x.%02x v%04x i%04x l%04x\n", + ctrl.bRequestType, ctrl.bRequest, + ctrl.wValue, ctrl.wIndex, ctrl.wLength); + + /* Set direction of EP0 */ + if (likely(ctrl.bRequestType & USB_DIR_IN)) { + ep->is_in = 1; + dev->ep0state = EP0_IN; + } else { + ep->is_in = 0; + dev->ep0state = EP0_OUT; + } + + /* Nuke all previous transfers */ + nuke(ep, 0); + ep->stopped = 0; + + /* delegate everything to the gadget driver. + * it may respond after this irq handler returns. + */ + if (likely((u32)dev->driver)) { + /* device-2-host (IN) or no data setup command, process immediately */ + spin_unlock(&dev->lock); + tmp = dev->driver->setup(&dev->gadget, &ctrl); + spin_lock(&dev->lock); + + if (unlikely(tmp < 0)) { + /* setup processing failed, force stall */ + DEBUG_EP0("req %02x.%02x protocol STALL; err %d\n", + ctrl.bRequestType, ctrl.bRequest, tmp); + dev->ep0state = EP0_STALL; + } + } +} + +static int jz4730_ep0_in(struct jz4730_udc *dev) +{ + struct jz4730_request *req; + struct jz4730_ep *ep = &dev->ep[0]; + int ret; + + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct jz4730_request, queue); + + if (!req) { + DEBUG_EP0("%s: NULL REQ\n", __FUNCTION__); + return 0; + } + + ret = write_fifo_ep0(ep, req); + + return ret; +} + +static void jz4730_ep0_out(struct jz4730_udc *dev) +{ + u32 epsr; + struct jz4730_ep *ep = &dev->ep[0]; + + epsr = REG_UDC_EP0OutSR; + REG_UDC_EP0OutSR &= ~UDC_EPSR_OUT_MASK; + + if (epsr & UDC_EPSR_OUT_RCVSETUP) { + jz4730_ep0_setup(dev); + } + else if (epsr & UDC_EPSR_OUT_RCVDATA) { + u32 count = __udc_ep0out_packet_size(); + if (count == 0) { + readl((unsigned int *)UDC_RXCONFIRM); // ack zero packet + } + else { + /* EP0 OUT Data */ + if (list_empty(&ep->queue)) { + ep->irq_pending = 1; + pio_irq_disable(ep); + } + else + jz4730_epn_out(dev, 0, count); + + } + } +} + +static void handle_reset_irq(struct jz4730_udc *dev) +{ + int i; + + /* clear any status */ + REG_UDC_EPIntR = 0xffffffff; + REG_UDC_DevIntR = 0xffffffff; + + /* reset udc */ + udc_reset(dev); + + /* reset driver status */ + for (i = 0; i < MAX_EP_NUM; i++) { + struct jz4730_ep *ep = &dev->ep[i]; + + ep->irq_pending = 0; +// nuke(ep, 0); + nuke(ep, -ESHUTDOWN); + } +} + +static irqreturn_t jz4730_udc_irq(int irq, void *_dev) +{ + struct jz4730_udc *dev = _dev; + struct jz4730_ep *ep; + + u32 intr_dev, intr_ep, stats, count; + + spin_lock(&dev->lock); + + intr_dev = REG_UDC_DevIntR; + intr_ep = REG_UDC_EPIntR; + + DEBUG("*** udc irq intr_dev=0x%x intr_ep=0x%x\n", intr_dev, intr_ep); + + if (!intr_dev && !intr_ep) { + spin_unlock(&dev->lock); + return IRQ_HANDLED; + } + + if (udc_debug) { +#ifdef CONFIG_JZ_UDC_HOTPLUG + jz_udc_active = 1; +#endif + REG_UDC_DevIntR = intr_dev; + REG_UDC_EPIntR = intr_ep; + __harb_usb0_uhc(); + __intc_mask_irq(IRQ_UDC); + spin_unlock(&dev->lock); + return IRQ_HANDLED; + } + + if (intr_dev) { + if (intr_dev & UDC_DevIntR_SC) { + psudo_set_config(); + udelay(100); + } + + if (intr_dev & UDC_DevIntR_UR) { +#ifdef CONFIG_JZ_UDC_HOTPLUG + jz_udc_active = 1; +#endif + handle_reset_irq(dev); + } + + REG_UDC_DevIntR = intr_dev; + } + + if (intr_ep & UDC_EPIntR_OUTEP0) { + REG_UDC_EPIntR = UDC_EPIntR_OUTEP0; + jz4730_ep0_out(dev); + } + + if (intr_ep & UDC_EPIntR_INEP0) { + ep = &dev->ep[0]; + if (list_empty(&ep->queue)) { + pio_irq_disable(ep); + } + else { + stats = REG_UDC_EP0InSR; + if (stats & UDC_EPSR_IN) { + REG_UDC_EPIntR = UDC_EPIntR_INEP0; + REG_UDC_EP0InSR &= ~UDC_EPSR_IN; + + jz4730_ep0_in(dev); + } + } + } + + if (intr_ep & UDC_EPIntR_OUTEP5) { + REG_UDC_EPIntR = UDC_EPIntR_OUTEP5; + ep = &dev->ep[5]; + if (list_empty(&ep->queue)) { + ep->irq_pending = 1; + pio_irq_disable(ep); + } + else { + stats = REG_UDC_EP5OutSR; + if (stats & UDC_EPSR_OUT_RCVDATA) { + REG_UDC_EP5OutSR &= ~UDC_EPSR_OUT_MASK; + + count = OUT_COUNT(stats); + jz4730_epn_out(dev, 5, count); + } + } + } + + if (intr_ep & UDC_EPIntR_INEP2) { + ep = &dev->ep[2]; + if (list_empty(&ep->queue)) { + ep->irq_pending = 1; + pio_irq_disable(ep); + } + else { + stats = REG_UDC_EP2InSR; + if (stats & UDC_EPSR_IN) { + REG_UDC_EP2InSR &= ~UDC_EPSR_IN; + jz4730_epn_in(dev, 2); + } + } + + REG_UDC_EPIntR = UDC_EPIntR_INEP2; + } + + if (intr_ep & UDC_EPIntR_INEP1) { + ep = &dev->ep[1]; + if (list_empty(&ep->queue)) { + ep->irq_pending = 1; + pio_irq_disable(ep); + } + else { + stats = REG_UDC_EP1InSR; + if (stats & UDC_EPSR_IN) { + REG_UDC_EP1InSR &= ~UDC_EPSR_IN; + jz4730_epn_in(dev, 1); + } + } + + REG_UDC_EPIntR = UDC_EPIntR_INEP1; + } + + spin_unlock(&dev->lock); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +static struct jz4730_udc udc_dev = { + .usb_address = 0, + + .gadget = { + .ops = &jz4730_ops, + .ep0 = &udc_dev.ep[0].ep, + .name = driver_name, + .dev = { + .bus_id = "gadget", + }, + }, + /* control endpoint no need to init here!*/ + /* control endpoint */ +}; + + +/* tear down the binding between this driver and the pci device */ +static int jz4730_udc_remove(struct platform_device *pdev) +{ + struct jz4730_udc *dev = platform_get_drvdata(pdev); + + if (dev->driver) + return -EBUSY; + + /* USB port0 as UHC */ + __harb_usb0_uhc(); + + /* reset udc */ + udc_reset(dev); + + /* clear any status */ + REG_UDC_EPIntR = 0xffffffff; + REG_UDC_DevIntR = 0xffffffff; + + /* disable all UDC interrupts */ + REG_UDC_DevIntMR = 0xffffffff; + REG_UDC_EPIntMR = 0xffffffff; + + free_irq(IRQ_UDC, dev); + platform_set_drvdata(pdev, 0); + device_unregister(&dev->gadget.dev); + the_controller = 0; + + return 0; +} + +static int jz4730_udc_probe(struct platform_device *pdev) +{ + struct jz4730_udc *dev = &udc_dev; + int retval,rc; + + /* if you want to support more than one controller in a system, + * usb_gadget_driver_{register,unregister}() must change. + */ + if (the_controller) { + printk("Check the_controller: %s\n", driver_name); + return -EBUSY; + } + + spin_lock_init(&dev->lock); + device_initialize(&dev->gadget.dev); + dev->gadget.dev.parent = &pdev->dev; //if no,can only insmod once!! + dev->gadget.dev.release = jz4730_udc_release; + rc = device_register (&dev->gadget.dev); + if (rc < 0) + return rc; + platform_set_drvdata(pdev, dev); + + /* + * Note: we just mask INTC irq but allow UDC irq. + * This avoid that we miss any UDC irqs. + */ + + /* To avoid any UDC irqs here, we call cli() first */ +// cli(); + + /* disable INTC irq */ + __intc_mask_irq(IRQ_UDC); + + /* init to known state, then setup irqs */ + udc_reset(dev); + udc_reinit(dev); + + /* request UDC irq */ + if (request_irq(IRQ_UDC, jz4730_udc_irq, IRQF_DISABLED, // SA_INTERRUPT, + driver_name, dev) != 0) { + printk(KERN_INFO "request UDC interrupt %d failed\n", IRQ_UDC); + retval = -EBUSY; + goto done; + } + + /* disable INTC irq again since request_irq has enabled it */ + __intc_mask_irq(IRQ_UDC); + __intc_ack_irq(IRQ_UDC); + + /* Re-enable irqs */ +// sti(); + + printk(KERN_INFO "%s\n", driver_desc); + printk(KERN_INFO "version: " DRIVER_VERSION "\n"); + + /* done */ + the_controller = dev; + + return 0; + +done: + if (dev) + jz4730_udc_remove (pdev); + return retval; +} + +static struct platform_driver udc_driver = { + .probe = jz4730_udc_probe, + .remove = jz4730_udc_remove, + .suspend = NULL, + .resume = NULL, + .driver = { + .name = (char *) driver_name, + .owner = THIS_MODULE, + }, +}; +static struct platform_device the_udc_pdev = { + .name = (char *) driver_name, + .id = -1, + .dev = { + .release = jz4730_udc_release, + }, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init udc_init (void) +{ + platform_driver_register(&udc_driver); + return platform_device_register (&the_udc_pdev); +} + +static void __exit udc_exit (void) +{ + platform_driver_unregister(&udc_driver); + platform_device_unregister(&the_udc_pdev); +} + +module_init(udc_init); +module_exit(udc_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Wei Jianli "); +MODULE_LICENSE("GPL"); --- /dev/null +++ b/drivers/usb/gadget/jz4730_udc.h @@ -0,0 +1,107 @@ +/* + * JZ4730 USB Device Controller driver + * + * Copyright (C) 2005 by Wei Jianli + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef __JZ4730_UDC_H__ +#define __JZ4730_UDC_H__ + +/* DRIVER DATA STRUCTURES and UTILITIES */ +#define MAX_EP_NUM 8 /* Number of endpoints on this UDC */ + +#define MAX_EP0_SIZE 32 +#define MAX_EP1_SIZE 64 +#define MAX_EP2_SIZE 64 +#define MAX_EP3_SIZE 64 +#define MAX_EP4_SIZE 64 +#define MAX_EP5_SIZE 64 +#define MAX_EP6_SIZE 64 +#define MAX_EP7_SIZE 64 + +// UDC FIFO +#define RXFIFO (UDC_RXFIFO) /* EP0 OUT, EP5-7 OUT */ +#define TXFIFOEP0 (UDC_TXFIFOEP0) /* EP0 IN */ +#define TXFIFOEP1 (TXFIFOEP0 + MAX_EP0_SIZE) /* EP1 IN */ +#define TXFIFOEP2 (TXFIFOEP1 + MAX_EP1_SIZE) /* EP2 IN */ +#define TXFIFOEP3 (TXFIFOEP2 + MAX_EP2_SIZE) /* EP3 IN */ +#define TXFIFOEP4 (TXFIFOEP3 + MAX_EP3_SIZE) /* EP4 IN */ + +static u32 ep_fifo[MAX_EP_NUM] = {TXFIFOEP0, TXFIFOEP1, TXFIFOEP2, + TXFIFOEP3, TXFIFOEP4, RXFIFO, RXFIFO, + RXFIFO}; + +#define OUT_COUNT(stats) \ + ((stats&UDC_EPSR_RXPKTSIZE_MASK)>>UDC_EPSR_RXPKTSIZE_BIT) + +struct jz4730_ep { + struct usb_ep ep; + struct jz4730_udc *dev; + + u8 index; + u8 is_in; + u8 stopped; + u8 irq_pending; + u32 fifo; + + struct list_head queue; + const struct usb_endpoint_descriptor *desc; +}; + +struct jz4730_request { + struct usb_request req; + struct list_head queue; +}; + +enum ep0state { + EP0_DISCONNECT, /* no host */ + EP0_IDLE, /* between STATUS ack and SETUP report */ + EP0_IN, EP0_OUT, /* data stage */ + EP0_STATUS, /* status stage */ + EP0_STALL, /* data or status stages */ + EP0_SUSPEND, /* usb suspend */ +}; + +struct jz4730_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + spinlock_t lock; + + struct jz4730_ep ep[MAX_EP_NUM]; + enum ep0state ep0state; + unsigned char usb_address; +}; + +/*-------------------------------------------------------------------------*/ + +/* 2.5 stuff that's sometimes missing in 2.4 */ + +#ifndef container_of +#define container_of list_entry +#endif + +#ifndef likely +#define likely(x) (x) +#define unlikely(x) (x) +#endif + +#ifndef BUG_ON +#define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0) +#endif + +#ifndef WARN_ON +#define WARN_ON(x) do { } while (0) +#endif + +#ifndef IRQ_NONE +typedef void irqreturn_t; +#define IRQ_NONE +#define IRQ_HANDLED +#define IRQ_RETVAL(x) +#endif + +#endif /* __JZ4730_UDC_H__ */ --- /dev/null +++ b/drivers/usb/gadget/jz4740_udc.c @@ -0,0 +1,2251 @@ +/* + * linux/drivers/usb/gadget/jz4740_udc.c + * + * Ingenic JZ4740 on-chip high speed USB device controller + * + * Copyright (C) 2006 - 2008 Ingenic Semiconductor Inc. + * Author: + * + * This program 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 device has ep0, two bulk-in/interrupt-in endpoints, and one bulk-out endpoint. + * + * - Endpoint numbering is fixed: ep0, ep1in-int, ep2in-bulk, ep1out-bulk. + * - DMA works with bulk-in (channel 1) and bulk-out (channel 2) endpoints. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "jz4740_udc.h" + +//#define DEBUG(fmt,args...) printk(KERN_DEBUG fmt , ## args) +//#define DEBUG(fmt,args...) printk(fmt , ## args) +//#define DEBUG_EP0(fmt,args...) printk(fmt , ## args) +//#define DEBUG_SETUP(fmt,args...) printk(fmt , ## args) + +#ifndef DEBUG +# define DEBUG(fmt,args...) do {} while(0) +#endif +#ifndef DEBUG_EP0 +# define NO_STATES +# define DEBUG_EP0(fmt,args...) do {} while(0) +#endif +#ifndef DEBUG_SETUP +# define DEBUG_SETUP(fmt,args...) do {} while(0) +#endif + +static unsigned int udc_debug = 0; /* 0: normal mode, 1: test udc cable type mode */ + +module_param(udc_debug, int, 0); +MODULE_PARM_DESC(udc_debug, "test udc cable or power type"); + +static unsigned int use_dma = 1; /* 1: use DMA, 0: use PIO */ + +module_param(use_dma, int, 0); +MODULE_PARM_DESC(use_dma, "DMA mode enable flag"); + +#ifdef CONFIG_JZ_UDC_HOTPLUG +extern int jz_udc_active; /* 0: No actions; 1: Have actions */ +#endif + +/* + * Local definintions. + */ + +#define DRIVER_VERSION "13-Mar-2008" +#define DRIVER_DESC "JZ4740 USB Device Controller" + +static const char gadget_name [] = "jz4740_udc"; + +struct jz4740_udc *the_controller; + +static const char driver_name [] = "jz4740_udc"; +static const char driver_desc [] = DRIVER_DESC; +static const char ep0name[] = "ep0"; + +#ifndef NO_STATES +static char *state_names[] = { + "WAIT_FOR_SETUP", + "DATA_STATE_XMIT", + "DATA_STATE_NEED_ZLP", + "WAIT_FOR_OUT_STATUS", + "DATA_STATE_RECV" +}; +#endif + +/* + * Local declarations. + */ +static int jz4740_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc); +static int jz4740_ep_disable(struct usb_ep *_ep); +static struct usb_request *jz4740_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags); +static void jz4740_free_request(struct usb_ep *_ep, struct usb_request *_req); + +static int jz4740_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags); +static int jz4740_dequeue(struct usb_ep *_ep, struct usb_request *_req); +static int jz4740_set_halt(struct usb_ep *_ep, int value); +static int jz4740_fifo_status(struct usb_ep *_ep); +static void jz4740_fifo_flush(struct usb_ep *_ep); + +static void jz4740_ep0_kick(struct jz4740_udc *dev, struct jz4740_ep *ep); +static void jz4740_handle_ep0(struct jz4740_udc *dev, u32 intr); + +static void done(struct jz4740_ep *ep, struct jz4740_request *req, + int status); +static void pio_irq_enable(struct jz4740_ep *ep); +static void pio_irq_disable(struct jz4740_ep *ep); +static void stop_activity(struct jz4740_udc *dev, + struct usb_gadget_driver *driver); +static void nuke(struct jz4740_ep *ep, int status); +static void flush(struct jz4740_ep *ep); +static void udc_enable(struct jz4740_udc *dev); +static void udc_set_address(struct jz4740_udc *dev, unsigned char address); +static void jz4740_udc_release (struct device *dev) {} + +extern void *dma_alloc_noncoherent(struct device *dev, size_t size, + dma_addr_t *dma_handle, gfp_t flag); +extern void dma_free_noncoherent(struct device *dev, size_t size, + void *vaddr, dma_addr_t dma_handle); + +static struct usb_ep_ops jz4740_ep_ops = { + .enable = jz4740_ep_enable, + .disable = jz4740_ep_disable, + + .alloc_request = jz4740_alloc_request, + .free_request = jz4740_free_request, + + .queue = jz4740_queue, + .dequeue = jz4740_dequeue, + + .set_halt = jz4740_set_halt, + .fifo_status = jz4740_fifo_status, + .fifo_flush = jz4740_fifo_flush, +}; + +/*-------------------------------------------------------------------------*/ + +/* inline functions of register read/write/set/clear */ + +static __inline__ u8 usb_readb(u32 port) +{ + return *(volatile u8 *)port; +} + +static __inline__ u16 usb_readw(u32 port) +{ + return *(volatile u16 *)port; +} + +static __inline__ u32 usb_readl(u32 port) +{ + return *(volatile u32 *)port; +} + +static __inline__ void usb_writeb(u32 port, u8 val) +{ + *(volatile u8 *)port = val; +} + +static __inline__ void usb_writew(u32 port, u16 val) +{ + *(volatile u16 *)port = val; +} + +static __inline__ void usb_writel(u32 port, u32 val) +{ + *(volatile u32 *)port = val; +} + +static __inline__ void usb_setb(u32 port, u8 val) +{ + volatile u8 *ioport = (volatile u8 *)(port); + *ioport = (*ioport) | val; +} + +static __inline__ void usb_setw(u32 port, u16 val) +{ + volatile u16 *ioport = (volatile u16 *)(port); + *ioport = (*ioport) | val; +} + +static __inline__ void usb_setl(u32 port, u32 val) +{ + volatile u32 *ioport = (volatile u32 *)(port); + *ioport = (*ioport) | val; +} + +static __inline__ void usb_clearb(u32 port, u8 val) +{ + volatile u8 *ioport = (volatile u8 *)(port); + *ioport = (*ioport) & ~val; +} + +static __inline__ void usb_clearw(u32 port, u16 val) +{ + volatile u16 *ioport = (volatile u16 *)(port); + *ioport = (*ioport) & ~val; +} + +static __inline__ void usb_clearl(u32 port, u32 val) +{ + volatile u32 *ioport = (volatile u32 *)(port); + *ioport = (*ioport) & ~val; +} + +/*-------------------------------------------------------------------------*/ + +static __inline__ int write_packet(struct jz4740_ep *ep, + struct jz4740_request *req, int max) +{ + u8 *buf; + int length, nlong, nbyte; + volatile u32 *fifo = (volatile u32 *)ep->fifo; + + buf = req->req.buf + req->req.actual; + prefetch(buf); + + length = req->req.length - req->req.actual; + length = min(length, max); + req->req.actual += length; + + DEBUG("Write %d (max %d), fifo %p\n", length, max, fifo); + + nlong = length >> 2; + nbyte = length & 0x3; + while (nlong--) { + *fifo = *((u32 *)buf); + buf += 4; + } + while (nbyte--) { + *((volatile u8 *)fifo) = *buf++; + } + + return length; +} + +static __inline__ int read_packet(struct jz4740_ep *ep, + struct jz4740_request *req, int count) +{ + u8 *buf; + int length, nlong, nbyte; + volatile u32 *fifo = (volatile u32 *)ep->fifo; + + buf = req->req.buf + req->req.actual; + prefetchw(buf); + + length = req->req.length - req->req.actual; + length = min(length, count); + req->req.actual += length; + + DEBUG("Read %d, fifo %p\n", length, fifo); + + nlong = length >> 2; + nbyte = length & 0x3; + while (nlong--) { + *((u32 *)buf) = *fifo; + buf += 4; + } + while (nbyte--) { + *buf++ = *((volatile u8 *)fifo); + } + + return length; +} + +/*-------------------------------------------------------------------------*/ + +/* + * udc_disable - disable USB device controller + */ +static void udc_disable(struct jz4740_udc *dev) +{ + DEBUG("%s, %p\n", __FUNCTION__, dev); + + udc_set_address(dev, 0); + + /* Disable interrupts */ + usb_writew(USB_REG_INTRINE, 0); + usb_writew(USB_REG_INTROUTE, 0); + usb_writeb(USB_REG_INTRUSBE, 0); + + /* Disable DMA */ + usb_writel(USB_REG_CNTL1, 0); + usb_writel(USB_REG_CNTL2, 0); + + /* Disconnect from usb */ + usb_clearb(USB_REG_POWER, USB_POWER_SOFTCONN); + + /* Disable the USB PHY */ +#ifdef CONFIG_SOC_JZ4740 + REG_CPM_SCR &= ~CPM_SCR_USBPHY_ENABLE; +#elif CONFIG_SOC_JZ4750 + REG_CPM_OPCR &= ~CPM_OPCR_UDCPHY_ENABLE; +#endif + + dev->ep0state = WAIT_FOR_SETUP; + dev->gadget.speed = USB_SPEED_UNKNOWN; +} + +/* + * udc_reinit - initialize software state + */ +static void udc_reinit(struct jz4740_udc *dev) +{ + u32 i; + + DEBUG("%s, %p\n", __FUNCTION__, dev); + + /* device/ep0 records init */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + dev->ep0state = WAIT_FOR_SETUP; + + for (i = 0; i < UDC_MAX_ENDPOINTS; i++) { + struct jz4740_ep *ep = &dev->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + + INIT_LIST_HEAD(&ep->queue); + ep->desc = 0; + ep->stopped = 0; + ep->pio_irqs = 0; + } +} + +/* until it's enabled, this UDC should be completely invisible + * to any USB host. + */ +static void udc_enable(struct jz4740_udc *dev) +{ + int i; + + DEBUG("%s, %p\n", __FUNCTION__, dev); + + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* Flush FIFO for each */ + for (i = 0; i < UDC_MAX_ENDPOINTS; i++) { + struct jz4740_ep *ep = &dev->ep[i]; + + usb_set_index(ep_index(ep)); + flush(ep); + } + + /* Set this bit to allow the UDC entering low-power mode when + * there are no actions on the USB bus. + * UDC still works during this bit was set. + */ + __cpm_stop_udc(); + + /* Enable the USB PHY */ +#ifdef CONFIG_SOC_JZ4740 + REG_CPM_SCR |= CPM_SCR_USBPHY_ENABLE; +#elif CONFIG_SOC_JZ4750 + REG_CPM_OPCR |= CPM_OPCR_UDCPHY_ENABLE; +#endif + + /* Disable interrupts */ + usb_writew(USB_REG_INTRINE, 0); + usb_writew(USB_REG_INTROUTE, 0); + usb_writeb(USB_REG_INTRUSBE, 0); + + /* Enable interrupts */ + usb_setw(USB_REG_INTRINE, USB_INTR_EP0); + usb_setb(USB_REG_INTRUSBE, USB_INTR_RESET); + /* Don't enable rest of the interrupts */ + /* usb_setw(USB_REG_INTRINE, USB_INTR_INEP1 | USB_INTR_INEP2); + usb_setw(USB_REG_INTROUTE, USB_INTR_OUTEP1); */ + + /* Enable SUSPEND */ + /* usb_setb(USB_REG_POWER, USB_POWER_SUSPENDM); */ + + /* Enable HS Mode */ + usb_setb(USB_REG_POWER, USB_POWER_HSENAB); + + /* Let host detect UDC: + * Software must write a 1 to the PMR:USB_POWER_SOFTCONN bit to turn this + * transistor on and pull the USBDP pin HIGH. + */ + usb_setb(USB_REG_POWER, USB_POWER_SOFTCONN); +} + +/*-------------------------------------------------------------------------*/ + +/* keeping it simple: + * - one bus driver, initted first; + * - one function driver, initted second + */ + +/* + * Register entry point for the peripheral controller driver. + */ + +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct jz4740_udc *dev = the_controller; + int retval; + + if (!driver + || !driver->bind + || !driver->unbind || !driver->disconnect || !driver->setup) + { + printk("\n-EINVAL"); + return -EINVAL; + } + if (!dev) + { + printk("\n-ENODEV"); + return -ENODEV; + } + if (dev->driver) + { + printk("\n-ENODEV"); + return -EBUSY; + } + + /* hook up the driver */ + dev->driver = driver; + retval = driver->bind(&dev->gadget); + if (retval) { + DEBUG("%s: bind to driver %s --> error %d\n", dev->gadget.name, + driver->driver.name, retval); + dev->driver = 0; + return retval; + } + + /* then enable host detection and ep0; and we're ready + * for set_configuration as well as eventual disconnect. + */ + udc_enable(dev); + DEBUG("%s: registered gadget driver '%s'\n", dev->gadget.name, + driver->driver.name); + + return 0; +} + +EXPORT_SYMBOL(usb_gadget_register_driver); + + +static void stop_activity(struct jz4740_udc *dev, + struct usb_gadget_driver *driver) +{ + int i; + + DEBUG("%s\n", __FUNCTION__); + + /* don't disconnect drivers more than once */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = 0; + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < UDC_MAX_ENDPOINTS; i++) { + struct jz4740_ep *ep = &dev->ep[i]; + + ep->stopped = 1; + + usb_set_index(ep_index(ep)); + nuke(ep, -ESHUTDOWN); + } + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&dev->lock); + driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + + /* re-init driver-visible data structures */ + udc_reinit(dev); +} + + +/* + * Unregister entry point for the peripheral controller driver. + */ +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct jz4740_udc *dev = the_controller; + unsigned long flags; + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + dev->driver = 0; + stop_activity(dev, driver); + spin_unlock_irqrestore(&dev->lock, flags); + + driver->unbind(&dev->gadget); + + udc_disable(dev); + + DEBUG("unregistered driver '%s'\n", driver->driver.name); + + return 0; +} + +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/*-------------------------------------------------------------------------*/ + +/* + * Starting DMA using mode 1 + */ +static void kick_dma(struct jz4740_ep *ep, struct jz4740_request *req) +{ + u32 count = req->req.length; + u32 physaddr = virt_to_phys((void *)req->req.buf); + + usb_set_index(ep_index(ep)); + if (ep_is_in(ep)) { /* Bulk-IN transfer using DMA channel 1 */ + ep->reg_addr = USB_REG_ADDR1; + + dma_cache_wback_inv((unsigned long)req->req.buf, count); + + pio_irq_enable(ep); + + usb_writeb(USB_REG_INCSRH, + USB_INCSRH_DMAREQENAB | USB_INCSRH_AUTOSET | USB_INCSRH_DMAREQMODE); + + usb_writel(USB_REG_ADDR1, physaddr); + usb_writel(USB_REG_COUNT1, count); + usb_writel(USB_REG_CNTL1, USB_CNTL_ENA | USB_CNTL_DIR_IN | USB_CNTL_MODE_1 | + USB_CNTL_INTR_EN | USB_CNTL_BURST_16 | USB_CNTL_EP(ep_index(ep))); + } + else { /* Bulk-OUT transfer using DMA channel 2 */ + ep->reg_addr = USB_REG_ADDR2; + + dma_cache_wback_inv((unsigned long)req->req.buf, count); + + pio_irq_enable(ep); + + usb_setb(USB_REG_OUTCSRH, + USB_OUTCSRH_DMAREQENAB | USB_OUTCSRH_AUTOCLR | USB_OUTCSRH_DMAREQMODE); + + usb_writel(USB_REG_ADDR2, physaddr); + usb_writel(USB_REG_COUNT2, count); + usb_writel(USB_REG_CNTL2, USB_CNTL_ENA | USB_CNTL_MODE_1 | + USB_CNTL_INTR_EN | USB_CNTL_BURST_16 | USB_CNTL_EP(ep_index(ep))); + } +} + +/*-------------------------------------------------------------------------*/ + +/** Write request to FIFO (max write == maxp size) + * Return: 0 = still running, 1 = completed, negative = errno + * NOTE: INDEX register must be set for EP + */ +static int write_fifo(struct jz4740_ep *ep, struct jz4740_request *req) +{ + u32 max, csr; + u32 physaddr = virt_to_phys((void *)req->req.buf); + + max = le16_to_cpu(ep->desc->wMaxPacketSize); + + if (use_dma) { + u32 dma_count; + + /* DMA interrupt generated due to the last packet loaded into the FIFO */ + + dma_count = usb_readl(ep->reg_addr) - physaddr; + req->req.actual += dma_count; + + if (dma_count % max) { + /* If the last packet is less than MAXP, set INPKTRDY manually */ + usb_setb(ep->csr, USB_INCSR_INPKTRDY); + } + + done(ep, req, 0); + if (list_empty(&ep->queue)) { + pio_irq_disable(ep); + return 1; + } + else { + /* advance the request queue */ + req = list_entry(ep->queue.next, struct jz4740_request, queue); + kick_dma(ep, req); + return 0; + } + } + + /* + * PIO mode handling starts here ... + */ + + csr = usb_readb(ep->csr); + + if (!(csr & USB_INCSR_FFNOTEMPT)) { + unsigned count; + int is_last, is_short; + + count = write_packet(ep, req, max); + usb_setb(ep->csr, USB_INCSR_INPKTRDY); + + /* last packet is usually short (or a zlp) */ + if (unlikely(count != max)) + is_last = is_short = 1; + else { + if (likely(req->req.length != req->req.actual) + || req->req.zero) + is_last = 0; + else + is_last = 1; + /* interrupt/iso maxpacket may not fill the fifo */ + is_short = unlikely(max < ep_maxpacket(ep)); + } + + DEBUG("%s: wrote %s %d bytes%s%s %d left %p\n", __FUNCTION__, + ep->ep.name, count, + is_last ? "/L" : "", is_short ? "/S" : "", + req->req.length - req->req.actual, req); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + done(ep, req, 0); + if (list_empty(&ep->queue)) { + pio_irq_disable(ep); + } + return 1; + } + } else { + DEBUG("Hmm.. %d ep FIFO is not empty!\n", ep_index(ep)); + } + + return 0; +} + +/** Read to request from FIFO (max read == bytes in fifo) + * Return: 0 = still running, 1 = completed, negative = errno + * NOTE: INDEX register must be set for EP + */ +static int read_fifo(struct jz4740_ep *ep, struct jz4740_request *req) +{ + u32 csr; + unsigned count, is_short; + u32 physaddr = virt_to_phys((void *)req->req.buf); + + if (use_dma) { + u32 dma_count; + + /* DMA interrupt generated due to a packet less than MAXP loaded into the FIFO */ + + dma_count = usb_readl(ep->reg_addr) - physaddr; + req->req.actual += dma_count; + + /* Disable interrupt and DMA */ + pio_irq_disable(ep); + usb_writel(USB_REG_CNTL2, 0); + + /* Read all bytes from this packet */ + count = usb_readw(USB_REG_OUTCOUNT); + count = read_packet(ep, req, count); + + if (count) { + /* If the last packet is greater than zero, clear OUTPKTRDY manually */ + usb_clearb(ep->csr, USB_OUTCSR_OUTPKTRDY); + } + + done(ep, req, 0); + + if (!list_empty(&ep->queue)) { + /* advance the request queue */ + req = list_entry(ep->queue.next, struct jz4740_request, queue); + kick_dma(ep, req); + } + + return 1; + } + + /* + * PIO mode handling starts here ... + */ + + /* make sure there's a packet in the FIFO. */ + csr = usb_readb(ep->csr); + if (!(csr & USB_OUTCSR_OUTPKTRDY)) { + DEBUG("%s: Packet NOT ready!\n", __FUNCTION__); + return -EINVAL; + } + + /* read all bytes from this packet */ + count = usb_readw(USB_REG_OUTCOUNT); + + is_short = (count < ep->ep.maxpacket); + + count = read_packet(ep, req, count); + + DEBUG("read %s %02x, %d bytes%s req %p %d/%d\n", + ep->ep.name, csr, count, + is_short ? "/S" : "", req, req->req.actual, req->req.length); + + /* Clear OutPktRdy */ + usb_clearb(ep->csr, USB_OUTCSR_OUTPKTRDY); + + /* completion */ + if (is_short || req->req.actual == req->req.length) { + done(ep, req, 0); + + if (list_empty(&ep->queue)) + pio_irq_disable(ep); + return 1; + } + + /* finished that packet. the next one may be waiting... */ + return 0; +} + +/* + * done - retire a request; caller blocked irqs + * INDEX register is preserved to keep same + */ +static void done(struct jz4740_ep *ep, struct jz4740_request *req, int status) +{ + unsigned int stopped = ep->stopped; + u32 index; + + DEBUG("%s, %p\n", __FUNCTION__, ep); + list_del_init(&req->queue); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + DEBUG("complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + /* Read current index (completion may modify it) */ + index = usb_readb(USB_REG_INDEX); + + spin_unlock(&ep->dev->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->dev->lock); + + /* Restore index */ + usb_set_index(index); + ep->stopped = stopped; +} + +/** Enable EP interrupt */ +static void pio_irq_enable(struct jz4740_ep *ep) +{ + DEBUG("%s: EP%d %s\n", __FUNCTION__, ep_index(ep), ep_is_in(ep) ? "IN": "OUT"); + + if (ep_is_in(ep)) { + switch (ep_index(ep)) { + case 1: + usb_setw(USB_REG_INTRINE, USB_INTR_INEP1); + break; + case 2: + usb_setw(USB_REG_INTRINE, USB_INTR_INEP2); + break; + default: + DEBUG("Unknown endpoint: %d\n", ep_index(ep)); + break; + } + } + else { + switch (ep_index(ep)) { + case 1: + usb_setw(USB_REG_INTROUTE, USB_INTR_OUTEP1); + break; + default: + DEBUG("Unknown endpoint: %d\n", ep_index(ep)); + break; + } + } +} + +/** Disable EP interrupt */ +static void pio_irq_disable(struct jz4740_ep *ep) +{ + DEBUG("%s: EP%d %s\n", __FUNCTION__, ep_index(ep), ep_is_in(ep) ? "IN": "OUT"); + + if (ep_is_in(ep)) { + switch (ep_index(ep)) { + case 1: + usb_clearw(USB_REG_INTRINE, USB_INTR_INEP1); + break; + case 2: + usb_clearw(USB_REG_INTRINE, USB_INTR_INEP2); + break; + default: + DEBUG("Unknown endpoint: %d\n", ep_index(ep)); + break; + } + } + else { + switch (ep_index(ep)) { + case 1: + usb_clearw(USB_REG_INTROUTE, USB_INTR_OUTEP1); + break; + default: + DEBUG("Unknown endpoint: %d\n", ep_index(ep)); + break; + } + } +} + +/* + * nuke - dequeue ALL requests + */ +static void nuke(struct jz4740_ep *ep, int status) +{ + struct jz4740_request *req; + + DEBUG("%s, %p\n", __FUNCTION__, ep); + + /* Flush FIFO */ + flush(ep); + + /* called with irqs blocked */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct jz4740_request, queue); + done(ep, req, status); + } + + /* Disable IRQ if EP is enabled (has descriptor) */ + if (ep->desc) + pio_irq_disable(ep); +} + +/** Flush EP FIFO + * NOTE: INDEX register must be set before this call + */ +static void flush(struct jz4740_ep *ep) +{ + DEBUG("%s, %p\n", __FUNCTION__, ep); + + switch (ep->ep_type) { + case ep_control: + break; + + case ep_bulk_in: + case ep_interrupt: + usb_setb(ep->csr, USB_INCSR_FF); + break; + + case ep_bulk_out: + usb_setb(ep->csr, USB_OUTCSR_FF); + break; + } +} + +/** + * jz4740_in_epn - handle IN interrupt + */ +static void jz4740_in_epn(struct jz4740_udc *dev, u32 ep_idx, u32 intr) +{ + u32 csr; + struct jz4740_ep *ep = &dev->ep[ep_idx + 1]; + struct jz4740_request *req; + + usb_set_index(ep_index(ep)); + + csr = usb_readb(ep->csr); + DEBUG("%s: %d, csr %x\n", __FUNCTION__, ep_idx, csr); + + if (csr & USB_INCSR_SENTSTALL) { + DEBUG("USB_INCSR_SENTSTALL\n"); + usb_clearb(ep->csr, USB_INCSR_SENTSTALL); + return; + } + + if (!ep->desc) { + DEBUG("%s: NO EP DESC\n", __FUNCTION__); + return; + } + + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct jz4740_request, queue); + + DEBUG("req: %p\n", req); + + if (!req) + return; + + write_fifo(ep, req); +} + +/* + * Bulk OUT (recv) + */ +static void jz4740_out_epn(struct jz4740_udc *dev, u32 ep_idx, u32 intr) +{ + struct jz4740_ep *ep = &dev->ep[ep_idx]; + struct jz4740_request *req; + + DEBUG("%s: %d\n", __FUNCTION__, ep_idx); + + usb_set_index(ep_index(ep)); + if (ep->desc) { + u32 csr; + + if (use_dma) { + /* DMA starts here ... */ + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct jz4740_request, queue); + + if (req) + read_fifo(ep, req); + return; + } + + /* + * PIO mode starts here ... + */ + + while ((csr = usb_readb(ep->csr)) & + (USB_OUTCSR_OUTPKTRDY | USB_OUTCSR_SENTSTALL)) { + DEBUG("%s: %x\n", __FUNCTION__, csr); + + if (csr & USB_OUTCSR_SENTSTALL) { + DEBUG("%s: stall sent, flush fifo\n", + __FUNCTION__); + /* usb_set(USB_OUT_CSR1_FIFO_FLUSH, ep->csr1); */ + flush(ep); + } else if (csr & USB_OUTCSR_OUTPKTRDY) { + if (list_empty(&ep->queue)) + req = 0; + else + req = + list_entry(ep->queue.next, + struct jz4740_request, + queue); + + if (!req) { + DEBUG("%s: NULL REQ %d\n", + __FUNCTION__, ep_idx); + break; + } else { + read_fifo(ep, req); + } + } + } + } else { + /* Throw packet away.. */ + printk("%s: ep %p ep_indx %d No descriptor?!?\n", __FUNCTION__, ep, ep_idx); + flush(ep); + } +} + +static int jz4740_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct jz4740_ep *ep; + struct jz4740_udc *dev; + unsigned long flags; + u32 max, csrh = 0; + + ep = container_of(_ep, struct jz4740_ep, ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || ep->bEndpointAddress != desc->bEndpointAddress) { + DEBUG("%s, bad ep or descriptor\n", __FUNCTION__); + return -EINVAL; + } + + /* xfer types must match, except that interrupt ~= bulk */ + if (ep->bmAttributes != desc->bmAttributes + && ep->bmAttributes != USB_ENDPOINT_XFER_BULK + && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { + DEBUG("%s, %s type mismatch\n", __FUNCTION__, _ep->name); + return -EINVAL; + } + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + DEBUG("%s, bogus device state\n", __FUNCTION__); + return -ESHUTDOWN; + } + + max = le16_to_cpu(desc->wMaxPacketSize); + + /* Configure the endpoint */ + usb_set_index(desc->bEndpointAddress & 0x0F); + if (ep_is_in(ep)) { + usb_writew(USB_REG_INMAXP, max); + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + csrh &= ~USB_INCSRH_ISO; + break; + case USB_ENDPOINT_XFER_ISOC: + csrh |= USB_INCSRH_ISO; + break; + } + usb_writeb(USB_REG_INCSRH, csrh); + } + else { + usb_writew(USB_REG_OUTMAXP, max); + switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + csrh &= ~USB_OUTCSRH_ISO; + break; + case USB_ENDPOINT_XFER_INT: + csrh &= ~USB_OUTCSRH_ISO; + csrh |= USB_OUTCSRH_DNYT; + break; + case USB_ENDPOINT_XFER_ISOC: + csrh |= USB_OUTCSRH_ISO; + break; + } + usb_writeb(USB_REG_OUTCSRH, csrh); + } + + spin_lock_irqsave(&ep->dev->lock, flags); + + ep->stopped = 0; + ep->desc = desc; + ep->pio_irqs = 0; + ep->ep.maxpacket = max; + + spin_unlock_irqrestore(&ep->dev->lock, flags); + + /* Reset halt state (does flush) */ + jz4740_set_halt(_ep, 0); + + DEBUG("%s: enabled %s\n", __FUNCTION__, _ep->name); + + return 0; +} + +/** Disable EP + * NOTE: Sets INDEX register + */ +static int jz4740_ep_disable(struct usb_ep *_ep) +{ + struct jz4740_ep *ep; + unsigned long flags; + + DEBUG("%s, %p\n", __FUNCTION__, _ep); + + ep = container_of(_ep, struct jz4740_ep, ep); + if (!_ep || !ep->desc) { + DEBUG("%s, %s not enabled\n", __FUNCTION__, + _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + spin_lock_irqsave(&ep->dev->lock, flags); + + usb_set_index(ep_index(ep)); + + /* Nuke all pending requests (does flush) */ + nuke(ep, -ESHUTDOWN); + + /* Disable ep IRQ */ + pio_irq_disable(ep); + + ep->desc = 0; + ep->stopped = 1; + + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DEBUG("%s: disabled %s\n", __FUNCTION__, _ep->name); + return 0; +} + +static struct usb_request *jz4740_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) +{ + struct jz4740_request *req; + + DEBUG("%s, %p\n", __FUNCTION__, ep); + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return 0; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void jz4740_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct jz4740_request *req; + + DEBUG("%s, %p\n", __FUNCTION__, ep); + + req = container_of(_req, struct jz4740_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +/*--------------------------------------------------------------------*/ + +/** Queue one request + * Kickstart transfer if needed + * NOTE: Sets INDEX register + */ +static int jz4740_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct jz4740_request *req; + struct jz4740_ep *ep; + struct jz4740_udc *dev; + unsigned long flags; + + DEBUG("%s, %p\n", __FUNCTION__, _ep); + + req = container_of(_req, struct jz4740_request, req); + if (unlikely + (!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue))) { + DEBUG("%s, bad params\n", __FUNCTION__); + return -EINVAL; + } + + ep = container_of(_ep, struct jz4740_ep, ep); + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + DEBUG("%s, bad ep\n", __FUNCTION__); + return -EINVAL; + } + + dev = ep->dev; + if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + DEBUG("%s, bogus device state %p\n", __FUNCTION__, dev->driver); + return -ESHUTDOWN; + } + + DEBUG("%s queue req %p, len %d buf %p\n", _ep->name, _req, _req->length, + _req->buf); + + spin_lock_irqsave(&dev->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* kickstart this i/o queue? */ + DEBUG("Add to %d Q %d %d\n", ep_index(ep), list_empty(&ep->queue), + ep->stopped); + if (list_empty(&ep->queue) && likely(!ep->stopped)) { + u32 csr; + + if (unlikely(ep_index(ep) == 0)) { + /* EP0 */ + list_add_tail(&req->queue, &ep->queue); + jz4740_ep0_kick(dev, ep); + req = 0; + } else if (use_dma) { + /* DMA */ + kick_dma(ep, req); + } + /* PIO */ + else if (ep_is_in(ep)) { + /* EP1 & EP2 */ + usb_set_index(ep_index(ep)); + csr = usb_readb(ep->csr); + pio_irq_enable(ep); + if (!(csr & USB_INCSR_FFNOTEMPT)) { + if (write_fifo(ep, req) == 1) + req = 0; + } + } else { + /* EP1 */ + usb_set_index(ep_index(ep)); + csr = usb_readb(ep->csr); + pio_irq_enable(ep); + if (csr & USB_OUTCSR_OUTPKTRDY) { + if (read_fifo(ep, req) == 1) + req = 0; + } + } + } + + /* pio or dma irq handler advances the queue. */ + if (likely(req != 0)) + list_add_tail(&req->queue, &ep->queue); + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/* dequeue JUST ONE request */ +static int jz4740_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct jz4740_ep *ep; + struct jz4740_request *req; + unsigned long flags; + + DEBUG("%s, %p\n", __FUNCTION__, _ep); + + ep = container_of(_ep, struct jz4740_ep, ep); + if (!_ep || ep->ep.name == ep0name) + return -EINVAL; + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->dev->lock, flags); + return -EINVAL; + } + + done(ep, req, -ECONNRESET); + + spin_unlock_irqrestore(&ep->dev->lock, flags); + return 0; +} + +/** Halt specific EP + * Return 0 if success + * NOTE: Sets INDEX register to EP ! + */ +static int jz4740_set_halt(struct usb_ep *_ep, int value) +{ + struct jz4740_ep *ep; + unsigned long flags; + + ep = container_of(_ep, struct jz4740_ep, ep); + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + DEBUG("%s, bad ep\n", __FUNCTION__); + return -EINVAL; + } + + usb_set_index(ep_index(ep)); + + DEBUG("%s, ep %d, val %d\n", __FUNCTION__, ep_index(ep), value); + + spin_lock_irqsave(&ep->dev->lock, flags); + + if (ep_index(ep) == 0) { + /* EP0 */ + usb_setb(USB_REG_CSR0, USB_CSR0_SENDSTALL); + } else if (ep_is_in(ep)) { + u32 csr = usb_readb(ep->csr); + if (value && ((csr & USB_INCSR_FFNOTEMPT) + || !list_empty(&ep->queue))) { + /* + * Attempts to halt IN endpoints will fail (returning -EAGAIN) + * if any transfer requests are still queued, or if the controller + * FIFO still holds bytes that the host hasnÂ’t collected. + */ + spin_unlock_irqrestore(&ep->dev->lock, flags); + DEBUG + ("Attempt to halt IN endpoint failed (returning -EAGAIN) %d %d\n", + (csr & USB_INCSR_FFNOTEMPT), + !list_empty(&ep->queue)); + return -EAGAIN; + } + flush(ep); + if (value) { + usb_setb(ep->csr, USB_INCSR_SENDSTALL); + } + else { + usb_clearb(ep->csr, USB_INCSR_SENDSTALL); + usb_setb(ep->csr, USB_INCSR_CDT); + } + } else { + + flush(ep); + if (value) { + usb_setb(ep->csr, USB_OUTCSR_SENDSTALL); + } + else { + usb_clearb(ep->csr, USB_OUTCSR_SENDSTALL); + usb_setb(ep->csr, USB_OUTCSR_CDT); + } + } + + if (value) { + ep->stopped = 1; + } else { + ep->stopped = 0; + } + + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DEBUG("%s %s halted\n", _ep->name, value == 0 ? "NOT" : "IS"); + + return 0; +} + +/** Return bytes in EP FIFO + * NOTE: Sets INDEX register to EP + */ +static int jz4740_fifo_status(struct usb_ep *_ep) +{ + u32 csr; + int count = 0; + struct jz4740_ep *ep; + + ep = container_of(_ep, struct jz4740_ep, ep); + if (!_ep) { + DEBUG("%s, bad ep\n", __FUNCTION__); + return -ENODEV; + } + + DEBUG("%s, %d\n", __FUNCTION__, ep_index(ep)); + + /* LPD can't report unclaimed bytes from IN fifos */ + if (ep_is_in(ep)) + return -EOPNOTSUPP; + + usb_set_index(ep_index(ep)); + + csr = usb_readb(ep->csr); + if (ep->dev->gadget.speed != USB_SPEED_UNKNOWN || + csr & 0x1) { + count = usb_readw(USB_REG_OUTCOUNT); + } + + return count; +} + +/** Flush EP FIFO + * NOTE: Sets INDEX register to EP + */ +static void jz4740_fifo_flush(struct usb_ep *_ep) +{ + struct jz4740_ep *ep; + + ep = container_of(_ep, struct jz4740_ep, ep); + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + DEBUG("%s, bad ep\n", __FUNCTION__); + return; + } + + usb_set_index(ep_index(ep)); + flush(ep); +} + +/****************************************************************/ +/* End Point 0 related functions */ +/****************************************************************/ + +/* return: 0 = still running, 1 = completed, negative = errno */ +static int write_fifo_ep0(struct jz4740_ep *ep, struct jz4740_request *req) +{ + u32 max; + unsigned count; + int is_last; + + max = ep_maxpacket(ep); + + count = write_packet(ep, req, max); + + /* last packet is usually short (or a zlp) */ + if (unlikely(count != max)) + is_last = 1; + else { + if (likely(req->req.length != req->req.actual) || req->req.zero) + is_last = 0; + else + is_last = 1; + } + + DEBUG_EP0("%s: wrote %s %d bytes%s %d left %p\n", __FUNCTION__, + ep->ep.name, count, + is_last ? "/L" : "", req->req.length - req->req.actual, req); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + done(ep, req, 0); + return 1; + } + + return 0; +} + +static __inline__ int jz4740_fifo_read(struct jz4740_ep *ep, + unsigned char *cp, int max) +{ + int bytes; + int count = usb_readw(USB_REG_OUTCOUNT); + volatile u8 *fifo = (volatile u8 *)ep->fifo; + + if (count > max) + count = max; + bytes = count; + while (count--) + *cp++ = *fifo; + return bytes; +} + +static __inline__ void jz4740_fifo_write(struct jz4740_ep *ep, + unsigned char *cp, int count) +{ + volatile u8 *fifo = (volatile u8 *)ep->fifo; + DEBUG_EP0("fifo_write: %d %d\n", ep_index(ep), count); + while (count--) + *fifo = *cp++; +} + +static int read_fifo_ep0(struct jz4740_ep *ep, struct jz4740_request *req) +{ + u32 csr; + u8 *buf; + unsigned bufferspace, count, is_short; + volatile u8 *fifo = (volatile u8 *)ep->fifo; + + DEBUG_EP0("%s\n", __FUNCTION__); + + csr = usb_readb(USB_REG_CSR0); + if (!(csr & USB_CSR0_OUTPKTRDY)) + return 0; + + buf = req->req.buf + req->req.actual; + prefetchw(buf); + bufferspace = req->req.length - req->req.actual; + + /* read all bytes from this packet */ + if (likely(csr & USB_CSR0_OUTPKTRDY)) { + count = usb_readw(USB_REG_OUTCOUNT); + req->req.actual += min(count, bufferspace); + } else /* zlp */ + count = 0; + + is_short = (count < ep->ep.maxpacket); + DEBUG_EP0("read %s %02x, %d bytes%s req %p %d/%d\n", + ep->ep.name, csr, count, + is_short ? "/S" : "", req, req->req.actual, req->req.length); + + while (likely(count-- != 0)) { + u8 byte = (u8) (*fifo & 0xff); + + if (unlikely(bufferspace == 0)) { + /* this happens when the driver's buffer + * is smaller than what the host sent. + * discard the extra data. + */ + if (req->req.status != -EOVERFLOW) + DEBUG_EP0("%s overflow %d\n", ep->ep.name, + count); + req->req.status = -EOVERFLOW; + } else { + *buf++ = byte; + bufferspace--; + } + } + + /* completion */ + if (is_short || req->req.actual == req->req.length) { + done(ep, req, 0); + return 1; + } + + /* finished that packet. the next one may be waiting... */ + return 0; +} + +/** + * udc_set_address - set the USB address for this device + * @address: + * + * Called from control endpoint function after it decodes a set address setup packet. + */ +static void udc_set_address(struct jz4740_udc *dev, unsigned char address) +{ + DEBUG_EP0("%s: %d\n", __FUNCTION__, address); + + dev->usb_address = address; + usb_writeb(USB_REG_FADDR, address); +} + +/* + * DATA_STATE_RECV (USB_CSR0_OUTPKTRDY) + * - if error + * set USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND | USB_CSR0_SENDSTALL bits + * - else + * set USB_CSR0_SVDOUTPKTRDY bit + if last set USB_CSR0_DATAEND bit + */ +static void jz4740_ep0_out(struct jz4740_udc *dev, u32 csr) +{ + struct jz4740_request *req; + struct jz4740_ep *ep = &dev->ep[0]; + int ret; + + DEBUG_EP0("%s: %x\n", __FUNCTION__, csr); + + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct jz4740_request, queue); + + if (req) { + if (req->req.length == 0) { + DEBUG_EP0("ZERO LENGTH OUT!\n"); +/* usb_setb(USB_REG_CSR0, (USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND)); */ + usb_setb(USB_REG_CSR0, (USB_CSR0_SVDOUTPKTRDY)); + dev->ep0state = WAIT_FOR_SETUP; + return; + } + ret = read_fifo_ep0(ep, req); + if (ret) { + /* Done! */ + DEBUG_EP0("%s: finished, waiting for status\n", + __FUNCTION__); + usb_setb(USB_REG_CSR0, (USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND)); + dev->ep0state = WAIT_FOR_SETUP; + } else { + /* Not done yet.. */ + DEBUG_EP0("%s: not finished\n", __FUNCTION__); + usb_setb(USB_REG_CSR0, USB_CSR0_SVDOUTPKTRDY); + } + } else { + DEBUG_EP0("NO REQ??!\n"); + } +} + +/* + * DATA_STATE_XMIT + */ +static int jz4740_ep0_in(struct jz4740_udc *dev, u32 csr) +{ + struct jz4740_request *req; + struct jz4740_ep *ep = &dev->ep[0]; + int ret, need_zlp = 0; + + DEBUG_EP0("%s: %x\n", __FUNCTION__, csr); + + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct jz4740_request, queue); + + if (!req) { + DEBUG_EP0("%s: NULL REQ\n", __FUNCTION__); + return 0; + } + + if (req->req.length == 0) { + usb_setb(USB_REG_CSR0, (USB_CSR0_INPKTRDY | USB_CSR0_DATAEND)); + dev->ep0state = WAIT_FOR_SETUP; + return 1; + } + + if (req->req.length - req->req.actual == EP0_MAXPACKETSIZE) { + /* Next write will end with the packet size, */ + /* so we need zero-length-packet */ + need_zlp = 1; + } + + ret = write_fifo_ep0(ep, req); + + if (ret == 1 && !need_zlp) { + /* Last packet */ + DEBUG_EP0("%s: finished, waiting for status\n", __FUNCTION__); + + usb_setb(USB_REG_CSR0, (USB_CSR0_INPKTRDY | USB_CSR0_DATAEND)); + dev->ep0state = WAIT_FOR_SETUP; + } else { + DEBUG_EP0("%s: not finished\n", __FUNCTION__); + usb_setb(USB_REG_CSR0, USB_CSR0_INPKTRDY); + } + + if (need_zlp) { + DEBUG_EP0("%s: Need ZLP!\n", __FUNCTION__); + usb_setb(USB_REG_CSR0, USB_CSR0_INPKTRDY); + dev->ep0state = DATA_STATE_NEED_ZLP; + } + + return 1; +} + +#if 0 +static int jz4740_handle_get_status(struct jz4740_udc *dev, + struct usb_ctrlrequest *ctrl) +{ + struct jz4740_ep *ep0 = &dev->ep[0]; + struct jz4740_ep *qep; + int reqtype = (ctrl->bRequestType & USB_RECIP_MASK); + u16 val = 0; + + if (reqtype == USB_RECIP_INTERFACE) { + /* This is not supported. + * And according to the USB spec, this one does nothing.. + * Just return 0 + */ + DEBUG_SETUP("GET_STATUS: USB_RECIP_INTERFACE\n"); + } else if (reqtype == USB_RECIP_DEVICE) { + DEBUG_SETUP("GET_STATUS: USB_RECIP_DEVICE\n"); + val |= (1 << 0); /* Self powered */ + /*val |= (1<<1); *//* Remote wakeup */ + } else if (reqtype == USB_RECIP_ENDPOINT) { + int ep_num = (ctrl->wIndex & ~USB_DIR_IN); + + DEBUG_SETUP + ("GET_STATUS: USB_RECIP_ENDPOINT (%d), ctrl->wLength = %d\n", + ep_num, ctrl->wLength); + + if (ctrl->wLength > 2 || ep_num > 3) + return -EOPNOTSUPP; + + qep = &dev->ep[ep_num]; + if (ep_is_in(qep) != ((ctrl->wIndex & USB_DIR_IN) ? 1 : 0) + && ep_index(qep) != 0) { + return -EOPNOTSUPP; + } + + usb_set_index(ep_index(qep)); + + /* Return status on next IN token */ + switch (qep->ep_type) { + case ep_control: + val = + (usb_readb(qep->csr) & USB_CSR0_SENDSTALL) == + USB_CSR0_SENDSTALL; + break; + case ep_bulk_in: + case ep_interrupt: + val = + (usb_readb(qep->csr) & USB_INCSR_SENDSTALL) == + USB_INCSR_SENDSTALL; + break; + case ep_bulk_out: + val = + (usb_readb(qep->csr) & USB_OUTCSR_SENDSTALL) == + USB_OUTCSR_SENDSTALL; + break; + } + + /* Back to EP0 index */ + usb_set_index(0); + + DEBUG_SETUP("GET_STATUS, ep: %d (%x), val = %d\n", ep_num, + ctrl->wIndex, val); + } else { + DEBUG_SETUP("Unknown REQ TYPE: %d\n", reqtype); + return -EOPNOTSUPP; + } + + /* Clear "out packet ready" */ + usb_setb(USB_REG_CSR0, USB_CSR0_SVDOUTPKTRDY); + /* Put status to FIFO */ + jz4740_fifo_write(ep0, (u8 *) & val, sizeof(val)); + /* Issue "In packet ready" */ + usb_setb(USB_REG_CSR0, (USB_CSR0_INPKTRDY | USB_CSR0_DATAEND)); + + return 0; +} +#endif + +/* + * WAIT_FOR_SETUP (OUTPKTRDY) + * - read data packet from EP0 FIFO + * - decode command + * - if error + * set USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND | USB_CSR0_SENDSTALL bits + * - else + * set USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND bits + */ +static void jz4740_ep0_setup(struct jz4740_udc *dev, u32 csr) +{ + struct jz4740_ep *ep = &dev->ep[0]; + struct usb_ctrlrequest ctrl; + int i; + + DEBUG_SETUP("%s: %x\n", __FUNCTION__, csr); + + /* Nuke all previous transfers */ + nuke(ep, -EPROTO); + + /* read control req from fifo (8 bytes) */ + jz4740_fifo_read(ep, (unsigned char *)&ctrl, 8); + + DEBUG_SETUP("SETUP %02x.%02x v%04x i%04x l%04x\n", + ctrl.bRequestType, ctrl.bRequest, + ctrl.wValue, ctrl.wIndex, ctrl.wLength); + + /* Set direction of EP0 */ + if (likely(ctrl.bRequestType & USB_DIR_IN)) { + ep->bEndpointAddress |= USB_DIR_IN; + } else { + ep->bEndpointAddress &= ~USB_DIR_IN; + } + + /* Handle some SETUP packets ourselves */ + switch (ctrl.bRequest) { + case USB_REQ_SET_ADDRESS: + if (ctrl.bRequestType != (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) + break; + + DEBUG_SETUP("USB_REQ_SET_ADDRESS (%d)\n", ctrl.wValue); + udc_set_address(dev, ctrl.wValue); + usb_setb(USB_REG_CSR0, (USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND)); + return; + + case USB_REQ_SET_CONFIGURATION: + if (ctrl.bRequestType != (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) + break; + + DEBUG_SETUP("USB_REQ_SET_CONFIGURATION (%d)\n", ctrl.wValue); + usb_setb(USB_REG_CSR0, (USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND)); + + /* Enable RESUME and SUSPEND interrupts */ + usb_setb(USB_REG_INTRUSBE, (USB_INTR_RESUME | USB_INTR_SUSPEND)); + break; + + case USB_REQ_SET_INTERFACE: + if (ctrl.bRequestType != (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) + break; + + DEBUG_SETUP("USB_REQ_SET_INTERFACE (%d)\n", ctrl.wValue); + usb_setb(USB_REG_CSR0, (USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND)); + break; + +// case USB_REQ_GET_STATUS: +// if (jz4740_handle_get_status(dev, &ctrl) == 0) +// return; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + if (ctrl.bRequestType == USB_RECIP_ENDPOINT) { + struct jz4740_ep *qep; + int ep_num = (ctrl.wIndex & 0x0f); + + /* Support only HALT feature */ + if (ctrl.wValue != 0 || ctrl.wLength != 0 + || ep_num > 3 || ep_num < 1) + break; + + qep = &dev->ep[ep_num]; + spin_unlock(&dev->lock); + if (ctrl.bRequest == USB_REQ_SET_FEATURE) { + DEBUG_SETUP("SET_FEATURE (%d)\n", + ep_num); + jz4740_set_halt(&qep->ep, 1); + } else { + DEBUG_SETUP("CLR_FEATURE (%d)\n", + ep_num); + jz4740_set_halt(&qep->ep, 0); + } + spin_lock(&dev->lock); + + usb_set_index(0); + + /* Reply with a ZLP on next IN token */ + usb_setb(USB_REG_CSR0, + (USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND)); + return; + } + break; + + default: + break; + } + + /* gadget drivers see class/vendor specific requests, + * {SET,GET}_{INTERFACE,DESCRIPTOR,CONFIGURATION}, + * and more. + */ + if (likely((u32)dev->driver)) { + /* device-2-host (IN) or no data setup command, process immediately */ + spin_unlock(&dev->lock); + + i = dev->driver->setup(&dev->gadget, &ctrl); + spin_lock(&dev->lock); + + if (unlikely(i < 0)) { + /* setup processing failed, force stall */ + DEBUG_SETUP + (" --> ERROR: gadget setup FAILED (stalling), setup returned %d\n", + i); + usb_set_index(0); + usb_setb(USB_REG_CSR0, (USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND | USB_CSR0_SENDSTALL)); + + /* ep->stopped = 1; */ + dev->ep0state = WAIT_FOR_SETUP; + } + else { + DEBUG_SETUP("gadget driver setup ok (%d)\n", ctrl.wLength); + if (!ctrl.wLength) { + usb_setb(USB_REG_CSR0, USB_CSR0_SVDOUTPKTRDY); + } + } + } +} + +/* + * DATA_STATE_NEED_ZLP + */ +static void jz4740_ep0_in_zlp(struct jz4740_udc *dev, u32 csr) +{ + DEBUG_EP0("%s: %x\n", __FUNCTION__, csr); + + usb_setb(USB_REG_CSR0, (USB_CSR0_INPKTRDY | USB_CSR0_DATAEND)); + dev->ep0state = WAIT_FOR_SETUP; +} + +/* + * handle ep0 interrupt + */ +static void jz4740_handle_ep0(struct jz4740_udc *dev, u32 intr) +{ + struct jz4740_ep *ep = &dev->ep[0]; + u32 csr; + + /* Set index 0 */ + usb_set_index(0); + csr = usb_readb(USB_REG_CSR0); + + DEBUG_EP0("%s: csr = %x state = \n", __FUNCTION__, csr);//, state_names[dev->ep0state]); + + /* + * if SENT_STALL is set + * - clear the SENT_STALL bit + */ + if (csr & USB_CSR0_SENTSTALL) { + DEBUG_EP0("%s: USB_CSR0_SENTSTALL is set: %x\n", __FUNCTION__, csr); + usb_clearb(USB_REG_CSR0, USB_CSR0_SENDSTALL | USB_CSR0_SENTSTALL); + nuke(ep, -ECONNABORTED); + dev->ep0state = WAIT_FOR_SETUP; + return; + } + + /* + * if a transfer is in progress && INPKTRDY and OUTPKTRDY are clear + * - fill EP0 FIFO + * - if last packet + * - set IN_PKT_RDY | DATA_END + * - else + * set IN_PKT_RDY + */ + if (!(csr & (USB_CSR0_INPKTRDY | USB_CSR0_OUTPKTRDY))) { + DEBUG_EP0("%s: INPKTRDY and OUTPKTRDY are clear\n", + __FUNCTION__); + + switch (dev->ep0state) { + case DATA_STATE_XMIT: + DEBUG_EP0("continue with DATA_STATE_XMIT\n"); + jz4740_ep0_in(dev, csr); + return; + case DATA_STATE_NEED_ZLP: + DEBUG_EP0("continue with DATA_STATE_NEED_ZLP\n"); + jz4740_ep0_in_zlp(dev, csr); + return; + default: + /* Stall? */ +// DEBUG_EP0("Odd state!! state = %s\n", +// state_names[dev->ep0state]); + dev->ep0state = WAIT_FOR_SETUP; + /* nuke(ep, 0); */ + /* usb_setb(ep->csr, USB_CSR0_SENDSTALL); */ +// break; + return; + } + } + + /* + * if SETUPEND is set + * - abort the last transfer + * - set SERVICED_SETUP_END_BIT + */ + if (csr & USB_CSR0_SETUPEND) { + DEBUG_EP0("%s: USB_CSR0_SETUPEND is set: %x\n", __FUNCTION__, csr); + + usb_setb(USB_REG_CSR0, USB_CSR0_SVDSETUPEND); + nuke(ep, 0); + dev->ep0state = WAIT_FOR_SETUP; + } + + /* + * if USB_CSR0_OUTPKTRDY is set + * - read data packet from EP0 FIFO + * - decode command + * - if error + * set SVDOUTPKTRDY | DATAEND | SENDSTALL bits + * - else + * set SVDOUTPKTRDY | DATAEND bits + */ + if (csr & USB_CSR0_OUTPKTRDY) { + + DEBUG_EP0("%s: EP0_OUT_PKT_RDY is set: %x\n", __FUNCTION__, + csr); + + switch (dev->ep0state) { + case WAIT_FOR_SETUP: + DEBUG_EP0("WAIT_FOR_SETUP\n"); + jz4740_ep0_setup(dev, csr); + break; + + case DATA_STATE_RECV: + DEBUG_EP0("DATA_STATE_RECV\n"); + jz4740_ep0_out(dev, csr); + break; + + default: + /* send stall? */ + DEBUG_EP0("strange state!! 2. send stall? state = %d\n", + dev->ep0state); + break; + } + } +} + +static void jz4740_ep0_kick(struct jz4740_udc *dev, struct jz4740_ep *ep) +{ + u32 csr; + + usb_set_index(0); + csr = usb_readb(USB_REG_CSR0); + + DEBUG_EP0("%s: %x\n", __FUNCTION__, csr); + + /* Clear "out packet ready" */ + usb_setb(USB_REG_CSR0, USB_CSR0_SVDOUTPKTRDY); + + if (ep_is_in(ep)) { + dev->ep0state = DATA_STATE_XMIT; + jz4740_ep0_in(dev, csr); + } else { + dev->ep0state = DATA_STATE_RECV; + jz4740_ep0_out(dev, csr); + } +} + +/** Handle USB RESET interrupt + */ +static void jz4740_reset_irq(struct jz4740_udc *dev) +{ + dev->gadget.speed = (usb_readb(USB_REG_POWER) & USB_POWER_HSMODE) ? + USB_SPEED_HIGH : USB_SPEED_FULL; + + DEBUG_SETUP("%s: address = %d, speed = %s\n", __FUNCTION__, dev->usb_address, + (dev->gadget.speed == USB_SPEED_HIGH) ? "HIGH":"FULL" ); +} + +/* + * jz4740 usb device interrupt handler. + */ +static irqreturn_t jz4740_udc_irq(int irq, void *_dev) +{ + struct jz4740_udc *dev = _dev; + + u32 intr_usb = usb_readb(USB_REG_INTRUSB) & 0x7; /* mask SOF */ + u32 intr_in = usb_readw(USB_REG_INTRIN); + u32 intr_out = usb_readw(USB_REG_INTROUT); + u32 intr_dma = usb_readb(USB_REG_INTR); + + if (!intr_usb && !intr_in && !intr_out && !intr_dma) + return IRQ_HANDLED; + + DEBUG("intr_out = %x intr_in=%x intr_usb=%x\n", + intr_out, intr_in, intr_usb); + + spin_lock(&dev->lock); + + /* Check for resume from suspend mode */ + if ((intr_usb & USB_INTR_RESUME) && + (usb_readb(USB_REG_INTRUSBE) & USB_INTR_RESUME)) { + DEBUG("USB resume\n"); + } + + /* Check for system interrupts */ + if (intr_usb & USB_INTR_RESET) { + DEBUG("USB reset\n"); +#ifdef CONFIG_JZ_UDC_HOTPLUG + jz_udc_active = 1; +#endif + if (udc_debug) { + /* We have tested the cable type, disable module and + * disconnect from host right now. + */ + udc_disable(dev); + spin_unlock(&dev->lock); + return IRQ_HANDLED; + } + jz4740_reset_irq(dev); + } + + /* Check for endpoint 0 interrupt */ + if (intr_in & USB_INTR_EP0) { + DEBUG("USB_INTR_EP0 (control)\n"); + jz4740_handle_ep0(dev, intr_in); + } + + /* Check for Bulk-IN DMA interrupt */ + if (intr_dma & 0x1) { + int ep_num; + ep_num = (usb_readl(USB_REG_CNTL1) >> 4) & 0xf; + jz4740_in_epn(dev, ep_num, intr_in); + } + + /* Check for Bulk-OUT DMA interrupt */ + if (intr_dma & 0x2) { + int ep_num; + ep_num = (usb_readl(USB_REG_CNTL2) >> 4) & 0xf; + jz4740_out_epn(dev, ep_num, intr_out); + } + + /* Check for each configured endpoint interrupt */ + if (intr_in & USB_INTR_INEP1) { + DEBUG("USB_INTR_INEP1\n"); + jz4740_in_epn(dev, 1, intr_in); + } + + if (intr_in & USB_INTR_INEP2) { + DEBUG("USB_INTR_INEP2\n"); + jz4740_in_epn(dev, 2, intr_in); + } + + if (intr_out & USB_INTR_OUTEP1) { + DEBUG("USB_INTR_OUTEP1\n"); + jz4740_out_epn(dev, 1, intr_out); + } + + /* Check for suspend mode */ + if ((intr_usb & USB_INTR_SUSPEND) && + (usb_readb(USB_REG_INTRUSBE) & USB_INTR_SUSPEND)) { + DEBUG("USB suspend\n"); + dev->driver->suspend(&dev->gadget); + /* Host unloaded from us, can do something, such as flushing + the NAND block cache etc. */ + } + +#ifdef CONFIG_JZ_UDC_HOTPLUG + jz_udc_active = 1; +#endif + + spin_unlock(&dev->lock); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +static int jz4740_udc_get_frame(struct usb_gadget *_gadget) +{ + DEBUG("%s, %p\n", __FUNCTION__, _gadget); + return usb_readw(USB_REG_FRAME); +} + +static int jz4740_udc_wakeup(struct usb_gadget *_gadget) +{ + /* host may not have enabled remote wakeup */ + /*if ((UDCCS0 & UDCCS0_DRWF) == 0) + return -EHOSTUNREACH; + udc_set_mask_UDCCR(UDCCR_RSM); */ + return -ENOTSUPP; +} + +static const struct usb_gadget_ops jz4740_udc_ops = { + .get_frame = jz4740_udc_get_frame, + .wakeup = jz4740_udc_wakeup, + /* current versions must always be self-powered */ +}; + +/*-------------------------------------------------------------------------*/ + +static struct jz4740_udc udc_dev = { + .usb_address = 0, + + .gadget = { + .ops = &jz4740_udc_ops, + .ep0 = &udc_dev.ep[0].ep, + .name = driver_name, + .dev = { + .bus_id = "gadget", + }, + }, + + /* control endpoint */ + .ep[0] = { + .ep = { + .name = ep0name, + .ops = &jz4740_ep_ops, + .maxpacket = EP0_MAXPACKETSIZE, + }, + .dev = &udc_dev, + + .bEndpointAddress = 0, + .bmAttributes = 0, + + .ep_type = ep_control, + .fifo = USB_FIFO_EP0, + .csr = USB_REG_CSR0, + }, + + /* bulk out endpoint */ + .ep[1] = { + .ep = { + .name = "ep1out-bulk", + .ops = &jz4740_ep_ops, + .maxpacket = EPBULK_MAXPACKETSIZE, + }, + .dev = &udc_dev, + + .bEndpointAddress = 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = USB_FIFO_EP1, + .csr = USB_REG_OUTCSR, + }, + + /* bulk in endpoint */ + .ep[2] = { + .ep = { + .name = "ep1in-bulk", + .ops = &jz4740_ep_ops, + .maxpacket = EPBULK_MAXPACKETSIZE, + }, + .dev = &udc_dev, + + .bEndpointAddress = USB_DIR_IN | 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = USB_FIFO_EP1, + .csr = USB_REG_INCSR, + }, + + /* interrupt in endpoint */ + .ep[3] = { + .ep = { + .name = "ep2in-int", + .ops = &jz4740_ep_ops, + .maxpacket = EPINTR_MAXPACKETSIZE, + }, + .dev = &udc_dev, + + .bEndpointAddress = USB_DIR_IN | 2, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = USB_FIFO_EP2, + .csr = USB_REG_INCSR, + }, +}; + +static int jz4740_udc_probe(struct platform_device *pdev) +{ + struct jz4740_udc *dev = &udc_dev; + int rc; + + DEBUG("%s\n", __FUNCTION__); + + spin_lock_init(&dev->lock); + the_controller = dev; + + dev->dev = &pdev->dev; + device_initialize(&dev->gadget.dev); + dev->gadget.dev.parent = &pdev->dev; + +// strcpy (dum->gadget.dev.bus_id, "gadget"); + dev->gadget.dev.release = jz4740_udc_release; + if ((rc = device_register (&dev->gadget.dev)) < 0) + return rc; + platform_set_drvdata(pdev, dev); + + udc_disable(dev); + udc_reinit(dev); + + /* irq setup */ + if (request_irq(IRQ_UDC, jz4740_udc_irq, IRQF_DISABLED,//SA_SHIRQ/*|SA_SAMPLE_RANDOM*/, + driver_name, dev) != 0) { + printk(KERN_INFO "request UDC interrupt %d failed\n", IRQ_UDC); + return -EBUSY; + } + + printk(KERN_INFO "%s\n", driver_desc); + printk(KERN_INFO "version: " DRIVER_VERSION "\n"); + + return 0; +} + +static int jz4740_udc_remove(struct platform_device *pdev) +{ + struct jz4740_udc *dev = platform_get_drvdata(pdev); + DEBUG("%s: %p\n", __FUNCTION__, dev); + + if (dev->driver) + return -EBUSY; + + udc_disable(dev); +#ifdef UDC_PROC_FILE + remove_proc_entry(proc_node_name, NULL); +#endif + + free_irq(IRQ_UDC, dev); + platform_set_drvdata(pdev, 0); + device_unregister(&dev->gadget.dev); + the_controller = 0; + + return 0; +} + +static struct platform_driver udc_driver = { + .probe = jz4740_udc_probe, + .remove = jz4740_udc_remove, + .suspend = NULL, + .resume = NULL, + .driver = { + .name = (char *) driver_name, + .owner = THIS_MODULE, + }, +}; + + + +static struct platform_device the_udc_pdev = { + .name = (char *) gadget_name, + .id = -1, + .dev = { + .release = jz4740_udc_release, + }, +}; + + +/*-------------------------------------------------------------------------*/ + +static int __init udc_init (void) +{ + platform_driver_register(&udc_driver); + return platform_device_register (&the_udc_pdev); +} + +static void __exit udc_exit (void) +{ + platform_driver_unregister(&udc_driver); + platform_device_unregister(&the_udc_pdev); +} + +module_init(udc_init); +module_exit(udc_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Wei Jianli "); +MODULE_LICENSE("GPL"); --- /dev/null +++ b/drivers/usb/gadget/jz4740_udc.h @@ -0,0 +1,112 @@ +/* + * linux/drivers/usb/gadget/jz4740_udc.h + * + * Ingenic JZ4740 on-chip high speed USB device controller + * + * Copyright (C) 2006 Ingenic Semiconductor Inc. + * Author: + * + * This program 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. + */ + +#ifndef __USB_GADGET_JZ4740_H__ +#define __USB_GADGET_JZ4740_H__ + +/*-------------------------------------------------------------------------*/ + +// Max packet size +#define EP0_MAXPACKETSIZE 64 +#define EPBULK_MAXPACKETSIZE 512 +#define EPINTR_MAXPACKETSIZE 64 + +#define UDC_MAX_ENDPOINTS 4 + +/*-------------------------------------------------------------------------*/ + +typedef enum ep_type { + ep_control, ep_bulk_in, ep_bulk_out, ep_interrupt +} ep_type_t; + +struct jz4740_ep { + struct usb_ep ep; + struct jz4740_udc *dev; + + const struct usb_endpoint_descriptor *desc; + struct list_head queue; + unsigned long pio_irqs; + + u8 stopped; + u8 bEndpointAddress; + u8 bmAttributes; + + ep_type_t ep_type; + u32 fifo; + u32 csr; + + u32 reg_addr; +}; + +struct jz4740_request { + struct usb_request req; + struct list_head queue; +}; + +enum ep0state { + WAIT_FOR_SETUP, /* between STATUS ack and SETUP report */ + DATA_STATE_XMIT, /* data tx stage */ + DATA_STATE_NEED_ZLP, /* data tx zlp stage */ + WAIT_FOR_OUT_STATUS, /* status stages */ + DATA_STATE_RECV, /* data rx stage */ +}; + +struct jz4740_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct device *dev; + spinlock_t lock; + + enum ep0state ep0state; + struct jz4740_ep ep[UDC_MAX_ENDPOINTS]; + + unsigned char usb_address; +}; + +extern struct jz4740_udc *the_controller; + +#define ep_is_in(EP) (((EP)->bEndpointAddress&USB_DIR_IN)==USB_DIR_IN) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) +#define ep_index(EP) ((EP)->bEndpointAddress&0xF) +#define usb_set_index(i) (REG8(USB_REG_INDEX) = (i)) + +/*-------------------------------------------------------------------------*/ + +/* 2.5 stuff that's sometimes missing in 2.4 */ + +#ifndef container_of +#define container_of list_entry +#endif + +#ifndef likely +#define likely(x) (x) +#define unlikely(x) (x) +#endif + +#ifndef BUG_ON +#define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0) +#endif + +#ifndef WARN_ON +#define WARN_ON(x) do { } while (0) +#endif + +#ifndef IRQ_NONE +typedef void irqreturn_t; +#define IRQ_NONE +#define IRQ_HANDLED +#define IRQ_RETVAL(x) +#endif + +#endif /* __USB_GADGET_JZ4740_H__ */ --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -992,6 +992,11 @@ MODULE_LICENSE ("GPL"); #define PCI_DRIVER ohci_pci_driver #endif +#ifdef CONFIG_JZSOC +#include "ohci-jz.c" +#define PLATFORM_DRIVER ohci_hcd_jz_driver +#endif + #if defined(CONFIG_ARCH_SA1100) && defined(CONFIG_SA1111) #include "ohci-sa1111.c" #define SA1111_DRIVER ohci_hcd_sa1111_driver --- /dev/null +++ b/drivers/usb/host/ohci-jz.c @@ -0,0 +1,260 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2002 David Brownell + * (C) Copyright 2002 Hewlett-Packard Company + * + * Bus Glue for Ingenic Jz47xx. + * + * Written by Christopher Hoover + * Based on fragments of previous driver by Rusell King et al. + * + * Modified for LH7A404 from ohci-sa1111.c + * by Durgesh Pattamatta + * Modified for AMD Alchemy Au1xxx + * by Matt Porter + * Modified for Jz47xx from ohci-au1xxx.c + * by Peter + * + * This file is licenced under the GPL. + */ + +#include +#include + +#include + +extern int usb_disabled(void); + +/*-------------------------------------------------------------------------*/ + +static void jz_start_ohc(struct platform_device *dev) +{ + printk(KERN_DEBUG __FILE__ + ": starting JZ OHCI USB Controller\n"); + + /* enable host controller */ + __cpm_start_uhc(); + +#ifdef CONFIG_SOC_JZ4750 + __cpm_enable_uhcphy(); +#endif + + printk(KERN_DEBUG __FILE__ + ": Clock to USB host has been enabled \n"); +} + +static void jz_stop_ohc(struct platform_device *dev) +{ + printk(KERN_DEBUG __FILE__ + ": stopping JZ OHCI USB Controller\n"); + +#ifdef CONFIG_SOC_JZ4750 + __cpm_suspend_uhcphy(); +#endif + + __cpm_stop_uhc(); +} + + +/*-------------------------------------------------------------------------*/ + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + + +/** + * usb_ohci_jz_probe - initialize Jz-based HCDs + * Context: !in_interrupt() + * + * Allocates basic resources for this USB host controller, and + * then invokes the start() method for the HCD associated with it + * through the hotplug entry's driver_data. + * + */ +static int usb_ohci_jz_probe(const struct hc_driver *driver, + struct platform_device *dev) +{ + int retval; + struct usb_hcd *hcd; + + if (dev->resource[1].flags != IORESOURCE_IRQ) { + pr_debug("resource[1] is not IORESOURCE_IRQ\n"); + return -ENOMEM; + } + + hcd = usb_create_hcd(driver, &dev->dev, "jz"); + if (!hcd) + return -ENOMEM; + hcd->rsrc_start = dev->resource[0].start; + hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1; + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + pr_debug("request_mem_region failed\n"); + retval = -EBUSY; + goto err1; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + pr_debug("ioremap failed\n"); + retval = -ENOMEM; + goto err2; + } + + jz_start_ohc(dev); + ohci_hcd_init(hcd_to_ohci(hcd)); + + retval = usb_add_hcd(hcd, dev->resource[1].start, IRQF_DISABLED | IRQF_SHARED); + if (retval == 0) + return retval; + + jz_stop_ohc(dev); + iounmap(hcd->regs); + err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + err1: + usb_put_hcd(hcd); + return retval; +} + + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * usb_hcd_jz_remove - shutdown processing for Jz-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_jz_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + * + */ +static void usb_ohci_jz_remove(struct usb_hcd *hcd, struct platform_device *dev) +{ + usb_remove_hcd(hcd); + jz_stop_ohc(dev); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +} + +/*-------------------------------------------------------------------------*/ + +static int __devinit +ohci_jz_start (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ret; + + ohci_dbg (ohci, "ohci_jz_start, ohci:%p", ohci); + + if ((ret = ohci_init (ohci)) < 0) + return ret; + + if ((ret = ohci_run (ohci)) < 0) { + err ("can't start %s", hcd->self.bus_name); + ohci_stop (hcd); + return ret; + } + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static const struct hc_driver ohci_jz_hc_driver = { + .description = hcd_name, + .product_desc = "JZ OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .start = ohci_jz_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, + .hub_irq_enable = ohci_rhsc_enable, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +/*-------------------------------------------------------------------------*/ + +static int ohci_hcd_jz_drv_probe(struct platform_device *pdev) +{ + int ret; + + pr_debug ("In ohci_hcd_jz_drv_probe"); + + if (usb_disabled()) + return -ENODEV; + + ret = usb_ohci_jz_probe(&ohci_jz_hc_driver, pdev); + return ret; +} + +static int ohci_hcd_jz_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_ohci_jz_remove(hcd, pdev); + return 0; +} + /*TBD*/ +/*static int ohci_hcd_jz_drv_suspend(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + + return 0; +} +static int ohci_hcd_jz_drv_resume(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + + return 0; +} +*/ + +static struct platform_driver ohci_hcd_jz_driver = { + .probe = ohci_hcd_jz_drv_probe, + .remove = ohci_hcd_jz_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + /*.suspend = ohci_hcd_jz_drv_suspend, */ + /*.resume = ohci_hcd_jz_drv_resume, */ + .driver = { + .name = "jz-ohci", + .owner = THIS_MODULE, + }, +}; + --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -237,6 +237,222 @@ config FB_TILEBLITTING comment "Frame buffer hardware drivers" depends on FB +config FB_JZSOC + tristate "JZSOC LCD controller support" + depends on FB && JZSOC + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + JZSOC LCD Controller and Smart LCD Controller driver support. + +config FB_JZ4740_SLCD + tristate "JZ4740 Smart LCD controller support" + depends on FB_JZSOC && SOC_JZ4740 + default n + ---help--- + This is the frame buffer device driver for the JZ4740 Smart LCD controller. + If select this, please set to . + +choice + depends on FB_JZ4740_SLCD + prompt "SLCD Panel" + default JZ_SLCD_LGDP4551_8BUS + +config JZ_SLCD_LGDP4551 + bool "LG LGDP4551 Smart LCD panel" + ---help--- + Driver for Smart LCD LGDP4551, 8-bit sytem interface, 16BPP. + +config JZ_SLCD_SPFD5420A + bool "SPFD5420A Smart LCD panel" + ---help--- + Driver for Smart LCD SPFD5420A 18-bit sytem interface, 18BPP. + +config JZ_SLCD_TRULY + bool "TRULY Smart LCD panel (MAX Pixels 400x240)" + ---help--- + +endchoice + +config FB_JZLCD_4730_4740 + tristate "JZ4730 JZ4740 LCD controller support" + depends on FB_JZSOC && (SOC_JZ4730 || SOC_JZ4740) + help + This is the frame buffer device driver for the JZ4730 and JZ4740 LCD controller. +config JZLCD_FRAMEBUFFER_MAX + int "Default FrameBuffer num" + depends on FB_JZLCD_4730_4740 + default "1" + ---help--- + JZ LCD driver support multi-framebuffers for video applications. +config JZLCD_FRAMEBUFFER_ROTATE_SUPPORT + bool "JZLCD FrameBuffer Rotate Support(For TEST)" + depends on FB_JZLCD_4730_4740 + default n + ---help--- + JZ LCD driver framebuffer rotate support. Rotate angle can be 0,90,180,270. + Note, this fearture is implemented by software, and will cost a lot of cpu capcity. + That is to say, if you select this function, you system will become slowly. + Rotate cost cpu about: + ratate angle 0'C: 0% cpu + ratate angle 90'C: 40% cpu + ratate angle 180'C: 20% cpu + ratate angle 270'C: 40% cpu + +config JZLCD_FRAMEBUFFER_DEFAULT_ROTATE_ANGLE + int "FrameBuffer default rotate angle" + depends on JZLCD_FRAMEBUFFER_ROTATE_SUPPORT + default 0 + ---help--- + JZ LCD driver framebuffer angle value can be: + 0: 0'C + 1: 90'C + 2: 180'C + 3: 270'C +config JZLCD_FRAMEBUFFER_BPP + int "FrameBuffer bit per pixel" + depends on JZLCD_FRAMEBUFFER_ROTATE_SUPPORT + default 32 + ---help--- + JZ LCD driver framebuffer support 8bpp, 16bpp, 32bpp +choice + depends on FB_JZLCD_4730_4740 + prompt "LCD Panel" + default JZLCD_SAMSUNG_LTP400WQF01 + +config JZLCD_SHARP_LQ035Q7 + bool "SHARP LQ035Q7 TFT panel (240x320)" + +config JZLCD_SAMSUNG_LTS350Q1 + bool "SAMSUNG LTS350Q1 TFT panel (240x320)" + +config JZLCD_SAMSUNG_LTV350QVF04 + bool "SAMSUNG LTV350QV_F04 TFT panel (320x240)" + +config JZLCD_SAMSUNG_LTP400WQF01 + bool "SAMSUNG LTP400WQF01 TFT panel (480x272)(16bits)" + +config JZLCD_SAMSUNG_LTP400WQF02 + bool "SAMSUNG LTP400WQF02 TFT panel (480x272)(18bits)" + +config JZLCD_AUO_A030FL01_V1 + bool "AUO A030FL01_V1 TFT panel (480x272)" + +config JZLCD_TRULY_TFTG320240DTSW + bool "TRULY TFTG320240DTSW TFT panel (320x240)" + +config JZLCD_TRULY_TFTG320240DTSW_SERIAL + bool "TRULY TFTG320240DTSW TFT panel (320x240)(8bit-serial mode)" + +config JZLCD_TRULY_TFTG240320UTSW_63W_E + bool "TRULY TFTG240320UTSW-63W-E TFT panel (240x320,2.5in)" + +config JZLCD_FOXCONN_PT035TN01 + bool "FOXCONN PT035TN01 TFT panel (320x240)" + +config JZLCD_INNOLUX_PT035TN01_SERIAL + bool "INNOLUX PT035TN01 TFT panel (320x240,3.5in)(8bit-serial mode)" + +config JZLCD_TOSHIBA_LTM084P363 + bool "Toshiba LTM084P363 TFT panel (800x600)" + +config JZLCD_HYNIX_HT10X21 + bool "Hynix HT10X21_300 TFT panel (1024x768)" + +config JZLCD_INNOLUX_AT080TN42 + bool "INNOLUX AT080TN42 TFT panel (800x600)" + +config JZLCD_CSTN_800x600 + bool "800x600 colorDSTN panel" + +config JZLCD_CSTN_320x240 + bool "320x240 colorSTN panel" + +config JZLCD_MSTN_480x320 + bool "480x320 monoSTN panel" + +config JZLCD_MSTN_320x240 + bool "320x240 monoSTN panel" + +config JZLCD_MSTN_240x128 + bool "240x128 monoSTN panel" + +config JZLCD_MSTN_INVERSE + bool "Use an inverse color display." + depends on (JZLCD_MSTN_480x320 || JZLCD_MSTN_240x128) + +endchoice + + +config FB_JZ4750_TVE + tristate + depends on FB_JZSOC && SOC_JZ4750 + default n + +config IPU_JZ4750 + bool "Jz4750 ipu support" + depends on FB_JZSOC && SOC_JZ4750 + ---help--- + Use JZ4750 IPU, if need, please select this. + +config FB_JZ4750_LCD + tristate "JZ4750 LCD Controller support" + depends on FB_JZSOC && SOC_JZ4750 + select FB_JZ4750_TVE + ---help--- + JZ4750 LCD Controller driver. + JZ4750 LCD Controller support OSD function(refer jz4750_lcdc_spec.pdf).JZ4750 LCD OSD implement 2 framebuffer layers: foreground0 and foreground1. JZ4750 LCD driver support only foreground0 default. + +config FB_JZ4750_LCD_USE_2LAYER_FRAMEBUFFER + bool "JZ4750 LCD driver 2 layers framebuffer support." + depends on FB_JZ4750_LCD + ---help--- + JZ4750 LCD driver support only foreground0 by default. + If you need both foreground0 and foreground1, please select this. + +config FB_JZ4750_SLCD + bool + depends on FB_JZ4750_LCD + default n + +choice + depends on FB_JZ4750_LCD + prompt "JZ4750 LCD Panels Support" + default JZ4750_LCD_SAMSUNG_LTP400WQF02 + ---help--- + Please select the lcd panel in you board + +config JZ4750_LCD_SAMSUNG_LTP400WQF01 + bool "SAMSUNG LTP400WQF01 TFT panel (480x272)(16bits)" + +config JZ4750_LCD_SAMSUNG_LTP400WQF02 + bool "SAMSUNG LTP400WQF02 TFT panel (480x272)(18bits)" + +config JZ4750_LCD_AUO_A043FL01V2 + bool "AUO A043FL01V2 TFT panel (480x272)(24bits)" + +config JZ4750_LCD_FOXCONN_PT035TN01 + bool "FOXCONN PT035TN01 TFT panel (320x240,3.5in)(18bit-parallel mode)" + +config JZ4750_LCD_INNOLUX_PT035TN01_SERIAL + bool "INNOLUX PT035TN01 TFT panel (320x240,3.5in)(8bit-serial mode)" + +config JZ4750_LCD_TOPPOLY_TD025THEA7_RGB_DELTA + bool "TOPPOLY_TD025THEA7 TFT panel(320x240)(serial RGB delta mode)" + +config JZ4750_LCD_TRULY_TFTG320240DTSW_18BIT + bool "TRULY_TFTG320240DTSW TFT panel (320x240) (Parallel 18bit mode)" + +config JZ4750_LCD_TRULY_TFT_GG1P0319LTSW_W + bool "TRULY_TFT_GG1P0319LTSW_W (240x320) (Smart LCD 16bit)" + +config JZ4750_SLCD_KGM701A3_TFT_SPFD5420A + bool "KGM701A3_TFT_SPFD5420A (400x240) (Smart LCD 18bit)" + select FB_JZ4750_SLCD +endchoice + + config FB_CIRRUS tristate "Cirrus Logic support" depends on FB && (ZORRO || PCI) --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -28,6 +28,10 @@ obj-$(CONFIG_FB_DDC) += fb_ddc obj-$(CONFIG_FB_DEFERRED_IO) += fb_defio.o # Hardware specific drivers go first +obj-$(CONFIG_FB_JZLCD_4730_4740) += jzlcd.o +obj-$(CONFIG_FB_JZ4740_SLCD) += jz4740_slcd.o +obj-$(CONFIG_FB_JZ4750_LCD) += jz4750_lcd.o +obj-$(CONFIG_FB_JZ4750_TVE) += jz4750_tve.o obj-$(CONFIG_FB_AMIGA) += amifb.o c2p.o obj-$(CONFIG_FB_ARC) += arcfb.o obj-$(CONFIG_FB_CLPS711X) += clps711xfb.o --- a/drivers/video/console/Kconfig +++ b/drivers/video/console/Kconfig @@ -118,6 +118,14 @@ config FRAMEBUFFER_CONSOLE_DETECT_PRIMAR If unsure, select n. +config FRAMEBUFFER_CONSOLE_CURSOR_FLASH + bool "Framebuffer Console Cursor flash" + depends on FRAMEBUFFER_CONSOLE + help + Enable cursor flush for the framebuffer console. This is done + in software and may be significantly slower than a normally oriented + display. + config FRAMEBUFFER_CONSOLE_ROTATION bool "Framebuffer Console Rotation" depends on FRAMEBUFFER_CONSOLE --- a/drivers/video/console/fbcon.c +++ b/drivers/video/console/fbcon.c @@ -394,6 +394,7 @@ static void fbcon_update_softback(struct static void fb_flashcursor(struct work_struct *work) { +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_CURSOR_FLASH struct fb_info *info = container_of(work, struct fb_info, queue); struct fbcon_ops *ops = info->fbcon_par; struct display *p; @@ -419,6 +420,7 @@ static void fb_flashcursor(struct work_s ops->cursor(vc, info, mode, softback_lines, get_color(vc, info, c, 1), get_color(vc, info, c, 0)); release_console_sem(); +#endif } #if defined(CONFIG_ATARI) || defined(CONFIG_MAC) --- /dev/null +++ b/drivers/video/jz4740_slcd.c @@ -0,0 +1,1334 @@ +/* + * linux/drivers/video/jzslcd.c -- Ingenic On-Chip Smart LCD frame buffer device + * + * Copyright (C) 2005-2007, Ingenic Semiconductor Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "console/fbcon.h" + +#include "jz4740_slcd.h" + +#undef DEBUG +//#define DEBUG +#ifdef DEBUG +#define dprintk(x...) printk(x) +#else +#define dprintk(x...) +#endif + +#define print_err(f, arg...) printk(KERN_ERR DRIVER_NAME ": " f "\n", ## arg) +#define print_warn(f, arg...) printk(KERN_WARNING DRIVER_NAME ": " f "\n", ## arg) +#define print_info(f, arg...) printk(KERN_INFO DRIVER_NAME ": " f "\n", ## arg) +#ifdef DEBUG +#define print_dbg(f, arg...) printk("dbg::" __FILE__ ",LINE(%d): " f "\n", __LINE__, ## arg) +#else +#define print_dbg(f, arg...) do {} while (0) +#endif + +static jz_dma_desc slcd_palette_desc __attribute__ ((aligned (16))); +static jz_dma_desc slcd_frame_desc __attribute__ ((aligned (16))); + +static int dma_chan; +static dma_addr_t slcd_frame_desc_phys_addr, slcd_palette_desc_phys_addr; + +static unsigned char non_link_desp = 0; +static unsigned char is_set_reg = 0; +struct lcd_cfb_info { + struct fb_info fb; + struct display_switch *dispsw; + signed int currcon; + int func_use_count; + + struct { + u16 red, green, blue; + } palette[NR_PALETTE]; +#ifdef CONFIG_PM + struct pm_dev *pm; +#endif +}; + +struct slcd_reg_info { + unsigned int cmd; + unsigned int data; +}; +static struct slcd_reg_info reg_buf; +static struct lcd_cfb_info *jzslcd_info; + +struct jzfb_info { + unsigned int cfg; /* panel mode and pin usage etc. */ + unsigned int w; + unsigned int h; + unsigned int bpp; /* bit per pixel */ + unsigned int bus; + unsigned int pclk; /* pixel clk */ + +}; + +static struct jzfb_info jzfb = { +#ifdef CONFIG_JZ_SLCD_LGDP4551 + SLCD_CFG_CS_ACTIVE_LOW | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_TYPE_PARALLEL, + 400, 240, 16, 8, 16000000 /*16 bpp, 8 bus*/ +// 240, 400, 18, 8, 16000000 /*18 bpp, 8 bus*/ +// 400, 240, 18, 8, 16000000 /*18 bpp, 8 bus*/ +#endif + +#ifdef CONFIG_JZ_SLCD_SPFD5420A + SLCD_CFG_CS_ACTIVE_LOW | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_TYPE_PARALLEL, + 400, 240, 18, 18, 16000000 /*18 bpp, 18 bus*/ +#endif +}; + + +static volatile unsigned char *slcd_palette; +static volatile unsigned char *slcd_frame; + +//extern struct display fb_display[MAX_NR_CONSOLES]; +static irqreturn_t slcd_dma_irq(int irq, void *dev_id); + + +static void Mcupanel_RegSet(UINT32 cmd, UINT32 data) +{ + switch (jzfb.bus) { + case 8: + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff00) >> 8); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff) >> 0); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_DATA | (data&0xffff); + break; + case 9: + data = ((data & 0xff) << 1) | ((data & 0xff00) << 2); + data = ((data << 6) & 0xfc0000) | ((data << 4) & 0xfc00) | ((data << 2) & 0xfc); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff00) >> 8); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff) >> 0); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_DATA | data; + break; + case 16: + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | (cmd&0xffff); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_DATA | (data&0xffff); + break; + case 18: + cmd = ((cmd & 0xff) << 1) | ((cmd & 0xff00) << 2); + data = ((data & 0xff) << 1) | ((data & 0xff00) << 2); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | cmd; + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_DATA | ((data<<6)&0xfc0000)|((data<<4)&0xfc00) | ((data<<2)&0xfc); + break; + default: + printk("Don't support %d bit Bus\n", jzfb.bus ); + break; + } +} + +/* Sent a command withou data */ +static void Mcupanel_Command(UINT32 cmd) { + switch (jzfb.bus) { + case 8: + case 9: + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff00) >> 8); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff) >> 0); + break; + case 16: + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | (cmd&0xffff); + break; + case 18: + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff00) << 2) | ((cmd&0xff) << 1); + break; + default: + printk("Don't support %d bit Bus\n", jzfb.bus ); + break; + } +} + +/* Set the start address of screen, for example (0, 0) */ +#ifdef CONFIG_JZ_SLCD_LGDP4551 +static void Mcupanel_SetAddr(UINT16 x, UINT16 y) +{ + Mcupanel_RegSet(0x20,x) ; + udelay(1); + Mcupanel_RegSet(0x21,y) ; + udelay(1); + Mcupanel_Command(0x22); + +} +#endif +#ifdef CONFIG_JZ_SLCD_SPFD5420A +void Mcupanel_SetAddr(u32 x, u32 y) //u32 +{ + Mcupanel_RegSet(0x200,x) ; + udelay(1); + Mcupanel_RegSet(0x201,y) ; + udelay(1); + Mcupanel_Command(0x202); + +} + +#endif + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int jzfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + unsigned short *ptr, ctmp; + + print_dbg("regno:%d,RGBt:(%d,%d,%d,%d)\t", regno, red, green, blue, transp); + if (regno >= NR_PALETTE) + return 1; + + cfb->palette[regno].red = red ; + cfb->palette[regno].green = green; + cfb->palette[regno].blue = blue; + if (cfb->fb.var.bits_per_pixel <= 16) { + red >>= 8; + green >>= 8; + blue >>= 8; + + red &= 0xff; + green &= 0xff; + blue &= 0xff; + } + switch (cfb->fb.var.bits_per_pixel) { + case 1: + case 2: + case 4: + case 8: + /* RGB 565 */ + if (((red >> 3) == 0) && ((red >> 2) != 0)) + red = 1 << 3; + if (((blue >> 3) == 0) && ((blue >> 2) != 0)) + blue = 1 << 3; + ctmp = ((red >> 3) << 11) + | ((green >> 2) << 5) | (blue >> 3); + + ptr = (unsigned short *)slcd_palette; + ptr = (unsigned short *)(((u32)ptr)|0xa0000000); + ptr[regno] = ctmp; + + break; + + case 15: + if (regno < 16) + ((u32 *)cfb->fb.pseudo_palette)[regno] = + ((red >> 3) << 10) | + ((green >> 3) << 5) | + (blue >> 3); + break; + case 16: + if (regno < 16) { + ((u32 *)cfb->fb.pseudo_palette)[regno] = + ((red >> 3) << 11) | + ((green >> 2) << 5) | + (blue >> 3); + } + break; + case 18: + case 24: + case 32: + if (regno < 16) + ((u32 *)cfb->fb.pseudo_palette)[regno] = + (red << 16) | + (green << 8) | + (blue << 0); + +/* if (regno < 16) { + unsigned val; + val = chan_to_field(red, &cfb->fb.var.red); + val |= chan_to_field(green, &cfb->fb.var.green); + val |= chan_to_field(blue, &cfb->fb.var.blue); + ((u32 *)cfb->fb.pseudo_palette)[regno] = val; + } +*/ + + break; + } + return 0; +} + +static int jzfb_ioctl (struct fb_info *info, unsigned int cmd, unsigned long arg ) +{ + int ret = 0; + void __user *argp = (void __user *)arg; + + switch (cmd) { + case FBIOSETBACKLIGHT: + __slcd_set_backlight_level(arg); /* We support 8 levels here. */ + break; + case FBIODISPON: + __slcd_display_on(); + break; + case FBIODISPOFF: + __slcd_display_off(); + break; + case FBIO_REFRESH_ALWAYS: + dprintk("slcd_frame_desc.dcmd = 0x%08x\n", slcd_frame_desc.dcmd); + if (slcd_frame_desc.dcmd & DMAC_DCMD_LINK) + printk("The Smart LCD refreshes automatically. Option is omitted!\n"); + else { + dprintk("OPEN DMAC_DCMD_LINK \n"); + slcd_frame_desc.dcmd &= ~DMAC_DCMD_TIE; + slcd_frame_desc.dcmd |= DMAC_DCMD_LINK; + dma_cache_wback((unsigned long)(&slcd_frame_desc), 16); + REG_DMAC_DCMD(dma_chan) &= ~DMAC_DCMD_TIE; + __dmac_channel_set_doorbell(dma_chan); + } + break; + case FBIO_REFRESH_EVENTS: + dprintk("slcd_frame_desc.dcmd = 0x%08x\n", slcd_frame_desc.dcmd); + if (!(slcd_frame_desc.dcmd & DMAC_DCMD_LINK)) + printk("The Smart LCD is refreshed by envents. Option is omitted!\n"); + else { + non_link_desp = 1; + REG_DMAC_DCMD(dma_chan) |= DMAC_DCMD_TIE; + REG_DMAC_DCMD(dma_chan) &= ~DMAC_DCMD_LINK; + } + break; + case FBIO_DO_REFRESH: + + dprintk("slcd_frame_desc.dcmd = 0x%08x\n", slcd_frame_desc.dcmd); + if (slcd_frame_desc.dcmd & DMAC_DCMD_LINK) + printk("The Smart LCD can refresh automatically. Option is omitted!\n"); + else { + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + __dmac_channel_set_doorbell(dma_chan); + } + break; + case FBIO_SET_REG: + if (copy_from_user(®_buf, argp, sizeof(reg_buf))) + return -EFAULT; + is_set_reg = 1; + REG_DMAC_DCMD(dma_chan) |= DMAC_DCMD_TIE; + REG_DMAC_DCMD(dma_chan) &= ~DMAC_DCMD_LINK; + break; + default: + break; + } + + return ret; +} + +/* Use mmap /dev/fb can only get a non-cacheable Virtual Address. */ +static int jzfb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + unsigned long start; + unsigned long off; + u32 len; + + off = vma->vm_pgoff << PAGE_SHIFT; + //fb->fb_get_fix(&fix, PROC_CONSOLE(info), info); + + /* frame buffer memory */ + start = cfb->fb.fix.smem_start; + len = PAGE_ALIGN((start & ~PAGE_MASK) + cfb->fb.fix.smem_len); + start &= PAGE_MASK; + + if ((vma->vm_end - vma->vm_start + off) > len) + return -EINVAL; + off += start; + + vma->vm_pgoff = off >> PAGE_SHIFT; + vma->vm_flags |= VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); /* Uncacheable */ + +#if 1 + pgprot_val(vma->vm_page_prot) &= ~_CACHE_MASK; +// pgprot_val(vma->vm_page_prot) |= _CACHE_UNCACHED; /* Uncacheable */ + pgprot_val(vma->vm_page_prot) |= _CACHE_CACHABLE_NONCOHERENT; /* Write-Through */ +#endif + + if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) { + return -EAGAIN; + } + return 0; +} + +/* checks var and eventually tweaks it to something supported, + * DO NOT MODIFY PAR */ +static int jzfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + print_dbg("jzfb_check_var"); + return 0; +} + + +/* + * set the video mode according to info->var + */ +static int jzfb_set_par(struct fb_info *info) +{ +// print_dbg("jzfb_set_par"); + printk("jzfb_set_par"); + return 0; +} + + +/* + * (Un)Blank the display. + * Fix me: should we use VESA value? + */ +static int jzfb_blank(int blank_mode, struct fb_info *info) +{ + + dprintk("fb_blank %d %p", blank_mode, info); + + switch (blank_mode) { + + case FB_BLANK_UNBLANK: + /* Turn on panel */ + break; + + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + /* Turn off panel */ + break; + default: + break; + + } + return 0; +} + +/* + * pan display + */ +static int jzfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + int dy; + + if (!var || !cfb) { + return -EINVAL; + } + + if (var->xoffset - cfb->fb.var.xoffset) { + /* No support for X panning for now! */ + return -EINVAL; + } + + dy = var->yoffset - cfb->fb.var.yoffset; + print_dbg("var.yoffset: %d", dy); + if (dy) { + + print_dbg("Panning screen of %d lines", dy); +// slcd_frame_desc->databuf += (cfb->fb.fix.line_length * dy); +// slcd_frame_desc->dsadr += (cfb->fb.fix.line_length * dy); + /* TODO: Wait for current frame to finished */ + } + + return 0; +} + + +/* use default function cfb_fillrect, cfb_copyarea, cfb_imageblit */ +static struct fb_ops jzfb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = jzfb_setcolreg, + .fb_check_var = jzfb_check_var, + .fb_set_par = jzfb_set_par, + .fb_blank = jzfb_blank, + .fb_pan_display = jzfb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = jzfb_mmap, + .fb_ioctl = jzfb_ioctl, +}; + +static int jzfb_set_var(struct fb_var_screeninfo *var, int con, + struct fb_info *info) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + //struct display *display; + int chgvar = 0; + + var->height = jzfb.h ; + var->width = jzfb.w ; + var->bits_per_pixel = jzfb.bpp; + + var->vmode = FB_VMODE_NONINTERLACED; + var->activate = cfb->fb.var.activate; + var->xres = var->width; + var->yres = var->height; + var->xres_virtual = var->width; + var->yres_virtual = var->height; + var->xoffset = 0; + var->yoffset = 0; + var->pixclock = 0; + var->left_margin = 0; + var->right_margin = 0; + var->upper_margin = 0; + var->lower_margin = 0; + var->hsync_len = 0; + var->vsync_len = 0; + var->sync = 0; + var->activate &= ~FB_ACTIVATE_TEST; + + /* + * CONUPDATE and SMOOTH_XPAN are equal. However, + * SMOOTH_XPAN is only used internally by fbcon. + */ + if (var->vmode & FB_VMODE_CONUPDATE) { + var->vmode |= FB_VMODE_YWRAP; + var->xoffset = cfb->fb.var.xoffset; + var->yoffset = cfb->fb.var.yoffset; + } + + if (var->activate & FB_ACTIVATE_TEST) + return 0; + + if ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NOW) + return -EINVAL; + + if (cfb->fb.var.xres != var->xres) + chgvar = 1; + if (cfb->fb.var.yres != var->yres) + chgvar = 1; + if (cfb->fb.var.xres_virtual != var->xres_virtual) + chgvar = 1; + if (cfb->fb.var.yres_virtual != var->yres_virtual) + chgvar = 1; + if (cfb->fb.var.bits_per_pixel != var->bits_per_pixel) + chgvar = 1; + + //display = fb_display + con; + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + + switch(var->bits_per_pixel){ + case 1: /* Mono */ + cfb->fb.fix.visual = FB_VISUAL_MONO01; + cfb->fb.fix.line_length = (var->xres * var->bits_per_pixel) / 8; + break; + case 2: /* Mono */ + var->red.offset = 0; + var->red.length = 2; + var->green.offset = 0; + var->green.length = 2; + var->blue.offset = 0; + var->blue.length = 2; + + cfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + cfb->fb.fix.line_length = (var->xres * var->bits_per_pixel) / 8; + break; + case 4: /* PSEUDOCOLOUR*/ + var->red.offset = 0; + var->red.length = 4; + var->green.offset = 0; + var->green.length = 4; + var->blue.offset = 0; + var->blue.length = 4; + + cfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + cfb->fb.fix.line_length = var->xres / 2; + break; + case 8: /* PSEUDOCOLOUR, 256 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + + cfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + cfb->fb.fix.line_length = var->xres ; + break; + case 15: /* DIRECTCOLOUR, 32k */ + var->bits_per_pixel = 15; + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + + cfb->fb.fix.visual = FB_VISUAL_DIRECTCOLOR; + cfb->fb.fix.line_length = var->xres_virtual * 2; + break; + case 16: /* DIRECTCOLOUR, 64k */ + var->bits_per_pixel = 16; + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + + cfb->fb.fix.visual = FB_VISUAL_TRUECOLOR; + cfb->fb.fix.line_length = var->xres_virtual * 2; + break; + case 18: + case 24: + case 32: + /* DIRECTCOLOUR, 256 */ + var->bits_per_pixel = 32; + + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + + cfb->fb.fix.visual = FB_VISUAL_TRUECOLOR; + cfb->fb.fix.line_length = var->xres_virtual * 4; + break; + + default: /* in theory this should never happen */ + printk(KERN_WARNING "%s: don't support for %dbpp\n", + cfb->fb.fix.id, var->bits_per_pixel); + break; + } + + cfb->fb.var = *var; + cfb->fb.var.activate &= ~FB_ACTIVATE_ALL; + + /* + * If we are setting all the virtual consoles, also set the + * defaults used to create new consoles. + */ + fb_set_cmap(&cfb->fb.cmap, &cfb->fb); + dprintk("jzfb_set_var: after fb_set_cmap...\n"); + + return 0; +} + +static struct lcd_cfb_info * jzfb_alloc_fb_info(void) +{ + struct lcd_cfb_info *cfb; + + cfb = kmalloc(sizeof(struct lcd_cfb_info) + sizeof(u32) * 16, GFP_KERNEL); + + if (!cfb) + return NULL; + + jzslcd_info = cfb; + + memset(cfb, 0, sizeof(struct lcd_cfb_info) ); + + cfb->currcon = -1; + + + strcpy(cfb->fb.fix.id, "jz-slcd"); + cfb->fb.fix.type = FB_TYPE_PACKED_PIXELS; + cfb->fb.fix.type_aux = 0; + cfb->fb.fix.xpanstep = 1; + cfb->fb.fix.ypanstep = 1; + cfb->fb.fix.ywrapstep = 0; + cfb->fb.fix.accel = FB_ACCEL_NONE; + + cfb->fb.var.nonstd = 0; + cfb->fb.var.activate = FB_ACTIVATE_NOW; + cfb->fb.var.height = -1; + cfb->fb.var.width = -1; + cfb->fb.var.accel_flags = FB_ACCELF_TEXT; + + cfb->fb.fbops = &jzfb_ops; + cfb->fb.flags = FBINFO_FLAG_DEFAULT; + + cfb->fb.pseudo_palette = (void *)(cfb + 1); + + switch (jzfb.bpp) { + case 1: + fb_alloc_cmap(&cfb->fb.cmap, 4, 0); + break; + case 2: + fb_alloc_cmap(&cfb->fb.cmap, 8, 0); + break; + case 4: + fb_alloc_cmap(&cfb->fb.cmap, 32, 0); + break; + case 8: + + default: + fb_alloc_cmap(&cfb->fb.cmap, 256, 0); + break; + } + dprintk("fb_alloc_cmap,fb.cmap.len:%d....\n", cfb->fb.cmap.len); + + return cfb; +} + +/* + * Map screen memory + */ +static int jzfb_map_smem(struct lcd_cfb_info *cfb) +{ + struct page * map = NULL; + unsigned char *tmp; + unsigned int page_shift, needroom, t; + + t = jzfb.bpp; + if (jzfb.bpp == 15) + t = 16; + if (jzfb.bpp == 18 || jzfb.bpp == 24) + t = 32; + needroom = ((jzfb.w * t + 7) >> 3) * jzfb.h; + + for (page_shift = 0; page_shift < 12; page_shift++) + if ((PAGE_SIZE << page_shift) >= needroom) + break; + + slcd_palette = (unsigned char *)__get_free_pages(GFP_KERNEL, 0); + slcd_frame = (unsigned char *)__get_free_pages(GFP_KERNEL, page_shift); + if ((!slcd_palette) || (!slcd_frame)) + return -ENOMEM; + + memset((void *)slcd_palette, 0, PAGE_SIZE); + memset((void *)slcd_frame, 0, PAGE_SIZE << page_shift); + + map = virt_to_page(slcd_palette); + set_bit(PG_reserved, &map->flags); + + for (tmp=(unsigned char *)slcd_frame; + tmp < slcd_frame + (PAGE_SIZE << page_shift); + tmp += PAGE_SIZE) { + map = virt_to_page(tmp); + set_bit(PG_reserved, &map->flags); + } + + cfb->fb.fix.smem_start = virt_to_phys((void *)slcd_frame); + + cfb->fb.fix.smem_len = (PAGE_SIZE << page_shift); + + cfb->fb.screen_base = + (unsigned char *)(((unsigned int)slcd_frame & 0x1fffffff) | 0xa0000000); + + if (!cfb->fb.screen_base) { + printk("%s: unable to map screen memory\n", cfb->fb.fix.id); + return -ENOMEM; + } + + return 0; +} + +static void jzfb_free_fb_info(struct lcd_cfb_info *cfb) +{ + if (cfb) { + fb_alloc_cmap(&cfb->fb.cmap, 0, 0); + kfree(cfb); + } +} + +static void jzfb_unmap_smem(struct lcd_cfb_info *cfb) +{ + struct page * map = NULL; + unsigned char *tmp; + unsigned int page_shift, needroom, t; + + t = jzfb.bpp; + if (jzfb.bpp == 18 || jzfb.bpp == 24) + t = 32; + if (jzfb.bpp == 15) + t = 16; + needroom = ((jzfb.w * t + 7) >> 3) * jzfb.h; + for (page_shift = 0; page_shift < 12; page_shift++) + if ((PAGE_SIZE << page_shift) >= needroom) + break; + + if (cfb && cfb->fb.screen_base) { + iounmap(cfb->fb.screen_base); + cfb->fb.screen_base = NULL; + release_mem_region(cfb->fb.fix.smem_start, + cfb->fb.fix.smem_len); + } + + if (slcd_palette) { + map = virt_to_page(slcd_palette); + clear_bit(PG_reserved, &map->flags); + free_pages((int)slcd_palette, 0); + } + + if (slcd_frame) { + + for (tmp=(unsigned char *)slcd_frame; + tmp < slcd_frame + (PAGE_SIZE << page_shift); + tmp += PAGE_SIZE) { + map = virt_to_page(tmp); + clear_bit(PG_reserved, &map->flags); + } + + free_pages((int)slcd_frame, page_shift); + } +} + +static void slcd_descriptor_init(void) +{ + int i; + int frm_size, pal_size; + unsigned int next; + unsigned int slcd_frame_src_phys_addr, slcd_palette_src_phys_addr, slcd_dma_dst_phys_addr; + + i = jzfb.bpp; + if (i == 18 || i == 24) + i = 32; + if (i == 15) + i = 16; + + switch (jzfb.bpp) { + case 1: + pal_size = 4; + break; + case 2: + pal_size = 8; + break; + case 4: + pal_size = 32; + break; + case 8: + default: + pal_size = 512; + } + + frm_size = jzfb.w * jzfb.h * jzfb.bpp / 8; + + /*Offset of next descriptor*/ + slcd_frame_desc_phys_addr = (dma_addr_t)CPHYSADDR((unsigned long)(&slcd_frame_desc)); + slcd_palette_desc_phys_addr = (dma_addr_t)CPHYSADDR((unsigned long)(&slcd_palette_desc)); + + /*Soure address and Target address*/ + slcd_palette_src_phys_addr = (unsigned int)virt_to_phys(slcd_palette); + slcd_frame_src_phys_addr = (unsigned int)virt_to_phys(slcd_frame); + slcd_dma_dst_phys_addr = (unsigned int)CPHYSADDR(SLCD_FIFO); + next = slcd_frame_desc_phys_addr >> 4; + + /* Prepare Palette Descriptor */ + slcd_palette_desc.dcmd = DMAC_DCMD_SAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 + | DMAC_DCMD_DWDH_16 | DMAC_DCMD_DS_16BYTE | DMAC_DCMD_TM | DMAC_DCMD_DES_V + | DMAC_DCMD_DES_VIE | DMAC_DCMD_LINK; + switch (slcd_palette_desc.dcmd & DMAC_DCMD_DS_MASK) { + case DMAC_DCMD_DS_32BYTE: + pal_size /= 32; + break; + case DMAC_DCMD_DS_16BYTE: + pal_size /= 16; + break; + case DMAC_DCMD_DS_32BIT: + pal_size /= 4; + break; + case DMAC_DCMD_DS_16BIT: + pal_size /= 2; + break; + case DMAC_DCMD_DS_8BIT: + default: + break; + } + + slcd_palette_desc.dsadr = (unsigned int)virt_to_phys(slcd_palette); /* DMA source address */ + slcd_palette_desc.dtadr = (unsigned int)CPHYSADDR(SLCD_FIFO); /* DMA target address */ + slcd_palette_desc.ddadr = (volatile unsigned int)((next << 24) | (pal_size & 0xffffff)); /* offset and size*/ + dma_cache_wback((unsigned long)(&slcd_palette_desc), 16); + + /*Prepare Frame Descriptor in memory*/ + switch (jzfb.bpp) { + case 8 ... 16: + slcd_frame_desc.dcmd = DMAC_DCMD_SAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 + | DMAC_DCMD_DWDH_16 | DMAC_DCMD_DS_16BYTE | DMAC_DCMD_TM | DMAC_DCMD_DES_V + | DMAC_DCMD_DES_VIE | DMAC_DCMD_LINK; + break; + + case 17 ... 32: + slcd_frame_desc.dcmd = DMAC_DCMD_SAI | DMAC_DCMD_RDIL_IGN | DMAC_DCMD_SWDH_32 + | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_32BYTE | DMAC_DCMD_TM | DMAC_DCMD_DES_V + | DMAC_DCMD_DES_VIE | DMAC_DCMD_LINK; + break; + } + switch (slcd_frame_desc.dcmd & DMAC_DCMD_DS_MASK) { + case DMAC_DCMD_DS_32BYTE: + frm_size /= 32; + break; + case DMAC_DCMD_DS_16BYTE: + frm_size /= 16; + break; + case DMAC_DCMD_DS_32BIT: + frm_size /= 4; + break; + case DMAC_DCMD_DS_16BIT: + frm_size /= 2; + break; + case DMAC_DCMD_DS_8BIT: + default: + break; + } + + slcd_frame_desc.dsadr = slcd_frame_src_phys_addr; /* DMA source address */ + slcd_frame_desc.dtadr = slcd_dma_dst_phys_addr; /* DMA target address */ + slcd_frame_desc.ddadr = (volatile unsigned int)((next << 24) | (frm_size & 0xffffff)); /* offset and size*/ + dma_cache_wback((unsigned long)(&slcd_frame_desc), 16); +} + +void slcd_hw_init(void) +{ + unsigned int val, pclk; + int pll_div; + + REG_LCD_CFG &= ~LCD_CFG_LCDPIN_MASK; + REG_LCD_CFG |= LCD_CFG_LCDPIN_SLCD; + + if ((jzfb.bpp == 18) | (jzfb.bpp == 24)) + jzfb.bpp = 32; + + /* Configure SLCD module for initialize smart lcd registers*/ + switch (jzfb.bus) { + case 8: + REG_SLCD_CFG = SLCD_CFG_BURST_8_WORD | SLCD_CFG_DWIDTH_8_x2 + | SLCD_CFG_CWIDTH_8BIT | SLCD_CFG_CS_ACTIVE_LOW + | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_CLK_ACTIVE_FALLING + | SLCD_CFG_TYPE_PARALLEL; + __gpio_as_slcd_8bit(); + break; + case 9: + REG_SLCD_CFG = SLCD_CFG_BURST_8_WORD | SLCD_CFG_DWIDTH_8_x2 + | SLCD_CFG_CWIDTH_8BIT | SLCD_CFG_CS_ACTIVE_LOW + | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_CLK_ACTIVE_FALLING + | SLCD_CFG_TYPE_PARALLEL; + __gpio_as_slcd_9bit(); + break; + case 16: + REG_SLCD_CFG = SLCD_CFG_BURST_8_WORD | SLCD_CFG_DWIDTH_16 + | SLCD_CFG_CWIDTH_16BIT | SLCD_CFG_CS_ACTIVE_LOW + | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_CLK_ACTIVE_FALLING + | SLCD_CFG_TYPE_PARALLEL; + __gpio_as_slcd_16bit(); + break; + case 18: + REG_SLCD_CFG = SLCD_CFG_BURST_8_WORD | SLCD_CFG_DWIDTH_18 + | SLCD_CFG_CWIDTH_18BIT | SLCD_CFG_CS_ACTIVE_LOW + | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_CLK_ACTIVE_FALLING + | SLCD_CFG_TYPE_PARALLEL; + __gpio_as_slcd_18bit(); + break; + default: + printk("Error: Don't support BUS %d!\n", jzfb.bus); + break; + } + + REG_SLCD_CTRL = SLCD_CTRL_DMA_EN; + __cpm_stop_lcd(); + pclk = jzfb.pclk; + pll_div = ( REG_CPM_CPCCR & CPM_CPCCR_PCS ); /* clock source,0:pllout/2 1: pllout */ + pll_div = pll_div ? 1 : 2 ; + val = ( __cpm_get_pllout()/pll_div ) / pclk; + val--; + if ( val > 0x1ff ) { + printk("CPM_LPCDR too large, set it to 0x1ff\n"); + val = 0x1ff; + } + __cpm_set_pixdiv(val); + + REG_CPM_CPCCR |= CPM_CPCCR_CE ; /* update divide */ + + jz_clocks.pixclk = __cpm_get_pixclk(); + jz_clocks.lcdclk = __cpm_get_lcdclk(); + printk("SLCDC: PixClock:%d LcdClock:%d\n", + jz_clocks.pixclk, jz_clocks.lcdclk); + + __cpm_start_lcd(); + udelay(1000); + __slcd_display_pin_init(); + __slcd_special_on(); + + /* Configure SLCD module for transfer data to smart lcd GRAM*/ + switch (jzfb.bus) { + case 8: + switch (jzfb.bpp) { + case 8: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x1; + break; + case 15: + case 16: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x2; + break; + case 17 ... 32: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x3; + break; + default: + printk("The BPP %d is not supported\n", jzfb.bpp); + break; + } + break; + case 9: + switch (jzfb.bpp) { + case 8: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x1; + break; + case 15 ... 16: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x2; + break; + case 17 ... 32: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_9_x2; + break; + default: + printk("The BPP %d is not supported\n", jzfb.bpp); + break; + } + break; + case 16: + switch (jzfb.bpp) { + case 8: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x1; + break; + case 15 ... 16: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_16; + break; + case 17 ... 32: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x3; + break; + default: + printk("The BPP %d is not supported\n", jzfb.bpp); + break; + } + break; + case 18: + switch (jzfb.bpp) { + case 8: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x1; + break; + case 15: + case 16: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_16; + break; + case 17 ... 32: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_18; + break; + default: + printk("The BPP %d is not supported\n", jzfb.bpp); + break; + } + break; + default: + printk("Error: The BUS %d is not supported\n", jzfb.bus); + break; + } + dprintk("SLCD_CFG=0x%x\n", REG_SLCD_CFG); +} + +static irqreturn_t slcd_dma_irq(int irq, void *dev_id) +{ + + if (__dmac_channel_transmit_halt_detected(dma_chan)) { + dprintk("DMA HALT\n"); + __dmac_channel_clear_transmit_halt(dma_chan); + } + + if (__dmac_channel_address_error_detected(dma_chan)) { + dprintk("DMA ADDR ERROR\n"); + __dmac_channel_clear_address_error(dma_chan); + } + + if (__dmac_channel_descriptor_invalid_detected(dma_chan)) { + dprintk("DMA DESC INVALID\n"); + __dmac_channel_clear_descriptor_invalid(dma_chan); + } + + if (__dmac_channel_count_terminated_detected(dma_chan)) { + dprintk("DMA CT\n"); + __dmac_channel_clear_count_terminated(dma_chan); + if(is_set_reg){ + printk("Close DMAC_DCMD_LINK \n"); + REG_DMAC_DCMD(dma_chan) &= ~DMAC_DCMD_LINK; + } + if (non_link_desp) { + printk("Close DMAC_DCMD_LINK \n"); + /*Set to Non-Link Descriptor*/ + REG_DMAC_DCMD(dma_chan) &= ~DMAC_DCMD_LINK; + } + } + + if (__dmac_channel_transmit_end_detected(dma_chan)) { + printk("DMA TT\n"); + __dmac_channel_clear_transmit_end(dma_chan); + if (non_link_desp) { + slcd_frame_desc.dcmd |= DMAC_DCMD_TIE; + slcd_frame_desc.dcmd &= ~DMAC_DCMD_LINK; + dma_cache_wback((unsigned long)(&slcd_frame_desc), 16); + non_link_desp = 0; + } + if (is_set_reg) { + is_set_reg = 0; + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_DMAC_DMACR &= ~DMAC_DMACR_DMAE; /* disable DMA */ + REG_DMAC_DCCSR(dma_chan) &= ~DMAC_DCCSR_EN; /* disable DMA */ + REG_SLCD_CTRL = 0; + + /* + *add operation here + */ + Mcupanel_RegSet(reg_buf.cmd, reg_buf.data); + Mcupanel_Command(0x0022);/*Write Data to GRAM */ + mdelay(100); + REG_SLCD_CTRL = SLCD_CTRL_DMA_EN; + REG_DMAC_DMACR = DMAC_DMACR_DMAE; + REG_DMAC_DCCSR(dma_chan) = DMAC_DCCSR_EN; + __dmac_channel_set_doorbell(dma_chan); + } + } + return IRQ_HANDLED; +} + +static int slcd_dma_init(void) +{ + /* Request DMA channel and setup irq handler */ + dma_chan = jz_request_dma(DMA_ID_AUTO, "auto", slcd_dma_irq, 0, NULL); + if (dma_chan < 0) { + printk("Request DMA Failed\n"); + return -1; + } + printk("DMA channel %d is requested by SLCD!\n", dma_chan); + + /*Init the SLCD DMA and Enable*/ + REG_DMAC_DRSR(dma_chan) = DMAC_DRSR_RS_SLCD; + REG_DMAC_DMACR = DMAC_DMACR_DMAE; + REG_DMAC_DCCSR(dma_chan) = DMAC_DCCSR_EN; /*Descriptor Transfer*/ + + if (jzfb.bpp <= 8) + REG_DMAC_DDA(dma_chan) = slcd_palette_desc_phys_addr; + else + REG_DMAC_DDA(dma_chan) = slcd_frame_desc_phys_addr; + + /* DMA doorbell set -- start DMA now ... */ + __dmac_channel_set_doorbell(dma_chan); + return 0; +} + +#ifdef CONFIG_PM + +/* + * Suspend the LCDC. + */ +static int jzfb_suspend(void) +{ + + __slcd_close_backlight(); + __dmac_disable_channel(dma_chan); + __slcd_dma_disable(); /* Quick Disable */ + __slcd_special_off(); + __cpm_stop_lcd(); + return 0; +} + +/* + * Resume the LCDC. + */ + +static int jzfb_resume(void) +{ + __cpm_start_lcd(); + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + switch (jzfb.bpp) { + case 8: + /* DATA 8-bit once*/ + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x1; + break; + case 15: + case 16: + case 18: + case 24: + case 32: + /* DATA 8-bit twice*/ + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x2; + break; + default: + REG_SLCD_CFG = SLCD_CFG_DWIDTH_8_x2; + break; + } + __slcd_display_pin_init(); + __slcd_special_on(); + + if (jzfb.bpp == 32) { + /* DATA 8-bit three time*/ + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x3; + } + __slcd_dma_enable(); + udelay(100); + __dmac_enable_channel(dma_chan); + __dmac_channel_set_doorbell(dma_chan); + mdelay(200); + __slcd_set_backlight_level(80); + return 0; +} + +/* + * Power management hook. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ +static int jzslcd_pm_callback(struct pm_dev *pm_dev, pm_request_t req, void *data) +{ + int ret; + struct lcd_cfb_info *cfb = pm_dev->data; + + if (!cfb) return -EINVAL; + + switch (req) { + case PM_SUSPEND: + ret = jzfb_suspend(); + break; + + case PM_RESUME: + ret = jzfb_resume(); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} +#else +#define jzfb_suspend NULL +#define jzfb_resume NULL +#endif /* CONFIG_PM */ +static int __init jzslcd_fb_init(void) +{ + + struct lcd_cfb_info *cfb; + int err = 0; + + /*the parameters of slcd*/ + cfb = jzfb_alloc_fb_info(); + if (!cfb) + goto failed; + + err = jzfb_map_smem(cfb); + if (err) + goto failed; + jzfb_set_var(&cfb->fb.var, -1, &cfb->fb); + + slcd_hw_init(); + + err = register_framebuffer(&cfb->fb); + if (err < 0) { + printk("jzslcd_fb_init(): slcd register framebuffer err.\n"); + goto failed; + } + + printk("fb%d: %s frame buffer device, using %dK of video memory\n", + cfb->fb.node, cfb->fb.fix.id, cfb->fb.fix.smem_len>>10); + + slcd_descriptor_init(); + err = slcd_dma_init(); + if (err != 0) { + printk("SLCD Init DMA Fail!\n"); + return err; + } + mdelay(100); + __slcd_set_backlight_level(80); + +#ifdef CONFIG_PM + /* + * Note that the console registers this as well, but we want to + * power down the display prior to sleeping. + */ +//struct pm_dev __deprecated *pm_register(pm_dev_t type, unsigned long id, pm_callback callback); + + cfb->pm = pm_register(PM_SYS_DEV, PM_SYS_VGA, jzslcd_pm_callback); + if (cfb->pm) + cfb->pm->data = cfb; + +#endif + return 0; + +failed: + jzfb_unmap_smem(cfb); + jzfb_free_fb_info(cfb); + + return err; +} + +#if 0 +static int jzfb_remove(struct device *dev) +{ + struct lcd_cfb_info *cfb = dev_get_drvdata(dev); + jzfb_unmap_smem(cfb); + jzfb_free_fb_info(cfb); + return 0; +} +#endif + +#if 0 +static struct device_driver jzfb_driver = { + .name = "jz-slcd", + .bus = &platform_bus_type, + .probe = jzfb_probe, + .remove = jzfb_remove, + .suspend = jzfb_suspend, + .resume = jzfb_resume, +}; +#endif + +static void __exit jzslcd_fb_cleanup(void) +{ + //driver_unregister(&jzfb_driver); + //jzfb_remove(); +} + +module_init(jzslcd_fb_init); +module_exit(jzslcd_fb_cleanup); + +MODULE_DESCRIPTION("JzSOC SLCD Controller driver"); +MODULE_LICENSE("GPL"); --- /dev/null +++ b/drivers/video/jz4740_slcd.h @@ -0,0 +1,376 @@ +/* + * linux/drivers/video/jzslcd.h -- Ingenic On-Chip SLCD frame buffer device + * + * Copyright (C) 2005-2007, Ingenic Semiconductor Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __JZSLCD_H__ +#define __JZSLCD_H__ + +#define UINT16 unsigned short +#define UINT32 unsigned int + +#define NR_PALETTE 256 +/* Jz LCDFB supported I/O controls. */ +#define FBIOSETBACKLIGHT 0x4688 +#define FBIODISPON 0x4689 +#define FBIODISPOFF 0x468a +#define FBIORESET 0x468b +#define FBIOPRINT_REG 0x468c +#define FBIO_REFRESH_ALWAYS 0x468d +#define FBIO_REFRESH_EVENTS 0x468e +#define FBIO_DO_REFRESH 0x468f +#define FBIO_SET_REG 0x4690 + +#ifdef CONFIG_JZ_SLCD_LGDP4551 +#define PIN_CS_N (32*2+18) /* Chip select :SLCD_WR: GPC18 */ +#define PIN_RESET_N (32*2+21) /* LCD reset :SLCD_RST: GPC21*/ +#define PIN_RS_N (32*2+19) + +#define __slcd_special_pin_init() \ +do { \ + __gpio_as_output(PIN_CS_N); \ + __gpio_as_output(PIN_RESET_N); \ + __gpio_clear_pin(PIN_CS_N); /* Clear CS */\ + mdelay(100); \ +} while(0) + +#define __slcd_special_on() \ +do { /* RESET# */ \ + __gpio_set_pin(PIN_RESET_N); \ + mdelay(10); \ + __gpio_clear_pin(PIN_RESET_N); \ + mdelay(10); \ + __gpio_set_pin(PIN_RESET_N); \ + mdelay(100); \ + Mcupanel_RegSet(0x0015,0x0050); \ + Mcupanel_RegSet(0x0011,0x0000); \ + Mcupanel_RegSet(0x0010,0x3628); \ + Mcupanel_RegSet(0x0012,0x0002); \ + Mcupanel_RegSet(0x0013,0x0E47); \ + udelay(100); \ + Mcupanel_RegSet(0x0012,0x0012); \ + udelay(100); \ + Mcupanel_RegSet(0x0010,0x3620); \ + Mcupanel_RegSet(0x0013,0x2E47); \ + udelay(50); \ + Mcupanel_RegSet(0x0030,0x0000); \ + Mcupanel_RegSet(0x0031,0x0502); \ + Mcupanel_RegSet(0x0032,0x0307); \ + Mcupanel_RegSet(0x0033,0x0304); \ + Mcupanel_RegSet(0x0034,0x0004); \ + Mcupanel_RegSet(0x0035,0x0401); \ + Mcupanel_RegSet(0x0036,0x0707); \ + Mcupanel_RegSet(0x0037,0x0303); \ + Mcupanel_RegSet(0x0038,0x1E02); \ + Mcupanel_RegSet(0x0039,0x1E02); \ + Mcupanel_RegSet(0x0001,0x0000); \ + Mcupanel_RegSet(0x0002,0x0300); \ + if (jzfb.bpp == 16) \ + Mcupanel_RegSet(0x0003,0x10B8); /*8-bit system interface two transfers + up:0x10B8 down:0x1088 left:0x1090 right:0x10a0*/ \ + else \ + if (jzfb.bpp == 32)\ + Mcupanel_RegSet(0x0003,0xD0B8);/*8-bit system interface three transfers,666 + up:0xD0B8 down:0xD088 left:0xD090 right:0xD0A0*/ \ + Mcupanel_RegSet(0x0008,0x0204);\ + Mcupanel_RegSet(0x000A,0x0008);\ + Mcupanel_RegSet(0x0060,0x3100);\ + Mcupanel_RegSet(0x0061,0x0001);\ + Mcupanel_RegSet(0x0090,0x0052);\ + Mcupanel_RegSet(0x0092,0x000F);\ + Mcupanel_RegSet(0x0093,0x0001);\ + Mcupanel_RegSet(0x009A,0x0008);\ + Mcupanel_RegSet(0x00A3,0x0010);\ + Mcupanel_RegSet(0x0050,0x0000);\ + Mcupanel_RegSet(0x0051,0x00EF);\ + Mcupanel_RegSet(0x0052,0x0000);\ + Mcupanel_RegSet(0x0053,0x018F);\ + /*===Display_On_Function=== */ \ + Mcupanel_RegSet(0x0007,0x0001);\ + Mcupanel_RegSet(0x0007,0x0021);\ + Mcupanel_RegSet(0x0007,0x0023);\ + Mcupanel_RegSet(0x0007,0x0033);\ + Mcupanel_RegSet(0x0007,0x0133);\ + Mcupanel_Command(0x0022);/*Write Data to GRAM */ \ + udelay(1); \ + Mcupanel_SetAddr(0,0); \ + mdelay(100); \ +} while (0) + +#define __slcd_special_off() \ +do { \ +} while(0) +#endif /*CONFIG_JZ_SLCD_LGDP4551_xxBUS*/ + +#ifdef CONFIG_JZ_SLCD_SPFD5420A + + //#define PIN_CS_N (32*2+18) // Chip select //GPC18; +#define PIN_CS_N (32*2+22) // Chip select //GPC18; +#define PIN_RESET_N (32*1+18) // LCD reset //GPB18; +#define PIN_RS_N (32*2+19) // LCD RS //GPC19; +#define PIN_POWER_N (32*3+0) //Power off //GPD0; +#define PIN_FMARK_N (32*3+1) //fmark //GPD1; + +#define GAMMA() \ +do { \ + Mcupanel_RegSet(0x0300,0x0101); \ + Mcupanel_RegSet(0x0301,0x0b27); \ + Mcupanel_RegSet(0x0302,0x132a); \ + Mcupanel_RegSet(0x0303,0x2a13); \ + Mcupanel_RegSet(0x0304,0x270b); \ + Mcupanel_RegSet(0x0305,0x0101); \ + Mcupanel_RegSet(0x0306,0x1205); \ + Mcupanel_RegSet(0x0307,0x0512); \ + Mcupanel_RegSet(0x0308,0x0005); \ + Mcupanel_RegSet(0x0309,0x0003); \ + Mcupanel_RegSet(0x030a,0x0f04); \ + Mcupanel_RegSet(0x030b,0x0f00); \ + Mcupanel_RegSet(0x030c,0x000f); \ + Mcupanel_RegSet(0x030d,0x040f); \ + Mcupanel_RegSet(0x030e,0x0300); \ + Mcupanel_RegSet(0x030f,0x0500); \ + /*** secorrect gamma2 ***/ \ + Mcupanel_RegSet(0x0400,0x3500); \ + Mcupanel_RegSet(0x0401,0x0001); \ + Mcupanel_RegSet(0x0404,0x0000); \ + Mcupanel_RegSet(0x0500,0x0000); \ + Mcupanel_RegSet(0x0501,0x0000); \ + Mcupanel_RegSet(0x0502,0x0000); \ + Mcupanel_RegSet(0x0503,0x0000); \ + Mcupanel_RegSet(0x0504,0x0000); \ + Mcupanel_RegSet(0x0505,0x0000); \ + Mcupanel_RegSet(0x0600,0x0000); \ + Mcupanel_RegSet(0x0606,0x0000); \ + Mcupanel_RegSet(0x06f0,0x0000); \ + Mcupanel_RegSet(0x07f0,0x5420); \ + Mcupanel_RegSet(0x07f3,0x288a); \ + Mcupanel_RegSet(0x07f4,0x0022); \ + Mcupanel_RegSet(0x07f5,0x0001); \ + Mcupanel_RegSet(0x07f0,0x0000); \ +} while(0) + +#define __slcd_special_on() \ +do { \ + __gpio_set_pin(PIN_RESET_N); \ + mdelay(10); \ + __gpio_clear_pin(PIN_RESET_N); \ + mdelay(10); \ + __gpio_set_pin(PIN_RESET_N); \ + mdelay(100); \ + if (jzfb.bus == 18) {\ + Mcupanel_RegSet(0x0606,0x0000); \ + udelay(10); \ + Mcupanel_RegSet(0x0007,0x0001); \ + udelay(10); \ + Mcupanel_RegSet(0x0110,0x0001); \ + udelay(10); \ + Mcupanel_RegSet(0x0100,0x17b0); \ + Mcupanel_RegSet(0x0101,0x0147); \ + Mcupanel_RegSet(0x0102,0x019d); \ + Mcupanel_RegSet(0x0103,0x8600); \ + Mcupanel_RegSet(0x0281,0x0010); \ + udelay(10); \ + Mcupanel_RegSet(0x0102,0x01bd); \ + udelay(10); \ + /************initial************/\ + Mcupanel_RegSet(0x0000,0x0000); \ + Mcupanel_RegSet(0x0001,0x0000); \ + Mcupanel_RegSet(0x0002,0x0400); \ + Mcupanel_RegSet(0x0003,0x1288); /*up:0x1288 down:0x12B8 left:0x1290 right:0x12A0*/ \ + Mcupanel_RegSet(0x0006,0x0000); \ + Mcupanel_RegSet(0x0008,0x0503); \ + Mcupanel_RegSet(0x0009,0x0001); \ + Mcupanel_RegSet(0x000b,0x0010); \ + Mcupanel_RegSet(0x000c,0x0000); \ + Mcupanel_RegSet(0x000f,0x0000); \ + Mcupanel_RegSet(0x0007,0x0001); \ + Mcupanel_RegSet(0x0010,0x0010); \ + Mcupanel_RegSet(0x0011,0x0202); \ + Mcupanel_RegSet(0x0012,0x0300); \ + Mcupanel_RegSet(0x0020,0x021e); \ + Mcupanel_RegSet(0x0021,0x0202); \ + Mcupanel_RegSet(0x0022,0x0100); \ + Mcupanel_RegSet(0x0090,0x0000); \ + Mcupanel_RegSet(0x0092,0x0000); \ + Mcupanel_RegSet(0x0100,0x16b0); \ + Mcupanel_RegSet(0x0101,0x0147); \ + Mcupanel_RegSet(0x0102,0x01bd); \ + Mcupanel_RegSet(0x0103,0x2c00); \ + Mcupanel_RegSet(0x0107,0x0000); \ + Mcupanel_RegSet(0x0110,0x0001); \ + Mcupanel_RegSet(0x0210,0x0000); \ + Mcupanel_RegSet(0x0211,0x00ef); \ + Mcupanel_RegSet(0x0212,0x0000); \ + Mcupanel_RegSet(0x0213,0x018f); \ + Mcupanel_RegSet(0x0280,0x0000); \ + Mcupanel_RegSet(0x0281,0x0001); \ + Mcupanel_RegSet(0x0282,0x0000); \ + GAMMA(); \ + Mcupanel_RegSet(0x0007,0x0173); \ + } else { \ + Mcupanel_RegSet(0x0600, 0x0001); /*soft reset*/ \ + mdelay(10); \ + Mcupanel_RegSet(0x0600, 0x0000); /*soft reset*/ \ + mdelay(10); \ + Mcupanel_RegSet(0x0606, 0x0000); /*i80-i/F Endian Control*/ \ + /*===User setting=== */ \ + Mcupanel_RegSet(0x0001, 0x0000);/* Driver Output Control-----0x0100 SM(bit10) | 0x400*/ \ + Mcupanel_RegSet(0x0002, 0x0100); /*LCD Driving Wave Control 0x0100 */ \ + if (jzfb.bpp == 16) \ + Mcupanel_RegSet(0x0003, 0x50A8);/*Entry Mode 0x1030*/ \ + else /*bpp = 18*/ \ + Mcupanel_RegSet(0x0003, 0x1010 | 0xC8); /*Entry Mode 0x1030*/ \ + /*#endif */ \ + Mcupanel_RegSet(0x0006, 0x0000); /*Outline Sharpening Control*/\ + Mcupanel_RegSet(0x0008, 0x0808); /*Sets the number of lines for front/back porch period*/\ + Mcupanel_RegSet(0x0009, 0x0001); /*Display Control 3 */\ + Mcupanel_RegSet(0x000B, 0x0010); /*Low Power Control*/\ + Mcupanel_RegSet(0x000C, 0x0000); /*External Display Interface Control 1 /*0x0001*/\ + Mcupanel_RegSet(0x000F, 0x0000); /*External Display Interface Control 2 */\ + Mcupanel_RegSet(0x0400, 0xB104);/*Base Image Number of Line---GS(bit15) | 0x8000*/ \ + Mcupanel_RegSet(0x0401, 0x0001); /*Base Image Display 0x0001*/\ + Mcupanel_RegSet(0x0404, 0x0000); /*Base Image Vertical Scroll Control 0x0000*/\ + Mcupanel_RegSet(0x0500, 0x0000); /*Partial Image 1: Display Position*/\ + Mcupanel_RegSet(0x0501, 0x0000); /*RAM Address (Start Line Address) */\ + Mcupanel_RegSet(0x0502, 0x018f); /*RAM Address (End Line Address) */ \ + Mcupanel_RegSet(0x0503, 0x0000); /*Partial Image 2: Display Position RAM Address*/\ + Mcupanel_RegSet(0x0504, 0x0000); /*RAM Address (Start Line Address) */\ + Mcupanel_RegSet(0x0505, 0x0000); /*RAM Address (End Line Address)*/\ + /*Panel interface control===*/\ + Mcupanel_RegSet(0x0010, 0x0011); /*Division Ratio,Clocks per Line 14 */\ + mdelay(10); \ + Mcupanel_RegSet(0x0011, 0x0202); /*Division Ratio,Clocks per Line*/\ + Mcupanel_RegSet(0x0012, 0x0300); /*Sets low power VCOM drive period. */\ + mdelay(10); \ + Mcupanel_RegSet(0x0020, 0x021e); /*Panel Interface Control 4 */\ + Mcupanel_RegSet(0x0021, 0x0202); /*Panel Interface Control 5 */\ + Mcupanel_RegSet(0x0022, 0x0100); /*Panel Interface Control 6*/\ + Mcupanel_RegSet(0x0090, 0x0000); /*Frame Marker Control */\ + Mcupanel_RegSet(0x0092, 0x0000); /*MDDI Sub-display Control */\ + /*===Gamma setting=== */\ + Mcupanel_RegSet(0x0300, 0x0101); /*γ Control*/\ + Mcupanel_RegSet(0x0301, 0x0000); /*γ Control*/\ + Mcupanel_RegSet(0x0302, 0x0016); /*γ Control*/\ + Mcupanel_RegSet(0x0303, 0x2913); /*γ Control*/\ + Mcupanel_RegSet(0x0304, 0x260B); /*γ Control*/\ + Mcupanel_RegSet(0x0305, 0x0101); /*γ Control*/\ + Mcupanel_RegSet(0x0306, 0x1204); /*γ Control*/\ + Mcupanel_RegSet(0x0307, 0x0415); /*γ Control*/\ + Mcupanel_RegSet(0x0308, 0x0205); /*γ Control*/\ + Mcupanel_RegSet(0x0309, 0x0303); /*γ Control*/\ + Mcupanel_RegSet(0x030a, 0x0E05); /*γ Control*/\ + Mcupanel_RegSet(0x030b, 0x0D01); /*γ Control*/\ + Mcupanel_RegSet(0x030c, 0x010D); /*γ Control*/\ + Mcupanel_RegSet(0x030d, 0x050E); /*γ Control*/\ + Mcupanel_RegSet(0x030e, 0x0303); /*γ Control*/\ + Mcupanel_RegSet(0x030f, 0x0502); /*γ Control*/\ + /*===Power on sequence===*/\ + Mcupanel_RegSet(0x0007, 0x0001); /*Display Control 1*/\ + Mcupanel_RegSet(0x0110, 0x0001); /*Power supply startup enable bit*/\ + Mcupanel_RegSet(0x0112, 0x0060); /*Power Control 7*/\ + Mcupanel_RegSet(0x0100, 0x16B0); /*Power Control 1 */\ + Mcupanel_RegSet(0x0101, 0x0115); /*Power Control 2*/\ + Mcupanel_RegSet(0x0102, 0x0119); /*Starts VLOUT3,Sets the VREG1OUT.*/\ + mdelay(50); \ + Mcupanel_RegSet(0x0103, 0x2E00); /*set the amplitude of VCOM*/\ + mdelay(50);\ + Mcupanel_RegSet(0x0282, 0x0093);/*0x008E);/*0x0093); /*VCOMH voltage*/\ + Mcupanel_RegSet(0x0281, 0x000A); /*Selects the factor of VREG1OUT to generate VCOMH. */\ + Mcupanel_RegSet(0x0102, 0x01BE); /*Starts VLOUT3,Sets the VREG1OUT.*/\ + mdelay(10);\ + /*Address */\ + Mcupanel_RegSet(0x0210, 0x0000); /*Window Horizontal RAM Address Start*/\ + Mcupanel_RegSet(0x0211, 0x00ef); /*Window Horizontal RAM Address End*/\ + Mcupanel_RegSet(0x0212, 0x0000); /*Window Vertical RAM Address Start*/\ + Mcupanel_RegSet(0x0213, 0x018f); /*Window Vertical RAM Address End */\ + Mcupanel_RegSet(0x0200, 0x0000); /*RAM Address Set (Horizontal Address)*/\ + Mcupanel_RegSet(0x0201, 0x018f); /*RAM Address Set (Vertical Address)*/ \ + /*===Display_On_Function===*/\ + Mcupanel_RegSet(0x0007, 0x0021); /*Display Control 1 */\ + mdelay(50); /*40*/\ + Mcupanel_RegSet(0x0007, 0x0061); /*Display Control 1 */\ + mdelay(50); /*100*/\ + Mcupanel_RegSet(0x0007, 0x0173); /*Display Control 1 */\ + mdelay(50); /*300*/\ + }\ + Mcupanel_Command(0x0202); /*Write Data to GRAM */ \ + udelay(10);\ + Mcupanel_SetAddr(0,0);\ + udelay(100);\ +} while(0) + +#define __slcd_special_pin_init() \ +do { \ + __gpio_as_output(PIN_CS_N); \ + __gpio_as_output(PIN_RESET_N); \ + __gpio_clear_pin(PIN_CS_N); /* Clear CS */ \ + __gpio_as_output(PIN_POWER_N); \ + mdelay(100); \ +} while(0) + +#endif /*CONFIG_JZ_SLCD_SPFD5420A*/ + +#ifndef __slcd_special_pin_init +#define __slcd_special_pin_init() +#endif +#ifndef __slcd_special_on +#define __slcd_special_on() +#endif +#ifndef __slcd_special_off +#define __slcd_special_off() +#endif + +/* + * Platform specific definition + */ +#if defined(CONFIG_SOC_JZ4740) +#if defined(CONFIG_JZ4740_PAVO) +#define GPIO_PWM 123 /* GP_D27 */ +#define PWM_CHN 4 /* pwm channel */ +#define PWM_FULL 101 +/* 100 level: 0,1,...,100 */ +#define __slcd_set_backlight_level(n)\ +do { \ + __gpio_as_output(32*3+27); \ + __gpio_set_pin(32*3+27); \ +} while (0) + +#define __slcd_close_backlight() \ +do { \ + __gpio_as_output(GPIO_PWM); \ + __gpio_clear_pin(GPIO_PWM); \ +} while (0) + +#else + +#define __slcd_set_backlight_level(n) +#define __slcd_close_backlight() + +#endif /* #if defined(CONFIG_MIPS_JZ4740_PAVO) */ + +#define __slcd_display_pin_init() \ +do { \ + __slcd_special_pin_init(); \ +} while (0) + +#define __slcd_display_on() \ +do { \ + __slcd_special_on(); \ + __slcd_set_backlight_level(80); \ +} while (0) + +#define __slcd_display_off() \ +do { \ + __slcd_special_off(); \ + __slcd_close_backlight(); \ +} while (0) + +#endif /* CONFIG_SOC_JZ4740 */ +#endif /*__JZSLCD_H__*/ + --- /dev/null +++ b/drivers/video/jz4750_lcd.c @@ -0,0 +1,2139 @@ +/* + * linux/drivers/video/jz4750_lcd.c -- Ingenic Jz4750 LCD frame buffer device + * + * Copyright (C) 2005-2008, Ingenic Semiconductor Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * -------------------------------- + * NOTE: + * This LCD driver support TFT16 TFT32 LCD, not support STN and Special TFT LCD + * now. + * It seems not necessory to support STN and Special TFT. + * If it's necessary, update this driver in the future. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "console/fbcon.h" + +#include "jz4750_lcd.h" +#include "jz4750_tve.h" + +#ifdef CONFIG_JZ4750_SLCD_KGM701A3_TFT_SPFD5420A +#include "jz_kgm_spfd5420a.h" +#endif + +MODULE_DESCRIPTION("Jz4750 LCD Controller driver"); +MODULE_AUTHOR("Wolfgang Wang, "); +MODULE_LICENSE("GPL"); + + +//#define DEBUG +#undef DEBUG + +#ifdef DEBUG +#define dprintk(x...) printk(x) +#define print_dbg(f, arg...) printk("dbg::" __FILE__ ",LINE(%d): " f "\n", __LINE__, ## arg) +#else +#define dprintk(x...) +#define print_dbg(f, arg...) do {} while (0) +#endif + +#define print_err(f, arg...) printk(KERN_ERR DRIVER_NAME ": " f "\n", ## arg) +#define print_warn(f, arg...) printk(KERN_WARNING DRIVER_NAME ": " f "\n", ## arg) +#define print_info(f, arg...) printk(KERN_INFO DRIVER_NAME ": " f "\n", ## arg) + +struct lcd_cfb_info { + struct fb_info fb; + struct display_switch *dispsw; + signed int currcon; + int func_use_count; + + struct { + u16 red, green, blue; + } palette[NR_PALETTE]; +#ifdef CONFIG_PM + struct pm_dev *pm; +#endif +}; + +static struct lcd_cfb_info *jz4750fb_info; +static struct jz4750_lcd_dma_desc *dma_desc_base; +static struct jz4750_lcd_dma_desc *dma0_desc_palette, *dma0_desc0, *dma0_desc1, *dma1_desc0, *dma1_desc1; +#define DMA_DESC_NUM 6 + +static unsigned char *lcd_palette; +static unsigned char *lcd_frame0; +static unsigned char *lcd_frame1; + +static struct jz4750_lcd_dma_desc *dma0_desc_cmd0, *dma0_desc_cmd; +static unsigned char *lcd_cmdbuf ; + +static void jz4750fb_set_mode( struct jz4750lcd_info * lcd_info ); +static void jz4750fb_deep_set_mode( struct jz4750lcd_info * lcd_info ); + + + +struct jz4750lcd_info jz4750_lcd_panel = { +#if defined(CONFIG_JZ4750_LCD_SAMSUNG_LTP400WQF02) + .panel = { + .cfg = LCD_CFG_LCDPIN_LCD | LCD_CFG_RECOVER | /* Underrun recover */ + LCD_CFG_NEWDES | /* 8words descriptor */ + LCD_CFG_MODE_GENERIC_TFT | /* General TFT panel */ + LCD_CFG_MODE_TFT_18BIT | /* output 18bpp */ + LCD_CFG_HSP | /* Hsync polarity: active low */ + LCD_CFG_VSP, /* Vsync polarity: leading edge is falling edge */ + .slcd_cfg = 0, + .ctrl = LCD_CTRL_OFUM | LCD_CTRL_BST_16, /* 16words burst, enable out FIFO underrun irq */ + 480, 272, 60, 41, 10, 2, 2, 2, 2, + }, + .osd = { + .osd_cfg = LCD_OSDC_OSDEN | /* Use OSD mode */ +// LCD_OSDC_ALPHAEN | /* enable alpha */ + LCD_OSDC_F0EN, /* enable Foreground0 */ + .osd_ctrl = 0, /* disable ipu, */ + .rgb_ctrl = 0, + .bgcolor = 0x000000, /* set background color Black */ + .colorkey0 = 0, /* disable colorkey */ + .colorkey1 = 0, /* disable colorkey */ + .alpha = 0xA0, /* alpha value */ + .ipu_restart = 0x80001000, /* ipu restart */ + .fg_change = FG_CHANGE_ALL, /* change all initially */ + .fg0 = {32, 0, 0, 480, 272}, /* bpp, x, y, w, h */ + .fg1 = {32, 0, 0, 480, 272}, /* bpp, x, y, w, h */ + }, +#elif defined(CONFIG_JZ4750_LCD_AUO_A043FL01V2) + .panel = { + .cfg = LCD_CFG_LCDPIN_LCD | LCD_CFG_RECOVER | /* Underrun recover */ + LCD_CFG_NEWDES | /* 8words descriptor */ + LCD_CFG_MODE_GENERIC_TFT | /* General TFT panel */ + LCD_CFG_MODE_TFT_24BIT | /* output 18bpp */ + LCD_CFG_HSP | /* Hsync polarity: active low */ + LCD_CFG_VSP, /* Vsync polarity: leading edge is falling edge */ + .slcd_cfg = 0, + .ctrl = LCD_CTRL_OFUM | LCD_CTRL_BST_16, /* 16words burst, enable out FIFO underrun irq */ + 480, 272, 60, 41, 10, 8, 4, 4, 2, + }, + .osd = { + .osd_cfg = LCD_OSDC_OSDEN | /* Use OSD mode */ +// LCD_OSDC_ALPHAEN | /* enable alpha */ +// LCD_OSDC_F1EN | /* enable Foreground1 */ + LCD_OSDC_F0EN, /* enable Foreground0 */ + .osd_ctrl = 0, /* disable ipu, */ + .rgb_ctrl = 0, + .bgcolor = 0x000000, /* set background color Black */ + .colorkey0 = 0, /* disable colorkey */ + .colorkey1 = 0, /* disable colorkey */ + .alpha = 0xA0, /* alpha value */ + .ipu_restart = 0x80001000, /* ipu restart */ + .fg_change = FG_CHANGE_ALL, /* change all initially */ + .fg0 = {32, 0, 0, 480, 272}, /* bpp, x, y, w, h */ + .fg1 = {32, 0, 0, 480, 272}, /* bpp, x, y, w, h */ + }, +#elif defined(CONFIG_JZ4750_LCD_TRULY_TFT_GG1P0319LTSW_W) + .panel = { +// .cfg = LCD_CFG_LCDPIN_SLCD | LCD_CFG_RECOVER | /* Underrun recover*/ + .cfg = LCD_CFG_LCDPIN_SLCD | /* Underrun recover*/ + LCD_CFG_NEWDES | /* 8words descriptor */ + LCD_CFG_MODE_SLCD, /* TFT Smart LCD panel */ + .slcd_cfg = SLCD_CFG_DWIDTH_16BIT | SLCD_CFG_CWIDTH_16BIT | SLCD_CFG_CS_ACTIVE_LOW | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_CLK_ACTIVE_FALLING | SLCD_CFG_TYPE_PARALLEL, + .ctrl = LCD_CTRL_OFUM | LCD_CTRL_BST_16, /* 16words burst, enable out FIFO underrun irq */ + 240, 320, 60, 0, 0, 0, 0, 0, 0, + }, + .osd = { + .osd_cfg = LCD_OSDC_OSDEN | /* Use OSD mode */ +// LCD_OSDC_ALPHAEN | /* enable alpha */ +// LCD_OSDC_F1EN | /* enable Foreground0 */ + LCD_OSDC_F0EN, /* enable Foreground0 */ + .osd_ctrl = 0, /* disable ipu, */ + .rgb_ctrl = 0, + .bgcolor = 0x000000, /* set background color Black */ + .colorkey0 = 0, /* disable colorkey */ + .colorkey1 = 0, /* disable colorkey */ + .alpha = 0xA0, /* alpha value */ + .ipu_restart = 0x80001000, /* ipu restart */ + .fg_change = FG_CHANGE_ALL, /* change all initially */ + .fg0 = {32, 0, 0, 240, 320}, /* bpp, x, y, w, h */ + .fg1 = {32, 0, 0, 240, 320}, /* bpp, x, y, w, h */ + }, + +#elif defined(CONFIG_JZ4750_LCD_FOXCONN_PT035TN01) + .panel = { + .cfg = LCD_CFG_LCDPIN_LCD | LCD_CFG_RECOVER | /* Underrun recover */ + LCD_CFG_NEWDES | /* 8words descriptor */ + LCD_CFG_MODE_GENERIC_TFT | /* General TFT panel */ +// LCD_CFG_MODE_TFT_18BIT | /* output 18bpp */ + LCD_CFG_MODE_TFT_24BIT | /* output 24bpp */ + LCD_CFG_HSP | /* Hsync polarity: active low */ + LCD_CFG_VSP | /* Vsync polarity: leading edge is falling edge */ + LCD_CFG_PCP, /* Pix-CLK polarity: data translations at falling edge */ + .slcd_cfg = 0, + .ctrl = LCD_CTRL_OFUM | LCD_CTRL_BST_16, /* 16words burst, enable out FIFO underrun irq */ + 320, 240, 80, 1, 1, 10, 50, 10, 13 + }, + .osd = { + .osd_cfg = LCD_OSDC_OSDEN | /* Use OSD mode */ +// LCD_OSDC_ALPHAEN | /* enable alpha */ +// LCD_OSDC_F1EN | /* enable Foreground1 */ + LCD_OSDC_F0EN, /* enable Foreground0 */ + .osd_ctrl = 0, /* disable ipu, */ + .rgb_ctrl = 0, + .bgcolor = 0x000000, /* set background color Black */ + .colorkey0 = 0, /* disable colorkey */ + .colorkey1 = 0, /* disable colorkey */ + .alpha = 0xA0, /* alpha value */ + .ipu_restart = 0x80001000, /* ipu restart */ + .fg_change = FG_CHANGE_ALL, /* change all initially */ + .fg0 = {32, 0, 0, 320, 240}, /* bpp, x, y, w, h */ + .fg1 = {32, 0, 0, 320, 240}, /* bpp, x, y, w, h */ + }, +#elif defined(CONFIG_JZ4750_LCD_INNOLUX_PT035TN01_SERIAL) + .panel = { + .cfg = LCD_CFG_LCDPIN_LCD | LCD_CFG_RECOVER | /* Underrun recover */ + LCD_CFG_NEWDES | /* 8words descriptor */ + LCD_CFG_MODE_SERIAL_TFT | /* Serial TFT panel */ + LCD_CFG_MODE_TFT_18BIT | /* output 18bpp */ + LCD_CFG_HSP | /* Hsync polarity: active low */ + LCD_CFG_VSP | /* Vsync polarity: leading edge is falling edge */ + LCD_CFG_PCP, /* Pix-CLK polarity: data translations at falling edge */ + .slcd_cfg = 0, + .ctrl = LCD_CTRL_OFUM | LCD_CTRL_BST_16, /* 16words burst, enable out FIFO underrun irq */ + 320, 240, 60, 1, 1, 10, 50, 10, 13 + }, + .osd = { + .osd_cfg = LCD_OSDC_OSDEN | /* Use OSD mode */ +// LCD_OSDC_ALPHAEN | /* enable alpha */ + LCD_OSDC_F0EN, /* enable Foreground0 */ + .osd_ctrl = 0, /* disable ipu, */ + .rgb_ctrl = 0, + .bgcolor = 0x000000, /* set background color Black */ + .colorkey0 = 0, /* disable colorkey */ + .colorkey1 = 0, /* disable colorkey */ + .alpha = 0xA0, /* alpha value */ + .ipu_restart = 0x80001000, /* ipu restart */ + .fg_change = FG_CHANGE_ALL, /* change all initially */ + .fg0 = {32, 0, 0, 320, 240}, /* bpp, x, y, w, h */ + .fg1 = {32, 0, 0, 320, 240}, /* bpp, x, y, w, h */ + }, +#elif defined(CONFIG_JZ4750_SLCD_KGM701A3_TFT_SPFD5420A) + .panel = { +// .cfg = LCD_CFG_LCDPIN_SLCD | LCD_CFG_RECOVER | /* Underrun recover*/ + .cfg = LCD_CFG_LCDPIN_SLCD | /* Underrun recover*/ +// LCD_CFG_DITHER | /* dither */ + LCD_CFG_NEWDES | /* 8words descriptor */ + LCD_CFG_MODE_SLCD, /* TFT Smart LCD panel */ + .slcd_cfg = SLCD_CFG_DWIDTH_18BIT | SLCD_CFG_CWIDTH_18BIT | SLCD_CFG_CS_ACTIVE_LOW | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_CLK_ACTIVE_FALLING | SLCD_CFG_TYPE_PARALLEL, + .ctrl = LCD_CTRL_OFUM | LCD_CTRL_BST_16, /* 16words burst, enable out FIFO underrun irq */ + 400, 240, 60, 0, 0, 0, 0, 0, 0, + }, + .osd = { + .osd_cfg = LCD_OSDC_OSDEN | /* Use OSD mode */ +// LCD_OSDC_ALPHAEN | /* enable alpha */ +// LCD_OSDC_ALPHAMD | /* alpha blending mode */ +// LCD_OSDC_F1EN | /* enable Foreground1 */ + LCD_OSDC_F0EN, /* enable Foreground0 */ + .osd_ctrl = 0, /* disable ipu, */ + .rgb_ctrl = 0, + .bgcolor = 0x000000, /* set background color Black */ + .colorkey0 = 0, /* disable colorkey */ + .colorkey1 = 0, /* disable colorkey */ + .alpha = 0xA0, /* alpha value */ + .ipu_restart = 0x80001000, /* ipu restart */ + .fg_change = FG_CHANGE_ALL, /* change all initially */ + .fg0 = {32, 0, 0, 400, 240}, /* bpp, x, y, w, h */ + .fg1 = {32, 0, 0, 400, 240}, /* bpp, x, y, w, h */ + }, +#else +#error "Select LCD panel first!!!" +#endif +}; + +struct jz4750lcd_info jz4750_info_tve = { + .panel = { + .cfg = LCD_CFG_TVEN | /* output to tve */ + LCD_CFG_NEWDES | /* 8words descriptor */ + LCD_CFG_RECOVER | /* underrun protect */ + LCD_CFG_MODE_INTER_CCIR656, /* Interlace CCIR656 mode */ + .ctrl = LCD_CTRL_OFUM | LCD_CTRL_BST_16, /* 16words burst */ + TVE_WIDTH_PAL, TVE_HEIGHT_PAL, TVE_FREQ_PAL, 0, 0, 0, 0, 0, 0, + }, + .osd = { + .osd_cfg = LCD_OSDC_OSDEN | /* Use OSD mode */ +// LCD_OSDC_ALPHAEN | /* enable alpha */ + LCD_OSDC_F0EN, /* enable Foreground0 */ + .osd_ctrl = 0, /* disable ipu, */ + .rgb_ctrl = LCD_RGBC_YCC, /* enable RGB => YUV */ + .bgcolor = 0x00000000, /* set background color Black */ + .colorkey0 = 0, /* disable colorkey */ + .colorkey1 = 0, /* disable colorkey */ + .alpha = 0xA0, /* alpha value */ + .ipu_restart = 0x80000100, /* ipu restart */ + .fg_change = FG_CHANGE_ALL, /* change all initially */ + .fg0 = {32,}, /* */ + .fg0 = {32,}, + }, +}; + + +struct jz4750lcd_info *jz4750_lcd_info = &jz4750_lcd_panel; /* default output to lcd panel */ + +#ifdef DEBUG +static void print_lcdc_registers(void) /* debug */ +{ + /* LCD Controller Resgisters */ + printk("REG_LCD_CFG:\t0x%08x\n", REG_LCD_CFG); + printk("REG_LCD_CTRL:\t0x%08x\n", REG_LCD_CTRL); + printk("REG_LCD_STATE:\t0x%08x\n", REG_LCD_STATE); + printk("REG_LCD_OSDC:\t0x%08x\n", REG_LCD_OSDC); + printk("REG_LCD_OSDCTRL:\t0x%08x\n", REG_LCD_OSDCTRL); + printk("REG_LCD_OSDS:\t0x%08x\n", REG_LCD_OSDS); + printk("REG_LCD_BGC:\t0x%08x\n", REG_LCD_BGC); + printk("REG_LCD_KEK0:\t0x%08x\n", REG_LCD_KEY0); + printk("REG_LCD_KEY1:\t0x%08x\n", REG_LCD_KEY1); + printk("REG_LCD_ALPHA:\t0x%08x\n", REG_LCD_ALPHA); + printk("REG_LCD_IPUR:\t0x%08x\n", REG_LCD_IPUR); + printk("REG_LCD_VAT:\t0x%08x\n", REG_LCD_VAT); + printk("REG_LCD_DAH:\t0x%08x\n", REG_LCD_DAH); + printk("REG_LCD_DAV:\t0x%08x\n", REG_LCD_DAV); + printk("REG_LCD_XYP0:\t0x%08x\n", REG_LCD_XYP0); + printk("REG_LCD_XYP1:\t0x%08x\n", REG_LCD_XYP1); + printk("REG_LCD_SIZE0:\t0x%08x\n", REG_LCD_SIZE0); + printk("REG_LCD_SIZE1:\t0x%08x\n", REG_LCD_SIZE1); + printk("REG_LCD_RGBC\t0x%08x\n", REG_LCD_RGBC); + printk("REG_LCD_VSYNC:\t0x%08x\n", REG_LCD_VSYNC); + printk("REG_LCD_HSYNC:\t0x%08x\n", REG_LCD_HSYNC); + printk("REG_LCD_PS:\t0x%08x\n", REG_LCD_PS); + printk("REG_LCD_CLS:\t0x%08x\n", REG_LCD_CLS); + printk("REG_LCD_SPL:\t0x%08x\n", REG_LCD_SPL); + printk("REG_LCD_REV:\t0x%08x\n", REG_LCD_REV); + printk("REG_LCD_IID:\t0x%08x\n", REG_LCD_IID); + printk("REG_LCD_DA0:\t0x%08x\n", REG_LCD_DA0); + printk("REG_LCD_SA0:\t0x%08x\n", REG_LCD_SA0); + printk("REG_LCD_FID0:\t0x%08x\n", REG_LCD_FID0); + printk("REG_LCD_CMD0:\t0x%08x\n", REG_LCD_CMD0); + printk("REG_LCD_OFFS0:\t0x%08x\n", REG_LCD_OFFS0); + printk("REG_LCD_PW0:\t0x%08x\n", REG_LCD_PW0); + printk("REG_LCD_CNUM0:\t0x%08x\n", REG_LCD_CNUM0); + printk("REG_LCD_DESSIZE0:\t0x%08x\n", REG_LCD_DESSIZE0); + printk("REG_LCD_DA1:\t0x%08x\n", REG_LCD_DA1); + printk("REG_LCD_SA1:\t0x%08x\n", REG_LCD_SA1); + printk("REG_LCD_FID1:\t0x%08x\n", REG_LCD_FID1); + printk("REG_LCD_CMD1:\t0x%08x\n", REG_LCD_CMD1); + printk("REG_LCD_OFFS1:\t0x%08x\n", REG_LCD_OFFS1); + printk("REG_LCD_PW1:\t0x%08x\n", REG_LCD_PW1); + printk("REG_LCD_CNUM1:\t0x%08x\n", REG_LCD_CNUM1); + printk("REG_LCD_DESSIZE1:\t0x%08x\n", REG_LCD_DESSIZE1); + printk("==================================\n"); + printk("REG_LCD_VSYNC:\t%d:%d\n", REG_LCD_VSYNC>>16, REG_LCD_VSYNC&0xfff); + printk("REG_LCD_HSYNC:\t%d:%d\n", REG_LCD_HSYNC>>16, REG_LCD_HSYNC&0xfff); + printk("REG_LCD_VAT:\t%d:%d\n", REG_LCD_VAT>>16, REG_LCD_VAT&0xfff); + printk("REG_LCD_DAH:\t%d:%d\n", REG_LCD_DAH>>16, REG_LCD_DAH&0xfff); + printk("REG_LCD_DAV:\t%d:%d\n", REG_LCD_DAV>>16, REG_LCD_DAV&0xfff); + printk("==================================\n"); + + /* Smart LCD Controller Resgisters */ + printk("REG_SLCD_CFG:\t0x%08x\n", REG_SLCD_CFG); + printk("REG_SLCD_CTRL:\t0x%08x\n", REG_SLCD_CTRL); + printk("REG_SLCD_STATE:\t0x%08x\n", REG_SLCD_STATE); + printk("==================================\n"); + + /* TVE Controller Resgisters */ + printk("REG_TVE_CTRL:\t0x%08x\n", REG_TVE_CTRL); + printk("REG_TVE_FRCFG:\t0x%08x\n", REG_TVE_FRCFG); + printk("REG_TVE_SLCFG1:\t0x%08x\n", REG_TVE_SLCFG1); + printk("REG_TVE_SLCFG2:\t0x%08x\n", REG_TVE_SLCFG2); + printk("REG_TVE_SLCFG3:\t0x%08x\n", REG_TVE_SLCFG3); + printk("REG_TVE_LTCFG1:\t0x%08x\n", REG_TVE_LTCFG1); + printk("REG_TVE_LTCFG2:\t0x%08x\n", REG_TVE_LTCFG2); + printk("REG_TVE_CFREQ:\t0x%08x\n", REG_TVE_CFREQ); + printk("REG_TVE_CPHASE:\t0x%08x\n", REG_TVE_CPHASE); + printk("REG_TVE_CBCRCFG:\t0x%08x\n", REG_TVE_CBCRCFG); + printk("REG_TVE_WSSCR:\t0x%08x\n", REG_TVE_WSSCR); + printk("REG_TVE_WSSCFG1:\t0x%08x\n", REG_TVE_WSSCFG1); + printk("REG_TVE_WSSCFG2:\t0x%08x\n", REG_TVE_WSSCFG2); + printk("REG_TVE_WSSCFG3:\t0x%08x\n", REG_TVE_WSSCFG3); + + printk("==================================\n"); + + if ( 1 ) { + unsigned int * pii = (unsigned int *)dma_desc_base; + int i, j; + for (j=0;j< DMA_DESC_NUM ; j++) { + printk("dma_desc%d(0x%08x):\n", j, (unsigned int)pii); + for (i =0; i<8; i++ ) { + printk("\t\t0x%08x\n", *pii++); + } + } + } +} +#else +#define print_lcdc_registers() +#endif + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int jz4750fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + unsigned short *ptr, ctmp; + +// print_dbg("regno:%d,RGBt:(%d,%d,%d,%d)\t", regno, red, green, blue, transp); + if (regno >= NR_PALETTE) + return 1; + + cfb->palette[regno].red = red ; + cfb->palette[regno].green = green; + cfb->palette[regno].blue = blue; + if (cfb->fb.var.bits_per_pixel <= 16) { + red >>= 8; + green >>= 8; + blue >>= 8; + + red &= 0xff; + green &= 0xff; + blue &= 0xff; + } + switch (cfb->fb.var.bits_per_pixel) { + case 1: + case 2: + case 4: + case 8: + if (((jz4750_lcd_info->panel.cfg & LCD_CFG_MODE_MASK) == LCD_CFG_MODE_SINGLE_MSTN ) || + ((jz4750_lcd_info->panel.cfg & LCD_CFG_MODE_MASK) == LCD_CFG_MODE_DUAL_MSTN )) { + ctmp = (77L * red + 150L * green + 29L * blue) >> 8; + ctmp = ((ctmp >> 3) << 11) | ((ctmp >> 2) << 5) | + (ctmp >> 3); + } else { + /* RGB 565 */ + if (((red >> 3) == 0) && ((red >> 2) != 0)) + red = 1 << 3; + if (((blue >> 3) == 0) && ((blue >> 2) != 0)) + blue = 1 << 3; + ctmp = ((red >> 3) << 11) + | ((green >> 2) << 5) | (blue >> 3); + } + + ptr = (unsigned short *)lcd_palette; + ptr = (unsigned short *)(((u32)ptr)|0xa0000000); + ptr[regno] = ctmp; + + break; + + case 15: + if (regno < 16) + ((u32 *)cfb->fb.pseudo_palette)[regno] = + ((red >> 3) << 10) | + ((green >> 3) << 5) | + (blue >> 3); + break; + case 16: + if (regno < 16) { + ((u32 *)cfb->fb.pseudo_palette)[regno] = + ((red >> 3) << 11) | + ((green >> 2) << 5) | + (blue >> 3); + } + break; + case 17 ... 32: + if (regno < 16) + ((u32 *)cfb->fb.pseudo_palette)[regno] = + (red << 16) | + (green << 8) | + (blue << 0); + +/* if (regno < 16) { + unsigned val; + val = chan_to_field(red, &cfb->fb.var.red); + val |= chan_to_field(green, &cfb->fb.var.green); + val |= chan_to_field(blue, &cfb->fb.var.blue); + ((u32 *)cfb->fb.pseudo_palette)[regno] = val; + } +*/ + + break; + } + return 0; +} + + +/* + * switch to tve mode from lcd mode + * mode: + * PANEL_MODE_TVE_PAL: switch to TVE_PAL mode + * PANEL_MODE_TVE_NTSC: switch to TVE_NTSC mode + */ +static void jz4750lcd_info_switch_to_TVE(int mode) +{ + struct jz4750lcd_info *info; + struct jz4750lcd_osd_t *osd_lcd; + int x, y, w, h; + + info = jz4750_lcd_info = &jz4750_info_tve; + osd_lcd = &jz4750_lcd_panel.osd; + + switch ( mode ) { + case PANEL_MODE_TVE_PAL: + info->panel.cfg |= LCD_CFG_TVEPEH; /* TVE PAL enable extra halfline signal */ + info->panel.w = TVE_WIDTH_PAL; + info->panel.h = TVE_HEIGHT_PAL; + info->panel.fclk = TVE_FREQ_PAL; + w = ( osd_lcd->fg0.w < TVE_WIDTH_PAL )? osd_lcd->fg0.w:TVE_WIDTH_PAL; + h = ( osd_lcd->fg0.h < TVE_HEIGHT_PAL )?osd_lcd->fg0.h:TVE_HEIGHT_PAL; + x = (TVE_WIDTH_PAL-w)/2; + y = (TVE_HEIGHT_PAL-h)/2; + info->osd.fg0.bpp = osd_lcd->fg0.bpp; + info->osd.fg0.x = x; + info->osd.fg0.y = y; + info->osd.fg0.w = w; + info->osd.fg0.h = h; + w = ( osd_lcd->fg1.w < TVE_WIDTH_PAL )? osd_lcd->fg1.w:TVE_WIDTH_PAL; + h = ( osd_lcd->fg1.h < TVE_HEIGHT_PAL )?osd_lcd->fg1.h:TVE_HEIGHT_PAL; + x = (TVE_WIDTH_PAL-w)/2; + y = (TVE_HEIGHT_PAL-h)/2; + info->osd.fg1.bpp = 32; /* use RGB888 in TVE mode*/ + info->osd.fg1.x = x; + info->osd.fg1.y = y; + info->osd.fg1.w = w; + info->osd.fg1.h = h; + break; + case PANEL_MODE_TVE_NTSC: + info->panel.cfg &= ~LCD_CFG_TVEPEH; /* TVE NTSC disable extra halfline signal */ + info->panel.w = TVE_WIDTH_NTSC; + info->panel.h = TVE_HEIGHT_NTSC; + info->panel.fclk = TVE_FREQ_NTSC; + w = ( osd_lcd->fg0.w < TVE_WIDTH_NTSC )? osd_lcd->fg0.w:TVE_WIDTH_NTSC; + h = ( osd_lcd->fg0.h < TVE_HEIGHT_NTSC)?osd_lcd->fg0.h:TVE_HEIGHT_NTSC; + x = (TVE_WIDTH_NTSC - w)/2; + y = (TVE_HEIGHT_NTSC - h)/2; + info->osd.fg0.bpp = osd_lcd->fg0.bpp; + info->osd.fg0.x = x; + info->osd.fg0.y = y; + info->osd.fg0.w = w; + info->osd.fg0.h = h; + w = ( osd_lcd->fg1.w < TVE_WIDTH_NTSC )? osd_lcd->fg1.w:TVE_WIDTH_NTSC; + h = ( osd_lcd->fg1.h < TVE_HEIGHT_NTSC)?osd_lcd->fg1.h:TVE_HEIGHT_NTSC; + x = (TVE_WIDTH_NTSC - w)/2; + y = (TVE_HEIGHT_NTSC - h)/2; + info->osd.fg1.bpp = 32; /* use RGB888 int TVE mode */ + info->osd.fg1.x = x; + info->osd.fg1.y = y; + info->osd.fg1.w = w; + info->osd.fg1.h = h; + break; + default: + printk("%s, %s: Unknown tve mode\n", __FILE__, __FUNCTION__); + } +} + +static int jz4750fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + void __user *argp = (void __user *)arg; + +// struct jz4750lcd_info *lcd_info = jz4750_lcd_info; + + + switch (cmd) { + case FBIOSETBACKLIGHT: + __lcd_set_backlight_level(arg); /* We support 8 levels here. */ + break; + case FBIODISPON: + REG_LCD_STATE = 0; /* clear lcdc status */ + __lcd_slcd_special_on(); + __lcd_clr_dis(); + __lcd_set_ena(); /* enable lcdc */ + __lcd_display_on(); + break; + case FBIODISPOFF: + __lcd_display_off(); + if ( jz4750_lcd_info->panel.cfg & LCD_CFG_LCDPIN_SLCD || + jz4750_lcd_info->panel.cfg & LCD_CFG_TVEN ) /* */ + __lcd_clr_ena(); /* Smart lcd and TVE mode only support quick disable */ + else + __lcd_set_dis(); /* regular disable */ + break; + case FBIOPRINT_REG: + print_lcdc_registers(); + break; + case FBIO_GET_MODE: + print_dbg("fbio get mode\n"); + if (copy_to_user(argp, jz4750_lcd_info, sizeof(struct jz4750lcd_info))) + return -EFAULT; + break; + case FBIO_SET_MODE: + print_dbg("fbio set mode\n"); + if (copy_from_user(jz4750_lcd_info, argp, sizeof(struct jz4750lcd_info))) + return -EFAULT; + /* set mode */ + jz4750fb_set_mode(jz4750_lcd_info); + break; + case FBIO_DEEP_SET_MODE: + print_dbg("fbio deep set mode\n"); + if (copy_from_user(jz4750_lcd_info, argp, sizeof(struct jz4750lcd_info))) + return -EFAULT; + jz4750fb_deep_set_mode(jz4750_lcd_info); + break; + case FBIO_MODE_SWITCH: + print_dbg("lcd mode switch between tve and lcd, arg=%lu\n", arg); + switch ( arg ) { + case PANEL_MODE_TVE_PAL: /* switch to TVE_PAL mode */ + case PANEL_MODE_TVE_NTSC: /* switch to TVE_NTSC mode */ + jz4750lcd_info_switch_to_TVE(arg); + jz4750tve_init(arg); /* tve controller init */ + jz4750tve_enable_tve(); + /* turn off lcd backlight */ + __lcd_display_off(); + break; + case PANEL_MODE_LCD_PANEL: /* switch to LCD mode */ + default : + /* turn off TVE, turn off DACn... */ + jz4750tve_disable_tve(); + jz4750_lcd_info = &jz4750_lcd_panel; + /* turn on lcd backlight */ + __lcd_display_on(); + break; + } + jz4750fb_deep_set_mode(jz4750_lcd_info); + break; + case FBIO_GET_TVE_MODE: + print_dbg("fbio get TVE mode\n"); + if (copy_to_user(argp, jz4750_tve_info, sizeof(struct jz4750tve_info))) + return -EFAULT; + break; + case FBIO_SET_TVE_MODE: + print_dbg("fbio set TVE mode\n"); + if (copy_from_user(jz4750_tve_info, argp, sizeof(struct jz4750tve_info))) + return -EFAULT; + /* set tve mode */ + jz4750tve_set_tve_mode(jz4750_tve_info); + break; + default: + printk("%s, unknown command(0x%x)", __FILE__, cmd); + break; + } + + return ret; +} + +/* Use mmap /dev/fb can only get a non-cacheable Virtual Address. */ +static int jz4750fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + unsigned long start; + unsigned long off; + u32 len; + dprintk("%s, %s, %d\n", __FILE__, __FUNCTION__, __LINE__); + off = vma->vm_pgoff << PAGE_SHIFT; + //fb->fb_get_fix(&fix, PROC_CONSOLE(info), info); + + /* frame buffer memory */ + start = cfb->fb.fix.smem_start; + len = PAGE_ALIGN((start & ~PAGE_MASK) + cfb->fb.fix.smem_len); + start &= PAGE_MASK; + + if ((vma->vm_end - vma->vm_start + off) > len) + return -EINVAL; + off += start; + + vma->vm_pgoff = off >> PAGE_SHIFT; + vma->vm_flags |= VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); /* Uncacheable */ + +#if 1 + pgprot_val(vma->vm_page_prot) &= ~_CACHE_MASK; + pgprot_val(vma->vm_page_prot) |= _CACHE_UNCACHED; /* Uncacheable */ +// pgprot_val(vma->vm_page_prot) |= _CACHE_CACHABLE_NONCOHERENT; /* Write-Back */ +#endif + + if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) { + return -EAGAIN; + } + return 0; +} + +/* checks var and eventually tweaks it to something supported, + * DO NOT MODIFY PAR */ +static int jz4750fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + printk("jz4750fb_check_var, not implement\n"); + return 0; +} + + +/* + * set the video mode according to info->var + */ +static int jz4750fb_set_par(struct fb_info *info) +{ + printk("jz4750fb_set_par, not implemented\n"); + return 0; +} + + +/* + * (Un)Blank the display. + * Fix me: should we use VESA value? + */ +static int jz4750fb_blank(int blank_mode, struct fb_info *info) +{ + dprintk("jz4750 fb_blank %d %p", blank_mode, info); + switch (blank_mode) { + case FB_BLANK_UNBLANK: + //case FB_BLANK_NORMAL: + /* Turn on panel */ + __lcd_set_ena(); + __lcd_display_on(); + break; + + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: +#if 0 + /* Turn off panel */ + __lcd_display_off(); + __lcd_set_dis(); +#endif + break; + default: + break; + + } + return 0; +} + +/* + * pan display + */ +static int jz4750fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + int dy; + + if (!var || !cfb) { + return -EINVAL; + } + + if (var->xoffset - cfb->fb.var.xoffset) { + /* No support for X panning for now! */ + return -EINVAL; + } + + dy = var->yoffset - cfb->fb.var.yoffset; + print_dbg("var.yoffset: %d", dy); + if (dy) { + print_dbg("Panning screen of %d lines", dy); + dma0_desc0->databuf += (cfb->fb.fix.line_length * dy); + /* TODO: Wait for current frame to finished */ + } + + return 0; +} + + +/* use default function cfb_fillrect, cfb_copyarea, cfb_imageblit */ +static struct fb_ops jz4750fb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = jz4750fb_setcolreg, + .fb_check_var = jz4750fb_check_var, + .fb_set_par = jz4750fb_set_par, + .fb_blank = jz4750fb_blank, + .fb_pan_display = jz4750fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = jz4750fb_mmap, + .fb_ioctl = jz4750fb_ioctl, +}; + +static int jz4750fb_set_var(struct fb_var_screeninfo *var, int con, + struct fb_info *info) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + struct jz4750lcd_info *lcd_info = jz4750_lcd_info; + int chgvar = 0; + + var->height = lcd_info->osd.fg0.h; /* tve mode */ + var->width = lcd_info->osd.fg0.w; + var->bits_per_pixel = lcd_info->osd.fg0.bpp; + + var->vmode = FB_VMODE_NONINTERLACED; + var->activate = cfb->fb.var.activate; + var->xres = var->width; + var->yres = var->height; + var->xres_virtual = var->width; + var->yres_virtual = var->height; + var->xoffset = 0; + var->yoffset = 0; + var->pixclock = 0; + var->left_margin = 0; + var->right_margin = 0; + var->upper_margin = 0; + var->lower_margin = 0; + var->hsync_len = 0; + var->vsync_len = 0; + var->sync = 0; + var->activate &= ~FB_ACTIVATE_TEST; + + /* + * CONUPDATE and SMOOTH_XPAN are equal. However, + * SMOOTH_XPAN is only used internally by fbcon. + */ + if (var->vmode & FB_VMODE_CONUPDATE) { + var->vmode |= FB_VMODE_YWRAP; + var->xoffset = cfb->fb.var.xoffset; + var->yoffset = cfb->fb.var.yoffset; + } + + if (var->activate & FB_ACTIVATE_TEST) + return 0; + + if ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NOW) + return -EINVAL; + + if (cfb->fb.var.xres != var->xres) + chgvar = 1; + if (cfb->fb.var.yres != var->yres) + chgvar = 1; + if (cfb->fb.var.xres_virtual != var->xres_virtual) + chgvar = 1; + if (cfb->fb.var.yres_virtual != var->yres_virtual) + chgvar = 1; + if (cfb->fb.var.bits_per_pixel != var->bits_per_pixel) + chgvar = 1; + + //display = fb_display + con; + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + + switch(var->bits_per_pixel){ + case 1: /* Mono */ + cfb->fb.fix.visual = FB_VISUAL_MONO01; + cfb->fb.fix.line_length = (var->xres * var->bits_per_pixel) / 8; + break; + case 2: /* Mono */ + var->red.offset = 0; + var->red.length = 2; + var->green.offset = 0; + var->green.length = 2; + var->blue.offset = 0; + var->blue.length = 2; + + cfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + cfb->fb.fix.line_length = (var->xres * var->bits_per_pixel) / 8; + break; + case 4: /* PSEUDOCOLOUR*/ + var->red.offset = 0; + var->red.length = 4; + var->green.offset = 0; + var->green.length = 4; + var->blue.offset = 0; + var->blue.length = 4; + + cfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + cfb->fb.fix.line_length = var->xres / 2; + break; + case 8: /* PSEUDOCOLOUR, 256 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + + cfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + cfb->fb.fix.line_length = var->xres ; + break; + case 15: /* DIRECTCOLOUR, 32k */ + var->bits_per_pixel = 15; + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + + cfb->fb.fix.visual = FB_VISUAL_DIRECTCOLOR; + cfb->fb.fix.line_length = var->xres_virtual * 2; + break; + case 16: /* DIRECTCOLOUR, 64k */ + var->bits_per_pixel = 16; + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + + cfb->fb.fix.visual = FB_VISUAL_TRUECOLOR; + cfb->fb.fix.line_length = var->xres_virtual * 2; + break; + case 17 ... 32: + /* DIRECTCOLOUR, 256 */ + var->bits_per_pixel = 32; + + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + + cfb->fb.fix.visual = FB_VISUAL_TRUECOLOR; + cfb->fb.fix.line_length = var->xres_virtual * 4; + break; + + default: /* in theory this should never happen */ + printk(KERN_WARNING "%s: don't support for %dbpp\n", + cfb->fb.fix.id, var->bits_per_pixel); + break; + } + + cfb->fb.var = *var; + cfb->fb.var.activate &= ~FB_ACTIVATE_ALL; + + /* + * Update the old var. The fbcon drivers still use this. + * Once they are using cfb->fb.var, this can be dropped. + * --rmk + */ + //display->var = cfb->fb.var; + /* + * If we are setting all the virtual consoles, also set the + * defaults used to create new consoles. + */ + fb_set_cmap(&cfb->fb.cmap, &cfb->fb); + dprintk("jz4750fb_set_var: after fb_set_cmap...\n"); + + return 0; +} + +static struct lcd_cfb_info * jz4750fb_alloc_fb_info(void) +{ + struct lcd_cfb_info *cfb; + + cfb = kmalloc(sizeof(struct lcd_cfb_info) + sizeof(u32) * 16, GFP_KERNEL); + + if (!cfb) + return NULL; + + jz4750fb_info = cfb; + + memset(cfb, 0, sizeof(struct lcd_cfb_info) ); + + cfb->currcon = -1; + + + strcpy(cfb->fb.fix.id, "jz-lcd"); + cfb->fb.fix.type = FB_TYPE_PACKED_PIXELS; + cfb->fb.fix.type_aux = 0; + cfb->fb.fix.xpanstep = 1; + cfb->fb.fix.ypanstep = 1; + cfb->fb.fix.ywrapstep = 0; + cfb->fb.fix.accel = FB_ACCEL_NONE; + + cfb->fb.var.nonstd = 0; + cfb->fb.var.activate = FB_ACTIVATE_NOW; + cfb->fb.var.height = -1; + cfb->fb.var.width = -1; + cfb->fb.var.accel_flags = FB_ACCELF_TEXT; + + cfb->fb.fbops = &jz4750fb_ops; + cfb->fb.flags = FBINFO_FLAG_DEFAULT; + + cfb->fb.pseudo_palette = (void *)(cfb + 1); + + switch (jz4750_lcd_info->osd.fg0.bpp) { + case 1: + fb_alloc_cmap(&cfb->fb.cmap, 4, 0); + break; + case 2: + fb_alloc_cmap(&cfb->fb.cmap, 8, 0); + break; + case 4: + fb_alloc_cmap(&cfb->fb.cmap, 32, 0); + break; + case 8: + + default: + fb_alloc_cmap(&cfb->fb.cmap, 256, 0); + break; + } + dprintk("fb_alloc_cmap,fb.cmap.len:%d....\n", cfb->fb.cmap.len); + + return cfb; +} + +/* + * Map screen memory + */ +static int jz4750fb_map_smem(struct lcd_cfb_info *cfb) +{ + unsigned long page; + unsigned int page_shift, needroom, needroom1, bpp, w, h; + + bpp = jz4750_lcd_info->osd.fg0.bpp; + if ( bpp == 18 || bpp == 24) + bpp = 32; + if ( bpp == 15 ) + bpp = 16; + w = jz4750_lcd_info->osd.fg0.w; + h = jz4750_lcd_info->osd.fg0.h; + needroom1 = needroom = ((w * bpp + 7) >> 3) * h; +#if defined(CONFIG_FB_JZ4750_LCD_USE_2LAYER_FRAMEBUFFER) + bpp = jz4750_lcd_info->osd.fg1.bpp; + if ( bpp == 18 || bpp == 24) + bpp = 32; + if ( bpp == 15 ) + bpp = 16; + w = jz4750_lcd_info->osd.fg1.w; + h = jz4750_lcd_info->osd.fg1.h; + needroom += ((w * bpp + 7) >> 3) * h; +#endif + + for (page_shift = 0; page_shift < 12; page_shift++) + if ((PAGE_SIZE << page_shift) >= needroom) + break; + lcd_palette = (unsigned char *)__get_free_pages(GFP_KERNEL, 0); + lcd_frame0 = (unsigned char *)__get_free_pages(GFP_KERNEL, page_shift); + + if ((!lcd_palette) || (!lcd_frame0)) + return -ENOMEM; + memset((void *)lcd_palette, 0, PAGE_SIZE); + memset((void *)lcd_frame0, 0, PAGE_SIZE << page_shift); + + dma_desc_base = (struct jz4750_lcd_dma_desc *)((void*)lcd_palette + ((PALETTE_SIZE+3)/4)*4); + +#if defined(CONFIG_FB_JZ4750_SLCD) + + lcd_cmdbuf = (unsigned char *)__get_free_pages(GFP_KERNEL, 0); + memset((void *)lcd_cmdbuf, 0, PAGE_SIZE); + + { int data, i, *ptr; + ptr = (unsigned int *)lcd_cmdbuf; + data = WR_GRAM_CMD; + data = ((data & 0xff) << 1) | ((data & 0xff00) << 2); + for(i = 0; i < 3; i++){ + ptr[i] = data; + } + } +#endif + +#if defined(CONFIG_FB_JZ4750_LCD_USE_2LAYER_FRAMEBUFFER) + lcd_frame1 = lcd_frame0 + needroom1; +#endif + + /* + * Set page reserved so that mmap will work. This is necessary + * since we'll be remapping normal memory. + */ + page = (unsigned long)lcd_palette; + SetPageReserved(virt_to_page((void*)page)); + + for (page = (unsigned long)lcd_frame0; + page < PAGE_ALIGN((unsigned long)lcd_frame0 + (PAGE_SIZE<fb.fix.smem_start = virt_to_phys((void *)lcd_frame0); + cfb->fb.fix.smem_len = (PAGE_SIZE << page_shift); /* page_shift/2 ??? */ + cfb->fb.screen_base = + (unsigned char *)(((unsigned int)lcd_frame0&0x1fffffff) | 0xa0000000); + + if (!cfb->fb.screen_base) { + printk("jz4750fb, %s: unable to map screen memory\n", cfb->fb.fix.id); + return -ENOMEM; + } + + return 0; +} + +static void jz4750fb_free_fb_info(struct lcd_cfb_info *cfb) +{ + if (cfb) { + fb_alloc_cmap(&cfb->fb.cmap, 0, 0); + kfree(cfb); + } +} + +static void jz4750fb_unmap_smem(struct lcd_cfb_info *cfb) +{ + struct page * map = NULL; + unsigned char *tmp; + unsigned int page_shift, needroom, bpp, w, h; + + bpp = jz4750_lcd_info->osd.fg0.bpp; + if ( bpp == 18 || bpp == 24) + bpp = 32; + if ( bpp == 15 ) + bpp = 16; + w = jz4750_lcd_info->osd.fg0.w; + h = jz4750_lcd_info->osd.fg0.h; + needroom = ((w * bpp + 7) >> 3) * h; +#if defined(CONFIG_FB_JZ4750_LCD_USE_2LAYER_FRAMEBUFFER) + bpp = jz4750_lcd_info->osd.fg1.bpp; + if ( bpp == 18 || bpp == 24) + bpp = 32; + if ( bpp == 15 ) + bpp = 16; + w = jz4750_lcd_info->osd.fg1.w; + h = jz4750_lcd_info->osd.fg1.h; + needroom += ((w * bpp + 7) >> 3) * h; +#endif + + for (page_shift = 0; page_shift < 12; page_shift++) + if ((PAGE_SIZE << page_shift) >= needroom) + break; + + if (cfb && cfb->fb.screen_base) { + iounmap(cfb->fb.screen_base); + cfb->fb.screen_base = NULL; + release_mem_region(cfb->fb.fix.smem_start, + cfb->fb.fix.smem_len); + } + + if (lcd_palette) { + map = virt_to_page(lcd_palette); + clear_bit(PG_reserved, &map->flags); + free_pages((int)lcd_palette, 0); + } + + if (lcd_frame0) { + for (tmp=(unsigned char *)lcd_frame0; + tmp < lcd_frame0 + (PAGE_SIZE << page_shift); + tmp += PAGE_SIZE) { + map = virt_to_page(tmp); + clear_bit(PG_reserved, &map->flags); + } + free_pages((int)lcd_frame0, page_shift); + } +} + +/* initial dma descriptors */ +static void jz4750fb_descriptor_init( struct jz4750lcd_info * lcd_info ) +{ + unsigned int pal_size; + + switch ( lcd_info->osd.fg0.bpp ) { + case 1: + pal_size = 4; + break; + case 2: + pal_size = 8; + break; + case 4: + pal_size = 32; + break; + case 8: + default: + pal_size = 512; + } + + pal_size /= 4; + + dma0_desc_palette = dma_desc_base + 0; + dma0_desc0 = dma_desc_base + 1; + dma0_desc1 = dma_desc_base + 2; + dma0_desc_cmd0 = dma_desc_base + 3; /* use only once */ + dma0_desc_cmd = dma_desc_base + 4; + dma1_desc0 = dma_desc_base + 5; + dma1_desc1 = dma_desc_base + 6; + + /* + * Normal TFT panel's DMA Chan0: + * TO LCD Panel: + * no palette: dma0_desc0 <<==>> dma0_desc0 + * palette : dma0_desc_palette <<==>> dma0_desc0 + * TO TV Encoder: + * no palette: dma0_desc0 <<==>> dma0_desc1 + * palette: dma0_desc_palette --> dma0_desc0 + * --> dma0_desc1 --> dma0_desc_palette --> ... + * + * SMART LCD TFT panel(dma0_desc_cmd)'s DMA Chan0: + * TO LCD Panel: + * no palette: dma0_desc_cmd <<==>> dma0_desc0 + * palette : dma0_desc_palette --> dma0_desc_cmd + * --> dma0_desc0 --> dma0_desc_palette --> ... + * TO TV Encoder: + * no palette: dma0_desc_cmd --> dma0_desc0 + * --> dma0_desc1 --> dma0_desc_cmd --> ... + * palette: dma0_desc_palette --> dma0_desc_cmd + * --> dma0_desc0 --> dma0_desc1 + * --> dma0_desc_palette --> ... + * DMA Chan1: + * TO LCD Panel: + * dma1_desc0 <<==>> dma1_desc0 + * TO TV Encoder: + * dma1_desc0 <<==>> dma1_desc1 + */ + +#if defined(CONFIG_FB_JZ4750_SLCD) + /* First CMD descriptors, use only once, cmd_num isn't 0 */ + dma0_desc_cmd0->next_desc = (unsigned int)virt_to_phys(dma0_desc0); + dma0_desc_cmd0->databuf = (unsigned int)virt_to_phys((void *)lcd_cmdbuf); + dma0_desc_cmd0->frame_id = (unsigned int)0x0da0cad0; /* dma0's cmd0 */ + dma0_desc_cmd0->cmd = LCD_CMD_CMD | 3; /* command */ + dma0_desc_cmd0->offsize = 0; + dma0_desc_cmd0->page_width = 0; + dma0_desc_cmd0->cmd_num = 3; + + /* Dummy Command Descriptor, cmd_num is 0 */ + dma0_desc_cmd->next_desc = (unsigned int)virt_to_phys(dma0_desc0); + dma0_desc_cmd->databuf = 0; + dma0_desc_cmd->frame_id = (unsigned int)0x0da000cd; /* dma0's cmd0 */ + dma0_desc_cmd->cmd = LCD_CMD_CMD | 0; /* dummy command */ + dma0_desc_cmd->cmd_num = 0; + dma0_desc_cmd->offsize = 0; + dma0_desc_cmd->page_width = 0; + + /* Palette Descriptor */ + dma0_desc_palette->next_desc = (unsigned int)virt_to_phys(dma0_desc_cmd0); +#else + /* Palette Descriptor */ + dma0_desc_palette->next_desc = (unsigned int)virt_to_phys(dma0_desc0); +#endif + dma0_desc_palette->databuf = (unsigned int)virt_to_phys((void *)lcd_palette); + dma0_desc_palette->frame_id = (unsigned int)0xaaaaaaaa; + dma0_desc_palette->cmd = LCD_CMD_PAL | pal_size; /* Palette Descriptor */ + + /* DMA0 Descriptor0 */ + if ( lcd_info->panel.cfg & LCD_CFG_TVEN ) /* TVE mode */ + dma0_desc0->next_desc = (unsigned int)virt_to_phys(dma0_desc1); + else{ /* Normal TFT LCD */ +#if defined(CONFIG_FB_JZ4750_SLCD) + dma0_desc0->next_desc = (unsigned int)virt_to_phys(dma0_desc_cmd); +#else + dma0_desc0->next_desc = (unsigned int)virt_to_phys(dma0_desc0); +#endif + } + + dma0_desc0->databuf = virt_to_phys((void *)lcd_frame0); + dma0_desc0->frame_id = (unsigned int)0x0000da00; /* DMA0'0 */ + + /* DMA0 Descriptor1 */ + if ( lcd_info->panel.cfg & LCD_CFG_TVEN ) { /* TVE mode */ + + + if (lcd_info->osd.fg0.bpp <= 8) /* load palette only once at setup */ + dma0_desc1->next_desc = (unsigned int)virt_to_phys(dma0_desc_palette); + else +#if defined(CONFIG_FB_JZ4750_SLCD) /* for smatlcd */ + dma0_desc1->next_desc = (unsigned int)virt_to_phys(dma0_desc_cmd); +#else + dma0_desc1->next_desc = (unsigned int)virt_to_phys(dma0_desc0); +#endif + dma0_desc1->frame_id = (unsigned int)0x0000da01; /* DMA0'1 */ + } + + if (lcd_info->osd.fg0.bpp <= 8) /* load palette only once at setup */ + REG_LCD_DA0 = virt_to_phys(dma0_desc_palette); + else { +#if defined(CONFIG_FB_JZ4750_SLCD) /* for smartlcd */ + REG_LCD_DA0 = virt_to_phys(dma0_desc_cmd0); //smart lcd +#else + REG_LCD_DA0 = virt_to_phys(dma0_desc0); //tft +#endif + } + + /* DMA1 Descriptor0 */ + if ( lcd_info->panel.cfg & LCD_CFG_TVEN ) /* TVE mode */ + dma1_desc0->next_desc = (unsigned int)virt_to_phys(dma1_desc1); + else /* Normal TFT LCD */ + dma1_desc0->next_desc = (unsigned int)virt_to_phys(dma1_desc0); + + dma1_desc0->databuf = virt_to_phys((void *)lcd_frame1); + dma1_desc0->frame_id = (unsigned int)0x0000da10; /* DMA1'0 */ + + /* DMA1 Descriptor1 */ + if ( lcd_info->panel.cfg & LCD_CFG_TVEN ) { /* TVE mode */ + dma1_desc1->next_desc = (unsigned int)virt_to_phys(dma1_desc0); + dma1_desc1->frame_id = (unsigned int)0x0000da11; /* DMA1'1 */ + } + + REG_LCD_DA1 = virt_to_phys(dma1_desc0); /* set Dma-chan1's Descripter Addrress */ + dma_cache_wback_inv((unsigned int)(dma_desc_base), (DMA_DESC_NUM)*sizeof(struct jz4750_lcd_dma_desc)); + +#if 0 + /* Palette Descriptor */ + if ( lcd_info->panel.cfg & LCD_CFG_LCDPIN_SLCD ) +// dma0_desc_palette->next_desc = (unsigned int)virt_to_phys(dma0_desc_cmd); + dma0_desc_palette->next_desc = (unsigned int)virt_to_phys(dma0_desc_cmd1); + else + dma0_desc_palette->next_desc = (unsigned int)virt_to_phys(dma0_desc0); + dma0_desc_palette->databuf = (unsigned int)virt_to_phys((void *)lcd_palette); + dma0_desc_palette->frame_id = (unsigned int)0xaaaaaaaa; + dma0_desc_palette->cmd = LCD_CMD_PAL | pal_size; /* Palette Descriptor */ + + /* Dummy Command Descriptor, cmd_num is 0 */ + dma0_desc_cmd->next_desc = (unsigned int)virt_to_phys(dma0_desc0); + dma0_desc_cmd->databuf = (unsigned int)virt_to_phys((void *)lcd_cmdbuf); + dma0_desc_cmd->frame_id = (unsigned int)0x0da0cad0; /* dma0's cmd0 */ + dma0_desc_cmd->cmd = LCD_CMD_CMD | 3; /* dummy command */ + dma0_desc_cmd->offsize = 0; /* dummy command */ + dma0_desc_cmd->page_width = 0; /* dummy command */ + dma0_desc_cmd->cmd_num = 3; + +//--------------------------------- + dma0_desc_cmd1->next_desc = (unsigned int)virt_to_phys(dma0_desc0); + dma0_desc_cmd1->databuf = 0; + dma0_desc_cmd1->frame_id = (unsigned int)0x0da0cad1; /* dma0's cmd0 */ + dma0_desc_cmd1->cmd = LCD_CMD_CMD | 0; /* dummy command */ + dma0_desc_cmd1->cmd_num = 0; + dma0_desc_cmd1->offsize = 0; /* dummy command */ + dma0_desc_cmd1->page_width = 0; /* dummy command */ +//----------------------------------- + /* DMA0 Descriptor0 */ + if ( lcd_info->panel.cfg & LCD_CFG_TVEN ) /* TVE mode */ + dma0_desc0->next_desc = (unsigned int)virt_to_phys(dma0_desc1); + else{ /* Normal TFT LCD */ + if (lcd_info->osd.fg0.bpp <= 8) /* load palette only once at setup?? */ +// dma0_desc0->next_desc = (unsigned int)virt_to_phys(dma0_desc_palette); //tft + dma0_desc0->next_desc = (unsigned int)virt_to_phys(dma0_desc_cmd); // smart lcd + else if ( lcd_info->panel.cfg & LCD_CFG_LCDPIN_SLCD ) + dma0_desc0->next_desc = (unsigned int)virt_to_phys(dma0_desc_cmd1); +// dma0_desc0->next_desc = (unsigned int)virt_to_phys(dma0_desc_cmd); + else + dma0_desc0->next_desc = (unsigned int)virt_to_phys(dma0_desc0); + } + + dma0_desc0->databuf = virt_to_phys((void *)lcd_frame0); + dma0_desc0->frame_id = (unsigned int)0x0000da00; /* DMA0'0 */ + + /* DMA0 Descriptor1 */ + if ( lcd_info->panel.cfg & LCD_CFG_TVEN ) { /* TVE mode */ + if (lcd_info->osd.fg0.bpp <= 8) /* load palette only once at setup?? */ + dma0_desc1->next_desc = (unsigned int)virt_to_phys(dma0_desc_palette); + + else if ( lcd_info->panel.cfg & LCD_CFG_LCDPIN_SLCD ) + dma0_desc1->next_desc = (unsigned int)virt_to_phys(dma0_desc_cmd); + else + dma0_desc1->next_desc = (unsigned int)virt_to_phys(dma0_desc0); + dma0_desc1->frame_id = (unsigned int)0x0000da01; /* DMA0'1 */ + } + + /* DMA1 Descriptor0 */ + if ( lcd_info->panel.cfg & LCD_CFG_TVEN ) /* TVE mode */ + dma1_desc0->next_desc = (unsigned int)virt_to_phys(dma1_desc1); + else /* Normal TFT LCD */ + dma1_desc0->next_desc = (unsigned int)virt_to_phys(dma1_desc0); + + dma1_desc0->databuf = virt_to_phys((void *)lcd_frame1); + dma1_desc0->frame_id = (unsigned int)0x0000da10; /* DMA1'0 */ + + /* DMA1 Descriptor1 */ + if ( lcd_info->panel.cfg & LCD_CFG_TVEN ) { /* TVE mode */ + dma1_desc1->next_desc = (unsigned int)virt_to_phys(dma1_desc0); + dma1_desc1->frame_id = (unsigned int)0x0000da11; /* DMA1'1 */ + } + + if (lcd_info->osd.fg0.bpp <= 8) /* load palette only once at setup?? */ + REG_LCD_DA0 = virt_to_phys(dma0_desc_palette); + else +// REG_LCD_DA0 = virt_to_phys(dma0_desc_cmd); //smart lcd + REG_LCD_DA0 = virt_to_phys(dma0_desc0); //tft + REG_LCD_DA1 = virt_to_phys(dma1_desc0); /* set Dma-chan1's Descripter Addrress */ + dma_cache_wback_inv((unsigned int)(dma_desc_base), (DMA_DESC_NUM)*sizeof(struct jz4750_lcd_dma_desc)); +#endif +} + +static void jz4750fb_set_panel_mode( struct jz4750lcd_info * lcd_info ) +{ + struct jz4750lcd_panel_t *panel = &lcd_info->panel; + + /* set bpp */ + lcd_info->panel.ctrl &= ~LCD_CTRL_BPP_MASK; + if ( lcd_info->osd.fg0.bpp == 1 ) + lcd_info->panel.ctrl |= LCD_CTRL_BPP_1; + else if ( lcd_info->osd.fg0.bpp == 2 ) + lcd_info->panel.ctrl |= LCD_CTRL_BPP_2; + else if ( lcd_info->osd.fg0.bpp == 4 ) + lcd_info->panel.ctrl |= LCD_CTRL_BPP_4; + else if ( lcd_info->osd.fg0.bpp == 8 ) + lcd_info->panel.ctrl |= LCD_CTRL_BPP_8; + else if ( lcd_info->osd.fg0.bpp == 15 ) + lcd_info->panel.ctrl |= LCD_CTRL_BPP_16 | LCD_CTRL_RGB555; + else if ( lcd_info->osd.fg0.bpp == 16 ) + lcd_info->panel.ctrl |= LCD_CTRL_BPP_16 | LCD_CTRL_RGB565; + else if ( lcd_info->osd.fg0.bpp > 16 && lcd_info->osd.fg0.bpp < 32+1 ) { + lcd_info->osd.fg0.bpp = 32; + lcd_info->panel.ctrl |= LCD_CTRL_BPP_18_24; + } + else { + printk("The BPP %d is not supported\n", lcd_info->osd.fg0.bpp); + lcd_info->osd.fg0.bpp = 32; + lcd_info->panel.ctrl |= LCD_CTRL_BPP_18_24; + } + + lcd_info->panel.cfg |= LCD_CFG_NEWDES; /* use 8words descriptor always */ + + REG_LCD_CTRL = lcd_info->panel.ctrl; /* LCDC Controll Register */ + REG_LCD_CFG = lcd_info->panel.cfg; /* LCDC Configure Register */ + REG_SLCD_CFG = lcd_info->panel.slcd_cfg; /* Smart LCD Configure Register */ + + if ( lcd_info->panel.cfg & LCD_CFG_LCDPIN_SLCD ) /* enable Smart LCD DMA */ + REG_SLCD_CTRL = SLCD_CTRL_DMA_EN; + + switch ( lcd_info->panel.cfg & LCD_CFG_MODE_MASK ) { + case LCD_CFG_MODE_GENERIC_TFT: + case LCD_CFG_MODE_INTER_CCIR656: + case LCD_CFG_MODE_NONINTER_CCIR656: + case LCD_CFG_MODE_SLCD: + default: /* only support TFT16 TFT32, not support STN and Special TFT by now(10-06-2008)*/ + REG_LCD_VAT = (((panel->blw + panel->w + panel->elw + panel->hsw)) << 16) | (panel->vsw + panel->bfw + panel->h + panel->efw); + REG_LCD_DAH = ((panel->hsw + panel->blw) << 16) | (panel->hsw + panel->blw + panel->w); + REG_LCD_DAV = ((panel->vsw + panel->bfw) << 16) | (panel->vsw + panel->bfw + panel->h); + REG_LCD_HSYNC = (0 << 16) | panel->hsw; + REG_LCD_VSYNC = (0 << 16) | panel->vsw; + break; + } +} + + +static void jz4750fb_set_osd_mode( struct jz4750lcd_info * lcd_info ) +{ + dprintk("%s, %d\n", __FILE__, __LINE__ ); + lcd_info->osd.osd_ctrl &= ~(LCD_OSDCTRL_OSDBPP_MASK); + if ( lcd_info->osd.fg1.bpp == 15 ) + lcd_info->osd.osd_ctrl |= LCD_OSDCTRL_OSDBPP_15_16|LCD_OSDCTRL_RGB555; + else if ( lcd_info->osd.fg1.bpp == 16 ) + lcd_info->osd.osd_ctrl |= LCD_OSDCTRL_OSDBPP_15_16|LCD_OSDCTRL_RGB565; + else { + lcd_info->osd.fg1.bpp = 32; + lcd_info->osd.osd_ctrl |= LCD_OSDCTRL_OSDBPP_18_24; + } + + REG_LCD_OSDC = lcd_info->osd.osd_cfg; /* F0, F1, alpha, */ + + REG_LCD_OSDCTRL = lcd_info->osd.osd_ctrl; /* IPUEN, bpp */ + REG_LCD_RGBC = lcd_info->osd.rgb_ctrl; + REG_LCD_BGC = lcd_info->osd.bgcolor; + REG_LCD_KEY0 = lcd_info->osd.colorkey0; + REG_LCD_KEY1 = lcd_info->osd.colorkey1; + REG_LCD_ALPHA = lcd_info->osd.alpha; + REG_LCD_IPUR = lcd_info->osd.ipu_restart; +} + +static void jz4750fb_foreground_resize( struct jz4750lcd_info * lcd_info ) +{ + int fg0_line_size, fg0_frm_size, fg1_line_size, fg1_frm_size; + /* + * NOTE: + * Foreground change sequence: + * 1. Change Position Registers -> LCD_OSDCTL.Change; + * 2. LCD_OSDCTRL.Change -> descripter->Size + * Foreground, only one of the following can be change at one time: + * 1. F0 size; + * 2. F0 position + * 3. F1 size + * 4. F1 position + */ + + /* + * The rules of f0, f1's position: + * f0.x + f0.w <= panel.w; + * f0.y + f0.h <= panel.h; + * + * When output is LCD panel, fg.y and fg.h can be odd number or even number. + * When output is TVE, as the TVE has odd frame and even frame, + * to simplified operation, fg.y and fg.h should be even number always. + * + */ + + /* Foreground 0 */ + if ( lcd_info->osd.fg0.x >= lcd_info->panel.w ) + lcd_info->osd.fg0.x = lcd_info->panel.w - 1; + if ( lcd_info->osd.fg0.y >= lcd_info->panel.h ) + lcd_info->osd.fg0.y = lcd_info->panel.h - 1; + if ( lcd_info->osd.fg0.x + lcd_info->osd.fg0.w > lcd_info->panel.w ) + lcd_info->osd.fg0.w = lcd_info->panel.w - lcd_info->osd.fg0.x; + if ( lcd_info->osd.fg0.y + lcd_info->osd.fg0.h > lcd_info->panel.h ) + lcd_info->osd.fg0.h = lcd_info->panel.h - lcd_info->osd.fg0.y; + /* Foreground 1 */ + /* Case TVE ??? TVE 720x573 or 720x480*/ + if ( lcd_info->osd.fg1.x >= lcd_info->panel.w ) + lcd_info->osd.fg1.x = lcd_info->panel.w - 1; + if ( lcd_info->osd.fg1.y >= lcd_info->panel.h ) + lcd_info->osd.fg1.y = lcd_info->panel.h - 1; + if ( lcd_info->osd.fg1.x + lcd_info->osd.fg1.w > lcd_info->panel.w ) + lcd_info->osd.fg1.w = lcd_info->panel.w - lcd_info->osd.fg1.x; + if ( lcd_info->osd.fg1.y + lcd_info->osd.fg1.h > lcd_info->panel.h ) + lcd_info->osd.fg1.h = lcd_info->panel.h - lcd_info->osd.fg1.y; + +// fg0_line_size = lcd_info->osd.fg0.w*((lcd_info->osd.fg0.bpp+7)/8); + fg0_line_size = (lcd_info->osd.fg0.w*(lcd_info->osd.fg0.bpp)/8); + fg0_line_size = ((fg0_line_size+3)>>2)<<2; /* word aligned */ + fg0_frm_size = fg0_line_size * lcd_info->osd.fg0.h; + + fg1_line_size = lcd_info->osd.fg1.w*((lcd_info->osd.fg1.bpp+7)/8); + fg1_line_size = ((fg1_line_size+3)>>2)<<2; /* word aligned */ + fg1_frm_size = fg1_line_size * lcd_info->osd.fg1.h; + + if ( lcd_info->osd.fg_change ) { + if ( lcd_info->osd.fg_change & FG0_CHANGE_POSITION ) { /* F1 change position */ + REG_LCD_XYP0 = lcd_info->osd.fg0.y << 16 | lcd_info->osd.fg0.x; + } + if ( lcd_info->osd.fg_change & FG1_CHANGE_POSITION ) { /* F1 change position */ + REG_LCD_XYP1 = lcd_info->osd.fg1.y << 16 | lcd_info->osd.fg1.x; + } + + /* set change */ + if ( !(lcd_info->osd.osd_ctrl & LCD_OSDCTRL_IPU) && + (lcd_info->osd.fg_change != FG_CHANGE_ALL) ) + REG_LCD_OSDCTRL |= LCD_OSDCTRL_CHANGES; + + /* wait change ready??? */ +// while ( REG_LCD_OSDS & LCD_OSDS_READY ) /* fix in the future, Wolfgang, 06-20-2008 */ + print_dbg("wait LCD_OSDS_READY\n"); + + if ( lcd_info->osd.fg_change & FG0_CHANGE_SIZE ) { /* change FG0 size */ + if ( lcd_info->panel.cfg & LCD_CFG_TVEN ) { /* output to TV */ + dma0_desc0->cmd = dma0_desc1->cmd = (fg0_frm_size/4)/2; + dma0_desc0->offsize = dma0_desc1->offsize + = fg0_line_size/4; + dma0_desc0->page_width = dma0_desc1->page_width + = fg0_line_size/4; + dma0_desc1->databuf = virt_to_phys((void *)(lcd_frame0 + fg0_line_size)); + } + else { + dma0_desc0->cmd = dma0_desc1->cmd = fg0_frm_size/4; + dma0_desc0->offsize = dma0_desc1->offsize =0; + dma0_desc0->page_width = dma0_desc1->page_width = 0; + } + + dma0_desc0->desc_size = dma0_desc1->desc_size + = lcd_info->osd.fg0.h << 16 | lcd_info->osd.fg0.w; + REG_LCD_SIZE0 = (lcd_info->osd.fg0.h<<16)|lcd_info->osd.fg0.w; + + } + + if ( lcd_info->osd.fg_change & FG1_CHANGE_SIZE ) { /* change FG1 size*/ + if ( lcd_info->panel.cfg & LCD_CFG_TVEN ) { /* output to TV */ + dma1_desc0->cmd = dma1_desc1->cmd = (fg1_frm_size/4)/2; + dma1_desc0->offsize = dma1_desc1->offsize = fg1_line_size/4; + dma1_desc0->page_width = dma1_desc1->page_width = fg1_line_size/4; + dma1_desc1->databuf = virt_to_phys((void *)(lcd_frame1 + fg1_line_size)); + } + else { + dma1_desc0->cmd = dma1_desc1->cmd = fg1_frm_size/4; + dma1_desc0->offsize = dma1_desc1->offsize = 0; + dma1_desc0->page_width = dma1_desc1->page_width = 0; + } + + dma1_desc0->desc_size = dma1_desc1->desc_size + = lcd_info->osd.fg1.h << 16 | lcd_info->osd.fg1.w; + REG_LCD_SIZE1 = lcd_info->osd.fg1.h << 16|lcd_info->osd.fg1.w; + } + + dma_cache_wback((unsigned int)(dma_desc_base), (DMA_DESC_NUM)*sizeof(struct jz4750_lcd_dma_desc)); + lcd_info->osd.fg_change = FG_NOCHANGE; /* clear change flag */ + } +} + +static void jz4750fb_change_clock( struct jz4750lcd_info * lcd_info ) +{ +#if defined(CONFIG_JZ4750_FUWA) || defined(CONFIG_JZ4750_APUS) /* FPGA test, pixdiv */ +#if 0 + REG_LCD_REV = 0x00000004; + printk("Fuwa test, pixclk divide REG_LCD_REV=0x%08x\n", REG_LCD_REV); + printk("Fuwa test, pixclk %d\n", JZ_EXTAL/(((REG_LCD_REV&0xFF)+1)*2)); +#endif + unsigned int val = 0; + unsigned int pclk; + /* Timing setting */ + __cpm_stop_lcd(); + + val = lcd_info->panel.fclk; /* frame clk */ + + if ( (lcd_info->panel.cfg & LCD_CFG_MODE_MASK) != LCD_CFG_MODE_SERIAL_TFT) { + pclk = val * (lcd_info->panel.w + lcd_info->panel.hsw + lcd_info->panel.elw + lcd_info->panel.blw) * (lcd_info->panel.h + lcd_info->panel.vsw + lcd_info->panel.efw + lcd_info->panel.bfw); /* Pixclk */ + } + else { + /* serial mode: Hsync period = 3*Width_Pixel */ + pclk = val * (lcd_info->panel.w*3 + lcd_info->panel.hsw + lcd_info->panel.elw + lcd_info->panel.blw) * (lcd_info->panel.h + lcd_info->panel.vsw + lcd_info->panel.efw + lcd_info->panel.bfw); /* Pixclk */ + } + + /********* In TVE mode PCLK = 27MHz ***********/ + if ( lcd_info->panel.cfg & LCD_CFG_TVEN ) { /* LCDC output to TVE */ + REG_CPM_LPCDR |= CPM_LPCDR_LTCS; + val = 11; /* PLLCLK = 324 */ + __cpm_set_pixdiv(val); + + dprintk("REG_CPM_LPCDR = 0x%08x\n", REG_CPM_LPCDR); + + val = pclk * 3 ; /* LCDClock > 2.5*Pixclock */ + + val =(__cpm_get_pllout()) / val; + if ( val > 0x1f ) { + printk("lcd clock divide is too large, set it to 0x1f\n"); + val = 0x1f; + } + __cpm_set_ldiv( val ); + REG_CPM_LPCDR |= CPM_LPCDR_LTCS; + REG_CPM_CPCCR |= CPM_CPCCR_CE ; /* update divide */ + + } + else { /* LCDC output to LCD panel */ +// REG_CPM_LPCDR = 0; + val = __cpm_get_pllout2() / pclk; /* pclk */ + val--; + dprintk("ratio: val = %d\n", val); + if ( val > 0x7ff ) { + printk("pixel clock divid is too large, set it to 0x7ff\n"); + val = 0x7ff; + } + + __cpm_set_pixdiv(val); + + dprintk("REG_CPM_LPCDR = 0x%08x\n", REG_CPM_LPCDR); + val = pclk * 3 ; /* LCDClock > 2.5*Pixclock */ + val =__cpm_get_pllout2() / val; + if ( val > 0x1f ) { + printk("lcd clock divide is too large, set it to 0x1f\n"); + val = 0x1f; + } + __cpm_set_ldiv( val ); + REG_CPM_CPCCR |= CPM_CPCCR_CE ; /* update divide */ + + } + + dprintk("REG_CPM_LPCDR=0x%08x\n", REG_CPM_LPCDR); + dprintk("REG_CPM_CPCCR=0x%08x\n", REG_CPM_CPCCR); + + jz_clocks.pixclk = __cpm_get_pixclk(); + jz_clocks.lcdclk = __cpm_get_lcdclk(); + printk("LCDC: PixClock:%d LcdClock:%d\n", + jz_clocks.pixclk, jz_clocks.lcdclk); + + __cpm_start_lcd(); + udelay(1000); +#else + +#error "Set lcd clock first, Not support your chipset now!!!" + /* + * set lcd device clock and lcd pixel clock. + * what about TVE mode??? + * + */ +#endif + +} + +/* + * jz4750fb_set_mode(), set osd configure, resize foreground + * + */ +static void jz4750fb_set_mode( struct jz4750lcd_info * lcd_info ) +{ + struct lcd_cfb_info *cfb = jz4750fb_info; + + jz4750fb_set_osd_mode(lcd_info); + jz4750fb_foreground_resize(lcd_info); + jz4750fb_set_var(&cfb->fb.var, -1, &cfb->fb); +} + +/* + * jz4750fb_deep_set_mode, + * + */ +static void jz4750fb_deep_set_mode( struct jz4750lcd_info * lcd_info ) +{ + /* configurate sequence: + * 1. disable lcdc. + * 2. init frame descriptor. + * 3. set panel mode + * 4. set osd mode + * 5. start lcd clock in CPM + * 6. enable lcdc. + */ + + __lcd_clr_ena(); /* Quick Disable */ + lcd_info->osd.fg_change = FG_CHANGE_ALL; /* change FG0, FG1 size, postion??? */ + jz4750fb_descriptor_init(lcd_info); + jz4750fb_set_panel_mode(lcd_info); + jz4750fb_set_mode(lcd_info); + jz4750fb_change_clock(lcd_info); + __lcd_set_ena(); /* enable lcdc */ +} + + +static irqreturn_t jz4750fb_interrupt_handler(int irq, void *dev_id) +{ + unsigned int state; + static int irqcnt=0; + + state = REG_LCD_STATE; + dprintk("In the lcd interrupt handler, state=0x%x\n", state); + + if (state & LCD_STATE_EOF) /* End of frame */ + REG_LCD_STATE = state & ~LCD_STATE_EOF; + + if (state & LCD_STATE_IFU0) { + printk("%s, InFiFo0 underrun\n", __FUNCTION__); + REG_LCD_STATE = state & ~LCD_STATE_IFU0; + } + + if (state & LCD_STATE_IFU1) { + printk("%s, InFiFo1 underrun\n", __FUNCTION__); + REG_LCD_STATE = state & ~LCD_STATE_IFU1; + } + + if (state & LCD_STATE_OFU) { /* Out fifo underrun */ + REG_LCD_STATE = state & ~LCD_STATE_OFU; + if ( irqcnt++ > 100 ) { + __lcd_disable_ofu_intr(); + printk("disable Out FiFo underrun irq.\n"); + } + printk("%s, Out FiFo underrun.\n", __FUNCTION__); + } + + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM + +/* + * Suspend the LCDC. + */ +static int jzfb_suspend(void) +{ + __lcd_clr_ena(); /* Quick Disable */ + __lcd_display_off(); + __cpm_stop_lcd(); + + return 0; +} + +/* + * Resume the LCDC. + */ +static int jzfb_resume(void) +{ + __cpm_start_lcd(); + __gpio_set_pin(GPIO_DISP_OFF_N); + __lcd_special_on(); + __lcd_set_ena(); + mdelay(200); + __lcd_set_backlight_level(80); + + return 0; +} + +/* + * Power management hook. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ +static int jzlcd_pm_callback(struct pm_dev *pm_dev, pm_request_t req, void *data) +{ + int ret; + struct lcd_cfb_info *cfb = pm_dev->data; + + if (!cfb) return -EINVAL; + + switch (req) { + case PM_SUSPEND: + ret = jzfb_suspend(); + break; + + case PM_RESUME: + ret = jzfb_resume(); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} +#else +#define jzfb_suspend NULL +#define jzfb_resume NULL +#endif /* CONFIG_PM */ + +/* The following routine is only for test */ + +#ifdef DEBUG +static void test_gpio(int gpio_num, int delay) { + __gpio_as_output(gpio_num); + while(1) { + __gpio_set_pin(gpio_num); + udelay(delay); + __gpio_clear_pin(gpio_num); + udelay(delay); + } +} +static void display_v_color_bar(int w, int h, int bpp) { + int i, j, wpl, data = 0; + int *ptr; + ptr = (int *)lcd_frame0; +// ptr = (int *)lcd_frame1; + wpl = w*bpp/32; + if (!(bpp > 8)) + switch(bpp){ + case 1: + for (j = 0;j < h; j++) + for (i = 0;i < wpl; i++) { + *ptr++ = 0x00ff00ff; + } + break; + case 2: + for (j = 0;j < h; j++) + for (i = 0;i < wpl; i++) { + data = (i%4)*0x55555555; + *ptr++ = data; + } + break; + case 4: + for (j = 0;j < h; j++) + for (i = 0;i < wpl; i++) { + data = (i%16)*0x11111111; + *ptr++ = data; + } + break; + case 8: + for (j = 0;j < h; j++) + for (i = 0;i < wpl; i+=2) { + data = (i%(256))*0x01010101; + *ptr++ = data; + *ptr++ = data; + } + break; + } + else { + switch(bpp) { + case 16: + for (j = 0;j < h; j++) + for (i = 0;i < wpl; i++) { + if((i/4)%8==0) + *ptr++ = 0xffffffff; + else if ((i/4)%8==1) + *ptr++ = 0xf800f800; + else if ((i/4)%8==2) + *ptr++ = 0xffe0ffe0; + else if ((i/4)%8==3) + *ptr++ = 0x07e007e0; + else if ((i/4)%8==4) + *ptr++ = 0x07ff07ff; + else if ((i/4)%8==5) + *ptr++ = 0x001f001f; + else if ((i/4)%8==6) + *ptr++ = 0xf81ff81f; + else if ((i/4)%8==7) + *ptr++ = 0x00000000; + } + break; + case 18: + case 24: + case 32: + default: +#if 1 + for (j = 0;j < h; j++) + for (i = 0;i < wpl; i++) { + if((i/8)%8==7) + *ptr++ = 0xffffff; + else if ((i/8)%8==1) + *ptr++ = 0xff0000; + else if ((i/8)%8==2) + *ptr++ = 0xffff00; + else if ((i/8)%8==3) + *ptr++ = 0x00ff00; + else if ((i/8)%8==4) + *ptr++ = 0x00ffff; + else if ((i/8)%8==5) + *ptr++ = 0x0000ff; + else if ((i/8)%8==6) + *ptr++ = 0xff00ff; + else if ((i/8)%8==0) + *ptr++ = 0x000000; + } +#else + for (j = 0;j < h; j++) + for (i = 0;i < wpl; i++) { + if((i/8)%8==7) + *ptr++ = 0x00ff0000; + else if ((i/8)%8==1) + *ptr++ = 0xffff0000; + else if ((i/8)%8==2) + *ptr++ = 0x20ff0000; + else if ((i/8)%8==3) + *ptr++ = 0x40ff0000; + else if ((i/8)%8==4) + *ptr++ = 0x60ff0000; + else if ((i/8)%8==5) + *ptr++ = 0x80ff0000; + else if ((i/8)%8==6) + *ptr++ = 0xa0ff0000; + else if ((i/8)%8==0) + *ptr++ = 0xc0ff0000; + } +#endif + break; + } + } +} +static void display_h_color_bar(int w, int h, int bpp) { + int i, data = 0; + int *ptr; + int wpl; //word_per_line + ptr = (int *)lcd_frame0; +// ptr = (int *)lcd_frame1; + wpl = w*bpp/32; + if (!(bpp > 8)) + for (i = 0;i < wpl*h;i++) { + switch(bpp){ + case 1: + if(i%(wpl*8)==0) + data = ((i/(wpl*8))%2)*0xffffffff; + *ptr++ = data; + break; + case 2: + if(i%(wpl*8)==0) + data = ((i/(wpl*8))%4)*0x55555555; + *ptr++ = data; + break; + case 4: + if(i%(wpl*8)==0) + data = ((i/(wpl*8))%16)*0x11111111; + *ptr++ = data; + break; + case 8: + if(i%(wpl*8)==0) + data = ((i/(wpl*8))%256)*0x01010101; + *ptr++ = data; + break; + } + } + else { + + switch(bpp) { + case 15: + case 16: + for (i = 0;i < wpl*h;i++) { + if (((i/(wpl*8)) % 8) == 0) + *ptr++ = 0xffffffff; + else if (((i/(wpl*8)) % 8) == 1) + *ptr++ = 0xf800f800; + else if (((i/(wpl*8)) % 8) == 2) + *ptr++ = 0xffe0ffe0; + else if (((i/(wpl*8)) % 8) == 3) + *ptr++ = 0x07e007e0; + else if (((i/(wpl*8)) % 8) == 4) + *ptr++ = 0x07ff07ff; + else if (((i/(wpl*8)) % 8) == 5) + *ptr++ = 0x001f001f; + else if (((i/(wpl*8)) % 8) == 6) + *ptr++ = 0xf81ff81f; + else if (((i/(wpl*8)) % 8) == 7) + *ptr++ = 0x00000000; + } + break; + case 18: + case 24: + case 32: + default: + for (i = 0;i < wpl*h;i++) { + if (((i/(wpl*8)) % 8) == 7) + *ptr++ = 0xffffff; + else if (((i/(wpl*8)) % 8) == 2) + *ptr++ = 0xff0000; + else if (((i/(wpl*8)) % 8) == 4) + *ptr++ = 0xffff00; + else if (((i/(wpl*8)) % 8) == 6) + *ptr++ = 0x00ff00; + else if (((i/(wpl*8)) % 8) == 1) + *ptr++ = 0x00ffff; + else if (((i/(wpl*8)) % 8) == 3) + *ptr++ = 0x0000ff; + else if (((i/(wpl*8)) % 8) == 5) + *ptr++ = 0x000000; + else if (((i/(wpl*8)) % 8) == 0) + *ptr++ = 0xff00ff; + } + break; + } + + } + +} +#endif + +static int __init jz4750fb_init(void) +{ + struct lcd_cfb_info *cfb; + int err = 0; + + /* gpio init __gpio_as_lcd */ + if (jz4750_lcd_info->panel.cfg & LCD_CFG_MODE_TFT_16BIT) + __gpio_as_lcd_16bit(); + else if (jz4750_lcd_info->panel.cfg & LCD_CFG_MODE_TFT_24BIT) + __gpio_as_lcd_24bit(); + else + __gpio_as_lcd_18bit(); + /* In special mode, we only need init special pin, + * as general lcd pin has init in uboot */ +#if defined(CONFIG_SOC_JZ4750) + switch (jz4750_lcd_info->panel.cfg & LCD_CFG_MODE_MASK) { + case LCD_CFG_MODE_SPECIAL_TFT_1: + case LCD_CFG_MODE_SPECIAL_TFT_2: + case LCD_CFG_MODE_SPECIAL_TFT_3: + __gpio_as_lcd_special(); + break; + default: + ; + } +#endif + if ( jz4750_lcd_info->osd.fg0.bpp > 16 && + jz4750_lcd_info->osd.fg0.bpp < 32 ) { + jz4750_lcd_info->osd.fg0.bpp = 32; + } + + switch ( jz4750_lcd_info->osd.fg1.bpp ) { + case 15: + case 16: + break; + case 17 ... 32: + jz4750_lcd_info->osd.fg1.bpp = 32; + break; + default: + printk("jz4750fb fg1 not support bpp(%d), force to 32bpp\n", + jz4750_lcd_info->osd.fg1.bpp); + jz4750_lcd_info->osd.fg1.bpp = 32; + } + __lcd_clr_dis(); + __lcd_clr_ena(); + + /* Configure SLCD module for setting smart lcd control registers */ +#if defined(CONFIG_FB_JZ4750_SLCD) + __lcd_as_smart_lcd(); + __slcd_disable_dma(); + __init_slcd_bus(); /* Note: modify this depend on you lcd */ + +#endif + /* init clk */ + jz4750fb_change_clock(jz4750_lcd_info); + __lcd_display_pin_init(); + __lcd_slcd_special_on(); + + cfb = jz4750fb_alloc_fb_info(); + if (!cfb) + goto failed; + + err = jz4750fb_map_smem(cfb); + if (err) + goto failed; + + jz4750fb_deep_set_mode( jz4750_lcd_info ); + + err = register_framebuffer(&cfb->fb); + if (err < 0) { + dprintk("jzfb_init(): register framebuffer err.\n"); + goto failed; + } + printk("fb%d: %s frame buffer device, using %dK of video memory\n", + cfb->fb.node, cfb->fb.fix.id, cfb->fb.fix.smem_len>>10); + + if (request_irq(IRQ_LCD, jz4750fb_interrupt_handler, IRQF_DISABLED, + "lcd", 0)) { + err = -EBUSY; + goto failed; + } + +#ifdef CONFIG_PM + /* + * Note that the console registers this as well, but we want to + * power down the display prior to sleeping. + */ + cfb->pm = pm_register(PM_SYS_DEV, PM_SYS_VGA, jzlcd_pm_callback); + if (cfb->pm) + cfb->pm->data = cfb; +#endif + + __lcd_set_ena(); /* enalbe LCD Controller */ + __lcd_display_on(); + +#ifdef DEBUG + display_h_color_bar(jz4750_lcd_info->osd.fg0.w, jz4750_lcd_info->osd.fg0.h, jz4750_lcd_info->osd.fg0.bpp); +#endif + print_lcdc_registers(); + return 0; + +failed: + print_dbg(); + jz4750fb_unmap_smem(cfb); + jz4750fb_free_fb_info(cfb); + + return err; +} + +#if 0 +static int jzfb_remove(struct device *dev) +{ + struct lcd_cfb_info *cfb = dev_get_drvdata(dev); + jzfb_unmap_smem(cfb); + jzfb_free_fb_info(cfb); + return 0; +} +#endif + +#if 0 +static struct device_driver jzfb_driver = { + .name = "jz-lcd", + .bus = &platform_bus_type, + .probe = jzfb_probe, + .remove = jzfb_remove, + .suspend = jzfb_suspend, + .resume = jzfb_resume, +}; +#endif + +static void __exit jz4750fb_cleanup(void) +{ + //driver_unregister(&jzfb_driver); + //jzfb_remove(); +} + +module_init(jz4750fb_init); +module_exit(jz4750fb_cleanup); --- /dev/null +++ b/drivers/video/jz4750_lcd.h @@ -0,0 +1,756 @@ +/* + * linux/drivers/video/jz4750_lcd.h -- Ingenic Jz4750 On-Chip LCD frame buffer device + * + * Copyright (C) 2005-2008, Ingenic Semiconductor Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __JZ4750_LCD_H__ +#define __JZ4750_LCD_H__ + +//#include + + +#define NR_PALETTE 256 +#define PALETTE_SIZE (NR_PALETTE*2) + +/* use new descriptor(8 words) */ +struct jz4750_lcd_dma_desc { + unsigned int next_desc; /* LCDDAx */ + unsigned int databuf; /* LCDSAx */ + unsigned int frame_id; /* LCDFIDx */ + unsigned int cmd; /* LCDCMDx */ + unsigned int offsize; /* Stride Offsize(in word) */ + unsigned int page_width; /* Stride Pagewidth(in word) */ + unsigned int cmd_num; /* Command Number(for SLCD) */ + unsigned int desc_size; /* Foreground Size */ +}; + +struct jz4750lcd_panel_t { + unsigned int cfg; /* panel mode and pin usage etc. */ + unsigned int slcd_cfg; /* Smart lcd configurations */ + unsigned int ctrl; /* lcd controll register */ + unsigned int w; /* Panel Width(in pixel) */ + unsigned int h; /* Panel Height(in line) */ + unsigned int fclk; /* frame clk */ + unsigned int hsw; /* hsync width, in pclk */ + unsigned int vsw; /* vsync width, in line count */ + unsigned int elw; /* end of line, in pclk */ + unsigned int blw; /* begin of line, in pclk */ + unsigned int efw; /* end of frame, in line count */ + unsigned int bfw; /* begin of frame, in line count */ +}; + + +struct jz4750lcd_fg_t { + int bpp; /* foreground bpp */ + int x; /* foreground start position x */ + int y; /* foreground start position y */ + int w; /* foreground width */ + int h; /* foreground height */ +}; + +struct jz4750lcd_osd_t { + unsigned int osd_cfg; /* OSDEN, ALHPAEN, F0EN, F1EN, etc */ + unsigned int osd_ctrl; /* IPUEN, OSDBPP, etc */ + unsigned int rgb_ctrl; /* RGB Dummy, RGB sequence, RGB to YUV */ + unsigned int bgcolor; /* background color(RGB888) */ + unsigned int colorkey0; /* foreground0's Colorkey enable, Colorkey value */ + unsigned int colorkey1; /* foreground1's Colorkey enable, Colorkey value */ + unsigned int alpha; /* ALPHAEN, alpha value */ + unsigned int ipu_restart; /* IPU Restart enable, ipu restart interval time */ + +#define FG_NOCHANGE 0x0000 +#define FG0_CHANGE_SIZE 0x0001 +#define FG0_CHANGE_POSITION 0x0002 +#define FG1_CHANGE_SIZE 0x0010 +#define FG1_CHANGE_POSITION 0x0020 +#define FG_CHANGE_ALL ( FG0_CHANGE_SIZE | FG0_CHANGE_POSITION | \ + FG1_CHANGE_SIZE | FG1_CHANGE_POSITION ) + int fg_change; + struct jz4750lcd_fg_t fg0; /* foreground 0 */ + struct jz4750lcd_fg_t fg1; /* foreground 1 */ +}; + +struct jz4750lcd_info { + struct jz4750lcd_panel_t panel; + struct jz4750lcd_osd_t osd; +}; + + +/* Jz LCDFB supported I/O controls. */ +#define FBIOSETBACKLIGHT 0x4688 /* set back light level */ +#define FBIODISPON 0x4689 /* display on */ +#define FBIODISPOFF 0x468a /* display off */ +#define FBIORESET 0x468b /* lcd reset */ +#define FBIOPRINT_REG 0x468c /* print lcd registers(debug) */ +#define FBIOROTATE 0x46a0 /* rotated fb */ +#define FBIOGETBUFADDRS 0x46a1 /* get buffers addresses */ +#define FBIO_GET_MODE 0x46a2 /* get lcd info */ +#define FBIO_SET_MODE 0x46a3 /* set osd mode */ +#define FBIO_DEEP_SET_MODE 0x46a4 /* set panel and osd mode */ +#define FBIO_MODE_SWITCH 0x46a5 /* switch mode between LCD and TVE */ +#define FBIO_GET_TVE_MODE 0x46a6 /* get tve info */ +#define FBIO_SET_TVE_MODE 0x46a7 /* set tve mode */ + +/* + * LCD panel specific definition + */ +/* AUO */ +#if defined(CONFIG_JZ4750_LCD_AUO_A043FL01V2) +#if defined(CONFIG_JZ4750_APUS) /* board pavo */ + #define SPEN (32*3+29) /*LCD_CS*/ + #define SPCK (32*3+26) /*LCD_SCL*/ + #define SPDA (32*3+27) /*LCD_SDA*/ + #define LCD_RET (32*4+25) /*LCD_DISP_N use for lcd reset*/ +#elif defined(CONFIG_JZ4750_FUWA) /* board pavo */ + #define SPEN (32*3+29) /*LCD_CS*/ + #define SPCK (32*3+26) /*LCD_SCL*/ + #define SPDA (32*3+27) /*LCD_SDA*/ + #define LCD_RET (32*5+2) /*LCD_DISP_N use for lcd reset*/ +#else +#error "driver/video/Jzlcd.h, please define SPI pins on your board." +#endif + +#define __spi_write_reg(reg, val) \ + do { \ + unsigned char no; \ + unsigned short value; \ + unsigned char a=0; \ + unsigned char b=0; \ + __gpio_as_output(SPEN); /* use SPDA */ \ + __gpio_as_output(SPCK); /* use SPCK */ \ + __gpio_as_output(SPDA); /* use SPDA */ \ + a=reg; \ + b=val; \ + __gpio_set_pin(SPEN); \ + __gpio_clear_pin(SPCK); \ + udelay(50); \ + __gpio_clear_pin(SPDA); \ + __gpio_clear_pin(SPEN); \ + udelay(50); \ + value=((a<<8)|(b&0xFF)); \ + for(no=0;no<16;no++) \ + { \ + if((value&0x8000)==0x8000){ \ + __gpio_set_pin(SPDA);} \ + else{ \ + __gpio_clear_pin(SPDA); } \ + udelay(50); \ + __gpio_set_pin(SPCK); \ + value=(value<<1); \ + udelay(50); \ + __gpio_clear_pin(SPCK); \ + } \ + __gpio_set_pin(SPEN); \ + udelay(400); \ + } while (0) +#define __spi_read_reg(reg,val) \ + do{ \ + unsigned char no; \ + unsigned short value; \ + __gpio_as_output(SPEN); /* use SPDA */ \ + __gpio_as_output(SPCK); /* use SPCK */ \ + __gpio_as_output(SPDA); /* use SPDA */ \ + value = ((reg << 0) | (1 << 7)); \ + val = 0; \ + __gpio_as_output(SPDA); \ + __gpio_set_pin(SPEN); \ + __gpio_clear_pin(SPCK); \ + udelay(50); \ + __gpio_clear_pin(SPDA); \ + __gpio_clear_pin(SPEN); \ + udelay(50); \ + for (no = 0; no < 16; no++ ) { \ + udelay(50); \ + if(no < 8) \ + { \ + if (value & 0x80) /* send data */ \ + __gpio_set_pin(SPDA); \ + else \ + __gpio_clear_pin(SPDA); \ + udelay(50); \ + __gpio_set_pin(SPCK); \ + value = (value << 1); \ + udelay(50); \ + __gpio_clear_pin(SPCK); \ + if(no == 7) \ + __gpio_as_input(SPDA); \ + } \ + else \ + { \ + udelay(100); \ + __gpio_set_pin(SPCK); \ + udelay(50); \ + val = (val << 1); \ + val |= __gpio_get_pin(SPDA); \ + __gpio_clear_pin(SPCK); \ + } \ + } \ + __gpio_as_output(SPDA); \ + __gpio_set_pin(SPEN); \ + udelay(400); \ + } while(0) + +#define __lcd_special_pin_init() \ + do { \ + __gpio_as_output(SPEN); /* use SPDA */ \ + __gpio_as_output(SPCK); /* use SPCK */ \ + __gpio_as_output(SPDA); /* use SPDA */ \ + __gpio_as_output(LCD_RET); \ + udelay(50); \ + __gpio_clear_pin(LCD_RET); \ + udelay(100); \ + __gpio_set_pin(LCD_RET); \ + } while (0) +#define __lcd_special_on() \ + do { \ + udelay(50); \ + __gpio_clear_pin(LCD_RET); \ + udelay(100); \ + __gpio_set_pin(LCD_RET); \ +} while (0) + + #define __lcd_special_off() \ + do { \ + __gpio_clear_pin(LCD_RET); \ + } while (0) + +#endif /* CONFIG_JZLCD_AUO_A030FL01_V1 */ + +/* TRULY_TFTG320240DTSW */ +#if defined(CONFIG_JZ4750_LCD_TRULY_TFTG320240DTSW_16BIT) || defined(CONFIG_JZ4750_LCD_TRULY_TFTG320240DTSW_18BIT) + +#if defined(CONFIG_JZ4750_FUWA) +#define LCD_RESET_PIN (32*3+25)// LCD_REV, GPD25 +#else +#error "Define LCD_RESET_PIN on your board" +#endif + +#define __lcd_special_on() \ +do { \ + __gpio_as_output(32*3+30);\ + __gpio_clear_pin(32*3+30);\ + __gpio_as_output(LCD_RESET_PIN); \ + __gpio_set_pin(LCD_RESET_PIN); \ + udelay(100); \ + __gpio_clear_pin(LCD_RESET_PIN); \ + udelay(100); \ + __gpio_set_pin(LCD_RESET_PIN); \ +} while (0) + +#endif /* CONFIG_JZ4750_LCD_TRULY_TFTG320240DTSW */ + +// Wolfgang 2008.02.23 +#if defined(CONFIG_JZ4750_LCD_TOPPOLY_TD025THEA7_RGB_DELTA) || defined(CONFIG_JZ4750_LCD_TOPPOLY_TD025THEA7_RGB_DUMMY) + +#if defined(CONFIG_JZ4750_LCD_TOPPOLY_TD025THEA7_RGB_DELTA) +#define PANEL_MODE 0x02 /* RGB Delta */ +#elif defined(CONFIG_JZ4750_LCD_TOPPOLY_TD025THEA7_RGB_DUMMY) +#define PANEL_MODE 0x00 /* RGB Dummy */ +#endif + +#if defined(CONFIG_JZ4750_FUWA) /* board FuWa */ + #define SPEN (32*3+16) //LCD_D16 - GPD16 + #define SPCK (32*3+17) //LCD_D17 - GPD17 + #define SPDA (32*3+21) //LCD_DE - GPD21 + #define LCD_RET (32*3+25) //LCD_REV - GPD25 //use for lcd reset +#else +#error "please define SPI pins on your board." +#endif + + #define __spi_write_reg1(reg, val) \ + do { \ + unsigned char no;\ + unsigned short value;\ + unsigned char a=0;\ + unsigned char b=0;\ + a=reg;\ + b=val;\ + __gpio_set_pin(SPEN);\ + udelay(100);\ + __gpio_clear_pin(SPCK);\ + __gpio_clear_pin(SPDA);\ + __gpio_clear_pin(SPEN);\ + udelay(25);\ + value=((a<<8)|(b&0xFF));\ + for(no=0;no<16;no++)\ + {\ + __gpio_clear_pin(SPCK);\ + if((value&0x8000)==0x8000)\ + __gpio_set_pin(SPDA);\ + else\ + __gpio_clear_pin(SPDA);\ + udelay(25);\ + __gpio_set_pin(SPCK);\ + value=(value<<1); \ + udelay(25);\ + }\ + __gpio_clear_pin(SPCK);\ + __gpio_set_pin(SPEN);\ + udelay(100);\ + } while (0) + + #define __spi_write_reg(reg, val) \ + do {\ + __spi_write_reg1((reg<<2), val); \ + udelay(100); \ + }while(0) + + #define __lcd_special_pin_init() \ + do { \ + __gpio_as_output(SPEN); /* use SPDA */\ + __gpio_as_output(SPCK); /* use SPCK */\ + __gpio_as_output(SPDA); /* use SPDA */\ + __gpio_as_output(SPDA); /* use reset */\ + __gpio_as_output(LCD_RET); /* use reset */\ + __gpio_set_pin(LCD_RET);\ + mdelay(15);\ + __gpio_clear_pin(LCD_RET);\ + mdelay(15);\ + __gpio_set_pin(LCD_RET);\ + } while (0) + + #define __lcd_special_on() \ + do { \ + mdelay(10); \ + __spi_write_reg(0x00, 0x10); \ + __spi_write_reg(0x01, 0xB1); \ + __spi_write_reg(0x00, 0x10); \ + __spi_write_reg(0x01, 0xB1); \ + __spi_write_reg(0x02, PANEL_MODE); /* RGBD MODE */ \ + __spi_write_reg(0x03, 0x01); /* Noninterlace*/ \ + mdelay(10); \ + } while (0) + + #define __lcd_special_off() \ + do { \ + } while (0) + +#endif /* CONFIG_JZ4750_LCD_TOPPOLY_TD025THEA7_RGB_DELTA */ + + +#if defined(CONFIG_JZ4750_LCD_FOXCONN_PT035TN01) || defined(CONFIG_JZ4750_LCD_INNOLUX_PT035TN01_SERIAL) + +#if defined(CONFIG_JZ4750_LCD_FOXCONN_PT035TN01) /* board FUWA */ +#define MODE 0xcd /* 24bit parellel RGB */ +#endif +#if defined(CONFIG_JZ4750_LCD_INNOLUX_PT035TN01_SERIAL) +#define MODE 0xc9 /* 8bit serial RGB */ +#endif + +#if defined(CONFIG_JZ4750_FUWA) /* board FuWa */ +#if 0 + #define SPEN (32*5+7) //LCD_SPL GPF7 + #define SPCK (32*5+6) //LCD_CLS GPF6 + #define SPDA (32*5+5) //LCD_PS GPF5 + #define LCD_RET (32*5+4) //LCD_REV GPF4 //use for lcd reset +#endif + #define SPEN (32*3+29) /*LCD_CS*/ + #define SPCK (32*3+26) /*LCD_SCL*/ + #define SPDA (32*3+27) /*LCD_SDA*/ + #define LCD_RET (32*4+25) /*LCD_DISP_N use for lcd reset*/ +#else +#error "driver/video/Jzlcd.h, please define SPI pins on your board." +#endif + + #define __spi_write_reg1(reg, val) \ + do { \ + unsigned char no;\ + unsigned short value;\ + unsigned char a=0;\ + unsigned char b=0;\ + a=reg;\ + b=val;\ + __gpio_set_pin(SPEN);\ + __gpio_set_pin(SPCK);\ + __gpio_clear_pin(SPDA);\ + __gpio_clear_pin(SPEN);\ + udelay(25);\ + value=((a<<8)|(b&0xFF));\ + for(no=0;no<16;no++)\ + {\ + __gpio_clear_pin(SPCK);\ + if((value&0x8000)==0x8000)\ + __gpio_set_pin(SPDA);\ + else\ + __gpio_clear_pin(SPDA);\ + udelay(25);\ + __gpio_set_pin(SPCK);\ + value=(value<<1); \ + udelay(25);\ + }\ + __gpio_set_pin(SPEN);\ + udelay(100);\ + } while (0) + + #define __spi_write_reg(reg, val) \ + do {\ + __spi_write_reg1((reg<<2|2), val); \ + udelay(100); \ + }while(0) + + #define __lcd_special_pin_init() \ + do { \ + __gpio_as_output(SPEN); /* use SPDA */\ + __gpio_as_output(SPCK); /* use SPCK */\ + __gpio_as_output(SPDA); /* use SPDA */\ + __gpio_as_output(LCD_RET);\ + udelay(50);\ + __gpio_clear_pin(LCD_RET);\ + mdelay(150);\ + __gpio_set_pin(LCD_RET);\ + } while (0) + + #define __lcd_special_on() \ + do { \ + udelay(50);\ + __gpio_clear_pin(LCD_RET);\ + mdelay(150);\ + __gpio_set_pin(LCD_RET);\ + mdelay(10);\ + __spi_write_reg(0x00, 0x03); \ + __spi_write_reg(0x01, 0x40); \ + __spi_write_reg(0x02, 0x11); \ + __spi_write_reg(0x03, MODE); /* mode */ \ + __spi_write_reg(0x04, 0x32); \ + __spi_write_reg(0x05, 0x0e); \ + __spi_write_reg(0x07, 0x03); \ + __spi_write_reg(0x08, 0x08); \ + __spi_write_reg(0x09, 0x32); \ + __spi_write_reg(0x0A, 0x88); \ + __spi_write_reg(0x0B, 0xc6); \ + __spi_write_reg(0x0C, 0x20); \ + __spi_write_reg(0x0D, 0x20); \ + } while (0) //reg 0x0a is control the display direction:DB0->horizontal level DB1->vertical level + +/* __spi_write_reg(0x02, 0x03); \ + __spi_write_reg(0x06, 0x40); \ + __spi_write_reg(0x0a, 0x11); \ + __spi_write_reg(0x0e, 0xcd); \ + __spi_write_reg(0x12, 0x32); \ + __spi_write_reg(0x16, 0x0e); \ + __spi_write_reg(0x1e, 0x03); \ + __spi_write_reg(0x22, 0x08); \ + __spi_write_reg(0x26, 0x40); \ + __spi_write_reg(0x2a, 0x88); \ + __spi_write_reg(0x2e, 0x88); \ + __spi_write_reg(0x32, 0x20); \ + __spi_write_reg(0x36, 0x20); \ +*/ +// } while (0) //reg 0x0a is control the display direction:DB0->horizontal level DB1->vertical level + + #define __lcd_special_off() \ + do { \ + __spi_write_reg(0x00, 0x03); \ + } while (0) + +#endif /* CONFIG_JZ4750_LCD_FOXCONN_PT035TN01 or CONFIG_JZ4750_LCD_INNOLUX_PT035TN01_SERIAL */ + +#if defined(CONFIG_JZ4750_LCD_TRULY_TFT_GG1P0319LTSW_W) +static inline void CmdWrite(unsigned int cmd) +{ + while (REG_SLCD_STATE & SLCD_STATE_BUSY); /* wait slcd ready */ + udelay(30); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | cmd; +} + +static inline void DataWrite(unsigned int data) +{ + while (REG_SLCD_STATE & SLCD_STATE_BUSY); /* wait slcd ready */ +// udelay(30); + REG_SLCD_DATA = SLCD_DATA_RS_DATA | data; +} + + +static inline void delay(long delay_time) +{ + long cnt; + +// delay_time *= (384/8); + delay_time *= (43/8); + + for (cnt=0;cnt SETP 3 + CmdWrite(0x0000); + CmdWrite(0x01A0); + CmdWrite(0x3B01); + + CmdWrite(0x2809); + delay(1000); + CmdWrite(0x1900); + delay(1000); + CmdWrite(0x2110); + delay(1000); + CmdWrite(0x1805); + delay(1000); + CmdWrite(0x1E01); + delay(1000); + CmdWrite(0x1847); + delay(1000); + CmdWrite(0x1867); + delay(1000); + CmdWrite(0x18F7); + delay(1000); + CmdWrite(0x2100); + delay(1000); + CmdWrite(0x2809); + delay(1000); + CmdWrite(0x1A05); + delay(1000); + CmdWrite(0x19E8); + delay(1000); + CmdWrite(0x1F64); + delay(1000); + CmdWrite(0x2045); + delay(1000); + CmdWrite(0x1E81); + delay(1000); + CmdWrite(0x1B09); + delay(1000); + CmdWrite(0x0020); + delay(1000); + CmdWrite(0x0120); + delay(1000); + + CmdWrite(0x3B01); + delay(1000); + + /* Set Window(239,319), Set Cursor(239,319) */ + CmdWrite(0x0510); + CmdWrite(0x01C0); + CmdWrite(0x4500); + CmdWrite(0x46EF); + CmdWrite(0x4800); + CmdWrite(0x4700); + CmdWrite(0x4A3F); + CmdWrite(0x4901); + CmdWrite(0x42EF); + CmdWrite(0x443F); + CmdWrite(0x4301); + +} + +#if defined(CONFIG_JZ4750_FUWA) +//#define PIN_CS_N (32*2+xx) /* a low voltage */ +#define PIN_RD_N (32*3+21) /* LCD_DE: GP D21, a high voltage */ +#define PIN_RESET_N (32*3+25) /* LCD_REV GP D25 */ +#else +#error "Define special lcd pins for your platform." +#endif + +#define __lcd_slcd_pin_init() \ + do { \ + __gpio_as_output(PIN_RD_N); /* RD#: LCD_REV */ \ + __gpio_as_output(PIN_RESET_N); /* RESET#: LCD_SPL */ \ + __gpio_set_pin(PIN_RD_N); /*set read signal high */ \ + __gpio_set_pin(PIN_RESET_N); \ + mdelay(100); \ + __gpio_clear_pin(PIN_RESET_N); \ + mdelay(100); \ + __gpio_set_pin(PIN_RESET_N); \ + /* Configure SLCD module */ \ + REG_LCD_CTRL &= ~(LCD_CTRL_ENA|LCD_CTRL_DIS); /* disable lcdc */ \ + REG_LCD_CFG = LCD_CFG_LCDPIN_SLCD | 0x0D; /* LCM */ \ + REG_SLCD_CTRL &= ~SLCD_CTRL_DMA_EN; /* disable slcd dma */ \ + REG_SLCD_CFG = SLCD_CFG_DWIDTH_16BIT | SLCD_CFG_CWIDTH_16BIT | SLCD_CFG_CS_ACTIVE_LOW | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_CLK_ACTIVE_FALLING | SLCD_CFG_TYPE_PARALLEL; \ + REG_LCD_REV = 0x04; /* lcd clock??? */ \ + printk("Fuwa test, pixclk divide REG_LCD_REV=0x%08x\n", REG_LCD_REV); \ +}while (0) + +#define __lcd_slcd_special_on() \ + do { \ + __lcd_slcd_pin_init(); \ + SlcdInit(); \ + REG_SLCD_CTRL |= SLCD_CTRL_DMA_EN; /* slcdc dma enable */ \ + } while (0) + +#endif /* #if CONFIG_JZ4750_LCD_TRULY_TFT_GG1P0319LTSW_W */ + +#ifndef __lcd_special_pin_init +#define __lcd_special_pin_init() +#endif +#ifndef __lcd_special_on +#define __lcd_special_on() +#endif +#ifndef __lcd_special_off +#define __lcd_special_off() +#endif + + +/* + * Platform specific definition + */ +#if defined(CONFIG_SOC_JZ4750) + +#if defined(CONFIG_JZ4750_APUS) /* board apus */ +#define __lcd_display_pin_init() \ +do { \ + __gpio_as_output(GPIO_LCD_VCC_EN_N); \ + __lcd_special_pin_init(); \ +} while (0) +#define __lcd_display_on() \ +do { \ + __gpio_clear_pin(GPIO_LCD_VCC_EN_N); \ + __lcd_special_on(); \ + __lcd_set_backlight_level(80); \ +} while (0) + +#define __lcd_display_off() \ +do { \ + __lcd_close_backlight(); \ + __lcd_special_off(); \ +} while (0) + +#else /* other boards */ + +#define __lcd_display_pin_init() \ +do { \ + __lcd_special_pin_init(); \ +} while (0) +#define __lcd_display_on() \ +do { \ + __lcd_special_on(); \ + __lcd_set_backlight_level(80); \ +} while (0) + +#define __lcd_display_off() \ +do { \ + __lcd_close_backlight(); \ + __lcd_special_off(); \ +} while (0) +#endif /* APUS */ +#endif /* CONFIG_SOC_JZ4750 */ + + +/***************************************************************************** + * LCD display pin dummy macros + *****************************************************************************/ + +#ifndef __lcd_display_pin_init +#define __lcd_display_pin_init() +#endif +#ifndef __lcd_slcd_special_on +#define __lcd_slcd_special_on() +#endif +#ifndef __lcd_display_on +#define __lcd_display_on() +#endif +#ifndef __lcd_display_off +#define __lcd_display_off() +#endif +#ifndef __lcd_set_backlight_level +#define __lcd_set_backlight_level(n) +#endif + +#endif /* __JZ4750_LCD_H__ */ --- /dev/null +++ b/drivers/video/jz4750_tve.c @@ -0,0 +1,104 @@ + +/* + * linux/drivers/video/jz4750_tve.c -- Ingenic Jz4750 TVE Controller operation + * interface. + * Copyright (C) 2005-2008, Ingenic Semiconductor Inc. + * Author: Wolfgang Wang, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include +#include "jz4750_tve.h" + +struct jz4750tve_info jz4750_tve_info_PAL = { + .ctrl = (4 << TVE_CTRL_YCDLY_BIT) | TVE_CTRL_SYNCT | TVE_CTRL_PAL | TVE_CTRL_SWRST, /* PAL, SVIDEO */ + .frcfg = (23 << TVE_FRCFG_L1ST_BIT) | (625 << TVE_FRCFG_NLINE_BIT), + .slcfg1 = (800<ctrl = (jz4750_tve_info->ctrl | TVE_CTRL_DAPD) & ( ~( TVE_CTRL_DAPD1 | TVE_CTRL_DAPD2)); + jz4750_tve_info->ctrl &= ~TVE_CTRL_SWRST; + REG_TVE_CTRL = jz4750_tve_info->ctrl; +} + +/* turn off TVE, turn off DACn... */ +void jz4750tve_disable_tve(void) +{ + jz4750_tve_info->ctrl &= ~TVE_CTRL_DAPD;/* DACn disabled??? */ + jz4750_tve_info->ctrl |= TVE_CTRL_SWRST;/* DACn disabled??? */ + REG_TVE_CTRL = jz4750_tve_info->ctrl; +} + +void jz4750tve_set_tve_mode( struct jz4750tve_info *tve ) +{ + REG_TVE_CTRL = tve->ctrl; + REG_TVE_FRCFG = tve->frcfg; + REG_TVE_SLCFG1 = tve->slcfg1; + REG_TVE_SLCFG2 = tve->slcfg2; + REG_TVE_SLCFG3 = tve->slcfg3; + REG_TVE_LTCFG1 = tve->ltcfg1; + REG_TVE_LTCFG2 = tve->ltcfg2; + REG_TVE_CFREQ = tve->cfreq; + REG_TVE_CPHASE = tve->cphase; + REG_TVE_CBCRCFG = tve->cbcrcfg; + REG_TVE_WSSCR = tve->wsscr; + REG_TVE_WSSCFG1 = tve->wsscfg1; + REG_TVE_WSSCFG2 = tve->wsscfg2; + REG_TVE_WSSCFG3 = tve->wsscfg3; +} + +void jz4750tve_init( int tve_mode ) +{ + switch ( tve_mode ) { + case PANEL_MODE_TVE_PAL: + jz4750_tve_info = &jz4750_tve_info_PAL; + break; + case PANEL_MODE_TVE_NTSC: + jz4750_tve_info = &jz4750_tve_info_NTSC; + break; + } + + jz4750tve_set_tve_mode( jz4750_tve_info ); +// jz4750tve_enable_tve(); +} --- /dev/null +++ b/drivers/video/jz4750_tve.h @@ -0,0 +1,45 @@ +#ifndef __JZ4750_TVE_H__ +#define __JZ4750_TVE_H__ + + +#define PANEL_MODE_LCD_PANEL 0 +#define PANEL_MODE_TVE_PAL 1 +#define PANEL_MODE_TVE_NTSC 2 + +/* TV parameter */ +#define TVE_WIDTH_PAL 720 +#define TVE_HEIGHT_PAL 573 +#define TVE_FREQ_PAL 50 +#define TVE_WIDTH_NTSC 720 +#define TVE_HEIGHT_NTSC 482 +#define TVE_FREQ_NTSC 60 + + +/* Structure for TVE */ +struct jz4750tve_info { + unsigned int ctrl; + unsigned int frcfg; + unsigned int slcfg1; + unsigned int slcfg2; + unsigned int slcfg3; + unsigned int ltcfg1; + unsigned int ltcfg2; + unsigned int cfreq; + unsigned int cphase; + unsigned int cbcrcfg; + unsigned int wsscr; + unsigned int wsscfg1; + unsigned int wsscfg2; + unsigned int wsscfg3; +}; + +extern struct jz4750tve_info *jz4750_tve_info; + +extern void jz4750tve_enable_tve(void); +extern void jz4750tve_disable_tve(void); + +extern void jz4750tve_set_tve_mode( struct jz4750tve_info *tve ); +extern void jz4750tve_init( int tve_mode ); + + +#endif /* __JZ4750_TVE_H__ */ --- /dev/null +++ b/drivers/video/jz_kgm_spfd5420a.h @@ -0,0 +1,382 @@ +/* Set registers of smart lcd acording to the following routines + * Note: BUS width and CMD width and register value width + * This example: BUS is 8, 9, 16 or 18-bit; CMD and DATA is 16-bit + * Configure SLCD module to initialize smart lcd registers + + switch (bus) { + case 8: + REG_SLCD_CFG = SLCD_CFG_BURST_8_WORD | SLCD_CFG_DWIDTH_8_x2 + | SLCD_CFG_CWIDTH_8BIT | SLCD_CFG_CS_ACTIVE_LOW + | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_CLK_ACTIVE_FALLING + | SLCD_CFG_TYPE_PARALLEL; + __gpio_as_slcd_8bit(); + break; + case 9: + REG_SLCD_CFG = SLCD_CFG_BURST_8_WORD | SLCD_CFG_DWIDTH_8_x2 + | SLCD_CFG_CWIDTH_8BIT | SLCD_CFG_CS_ACTIVE_LOW + | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_CLK_ACTIVE_FALLING + | SLCD_CFG_TYPE_PARALLEL; + __gpio_as_slcd_9bit(); + break; + case 16: + REG_SLCD_CFG = SLCD_CFG_BURST_8_WORD | SLCD_CFG_DWIDTH_16 + | SLCD_CFG_CWIDTH_16BIT | SLCD_CFG_CS_ACTIVE_LOW + | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_CLK_ACTIVE_FALLING + | SLCD_CFG_TYPE_PARALLEL; + __gpio_as_slcd_16bit(); + break; + case 18: + REG_SLCD_CFG = SLCD_CFG_BURST_8_WORD | SLCD_CFG_DWIDTH_18 + | SLCD_CFG_CWIDTH_18BIT | SLCD_CFG_CS_ACTIVE_LOW + | SLCD_CFG_RS_CMD_LOW | SLCD_CFG_CLK_ACTIVE_FALLING + | SLCD_CFG_TYPE_PARALLEL; + __gpio_as_slcd_18bit(); + break; + } + + static void Mcupanel_RegSet(unsigned int cmd, unsigned int data) + { + switch (bus) { + case 8: + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff00) >> 8); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff) >> 0); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_DATA | (data&0xffff); + break; + case 9: + data = ((data & 0xff) << 1) | ((data & 0xff00) << 2); + data = ((data << 6) & 0xfc0000) | ((data << 4) & 0xfc00) | ((data << 2) & 0xfc); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff00) >> 8); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff) >> 0); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_DATA | data; + break; + case 16: + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | (cmd&0xffff); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_DATA | (data&0xffff); + break; + case 18: + cmd = ((cmd & 0xff) << 1) | ((cmd & 0xff00) << 2); + data = ((data & 0xff) << 1) | ((data & 0xff00) << 2); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | cmd; + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_DATA | ((data<<6)&0xfc0000)|((data<<4)&0xfc00) | ((data<<2)&0xfc); + break; + default: + printk("Don't support %d bit Bus\n", jzfb.bus ); + break; + } + } + + static void Mcupanel_Command(unsigned int cmd) { + switch (bus) { + case 8: + case 9: + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff00) >> 8); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff) >> 0); + break; + case 16: + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | (cmd&0xffff); + break; + case 18: + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff00) << 2) | ((cmd&0xff) << 1); + break; + default: + printk("Don't support %d bit Bus\n", jzfb.bus ); + break; + } + } + + *Display---------------------------------------- + Note: BUS and BPP, send data to gram data register to display + BUS: 8, 9, 16 or 18-bit; BPP: 8, 16, 18-bit + switch (bus) { + case 8: + switch (bpp) { + case 8: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x1; + break; + case 15: + case 16: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x2; + break; + case 17 ... 32: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x3; + break; + default: + printk("The BPP %d is not supported\n", jzfb.bpp); + break; + } + break; + case 9: + switch (bpp) { + case 8: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x1; + break; + case 15 ... 16: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x2; + break; + case 17 ... 32: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_9_x2; + break; + default: + printk("The BPP %d is not supported\n", jzfb.bpp); + break; + } + break; + case 16: + switch (bpp) { + case 8: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x1; + break; + case 15 ... 16: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_16; + break; + case 17 ... 32: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x3; + break; + default: + printk("The BPP %d is not supported\n", jzfb.bpp); + break; + } + break; + case 18: + switch (bpp) { + case 8: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_8_x1; + break; + case 15: + case 16: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_16; + break; + case 17 ... 32: + REG_SLCD_CFG &= ~SLCD_CFG_DWIDTH_MASK; + REG_SLCD_CFG |= SLCD_CFG_DWIDTH_18; + break; + default: + printk("The BPP %d is not supported\n", jzfb.bpp); + break; + } + break; + default: + printk("Error: The BUS %d is not supported\n", jzfb.bus); + break; + } + dprintk("SLCD_CFG=0x%x\n", REG_SLCD_CFG); +} + ************************************************************************************************/ + +#ifndef __JZ_KGM_SPF5420A_H__ +#define __JZ_KGM_SPF5420A_H__ + +#include + +#if defined(CONFIG_JZ4750_SLCD_KGM701A3_TFT_SPFD5420A) +#define WR_GRAM_CMD 0x0202 + +#if defined(CONFIG_JZ4750_FUWA) +#define PIN_CS_N (32*3+24) // Chip select //GPD24; +#define PIN_RESET_N (32*5+6) /* LCD_REV GPF6 */ +#else +#error "Define special lcd pins for your platform." +#endif + +/* Sent a command with data (18-bit bus, 16-bit index, 16-bit register value) */ +static void Mcupanel_RegSet(unsigned int cmd, unsigned int data) +{ + cmd = ((cmd & 0xff) << 1) | ((cmd & 0xff00) << 2); + data = ((data & 0xff) << 1) | ((data & 0xff00) << 2); + data = ((data<<6)&0xfc0000)|((data<<4)&0xfc00) | ((data<<2)&0xfc); + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | cmd; + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_DATA | data; +} + +/* Sent a command without data (18-bit bus, 16-bit index) */ +static void Mcupanel_Command(unsigned int cmd) { + while (REG_SLCD_STATE & SLCD_STATE_BUSY); + REG_SLCD_DATA = SLCD_DATA_RS_COMMAND | ((cmd&0xff00) << 2) | ((cmd&0xff) << 1); +} + +/* Set the start address of screen, for example (0, 0) */ +void Mcupanel_SetAddr(u32 x, u32 y) //u32 +{ + Mcupanel_RegSet(0x200,x) ; + udelay(1); + Mcupanel_RegSet(0x201,y) ; + udelay(1); + Mcupanel_Command(0x202); + +} + +#undef __lcd_special_pin_init +#define __lcd_special_pin_init() \ +do { \ + __gpio_as_output(PIN_CS_N); \ + __gpio_as_output(PIN_RESET_N); \ + __gpio_clear_pin(PIN_CS_N); /* Clear CS */ \ + mdelay(100); \ + __gpio_set_pin(PIN_RESET_N); \ + mdelay(10); \ + __gpio_clear_pin(PIN_RESET_N); \ + mdelay(10); \ + __gpio_set_pin(PIN_RESET_N); \ + mdelay(100); \ +} while(0) + + +#define GAMMA() \ +do { \ + Mcupanel_RegSet(0x0300,0x0101); \ + Mcupanel_RegSet(0x0301,0x0b27); \ + Mcupanel_RegSet(0x0302,0x132a); \ + Mcupanel_RegSet(0x0303,0x2a13); \ + Mcupanel_RegSet(0x0304,0x270b); \ + Mcupanel_RegSet(0x0305,0x0101); \ + Mcupanel_RegSet(0x0306,0x1205); \ + Mcupanel_RegSet(0x0307,0x0512); \ + Mcupanel_RegSet(0x0308,0x0005); \ + Mcupanel_RegSet(0x0309,0x0003); \ + Mcupanel_RegSet(0x030a,0x0f04); \ + Mcupanel_RegSet(0x030b,0x0f00); \ + Mcupanel_RegSet(0x030c,0x000f); \ + Mcupanel_RegSet(0x030d,0x040f); \ + Mcupanel_RegSet(0x030e,0x0300); \ + Mcupanel_RegSet(0x030f,0x0500); \ + /*** secorrect gamma2 ***/ \ + Mcupanel_RegSet(0x0400,0x3500); \ + Mcupanel_RegSet(0x0401,0x0001); \ + Mcupanel_RegSet(0x0404,0x0000); \ + Mcupanel_RegSet(0x0500,0x0000); \ + Mcupanel_RegSet(0x0501,0x0000); \ + Mcupanel_RegSet(0x0502,0x0000); \ + Mcupanel_RegSet(0x0503,0x0000); \ + Mcupanel_RegSet(0x0504,0x0000); \ + Mcupanel_RegSet(0x0505,0x0000); \ + Mcupanel_RegSet(0x0600,0x0000); \ + Mcupanel_RegSet(0x0606,0x0000); \ + Mcupanel_RegSet(0x06f0,0x0000); \ + Mcupanel_RegSet(0x07f0,0x5420); \ + Mcupanel_RegSet(0x07f3,0x288a); \ + Mcupanel_RegSet(0x07f4,0x0022); \ + Mcupanel_RegSet(0x07f5,0x0001); \ + Mcupanel_RegSet(0x07f0,0x0000); \ +} while(0) + +#define SlcdInit() \ +do { \ + __gpio_set_pin(PIN_RESET_N); \ + mdelay(10); \ + __gpio_clear_pin(PIN_RESET_N); \ + mdelay(10); \ + __gpio_set_pin(PIN_RESET_N); \ + mdelay(100); \ + Mcupanel_RegSet(0x0600, 0x0001); /*soft reset*/ \ + mdelay(10); \ + Mcupanel_RegSet(0x0600, 0x0000); /*soft reset*/ \ + mdelay(10); \ + Mcupanel_RegSet(0x0606,0x0000); \ + udelay(10); \ + Mcupanel_RegSet(0x0007,0x0001); \ + udelay(10); \ + Mcupanel_RegSet(0x0110,0x0001); \ + udelay(10); \ + Mcupanel_RegSet(0x0100,0x17b0); \ + Mcupanel_RegSet(0x0101,0x0147); \ + Mcupanel_RegSet(0x0102,0x019d); \ + Mcupanel_RegSet(0x0103,0x8600); \ + Mcupanel_RegSet(0x0281,0x0010); \ + udelay(10); \ + Mcupanel_RegSet(0x0102,0x01bd); \ + udelay(10); \ + /************initial************/\ + Mcupanel_RegSet(0x0000,0x0000); \ + Mcupanel_RegSet(0x0001,0x0000); \ + Mcupanel_RegSet(0x0002,0x0400); \ + Mcupanel_RegSet(0x0003,0x12b8); /*up:0x1288 down:0x12B8 left:0x1290 right:0x12A0*/ \ + Mcupanel_RegSet(0x0006,0x0000); \ + Mcupanel_RegSet(0x0008,0x0503); \ + Mcupanel_RegSet(0x0009,0x0001); \ + Mcupanel_RegSet(0x000b,0x0010); \ + Mcupanel_RegSet(0x000c,0x0000); \ + Mcupanel_RegSet(0x000f,0x0000); \ + Mcupanel_RegSet(0x0007,0x0001); \ + Mcupanel_RegSet(0x0010,0x0010); \ + Mcupanel_RegSet(0x0011,0x0202); \ + Mcupanel_RegSet(0x0012,0x0300); \ + Mcupanel_RegSet(0x0020,0x021e); \ + Mcupanel_RegSet(0x0021,0x0202); \ + Mcupanel_RegSet(0x0022,0x0100); \ + Mcupanel_RegSet(0x0090,0x0000); \ + Mcupanel_RegSet(0x0092,0x0000); \ + Mcupanel_RegSet(0x0100,0x16b0); \ + Mcupanel_RegSet(0x0101,0x0147); \ + Mcupanel_RegSet(0x0102,0x01bd); \ + Mcupanel_RegSet(0x0103,0x2c00); \ + Mcupanel_RegSet(0x0107,0x0000); \ + Mcupanel_RegSet(0x0110,0x0001); \ + Mcupanel_RegSet(0x0210,0x0000); \ + Mcupanel_RegSet(0x0211,0x00ef); \ + Mcupanel_RegSet(0x0212,0x0000); \ + Mcupanel_RegSet(0x0213,0x018f); \ + Mcupanel_RegSet(0x0280,0x0000); \ + Mcupanel_RegSet(0x0281,0x0001); \ + Mcupanel_RegSet(0x0282,0x0000); \ + GAMMA(); \ + Mcupanel_RegSet(0x0007,0x0173); \ + Mcupanel_Command(0x0202); /*Write Data to GRAM */ \ + udelay(10);\ + Mcupanel_SetAddr(0,0);\ + udelay(100);\ +} while(0) + +/*---- LCD Initial ----*/ +#undef __lcd_slcd_pin_init +#define __lcd_slcd_pin_init() \ + do { \ + __lcd_special_pin_init(); \ +}while (0) + +#undef __lcd_slcd_special_on +#define __lcd_slcd_special_on() \ + do { \ + __lcd_slcd_pin_init(); \ + SlcdInit(); \ + REG_SLCD_CTRL |= SLCD_CTRL_DMA_EN; /* slcdc dma enable */ \ + } while (0) + +#define __init_slcd_bus()\ +do{\ + __slcd_set_data_18bit();\ + __slcd_set_cmd_18bit();\ + __slcd_set_cs_low();\ + __slcd_set_rs_low();\ + __slcd_set_clk_falling();\ + __slcd_set_parallel_type();\ +}while(0) +#endif /* #if CONFIG_JZ4750_SLCD_KGM701A3_TFT_SPFD5420A */ + +#endif /* __JZ_KGM_SPF5420A_H__ */ --- /dev/null +++ b/drivers/video/jz_toppoly_td043mgeb1.h @@ -0,0 +1,264 @@ + +#ifndef __JZ_KGM_TOPPOLY_TD043MGEB1_H__ +#define __JZ_KGM_TOPPOLY_TD043MGEB1_H__ + +#include + +#if defined(CONFIG_JZ4750_LCD_TOPPOLY_TD043MGEB1) +#if defined(CONFIG_JZ4750_APUS) /* board FuWa */ + #define SPEN (32*3+29) /*LCD_CS*/ + #define SPCK (32*3+26) /*LCD_SCL*/ + #define SPDA (32*3+27) /*LCD_SDA*/ + #define LCD_RET (32*4+23) /*LCD_DISP_N use for lcd reset*/ + #define LCD_STBY (32*4+25) /*LCD_STBY, use for lcd standby*/ +#else +#error "driver/video/Jzlcd.h, please define SPI pins on your board." +#endif + +#define __spi_write_reg(reg, val) \ + do { \ + unsigned char no; \ + unsigned short value; \ + unsigned char a=0; \ + unsigned char b=0; \ + __gpio_as_output(SPEN); /* use SPDA */ \ + __gpio_as_output(SPCK); /* use SPCK */ \ + __gpio_as_output(SPDA); /* use SPDA */ \ + a=reg; \ + b=val; \ + __gpio_set_pin(SPEN); \ + __gpio_clear_pin(SPCK); \ + udelay(500); \ + __gpio_clear_pin(SPDA); \ + __gpio_clear_pin(SPEN); \ + udelay(500); \ + value=((a<<8)|(b&0xFF)); \ + for(no=0;no<16;no++) \ + { \ + if((value&0x8000)==0x8000){ \ + __gpio_set_pin(SPDA);} \ + else{ \ + __gpio_clear_pin(SPDA); } \ + udelay(500); \ + __gpio_set_pin(SPCK); \ + value=(value<<1); \ + udelay(500); \ + __gpio_clear_pin(SPCK); \ + } \ + __gpio_set_pin(SPEN); \ + udelay(4000); \ + } while (0) +#define __spi_read_reg(reg,val) \ + do{ \ + unsigned char no; \ + unsigned short value; \ + __gpio_as_output(SPEN); /* use SPDA */ \ + __gpio_as_output(SPCK); /* use SPCK */ \ + __gpio_as_output(SPDA); /* use SPDA */ \ + value = ((reg << 0) | (1 << 7)); \ + val = 0; \ + __gpio_as_output(SPDA); \ + __gpio_set_pin(SPEN); \ + __gpio_clear_pin(SPCK); \ + udelay(50); \ + __gpio_clear_pin(SPDA); \ + __gpio_clear_pin(SPEN); \ + udelay(50); \ + for (no = 0; no < 16; no++ ) { \ + udelay(50); \ + if(no < 8) \ + { \ + if (value & 0x80) /* send data */ \ + __gpio_set_pin(SPDA); \ + else \ + __gpio_clear_pin(SPDA); \ + udelay(50); \ + __gpio_set_pin(SPCK); \ + value = (value << 1); \ + udelay(50); \ + __gpio_clear_pin(SPCK); \ + if(no == 7) \ + __gpio_as_input(SPDA); \ + } \ + else \ + { \ + udelay(100); \ + __gpio_set_pin(SPCK); \ + udelay(50); \ + val = (val << 1); \ + val |= __gpio_get_pin(SPDA); \ + __gpio_clear_pin(SPCK); \ + } \ + } \ + __gpio_as_output(SPDA); \ + __gpio_set_pin(SPEN); \ + udelay(400); \ + } while(0) + +#define __lcd_special_pin_init() \ + do { \ + __gpio_as_output(SPEN); /* use SPDA */ \ + __gpio_as_output(SPCK); /* use SPCK */ \ + __gpio_as_output(SPDA); /* use SPDA */ \ + __gpio_as_output(LCD_STBY); \ + __gpio_as_output(LCD_RET); \ + udelay(500); \ + __gpio_clear_pin(LCD_RET); \ + udelay(1000); \ + __gpio_set_pin(LCD_RET); \ + udelay(1000); \ + __gpio_set_pin(LCD_STBY); \ + udelay(1000); \ + } while (0) +#define __lcd_special_on() \ + do { \ +} while (0) + + #define __lcd_special_off() \ + do { \ + __gpio_clear_pin(LCD_RET); \ + } while (0) + +#endif /* CONFIG_JZLCD_AUO_A030FL01_V1 */ + +#endif /* __JZ_KGM_TOPPOLY_TD043MGEB1_H__ */ +/* 2.2 + __spi_write_reg(0x02, 0x07 ); \ + __spi_write_reg(0x03, 0x5f); \ + __spi_write_reg(0x04, 0x17); \ + __spi_write_reg(0x05, 0x20); \ + __spi_write_reg(0x06, 0x08); \ + __spi_write_reg(0x07, 0x26); \ + __spi_write_reg(0x08, 0x13); \ + __spi_write_reg(0x09, 0x33); \ + __spi_write_reg(0x0a, 0x20); \ + __spi_write_reg(0x0b, 0x20); \ + __spi_write_reg(0x0c, 0x20); \ + __spi_write_reg(0x0d, 0x20); \ + __spi_write_reg(0x0e, 0x10); \ + __spi_write_reg(0x0f, 0x10); \ + __spi_write_reg(0x10, 0x10); \ + __spi_write_reg(0x11, 0x15); \ + __spi_write_reg(0x12, 0xaa); \ + __spi_write_reg(0x13, 0xff); \ + __spi_write_reg(0x14, 0x86); \ + __spi_write_reg(0x15, 0x8e); \ + __spi_write_reg(0x16, 0xd6); \ + __spi_write_reg(0x17, 0xfe); \ + __spi_write_reg(0x18, 0x28); \ + __spi_write_reg(0x19, 0x52); \ + __spi_write_reg(0x1a, 0x7c); \ + __spi_write_reg(0x1b, 0xe9); \ + __spi_write_reg(0x1c, 0x42); \ + __spi_write_reg(0x1d, 0x88); \ + __spi_write_reg(0x1e, 0xb8); \ + __spi_write_reg(0x1f, 0xff); \ + __spi_write_reg(0x20, 0xf0); \ + __spi_write_reg(0x21, 0xf0); \ + __spi_write_reg(0x22, 0x07); \ +*/ +/* 3.1 + __spi_write_reg(0x02, 0x07); \ + __spi_write_reg(0x03, 0x5f); \ + __spi_write_reg(0x04, 0x17); \ + __spi_write_reg(0x05, 0x20); \ + __spi_write_reg(0x06, 0x08); \ + __spi_write_reg(0x07, 0x20); \ + __spi_write_reg(0x08, 0x20); \ + __spi_write_reg(0x09, 0x20); \ + __spi_write_reg(0x0a, 0x20); \ + __spi_write_reg(0x0b, 0x20); \ + __spi_write_reg(0x0c, 0x20); \ + __spi_write_reg(0x0d, 0x22); \ + __spi_write_reg(0x0e, 0x10); \ + __spi_write_reg(0x0f, 0x10); \ + __spi_write_reg(0x10, 0x10); \ + __spi_write_reg(0x11, 0x15); \ + __spi_write_reg(0x12, 0x6a); \ + __spi_write_reg(0x13, 0xff); \ + __spi_write_reg(0x14, 0x86); \ + __spi_write_reg(0x15, 0x7c); \ + __spi_write_reg(0x16, 0xc2); \ + __spi_write_reg(0x17, 0xd1); \ + __spi_write_reg(0x18, 0xf5); \ + __spi_write_reg(0x19, 0x25); \ + __spi_write_reg(0x1a, 0x4a); \ + __spi_write_reg(0x1b, 0xbf); \ + __spi_write_reg(0x1c, 0x15); \ + __spi_write_reg(0x1d, 0x6a); \ + __spi_write_reg(0x1e, 0xa4); \ + __spi_write_reg(0x1f, 0xff); \ + __spi_write_reg(0x20, 0xf0); \ + __spi_write_reg(0x21, 0xf0); \ + __spi_write_reg(0x22, 0x08); \ + */ + /* 2.5 + __spi_write_reg(0x02, 0x07); \ + __spi_write_reg(0x03, 0x5f); \ + __spi_write_reg(0x04, 0x17); \ + __spi_write_reg(0x05, 0x20); \ + __spi_write_reg(0x06, 0x08); \ + __spi_write_reg(0x07, 0x20); \ + __spi_write_reg(0x08, 0x20); \ + __spi_write_reg(0x09, 0x20); \ + __spi_write_reg(0x0a, 0x20); \ + __spi_write_reg(0x0b, 0x20); \ + __spi_write_reg(0x0c, 0x20); \ + __spi_write_reg(0x0d, 0x22); \ + __spi_write_reg(0x0e, 0x10); \ + __spi_write_reg(0x0f, 0x10); \ + __spi_write_reg(0x10, 0x10); \ + __spi_write_reg(0x11, 0x15); \ + __spi_write_reg(0x12, 0xaa); \ + __spi_write_reg(0x13, 0xff); \ + __spi_write_reg(0x14, 0x86); \ + __spi_write_reg(0x15, 0x89); \ + __spi_write_reg(0x16, 0xc6); \ + __spi_write_reg(0x17, 0xea); \ + __spi_write_reg(0x18, 0x0c); \ + __spi_write_reg(0x19, 0x33); \ + __spi_write_reg(0x1a, 0x5e); \ + __spi_write_reg(0x1b, 0xd0); \ + __spi_write_reg(0x1c, 0x33); \ + __spi_write_reg(0x1d, 0x7e); \ + __spi_write_reg(0x1e, 0xb3); \ + __spi_write_reg(0x1f, 0xff); \ + __spi_write_reg(0x20, 0xf0); \ + __spi_write_reg(0x21, 0xf0); \ + __spi_write_reg(0x22, 0x08); \ +*/ +/* + __spi_write_reg(0x02, 0x07); \ + __spi_write_reg(0x03, 0x5f); \ + __spi_write_reg(0x04, 0x17); \ + __spi_write_reg(0x05, 0x20); \ + __spi_write_reg(0x06, 0x08); \ + __spi_write_reg(0x07, 0x20); \ + __spi_write_reg(0x08, 0x20); \ + __spi_write_reg(0x09, 0x20); \ + __spi_write_reg(0x0a, 0x20); \ + __spi_write_reg(0x0b, 0x20); \ + __spi_write_reg(0x0c, 0x20); \ + __spi_write_reg(0x0d, 0x22); \ + __spi_write_reg(0x0e, 0x10); \ + __spi_write_reg(0x0f, 0x10); \ + __spi_write_reg(0x10, 0x10); \ + __spi_write_reg(0x11, 0x15); \ + __spi_write_reg(0x12, 0xaa); \ + __spi_write_reg(0x13, 0xff); \ + __spi_write_reg(0x14, 0x86); \ + __spi_write_reg(0x15, 0x84); \ + __spi_write_reg(0x16, 0xc3); \ + __spi_write_reg(0x17, 0xd8); \ + __spi_write_reg(0x18, 0x01); \ + __spi_write_reg(0x19, 0x28); \ + __spi_write_reg(0x1a, 0x53); \ + __spi_write_reg(0x1b, 0xc5); \ + __spi_write_reg(0x1c, 0x26); \ + __spi_write_reg(0x1d, 0x74); \ + __spi_write_reg(0x1e, 0xae); \ + __spi_write_reg(0x1f, 0xff); \ + __spi_write_reg(0x20, 0xf0); \ + __spi_write_reg(0x21, 0xf0); \ + __spi_write_reg(0x22, 0x08); \ + */ --- /dev/null +++ b/drivers/video/jzlcd.c @@ -0,0 +1,1571 @@ +/* + * linux/drivers/video/jzlcd.c -- Ingenic On-Chip LCD frame buffer device + * + * Copyright (C) 2005-2007, Ingenic Semiconductor Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "console/fbcon.h" + +#include "jzlcd.h" + +#undef DEBUG +//#define DEBUG +#ifdef DEBUG +#define dprintk(x...) printk(x) +#else +#define dprintk(x...) +#endif + +#define print_err(f, arg...) printk(KERN_ERR DRIVER_NAME ": " f "\n", ## arg) +#define print_warn(f, arg...) printk(KERN_WARNING DRIVER_NAME ": " f "\n", ## arg) +#define print_info(f, arg...) printk(KERN_INFO DRIVER_NAME ": " f "\n", ## arg) +#ifdef DEBUG +#define print_dbg(f, arg...) printk("dbg::" __FILE__ ",LINE(%d): " f "\n", __LINE__, ## arg) +#else +#define print_dbg(f, arg...) do {} while (0) +#endif + +struct lcd_cfb_info { + struct fb_info fb; + struct display_switch *dispsw; + signed int currcon; + int func_use_count; + + struct { + u16 red, green, blue; + } palette[NR_PALETTE]; +#ifdef CONFIG_PM + struct pm_dev *pm; +#endif +#if defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) + struct task_struct *rotate_daemon_thread; +#endif +}; + +static struct lcd_cfb_info *jzlcd_info; + +struct jzfb_info { + unsigned int cfg; /* panel mode and pin usage etc. */ + unsigned int w; + unsigned int h; + unsigned int bpp; /* bit per pixel */ + unsigned int fclk; /* frame clk */ + unsigned int hsw; /* hsync width, in pclk */ + unsigned int vsw; /* vsync width, in line count */ + unsigned int elw; /* end of line, in pclk */ + unsigned int blw; /* begin of line, in pclk */ + unsigned int efw; /* end of frame, in line count */ + unsigned int bfw; /* begin of frame, in line count */ +}; + +static struct jzfb_info jzfb = { +#if defined(CONFIG_JZLCD_SHARP_LQ035Q7) + MODE_TFT_SHARP | PCLK_N | VSYNC_N, + 240, 320, 16, 60, 1, 2, 1, 2, 0, 6 +#endif +#if defined(CONFIG_JZLCD_SAMSUNG_LTS350Q1) + MODE_TFT_SAMSUNG | PCLK_N, + 240, 320, 16, 60, 1, 2, (254-240), 0, 7, 0 +#endif +#if defined(CONFIG_JZLCD_SAMSUNG_LTV350QVF04) + MODE_TFT_GEN | HSYNC_N | VSYNC_N, + 320, 240, 16, 70, 19, 4, 20, 14, 18, 6 +#endif +#if defined(CONFIG_JZLCD_SAMSUNG_LTP400WQF01) + MODE_TFT_GEN | HSYNC_N | VSYNC_N, + 480, 272, 16, 60, 41, 10, 2, 2, 2, 2 +#endif + +#if defined(CONFIG_JZLCD_SAMSUNG_LTP400WQF02) + /* MODE_TFT_18BIT: JZ4740@ version */ + MODE_TFT_GEN | MODE_TFT_18BIT | HSYNC_N | VSYNC_N, + 480, 272, 32, 60, 41, 10, 2, 2, 2, 2 +#endif +#if defined(CONFIG_JZLCD_TRULY_TFTG320240DTSW) + MODE_TFT_GEN | HSYNC_N | VSYNC_N | PCLK_N, + 320, 240, 16, 85, 30, 3, 38, 20, 11, 8 +#endif +#if defined(CONFIG_JZLCD_TRULY_TFTG320240DTSW_SERIAL) + MODE_8BIT_SERIAL_TFT | HSYNC_N | VSYNC_N | PCLK_N, + /* serial mode 280 lines, parallel mode 240 lines */ + 320, 280, 32, 60, (30*3), 3, (20*3), (38*3), 46, 23 +#endif +#if defined(CONFIG_JZLCD_AUO_A030FL01_V1) + MODE_TFT_GEN | MODE_TFT_18BIT | HSYNC_N | VSYNC_N, + 480, 272, 32, 60, 39, 10, 8, 4, 4, 2 +#endif +#if defined(CONFIG_JZLCD_TRULY_TFTG240320UTSW_63W_E) + MODE_TFT_GEN | HSYNC_N | VSYNC_N | PCLK_N | DE_N, + 320, 240, 16, 60, 3, 3, 3, 3, 3, 85 /* 320x240 */ +#endif +#if defined(CONFIG_JZLCD_FOXCONN_PT035TN01) && defined(CONFIG_JZ4740_PAVO) + MODE_TFT_GEN | HSYNC_N | VSYNC_N | MODE_TFT_18BIT | PCLK_N, +// 320, 240, 18, 110, 1, 1, 10, 50, 10, 13 + 320, 240, 18, 80, 1, 1, 10, 50, 10, 13 +#endif +#if defined(CONFIG_JZLCD_FOXCONN_PT035TN01) && !(defined(CONFIG_JZ4740_PAVO)) + MODE_TFT_GEN | HSYNC_N | VSYNC_N | PCLK_N, + 320, 240, 16, 110, 1, 1, 10, 50, 10, 13 +#endif +#if defined(CONFIG_JZLCD_INNOLUX_PT035TN01_SERIAL) + MODE_8BIT_SERIAL_TFT | PCLK_N | HSYNC_N | VSYNC_N, + 320, 240, 32, 60, 1, 1, 10, 50, 10, 13 +#endif +#if defined(CONFIG_JZLCD_HYNIX_HT10X21) + MODE_TFT_GEN | PCLK_N, + 1024, 768, 16, 45, 1, 1, 75, 0, 3, 0 +#endif +#if defined(CONFIG_JZLCD_TOSHIBA_LTM084P363) + MODE_TFT_GEN | PCLK_N, + 800, 600, 16, 50, 1, 2, 199, 0, 2, 0 +#endif +#if defined(CONFIG_JZLCD_INNOLUX_AT080TN42) + MODE_TFT_SHARP | PCLK_N, + 800, 600, 16, 40, 1, 1, 255, 0, 34, 0 +#endif +#if defined(CONFIG_JZLCD_CSTN_800x600) + MODE_STN_COLOR_DUAL | STN_DAT_PIN8, + 800, 600, 16, 30, 8, 1, 0, 0, 0, 0 +#endif +#if defined(CONFIG_JZLCD_CSTN_320x240) + MODE_STN_COLOR_SINGLE | STN_DAT_PIN8, + 320, 240, 16, 120, 8, 1, 8, 0, 0, 0 +#endif +#if defined(CONFIG_JZLCD_MSTN_640x480) + MODE_STN_MONO_DUAL | STN_DAT_PIN4, + 640, 480, 8, 110, 4, 1, 4, 0, 0, 0 +#endif +#if defined(CONFIG_JZLCD_MSTN_320x240) + MODE_STN_MONO_SINGLE | STN_DAT_PIN4, + 320, 240, 8, 110, 4, 1, 4, 0, 0, 0 +#endif +#if defined(CONFIG_JZLCD_MSTN_480x320) + MODE_STN_MONO_SINGLE | STN_DAT_PIN8 +#if defined(CONFIG_JZLCD_MSTN_INVERSE) + | DATA_INVERSE +#endif + , 480, 320, 8, 65, 8, 1, 8, 0, 0, 0 +#endif + +#if defined(CONFIG_JZLCD_MSTN_240x128) + MODE_STN_MONO_SINGLE | STN_DAT_PIN1 +#if defined(CONFIG_JZLCD_MSTN_INVERSE) + | DATA_INVERSE +#endif + , 240, 128, 8, 100, 1, 1, 1, 0, 0, 0 +#endif +}; + +static struct lcd_desc *lcd_desc_base; +static struct lcd_desc *lcd_palette_desc; +static struct lcd_desc *lcd_frame_desc0; +static struct lcd_desc *lcd_frame_desc1; + +static unsigned char *lcd_palette; +static unsigned char *lcd_frame[CONFIG_JZLCD_FRAMEBUFFER_MAX]; +struct jz_lcd_buffer_addrs_t jz_lcd_buffer_addrs; +//extern struct display fb_display[MAX_NR_CONSOLES]; +#if defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) +static unsigned char *lcd_frame_user_fb; +/* default rotate angle */ +static volatile int rotate_angle = CONFIG_JZLCD_FRAMEBUFFER_DEFAULT_ROTATE_ANGLE; +#endif + +#ifdef DEBUG +static void print_regs(void) /* debug */ +{ + printk("REG_LCD_CFG:\t0x%8.8x\n", REG_LCD_CFG); + printk("REG_LCD_VSYNC:\t0x%8.8x\n", REG_LCD_VSYNC); + printk("REG_LCD_HSYNC:\t0x%8.8x\n", REG_LCD_HSYNC); + printk("REG_LCD_VAT:\t0x%8.8x\n", REG_LCD_VAT); + printk("REG_LCD_DAH:\t0x%8.8x\n", REG_LCD_DAH); + printk("REG_LCD_DAV:\t0x%8.8x\n", REG_LCD_DAV); + printk("REG_LCD_PS:\t0x%8.8x\n", REG_LCD_PS); + printk("REG_LCD_CLS:\t0x%8.8x\n", REG_LCD_CLS); + printk("REG_LCD_SPL:\t0x%8.8x\n", REG_LCD_SPL); + printk("REG_LCD_REV:\t0x%8.8x\n", REG_LCD_REV); + printk("REG_LCD_CTRL:\t0x%8.8x\n", REG_LCD_CTRL); + printk("REG_LCD_STATE:\t0x%8.8x\n", REG_LCD_STATE); + printk("REG_LCD_IID:\t0x%8.8x\n", REG_LCD_IID); + printk("REG_LCD_DA0:\t0x%8.8x\n", REG_LCD_DA0); + printk("REG_LCD_SA0:\t0x%8.8x\n", REG_LCD_SA0); + printk("REG_LCD_FID0:\t0x%8.8x\n", REG_LCD_FID0); + printk("REG_LCD_CMD0:\t0x%8.8x\n", REG_LCD_CMD0); + + printk("==================================\n"); + printk("REG_LCD_VSYNC:\t%d:%d\n", REG_LCD_VSYNC>>16, REG_LCD_VSYNC&0xfff); + printk("REG_LCD_HSYNC:\t%d:%d\n", REG_LCD_HSYNC>>16, REG_LCD_HSYNC&0xfff); + printk("REG_LCD_VAT:\t%d:%d\n", REG_LCD_VAT>>16, REG_LCD_VAT&0xfff); + printk("REG_LCD_DAH:\t%d:%d\n", REG_LCD_DAH>>16, REG_LCD_DAH&0xfff); + printk("REG_LCD_DAV:\t%d:%d\n", REG_LCD_DAV>>16, REG_LCD_DAV&0xfff); + printk("==================================\n"); + +} +#else +#define print_regs() +#endif + +#if defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) +static int jzfb_rotate_daemon_thread(void *info) +{ + int i,j; + struct fb_info *fb = &jzlcd_info->fb; + + while (!kthread_should_stop()) { +#if (CONFIG_JZLCD_FRAMEBUFFER_BPP == 8) + unsigned char *plcd_frame = (unsigned char *)lcd_frame[0]; + unsigned char *pfb = (unsigned char *) (fb->screen_base); +#elif (CONFIG_JZLCD_FRAMEBUFFER_BPP == 16) + unsigned short *plcd_frame = (unsigned short *)lcd_frame[0]; + unsigned short *pfb = (unsigned short *) (fb->screen_base); +#elif (CONFIG_JZLCD_FRAMEBUFFER_BPP == 32) + unsigned int *plcd_frame = (unsigned int *)lcd_frame[0]; + unsigned int *pfb = (unsigned int *) (fb->screen_base); +#else +#error "ERROR, rotate not support this bpp." +#endif + switch ( rotate_angle ) { + case FB_ROTATE_UR: + printk("%s, Warning, this shouldn't reache\n", __FUNCTION__); + ssleep(1); + break; + case FB_ROTATE_UD: /* cost about 30ms, can be accelrated by dma in the future */ + plcd_frame += jzfb.w*jzfb.h -1; + for (i=0;ivar.height+1; i++) { + for (j=1; j < fb->var.width+1; j++) + plcd_frame[j*fb->var.height-i] = *pfb++; + } + msleep(100); /* sleep 100ms */ + break; + case FB_ROTATE_CCW: /* cost about 80ms */ + for (i=0;ivar.height;i++) { + for ( j=fb->var.width-1;j>=0;j--) + plcd_frame[j*fb->var.height+i] = *pfb++; + } + msleep(100); /* sleep 100ms */ + break; + default: /* FB_ROTATE_UR */ + dprintk("Unknown rotate(%d) type\n", rotate_angle); + ssleep(1); + } + + dma_cache_wback_inv((unsigned int)(lcd_frame_user_fb), fb->fix.smem_len); + } + return 0; +} +/* + * rotate param angle: + * 0: FB_ROTATE_UR, 0'C + * 1: FB_ROTATE_CW, 90'C + * 2: FB_ROTATE_UD, 180'C + * 3: FB_ROTATE_CCW, 270'C + */ +static int jzfb_rotate_change( int angle ) +{ + struct fb_info *fb = &jzlcd_info->fb; + + /* clear frame buffer */ + memset((void*)lcd_frame_user_fb, 0x00, fb->fix.smem_len); + switch ( angle ) { + case FB_ROTATE_UR: + fb->var.width = fb->var.xres = fb->var.xres_virtual = jzfb.w; + fb->var.height = fb->var.yres = fb->var.yres_virtual = jzfb.h; + /* change lcd controller's data buffer to lcd_frame_user_fb*/ + lcd_frame_desc0->databuf = virt_to_phys((void *)lcd_frame_user_fb); + if ( rotate_angle != FB_ROTATE_UR ) + kthread_stop(jzlcd_info->rotate_daemon_thread); + rotate_angle = angle; + break; + case FB_ROTATE_UD: + case FB_ROTATE_CW: + case FB_ROTATE_CCW: + if ( angle == FB_ROTATE_UD ) { + fb->var.width = fb->var.xres = fb->var.xres_virtual = jzfb.w; + fb->var.height = fb->var.yres = fb->var.yres_virtual = jzfb.h; + } + else { /* CW, CCW */ + fb->var.width = fb->var.xres = fb->var.xres_virtual = jzfb.h; + fb->var.height = fb->var.yres = fb->var.yres_virtual = jzfb.w; + } + /* change lcd controller's data buffer to lcd_frame[0]*/ + lcd_frame_desc0->databuf = virt_to_phys((void *)lcd_frame[0]); + if ( rotate_angle == FB_ROTATE_UR || \ + jzlcd_info->rotate_daemon_thread == NULL) + jzlcd_info->rotate_daemon_thread = kthread_run( jzfb_rotate_daemon_thread, jzlcd_info, "%s", "jzlcd-rotate-daemon"); /* start rotate daemon */ + rotate_angle = angle; + break; + default: + printk("Invalid angle(%d)\n", (unsigned int)angle); + } + fb->fix.line_length = fb->var.xres * CONFIG_JZLCD_FRAMEBUFFER_BPP/8; + dma_cache_wback_inv((unsigned int)(lcd_frame_desc0), sizeof(struct lcd_desc)); + return 0; +} + +void jzfb_fb_rotate(struct fb_info *fbi, int angle) +{ + jzfb_rotate_change( angle/90 ); +} +#endif /* #if defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) */ + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int jzfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + unsigned short *ptr, ctmp; + +// print_dbg("regno:%d,RGBt:(%d,%d,%d,%d)\t", regno, red, green, blue, transp); + if (regno >= NR_PALETTE) + return 1; + + cfb->palette[regno].red = red ; + cfb->palette[regno].green = green; + cfb->palette[regno].blue = blue; + if (cfb->fb.var.bits_per_pixel <= 16) { + red >>= 8; + green >>= 8; + blue >>= 8; + + red &= 0xff; + green &= 0xff; + blue &= 0xff; + } + switch (cfb->fb.var.bits_per_pixel) { + case 1: + case 2: + case 4: + case 8: + if (((jzfb.cfg & MODE_MASK) == MODE_STN_MONO_SINGLE) || + ((jzfb.cfg & MODE_MASK) == MODE_STN_MONO_DUAL)) { + ctmp = (77L * red + 150L * green + 29L * blue) >> 8; + ctmp = ((ctmp >> 3) << 11) | ((ctmp >> 2) << 5) | + (ctmp >> 3); + } else { + /* RGB 565 */ + if (((red >> 3) == 0) && ((red >> 2) != 0)) + red = 1 << 3; + if (((blue >> 3) == 0) && ((blue >> 2) != 0)) + blue = 1 << 3; + ctmp = ((red >> 3) << 11) + | ((green >> 2) << 5) | (blue >> 3); + } + + ptr = (unsigned short *)lcd_palette; + ptr = (unsigned short *)(((u32)ptr)|0xa0000000); + ptr[regno] = ctmp; + + break; + + case 15: + if (regno < 16) + ((u32 *)cfb->fb.pseudo_palette)[regno] = + ((red >> 3) << 10) | + ((green >> 3) << 5) | + (blue >> 3); + break; + case 16: + if (regno < 16) { + ((u32 *)cfb->fb.pseudo_palette)[regno] = + ((red >> 3) << 11) | + ((green >> 2) << 5) | + (blue >> 3); + } + break; + case 18: + case 24: + case 32: + if (regno < 16) + ((u32 *)cfb->fb.pseudo_palette)[regno] = + (red << 16) | + (green << 8) | + (blue << 0); + +/* if (regno < 16) { + unsigned val; + val = chan_to_field(red, &cfb->fb.var.red); + val |= chan_to_field(green, &cfb->fb.var.green); + val |= chan_to_field(blue, &cfb->fb.var.blue); + ((u32 *)cfb->fb.pseudo_palette)[regno] = val; + } +*/ + + break; + } + return 0; +} + + +static int jzfb_ioctl (struct fb_info *fb, unsigned int cmd, unsigned long arg ) +{ + int ret = 0; + void __user *argp = (void __user *)arg; + + switch (cmd) { + case FBIOSETBACKLIGHT: + __lcd_set_backlight_level(arg); /* We support 8 levels here. */ + break; + case FBIODISPON: + __lcd_display_on(); + break; + case FBIODISPOFF: + __lcd_display_off(); + break; + case FBIOPRINT_REGS: + print_regs(); + break; + case FBIOGETBUFADDRS: + if ( copy_to_user(argp, &jz_lcd_buffer_addrs, + sizeof(struct jz_lcd_buffer_addrs_t)) ) + return -EFAULT; + break; +#if defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) + case FBIOROTATE: + ret = jzfb_rotate_change(arg); + break; +#endif /* defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) */ + default: + printk("Warn: Command(%x) not support\n", cmd); + ret = -1; + break; + } + return ret; + +} + +/* Use mmap /dev/fb can only get a non-cacheable Virtual Address. */ +static int jzfb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + unsigned long start; + unsigned long off; + u32 len; + + off = vma->vm_pgoff << PAGE_SHIFT; + //fb->fb_get_fix(&fix, PROC_CONSOLE(info), info); + + /* frame buffer memory */ + start = cfb->fb.fix.smem_start; + len = PAGE_ALIGN((start & ~PAGE_MASK) + cfb->fb.fix.smem_len); + start &= PAGE_MASK; + + if ((vma->vm_end - vma->vm_start + off) > len) + return -EINVAL; + off += start; + + vma->vm_pgoff = off >> PAGE_SHIFT; + vma->vm_flags |= VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); /* Uncacheable */ + +#if 1 + pgprot_val(vma->vm_page_prot) &= ~_CACHE_MASK; + pgprot_val(vma->vm_page_prot) |= _CACHE_UNCACHED; /* Uncacheable */ +// pgprot_val(vma->vm_page_prot) |= _CACHE_CACHABLE_NONCOHERENT; /* Write-Through */ +#endif + + if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) { + return -EAGAIN; + } + return 0; +} + +/* checks var and eventually tweaks it to something supported, + * DO NOT MODIFY PAR */ +static int jzfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + print_dbg("jzfb_check_var"); + return 0; +} + + +/* + * set the video mode according to info->var + */ +static int jzfb_set_par(struct fb_info *info) +{ + print_dbg("jzfb_set_par"); + return 0; +} + + +/* + * (Un)Blank the display. + * Fix me: should we use VESA value? + */ +static int jzfb_blank(int blank_mode, struct fb_info *info) +{ + + dprintk("fb_blank %d %p", blank_mode, info); + + switch (blank_mode) { + + case FB_BLANK_UNBLANK: + //case FB_BLANK_NORMAL: + /* Turn on panel */ + __lcd_set_ena(); + __lcd_display_on(); + break; + + case FB_BLANK_NORMAL: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: +#if 0 + /* Turn off panel */ + __lcd_set_dis(); + __lcd_display_off(); +#endif + break; + default: + break; + + } + return 0; +} + +/* + * pan display + */ +static int jzfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + int dy; + + if (!var || !cfb) { + return -EINVAL; + } + + if (var->xoffset - cfb->fb.var.xoffset) { + /* No support for X panning for now! */ + return -EINVAL; + } + + dy = var->yoffset - cfb->fb.var.yoffset; + print_dbg("var.yoffset: %d", dy); + if (dy) { + + print_dbg("Panning screen of %d lines", dy); + + lcd_frame_desc0->databuf += (cfb->fb.fix.line_length * dy); + /* TODO: Wait for current frame to finished */ + } + + return 0; +} + + +/* use default function cfb_fillrect, cfb_copyarea, cfb_imageblit */ +static struct fb_ops jzfb_ops = { + .owner = THIS_MODULE, + .fb_setcolreg = jzfb_setcolreg, + .fb_check_var = jzfb_check_var, + .fb_set_par = jzfb_set_par, + .fb_blank = jzfb_blank, + .fb_pan_display = jzfb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = jzfb_mmap, + .fb_ioctl = jzfb_ioctl, +#if defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) + .fb_rotate = jzfb_fb_rotate, +#endif +}; + +static int jzfb_set_var(struct fb_var_screeninfo *var, int con, + struct fb_info *info) +{ + struct lcd_cfb_info *cfb = (struct lcd_cfb_info *)info; + int chgvar = 0; + + var->height = jzfb.h ; + var->width = jzfb.w ; + var->bits_per_pixel = jzfb.bpp; + + var->vmode = FB_VMODE_NONINTERLACED; + var->activate = cfb->fb.var.activate; + var->xres = var->width; + var->yres = var->height; + var->xres_virtual = var->width; + var->yres_virtual = var->height; + var->xoffset = 0; + var->yoffset = 0; + var->pixclock = 0; + var->left_margin = 0; + var->right_margin = 0; + var->upper_margin = 0; + var->lower_margin = 0; + var->hsync_len = 0; + var->vsync_len = 0; + var->sync = 0; + var->activate &= ~FB_ACTIVATE_TEST; + + /* + * CONUPDATE and SMOOTH_XPAN are equal. However, + * SMOOTH_XPAN is only used internally by fbcon. + */ + if (var->vmode & FB_VMODE_CONUPDATE) { + var->vmode |= FB_VMODE_YWRAP; + var->xoffset = cfb->fb.var.xoffset; + var->yoffset = cfb->fb.var.yoffset; + } + + if (var->activate & FB_ACTIVATE_TEST) + return 0; + + if ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NOW) + return -EINVAL; + + if (cfb->fb.var.xres != var->xres) + chgvar = 1; + if (cfb->fb.var.yres != var->yres) + chgvar = 1; + if (cfb->fb.var.xres_virtual != var->xres_virtual) + chgvar = 1; + if (cfb->fb.var.yres_virtual != var->yres_virtual) + chgvar = 1; + if (cfb->fb.var.bits_per_pixel != var->bits_per_pixel) + chgvar = 1; + + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + + switch(var->bits_per_pixel){ + case 1: /* Mono */ + cfb->fb.fix.visual = FB_VISUAL_MONO01; + cfb->fb.fix.line_length = (var->xres * var->bits_per_pixel) / 8; + break; + case 2: /* Mono */ + var->red.offset = 0; + var->red.length = 2; + var->green.offset = 0; + var->green.length = 2; + var->blue.offset = 0; + var->blue.length = 2; + + cfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + cfb->fb.fix.line_length = (var->xres * var->bits_per_pixel) / 8; + break; + case 4: /* PSEUDOCOLOUR*/ + var->red.offset = 0; + var->red.length = 4; + var->green.offset = 0; + var->green.length = 4; + var->blue.offset = 0; + var->blue.length = 4; + + cfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + cfb->fb.fix.line_length = var->xres / 2; + break; + case 8: /* PSEUDOCOLOUR, 256 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + + cfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + cfb->fb.fix.line_length = var->xres ; + break; + case 15: /* DIRECTCOLOUR, 32k */ + var->bits_per_pixel = 15; + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + + cfb->fb.fix.visual = FB_VISUAL_DIRECTCOLOR; + cfb->fb.fix.line_length = var->xres_virtual * 2; + break; + case 16: /* DIRECTCOLOUR, 64k */ + var->bits_per_pixel = 16; + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + + cfb->fb.fix.visual = FB_VISUAL_TRUECOLOR; + cfb->fb.fix.line_length = var->xres_virtual * 2; + break; + case 18: + case 24: + case 32: + /* DIRECTCOLOUR, 16M */ + var->bits_per_pixel = 32; + + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + + cfb->fb.fix.visual = FB_VISUAL_TRUECOLOR; + cfb->fb.fix.line_length = var->xres_virtual * 4; + break; + + default: /* in theory this should never happen */ + printk(KERN_WARNING "%s: don't support for %dbpp\n", + cfb->fb.fix.id, var->bits_per_pixel); + break; + } + + cfb->fb.var = *var; + cfb->fb.var.activate &= ~FB_ACTIVATE_ALL; + + /* + * Update the old var. The fbcon drivers still use this. + * Once they are using cfb->fb.var, this can be dropped. + * --rmk + */ + //display->var = cfb->fb.var; + /* + * If we are setting all the virtual consoles, also set the + * defaults used to create new consoles. + */ + fb_set_cmap(&cfb->fb.cmap, &cfb->fb); + dprintk("jzfb_set_var: after fb_set_cmap...\n"); + + return 0; +} + +static struct lcd_cfb_info * jzfb_alloc_fb_info(void) +{ + struct lcd_cfb_info *cfb; + + cfb = kmalloc(sizeof(struct lcd_cfb_info) + sizeof(u32) * 16, GFP_KERNEL); + + if (!cfb) + return NULL; + + jzlcd_info = cfb; + + memset(cfb, 0, sizeof(struct lcd_cfb_info) ); + + cfb->currcon = -1; + + + strcpy(cfb->fb.fix.id, "jz-lcd"); + cfb->fb.fix.type = FB_TYPE_PACKED_PIXELS; + cfb->fb.fix.type_aux = 0; + cfb->fb.fix.xpanstep = 1; + cfb->fb.fix.ypanstep = 1; + cfb->fb.fix.ywrapstep = 0; + cfb->fb.fix.accel = FB_ACCEL_NONE; + + cfb->fb.var.nonstd = 0; + cfb->fb.var.activate = FB_ACTIVATE_NOW; + cfb->fb.var.height = -1; + cfb->fb.var.width = -1; + cfb->fb.var.accel_flags = FB_ACCELF_TEXT; + + cfb->fb.fbops = &jzfb_ops; + cfb->fb.flags = FBINFO_FLAG_DEFAULT; + + cfb->fb.pseudo_palette = (void *)(cfb + 1); + + switch (jzfb.bpp) { + case 1: + fb_alloc_cmap(&cfb->fb.cmap, 4, 0); + break; + case 2: + fb_alloc_cmap(&cfb->fb.cmap, 8, 0); + break; + case 4: + fb_alloc_cmap(&cfb->fb.cmap, 32, 0); + break; + case 8: + + default: + fb_alloc_cmap(&cfb->fb.cmap, 256, 0); + break; + } + dprintk("fb_alloc_cmap,fb.cmap.len:%d....\n", cfb->fb.cmap.len); + + return cfb; +} + +/* + * Map screen memory + */ +static int jzfb_map_smem(struct lcd_cfb_info *cfb) +{ + struct page * map = NULL; + unsigned char *tmp; + unsigned int page_shift, needroom, t; +#if defined(CONFIG_SOC_JZ4740) + if (jzfb.bpp == 18 || jzfb.bpp == 24) + t = 32; + else + t = jzfb.bpp; +#else + if (jzfb.bpp == 15) + t = 16; + else + t = jzfb.bpp; +#endif + + needroom = ((jzfb.w * t + 7) >> 3) * jzfb.h; + for (page_shift = 0; page_shift < 12; page_shift++) + if ((PAGE_SIZE << page_shift) >= needroom) + break; + + /* lcd_palette room total 4KB: + * 0 -- 512: lcd palette + * 1024 -- [1024+16*3]: lcd descripters + * [1024+16*3] -- 4096: reserved + */ + lcd_palette = (unsigned char *)__get_free_pages(GFP_KERNEL, 0); + if ((!lcd_palette)) + return -ENOMEM; + + memset((void *)lcd_palette, 0, PAGE_SIZE); + map = virt_to_page(lcd_palette); + set_bit(PG_reserved, &map->flags); + lcd_desc_base = (struct lcd_desc *)(lcd_palette + 1024); + + jz_lcd_buffer_addrs.fb_num = CONFIG_JZLCD_FRAMEBUFFER_MAX; + printk("jzlcd use %d framebuffer:\n", CONFIG_JZLCD_FRAMEBUFFER_MAX); + /* alloc frame buffer space */ + for ( t = 0; t < CONFIG_JZLCD_FRAMEBUFFER_MAX; t++ ) { + lcd_frame[t] = (unsigned char *)__get_free_pages(GFP_KERNEL, page_shift); + if ((!lcd_frame[t])) { + printk("no mem for fb[%d]\n", t); + return -ENOMEM; + } +// memset((void *)lcd_frame[t], 0, PAGE_SIZE << page_shift); + for (tmp=(unsigned char *)lcd_frame[t]; + tmp < lcd_frame[t] + (PAGE_SIZE << page_shift); + tmp += PAGE_SIZE) { + map = virt_to_page(tmp); + set_bit(PG_reserved, &map->flags); + } + jz_lcd_buffer_addrs.fb_phys_addr[t] = virt_to_phys((void *)lcd_frame[t]); + printk("jzlcd fb[%d] phys addr =0x%08x\n", + t, jz_lcd_buffer_addrs.fb_phys_addr[t]); + } +#if !defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) + cfb->fb.fix.smem_start = virt_to_phys((void *)lcd_frame[0]); + cfb->fb.fix.smem_len = (PAGE_SIZE << page_shift); + cfb->fb.screen_base = + (unsigned char *)(((unsigned int)lcd_frame[0] & 0x1fffffff) | 0xa0000000); +#else /* Framebuffer rotate */ + lcd_frame_user_fb = (unsigned char *)__get_free_pages(GFP_KERNEL, page_shift); + if ((!lcd_frame_user_fb)) { + printk("no mem for fb[%d]\n", t); + return -ENOMEM; + } + memset((void *)lcd_frame_user_fb, 0, PAGE_SIZE << page_shift); + for (tmp=(unsigned char *)lcd_frame_user_fb; + tmp < lcd_frame_user_fb + (PAGE_SIZE << page_shift); + tmp += PAGE_SIZE) { + map = virt_to_page(tmp); + set_bit(PG_reserved, &map->flags); + } + + printk("Rotate userfb phys addr =0x%08x\n", + (unsigned int)virt_to_phys((void *)lcd_frame_user_fb)); + cfb->fb.fix.smem_start = virt_to_phys((void *)lcd_frame_user_fb); + cfb->fb.fix.smem_len = (PAGE_SIZE << page_shift); + cfb->fb.screen_base = (unsigned char *)(((unsigned int)lcd_frame_user_fb & 0x1fffffff) | 0xa0000000); + +#endif /* #if defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) */ + if (!cfb->fb.screen_base) { + printk("%s: unable to map screen memory\n", cfb->fb.fix.id); + return -ENOMEM; + } + + return 0; +} + +static void jzfb_free_fb_info(struct lcd_cfb_info *cfb) +{ + if (cfb) { + fb_alloc_cmap(&cfb->fb.cmap, 0, 0); + kfree(cfb); + } +} + +static void jzfb_unmap_smem(struct lcd_cfb_info *cfb) +{ + struct page * map = NULL; + unsigned char *tmp; + unsigned int page_shift, needroom, t; +#if defined(CONFIG_SOC_JZ4740) + if (jzfb.bpp == 18 || jzfb.bpp == 24) + t = 32; + else + t = jzfb.bpp; +#else + if (jzfb.bpp == 15) + t = 16; + else + t = jzfb.bpp; +#endif + needroom = ((jzfb.w * t + 7) >> 3) * jzfb.h; + for (page_shift = 0; page_shift < 12; page_shift++) + if ((PAGE_SIZE << page_shift) >= needroom) + break; + + if (cfb && cfb->fb.screen_base) { + iounmap(cfb->fb.screen_base); + cfb->fb.screen_base = NULL; + release_mem_region(cfb->fb.fix.smem_start, + cfb->fb.fix.smem_len); + } + + if (lcd_palette) { + map = virt_to_page(lcd_palette); + clear_bit(PG_reserved, &map->flags); + free_pages((int)lcd_palette, 0); + } + + for ( t=0; t < CONFIG_JZLCD_FRAMEBUFFER_MAX; t++ ) { + if (lcd_frame[t]) { + for (tmp=(unsigned char *)lcd_frame[t]; + tmp < lcd_frame[t] + (PAGE_SIZE << page_shift); + tmp += PAGE_SIZE) { + map = virt_to_page(tmp); + clear_bit(PG_reserved, &map->flags); + } + free_pages((int)lcd_frame[t], page_shift); + } + } +#if defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) + if (lcd_frame_user_fb) { + for (tmp=(unsigned char *)lcd_frame_user_fb; + tmp < lcd_frame_user_fb + (PAGE_SIZE << page_shift); + tmp += PAGE_SIZE) { + map = virt_to_page(tmp); + clear_bit(PG_reserved, &map->flags); + } + free_pages((int)lcd_frame_user_fb, page_shift); + } + +#endif +} + +static void lcd_descriptor_init(void) +{ + int i; + unsigned int pal_size; + unsigned int frm_size, ln_size; + unsigned char dual_panel = 0; + + i = jzfb.bpp; +#if defined(CONFIG_SOC_JZ4740) + if (i == 18 || i == 24) + i = 32; +#else + if (i == 15) + i = 16; +#endif + frm_size = (jzfb.w*jzfb.h*i)>>3; + ln_size = (jzfb.w*i)>>3; + + if (((jzfb.cfg & MODE_MASK) == MODE_STN_COLOR_DUAL) || + ((jzfb.cfg & MODE_MASK) == MODE_STN_MONO_DUAL)) { + dual_panel = 1; + frm_size >>= 1; + } + + frm_size = frm_size / 4; + ln_size = ln_size / 4; + + switch (jzfb.bpp) { + case 1: + pal_size = 4; + break; + case 2: + pal_size = 8; + break; + case 4: + pal_size = 32; + break; + case 8: + default: + pal_size = 512; + } + + pal_size /= 4; + + lcd_frame_desc0 = lcd_desc_base + 0; + lcd_frame_desc1 = lcd_desc_base + 1; + lcd_palette_desc = lcd_desc_base + 2; + + jz_lcd_buffer_addrs.lcd_desc_phys_addr = (unsigned int)virt_to_phys(lcd_frame_desc0); + + /* Palette Descriptor */ + lcd_palette_desc->next_desc = (int)virt_to_phys(lcd_frame_desc0); + lcd_palette_desc->databuf = (int)virt_to_phys((void *)lcd_palette); + lcd_palette_desc->frame_id = (unsigned int)0xdeadbeaf; + lcd_palette_desc->cmd = pal_size|LCD_CMD_PAL; /* Palette Descriptor */ + + /* Frame Descriptor 0 */ + if (jzfb.bpp <= 8) + lcd_frame_desc0->next_desc = (int)virt_to_phys(lcd_palette_desc); + else + lcd_frame_desc0->next_desc = (int)virt_to_phys(lcd_frame_desc0); + lcd_frame_desc0->databuf = virt_to_phys((void *)lcd_frame[0]); + lcd_frame_desc0->frame_id = (unsigned int)0xbeafbeaf; + lcd_frame_desc0->cmd = LCD_CMD_SOFINT | LCD_CMD_EOFINT | frm_size; + dma_cache_wback_inv((unsigned int)(lcd_palette_desc),0x10); + dma_cache_wback_inv((unsigned int)(lcd_frame_desc0),0x10); + + if (!(dual_panel)) + return; + + /* Frame Descriptor 1 */ + lcd_frame_desc1->next_desc = (int)virt_to_phys(lcd_frame_desc1); + lcd_frame_desc1->databuf = virt_to_phys((void *)(lcd_frame[0] + frm_size * 4)); + lcd_frame_desc1->frame_id = (unsigned int)0xdeaddead; + lcd_frame_desc1->cmd = LCD_CMD_SOFINT | LCD_CMD_EOFINT | frm_size; + dma_cache_wback_inv((unsigned int)(lcd_frame_desc1),0x10); +} + +static int lcd_hw_init(void) +{ + unsigned int val = 0; + unsigned int pclk; + unsigned int stnH; + int ret = 0; + + /* Setting Control register */ + switch (jzfb.bpp) { + case 1: + val |= LCD_CTRL_BPP_1; + break; + case 2: + val |= LCD_CTRL_BPP_2; + break; + case 4: + val |= LCD_CTRL_BPP_4; + break; + case 8: + val |= LCD_CTRL_BPP_8; + break; + case 15: + val |= LCD_CTRL_RGB555; + case 16: + val |= LCD_CTRL_BPP_16; + break; +#if defined(CONFIG_SOC_JZ4740) + case 17 ... 32: + val |= LCD_CTRL_BPP_18_24; /* target is 4bytes/pixel */ + break; +#endif + default: + printk("The BPP %d is not supported\n", jzfb.bpp); + val |= LCD_CTRL_BPP_16; + break; + } + + switch (jzfb.cfg & MODE_MASK) { + case MODE_STN_MONO_DUAL: + case MODE_STN_COLOR_DUAL: + case MODE_STN_MONO_SINGLE: + case MODE_STN_COLOR_SINGLE: + switch (jzfb.bpp) { + case 1: + case 2: + val |= LCD_CTRL_FRC_2; + break; + case 4: + val |= LCD_CTRL_FRC_4; + break; + case 8: + default: + val |= LCD_CTRL_FRC_16; + break; + } + break; + } + + val |= LCD_CTRL_BST_16; /* Burst Length is 16WORD=64Byte */ + + switch (jzfb.cfg & MODE_MASK) { + case MODE_STN_MONO_DUAL: + case MODE_STN_COLOR_DUAL: + case MODE_STN_MONO_SINGLE: + case MODE_STN_COLOR_SINGLE: + switch (jzfb.cfg & STN_DAT_PINMASK) { +#define align2(n) (n)=((((n)+1)>>1)<<1) +#define align4(n) (n)=((((n)+3)>>2)<<2) +#define align8(n) (n)=((((n)+7)>>3)<<3) + case STN_DAT_PIN1: + /* Do not adjust the hori-param value. */ + break; + case STN_DAT_PIN2: + align2(jzfb.hsw); + align2(jzfb.elw); + align2(jzfb.blw); + break; + case STN_DAT_PIN4: + align4(jzfb.hsw); + align4(jzfb.elw); + align4(jzfb.blw); + break; + case STN_DAT_PIN8: + align8(jzfb.hsw); + align8(jzfb.elw); + align8(jzfb.blw); + break; + } + break; + } + + val |= 1 << 26; /* Output FIFO underrun protection */ + REG_LCD_CTRL = val; + + switch (jzfb.cfg & MODE_MASK) { + case MODE_STN_MONO_DUAL: + case MODE_STN_COLOR_DUAL: + case MODE_STN_MONO_SINGLE: + case MODE_STN_COLOR_SINGLE: + if (((jzfb.cfg & MODE_MASK) == MODE_STN_MONO_DUAL) || + ((jzfb.cfg & MODE_MASK) == MODE_STN_COLOR_DUAL)) + stnH = jzfb.h >> 1; + else + stnH = jzfb.h; + + REG_LCD_VSYNC = (0 << 16) | jzfb.vsw; + REG_LCD_HSYNC = ((jzfb.blw+jzfb.w) << 16) | (jzfb.blw+jzfb.w+jzfb.hsw); + + /* Screen setting */ + REG_LCD_VAT = ((jzfb.blw + jzfb.w + jzfb.hsw + jzfb.elw) << 16) | (stnH + jzfb.vsw + jzfb.bfw + jzfb.efw); + REG_LCD_DAH = (jzfb.blw << 16) | (jzfb.blw + jzfb.w); + REG_LCD_DAV = (0 << 16) | (stnH); + + /* AC BIAs signal */ + REG_LCD_PS = (0 << 16) | (stnH+jzfb.vsw+jzfb.efw+jzfb.bfw); + + break; + + case MODE_TFT_GEN: + case MODE_TFT_SHARP: + case MODE_TFT_CASIO: + case MODE_TFT_SAMSUNG: + case MODE_8BIT_SERIAL_TFT: + case MODE_TFT_18BIT: + REG_LCD_VSYNC = (0 << 16) | jzfb.vsw; +#if defined(CONFIG_JZLCD_INNOLUX_AT080TN42) + REG_LCD_DAV = (0 << 16) | ( jzfb.h ); +#else + REG_LCD_DAV = ((jzfb.vsw + jzfb.bfw) << 16) | (jzfb.vsw + jzfb.bfw + jzfb.h); +#endif /*#if defined(CONFIG_JZLCD_INNOLUX_AT080TN42)*/ + REG_LCD_VAT = (((jzfb.blw + jzfb.w + jzfb.elw + jzfb.hsw)) << 16) | (jzfb.vsw + jzfb.bfw + jzfb.h + jzfb.efw); + REG_LCD_HSYNC = (0 << 16) | jzfb.hsw; + REG_LCD_DAH = ((jzfb.hsw + jzfb.blw) << 16) | (jzfb.hsw + jzfb.blw + jzfb.w); + break; + } + + switch (jzfb.cfg & MODE_MASK) { + case MODE_TFT_SAMSUNG: + { + unsigned int total, tp_s, tp_e, ckv_s, ckv_e; + unsigned int rev_s, rev_e, inv_s, inv_e; + total = jzfb.blw + jzfb.w + jzfb.elw + jzfb.hsw; + tp_s = jzfb.blw + jzfb.w + 1; + tp_e = tp_s + 1; + ckv_s = tp_s - jz_clocks.pixclk/(1000000000/4100); + ckv_e = tp_s + total; + rev_s = tp_s - 11; /* -11.5 clk */ + rev_e = rev_s + total; + inv_s = tp_s; + inv_e = inv_s + total; + REG_LCD_CLS = (tp_s << 16) | tp_e; + REG_LCD_PS = (ckv_s << 16) | ckv_e; + REG_LCD_SPL = (rev_s << 16) | rev_e; + REG_LCD_REV = (inv_s << 16) | inv_e; + jzfb.cfg |= STFT_REVHI | STFT_SPLHI; + break; + } + case MODE_TFT_SHARP: + { + unsigned int total, cls_s, cls_e, ps_s, ps_e; + unsigned int spl_s, spl_e, rev_s, rev_e; + total = jzfb.blw + jzfb.w + jzfb.elw + jzfb.hsw; +#if !defined(CONFIG_JZLCD_INNOLUX_AT080TN42) + spl_s = 1; + spl_e = spl_s + 1; + cls_s = 0; + cls_e = total - 60; /* > 4us (pclk = 80ns) */ + ps_s = cls_s; + ps_e = cls_e; + rev_s = total - 40; /* > 3us (pclk = 80ns) */ + rev_e = rev_s + total; + jzfb.cfg |= STFT_PSHI; +#else /*#if defined(CONFIG_JZLCD_INNOLUX_AT080TN42)*/ + spl_s = total - 5; /* LD */ + spl_e = total - 3; + cls_s = 32; /* CKV */ + cls_e = 145; + ps_s = 0; /* OEV */ + ps_e = 45; + rev_s = 0; /* POL */ + rev_e = 0; +#endif /*#if defined(CONFIG_JZLCD_INNOLUX_AT080TN42)*/ + REG_LCD_SPL = (spl_s << 16) | spl_e; + REG_LCD_CLS = (cls_s << 16) | cls_e; + REG_LCD_PS = (ps_s << 16) | ps_e; + REG_LCD_REV = (rev_s << 16) | rev_e; + break; + } + case MODE_TFT_CASIO: + break; + } + + /* Configure the LCD panel */ + REG_LCD_CFG = jzfb.cfg; + + /* Timing setting */ + __cpm_stop_lcd(); + + val = jzfb.fclk; /* frame clk */ + + if ( (jzfb.cfg & MODE_MASK) != MODE_8BIT_SERIAL_TFT) { + pclk = val * (jzfb.w + jzfb.hsw + jzfb.elw + jzfb.blw) * + (jzfb.h + jzfb.vsw + jzfb.efw + jzfb.bfw); /* Pixclk */ + } + else { + /* serial mode: Hsync period = 3*Width_Pixel */ + pclk = val * (jzfb.w*3 + jzfb.hsw + jzfb.elw + jzfb.blw) * + (jzfb.h + jzfb.vsw + jzfb.efw + jzfb.bfw); /* Pixclk */ + } + + if (((jzfb.cfg & MODE_MASK) == MODE_STN_COLOR_SINGLE) || + ((jzfb.cfg & MODE_MASK) == MODE_STN_COLOR_DUAL)) + pclk = (pclk * 3); + + if (((jzfb.cfg & MODE_MASK) == MODE_STN_COLOR_SINGLE) || + ((jzfb.cfg & MODE_MASK) == MODE_STN_COLOR_DUAL) || + ((jzfb.cfg & MODE_MASK) == MODE_STN_MONO_SINGLE) || + ((jzfb.cfg & MODE_MASK) == MODE_STN_MONO_DUAL)) + pclk = pclk >> ((jzfb.cfg & STN_DAT_PINMASK) >> 4); + + if (((jzfb.cfg & MODE_MASK) == MODE_STN_COLOR_DUAL) || + ((jzfb.cfg & MODE_MASK) == MODE_STN_MONO_DUAL)) + pclk >>= 1; +#if defined(CONFIG_SOC_JZ4730) + val = __cpm_get_pllout() / pclk; + REG_CPM_CFCR2 = val - 1; + val = __cpm_get_pllout() / (pclk * 4); + val = __cpm_divisor_encode(val); + __cpm_set_lcdclk_div(val); + REG_CPM_CFCR |= CPM_CFCR_UPE; +#elif defined(CONFIG_SOC_JZ4740) + val = ( __cpm_get_pllout2()) / pclk; + val--; + if ( val > 0x3ff ) { + printk("pixel clock divid is too large, set it to 0x3ff\n"); + val = 0x3ff; + } + __cpm_set_pixdiv(val); + + val = pclk * 3 ; /* LCDClock > 2.5*Pixclock */ + val =__cpm_get_pllout() / val; + if ( val > 0x1f ) { + printk("lcd clock divide is too large, set it to 0x1f\n"); + val = 0x1f; + } + __cpm_set_ldiv( val ); + REG_CPM_CPCCR |= CPM_CPCCR_CE ; /* update divide */ + +#else + printk("drivers/video/Jzlcd.c, CONFIG_MIPS, please set chip type.\n"); +#endif /*#ifdef CONFIG_MIPS_JZ4730 */ + + jz_clocks.pixclk = __cpm_get_pixclk(); + jz_clocks.lcdclk = __cpm_get_lcdclk(); + printk("LCDC: PixClock:%d LcdClock:%d\n", + jz_clocks.pixclk, jz_clocks.lcdclk); + + __cpm_start_lcd(); + udelay(1000); + return ret; +} + +static irqreturn_t lcd_interrupt_handler(int irq, void *dev_id) +{ + unsigned int state; + + state = REG_LCD_STATE; + + if (state & LCD_STATE_EOF) /* End of frame */ + REG_LCD_STATE = state & ~LCD_STATE_EOF; + + if (state & LCD_STATE_IFU0) { + dprintk("InFiFo0 underrun\n"); + REG_LCD_STATE = state & ~LCD_STATE_IFU0; + } + + if (state & LCD_STATE_OFU) { /* Out fifo underrun */ + REG_LCD_STATE = state & ~LCD_STATE_OFU; + dprintk("Out FiFo underrun.\n"); + } + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM + +/* + * Suspend the LCDC. + */ +static int jzfb_suspend(void) +{ + __lcd_clr_ena(); /* Quick Disable */ + __lcd_display_off(); + __cpm_stop_lcd(); + + return 0; +} + +/* + * Resume the LCDC. + */ +#ifdef CONFIG_SOC_JZ4730 +static int jzfb_resume(void) +{ + __cpm_start_lcd(); + + __lcd_display_pin_init(); + + __lcd_display_on(); + + lcd_hw_init(); + + if (jzfb.bpp <= 8) + REG_LCD_DA0 = virt_to_phys(lcd_palette_desc); + else + REG_LCD_DA0 = virt_to_phys(lcd_frame_desc0); + + if (((jzfb.cfg & MODE_MASK) == MODE_STN_COLOR_DUAL) || + ((jzfb.cfg & MODE_MASK) == MODE_STN_MONO_DUAL)) + REG_LCD_DA1 = virt_to_phys(lcd_frame_desc1); + + __lcd_set_ena(); + return 0; +} + +#else +/* + * Resume the LCDC. + */ +static int jzfb_resume(void) +{ + __cpm_start_lcd(); + __gpio_set_pin(GPIO_DISP_OFF_N); + __lcd_special_on(); + __lcd_set_ena(); + mdelay(200); + __lcd_set_backlight_level(80); + + return 0; +} +#endif /* CONFIG_MIPS_JZ4730 */ + +/* + * Power management hook. Note that we won't be called from IRQ context, + * unlike the blank functions above, so we may sleep. + */ +static int jzlcd_pm_callback(struct pm_dev *pm_dev, pm_request_t req, void *data) +{ + int ret; + struct lcd_cfb_info *cfb = pm_dev->data; + + if (!cfb) return -EINVAL; + + switch (req) { + case PM_SUSPEND: + ret = jzfb_suspend(); + break; + + case PM_RESUME: + ret = jzfb_resume(); + break; + + default: + ret = -EINVAL; + break; + } + return ret; +} +#else +#define jzfb_suspend NULL +#define jzfb_resume NULL +#endif /* CONFIG_PM */ + +static int __init jzfb_init(void) +{ + struct lcd_cfb_info *cfb; + int err = 0; + + /* In special mode, we only need init special pin, + * as general lcd pin has init in uboot */ +#if defined(CONFIG_SOC_JZ4740) || defined(CONFIG_SOC_JZ4750) + switch (jzfb.cfg & MODE_MASK) { + case LCD_CFG_MODE_SPECIAL_TFT_1: + case LCD_CFG_MODE_SPECIAL_TFT_2: + case LCD_CFG_MODE_SPECIAL_TFT_3: + __gpio_as_lcd_special(); + break; + default: + ; + } +#endif + __lcd_display_pin_init(); + + cfb = jzfb_alloc_fb_info(); + if (!cfb) + goto failed; + + err = jzfb_map_smem(cfb); + if (err) + goto failed; + + jzfb_set_var(&cfb->fb.var, -1, &cfb->fb); + + lcd_descriptor_init(); + + err = lcd_hw_init(); + if (err) + goto failed; + + if (jzfb.bpp <= 8) + REG_LCD_DA0 = virt_to_phys(lcd_palette_desc); + else + REG_LCD_DA0 = virt_to_phys(lcd_frame_desc0); + + if (((jzfb.cfg & MODE_MASK) == MODE_STN_COLOR_DUAL) || + ((jzfb.cfg & MODE_MASK) == MODE_STN_MONO_DUAL)) + REG_LCD_DA1 = virt_to_phys(lcd_frame_desc1); + + __lcd_set_ena(); + + if (request_irq(IRQ_LCD, lcd_interrupt_handler, IRQF_DISABLED, + "lcd", 0)) { + err = -EBUSY; + goto failed; + } + + __lcd_enable_ofu_intr(); /* enable OutFifo underrun */ +// __lcd_enable_ifu0_intr(); /* needn't enable InFifo underrun */ + +#if defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) + jzfb_rotate_change(rotate_angle); + /* sleep n??? */ +#endif + err = register_framebuffer(&cfb->fb); + if (err < 0) { + dprintk("jzfb_init(): register framebuffer err.\n"); + goto failed; + } + + printk("fb%d: %s frame buffer device, using %dK of video memory\n", + cfb->fb.node, cfb->fb.fix.id, cfb->fb.fix.smem_len>>10); + +#ifdef CONFIG_PM + /* + * Note that the console registers this as well, but we want to + * power down the display prior to sleeping. + */ + cfb->pm = pm_register(PM_SYS_DEV, PM_SYS_VGA, jzlcd_pm_callback); + if (cfb->pm) + cfb->pm->data = cfb; +#endif + + __lcd_display_on(); + + return 0; + +failed: + jzfb_unmap_smem(cfb); + jzfb_free_fb_info(cfb); + + return err; +} + +#if 0 +static int jzfb_remove(struct device *dev) +{ + struct lcd_cfb_info *cfb = dev_get_drvdata(dev); + jzfb_unmap_smem(cfb); + jzfb_free_fb_info(cfb); + return 0; +} +#endif + +#if 0 +static struct device_driver jzfb_driver = { + .name = "jz-lcd", + .bus = &platform_bus_type, + .probe = jzfb_probe, + .remove = jzfb_remove, + .suspend = jzfb_suspend, + .resume = jzfb_resume, +}; +#endif + +static void __exit jzfb_cleanup(void) +{ +#if defined(CONFIG_JZLCD_FRAMEBUFFER_ROTATE_SUPPORT) + kthread_stop(jzlcd_info->rotate_daemon_thread); +#endif +// driver_unregister(&jzfb_driver); +// jzfb_remove(); +} + +module_init(jzfb_init); +module_exit(jzfb_cleanup); + +MODULE_DESCRIPTION("JzSOC LCD Controller driver"); +MODULE_LICENSE("GPL"); --- /dev/null +++ b/drivers/video/jzlcd.h @@ -0,0 +1,791 @@ +/* + * linux/drivers/video/jzlcd.h -- Ingenic On-Chip LCD frame buffer device + * + * Copyright (C) 2005-2007, Ingenic Semiconductor Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#ifndef __JZLCD_H__ +#define __JZLCD_H__ + +#include + +#define NR_PALETTE 256 + +struct lcd_desc{ + unsigned int next_desc; /* LCDDAx */ + unsigned int databuf; /* LCDSAx */ + unsigned int frame_id; /* LCDFIDx */ + unsigned int cmd; /* LCDCMDx */ +}; + +#define MODE_MASK 0x0f +#define MODE_TFT_GEN 0x00 +#define MODE_TFT_SHARP 0x01 +#define MODE_TFT_CASIO 0x02 +#define MODE_TFT_SAMSUNG 0x03 +#define MODE_CCIR656_NONINT 0x04 +#define MODE_CCIR656_INT 0x05 +#define MODE_STN_COLOR_SINGLE 0x08 +#define MODE_STN_MONO_SINGLE 0x09 +#define MODE_STN_COLOR_DUAL 0x0a +#define MODE_STN_MONO_DUAL 0x0b +#define MODE_8BIT_SERIAL_TFT 0x0c + +#define MODE_TFT_18BIT (1<<7) + +#define STN_DAT_PIN1 (0x00 << 4) +#define STN_DAT_PIN2 (0x01 << 4) +#define STN_DAT_PIN4 (0x02 << 4) +#define STN_DAT_PIN8 (0x03 << 4) +#define STN_DAT_PINMASK STN_DAT_PIN8 + +#define STFT_PSHI (1 << 15) +#define STFT_CLSHI (1 << 14) +#define STFT_SPLHI (1 << 13) +#define STFT_REVHI (1 << 12) + +#define SYNC_MASTER (0 << 16) +#define SYNC_SLAVE (1 << 16) + +#define DE_P (0 << 9) +#define DE_N (1 << 9) + +#define PCLK_P (0 << 10) +#define PCLK_N (1 << 10) + +#define HSYNC_P (0 << 11) +#define HSYNC_N (1 << 11) + +#define VSYNC_P (0 << 8) +#define VSYNC_N (1 << 8) + +#define DATA_NORMAL (0 << 17) +#define DATA_INVERSE (1 << 17) + + +/* Jz LCDFB supported I/O controls. */ +#define FBIOSETBACKLIGHT 0x4688 +#define FBIODISPON 0x4689 +#define FBIODISPOFF 0x468a +#define FBIORESET 0x468b +#define FBIOPRINT_REGS 0x468c +#define FBIOGETBUFADDRS 0x468d +#define FBIOROTATE 0x46a0 /* rotated fb */ + +struct jz_lcd_buffer_addrs_t { + int fb_num; + unsigned int lcd_desc_phys_addr; + unsigned int fb_phys_addr[CONFIG_JZLCD_FRAMEBUFFER_MAX]; +}; + + +/* + * LCD panel specific definition + */ +#if defined(CONFIG_JZLCD_TRULY_TFTG320240DTSW) + +#if defined(CONFIG_JZ4730_PMP) +#define LCD_RESET_PIN 63 +#endif + +#define __lcd_special_on() \ +do { \ + __gpio_set_pin(LCD_RESET_PIN); \ + __gpio_as_output(LCD_RESET_PIN); \ + __gpio_clear_pin(LCD_RESET_PIN); \ + udelay(100); \ + __gpio_set_pin(LCD_RESET_PIN); \ +} while (0) + +#endif /* CONFIG_JZLCD_TRULY_TFTG320240DTSW */ + +#if defined(CONFIG_JZLCD_SAMSUNG_LTV350QVF04) + +#if defined(CONFIG_JZ4730_FPRINT) +#define PortSDI 60 +#define PortSCL 61 +#define PortCS 62 +#define PortRST 63 +#define PortSht 64 +#endif + +#if defined(CONFIG_JZ4730_GPS) +#define PortSDI 74 +#define PortSCL 72 +#define PortCS 73 +#define PortRST 60 +#define PortSht 59 +#endif + +#ifndef PortSDI +#define PortSDI 0 +#endif +#ifndef PortSCL +#define PortSCL 0 +#endif +#ifndef PortCS +#define PortCS 0 +#endif +#ifndef PortRST +#define PortRST 0 +#endif +#ifndef PortSht +#define PortSht 0 +#endif + +#define __lcd_special_pin_init() \ +do { \ + __gpio_as_output(PortSDI); /* SDI */\ + __gpio_as_output(PortSCL); /* SCL */ \ + __gpio_as_output(PortCS); /* CS */ \ + __gpio_as_output(PortRST); /* Reset */ \ + __gpio_as_output(PortSht); /* Shut Down # */ \ + __gpio_set_pin(PortCS); \ + __gpio_set_pin(PortSCL); \ + __gpio_set_pin(PortSDI); \ +} while (0) + +#define __spi_out(val) \ +do { \ + int __i__; \ + unsigned int _t_ = (val); \ + __gpio_clear_pin(PortCS); \ + udelay(25); \ + for (__i__ = 0; __i__ < 24; __i__++ ) { \ + __gpio_clear_pin(PortSCL); \ + if (_t_ & 0x800000) \ + __gpio_set_pin(PortSDI); \ + else \ + __gpio_clear_pin(PortSDI); \ + _t_ <<= 1; \ + udelay(25); \ + __gpio_set_pin(PortSCL); \ + udelay(25); \ + } \ + __gpio_set_pin(PortCS); \ + udelay(25); \ + __gpio_set_pin(PortSDI); \ + udelay(25); \ + __gpio_set_pin(PortSCL); \ +} while (0) + +#define __spi_id_op_data(rs, rw, val) \ + __spi_out((0x1d<<18)|((rs)<<17)|((rw)<<16)|(val)) + +#define __spi_write_reg(reg, val) \ +do { \ + __spi_id_op_data(0, 0, (reg)); \ + __spi_id_op_data(1, 0, (val)); \ +} while (0) + +#define __lcd_special_on() \ +do { \ + __gpio_set_pin(PortSht); \ + __gpio_clear_pin(PortRST); \ + mdelay(10); \ + __gpio_set_pin(PortRST); \ + mdelay(1); \ + __spi_write_reg(0x09, 0); \ + mdelay(10); \ + __spi_write_reg(0x09, 0x4000); \ + __spi_write_reg(0x0a, 0x2000); \ + mdelay(40); \ + __spi_write_reg(0x09, 0x4055); \ + mdelay(50); \ + __spi_write_reg(0x01, 0x409d); \ + __spi_write_reg(0x02, 0x0204); \ + __spi_write_reg(0x03, 0x0100); \ + __spi_write_reg(0x04, 0x3000); \ + __spi_write_reg(0x05, 0x4003); \ + __spi_write_reg(0x06, 0x000a); \ + __spi_write_reg(0x07, 0x0021); \ + __spi_write_reg(0x08, 0x0c00); \ + __spi_write_reg(0x10, 0x0103); \ + __spi_write_reg(0x11, 0x0301); \ + __spi_write_reg(0x12, 0x1f0f); \ + __spi_write_reg(0x13, 0x1f0f); \ + __spi_write_reg(0x14, 0x0707); \ + __spi_write_reg(0x15, 0x0307); \ + __spi_write_reg(0x16, 0x0707); \ + __spi_write_reg(0x17, 0x0000); \ + __spi_write_reg(0x18, 0x0004); \ + __spi_write_reg(0x19, 0x0000); \ + mdelay(60); \ + __spi_write_reg(0x09, 0x4a55); \ + __spi_write_reg(0x05, 0x5003); \ +} while (0) + +#define __lcd_special_off() \ +do { \ + __spi_write_reg(0x09, 0x4055); \ + __spi_write_reg(0x05, 0x4003); \ + __spi_write_reg(0x0a, 0x0000); \ + mdelay(10); \ + __spi_write_reg(0x09, 0x4000); \ + __gpio_clear_pin(PortSht); \ +} while (0) + +#endif /* CONFIG_JZLCD_SAMSUNG_LTV350QVF04 */ + +#if defined(CONFIG_JZLCD_AUO_A030FL01_V1) +#if defined(CONFIG_JZ4740_PAVO) /* board pavo */ + #define SPEN (32*1+18) /*LCD_CS*/ + #define SPCK (32*1+17) /*LCD_SCL*/ + #define SPDA (32*2+12) /*LCD_SDA*/ + #define LCD_RET (32*2+23) /*use for lcd reset*/ +#elif defined(CONFIG_JZ4740_LYRA) /* board lyra */ + #define SPEN (32*3+19) //LCD_CS + #define SPCK (32*3+18) //LCD_SCL + #define SPDA (32*3+20) //LCD_SDA + #define LCD_RET (32*3+31) //use for lcd reset +#else +#error "driver/video/Jzlcd.h, please define SPI pins on your board." +#endif + +#define __spi_write_reg(reg, val) \ + do { \ + unsigned char no; \ + unsigned short value; \ + unsigned char a=0; \ + unsigned char b=0; \ + __gpio_as_output(SPEN); /* use SPDA */ \ + __gpio_as_output(SPCK); /* use SPCK */ \ + __gpio_as_output(SPDA); /* use SPDA */ \ + a=reg; \ + b=val; \ + __gpio_set_pin(SPEN); \ + __gpio_clear_pin(SPCK); \ + udelay(50); \ + __gpio_clear_pin(SPDA); \ + __gpio_clear_pin(SPEN); \ + udelay(50); \ + value=((a<<8)|(b&0xFF)); \ + for(no=0;no<16;no++) \ + { \ + if((value&0x8000)==0x8000){ \ + __gpio_set_pin(SPDA);} \ + else{ \ + __gpio_clear_pin(SPDA); } \ + udelay(400); \ + __gpio_set_pin(SPCK); \ + value=(value<<1); \ + udelay(50); \ + __gpio_clear_pin(SPCK); \ + } \ + __gpio_set_pin(SPEN); \ + udelay(400); \ + } while (0) +#define __spi_read_reg(reg,val) \ + do{ \ + unsigned char no; \ + unsigned short value; \ + __gpio_as_output(SPEN); /* use SPDA */ \ + __gpio_as_output(SPCK); /* use SPCK */ \ + __gpio_as_output(SPDA); /* use SPDA */ \ + value = ((reg << 0) | (1 << 7)); \ + val = 0; \ + __gpio_as_output(SPDA); \ + __gpio_set_pin(SPEN); \ + __gpio_set_pin(SPCK); \ + udelay(1); \ + __gpio_clear_pin(SPDA); \ + __gpio_clear_pin(SPEN); \ + udelay(1); \ + for (no = 0; no < 16; no++ ) { \ + __gpio_clear_pin(SPCK); \ + udelay(1); \ + if(no < 8) \ + { \ + if (value & 0x80) /* send data */ \ + __gpio_set_pin(SPDA); \ + else \ + __gpio_clear_pin(SPDA); \ + value = (value << 1); \ + udelay(1); \ + __gpio_set_pin(SPCK); \ + udelay(1); \ + } \ + else \ + { \ + __gpio_as_input(SPDA); \ + udelay(1); \ + __gpio_set_pin(SPCK); \ + udelay(1); \ + val = (val << 1); \ + val |= __gpio_get_pin(SPDA); \ + udelay(1); \ + } \ + udelay(400); \ + } \ + __gpio_as_output(SPDA); \ + __gpio_set_pin(SPEN); \ + udelay(400); \ + } while(0) + +#define __lcd_special_pin_init() \ + do { \ + __gpio_as_output(SPEN); /* use SPDA */ \ + __gpio_as_output(SPCK); /* use SPCK */ \ + __gpio_as_output(SPDA); /* use SPDA */ \ + __gpio_as_output(LCD_RET); \ + udelay(50); \ + __gpio_clear_pin(LCD_RET); \ + udelay(100); \ + __gpio_set_pin(LCD_RET); \ + } while (0) +#define __lcd_special_on() \ + do { \ + udelay(50); \ + __gpio_clear_pin(LCD_RET); \ + udelay(100); \ + __gpio_set_pin(LCD_RET); \ + __spi_write_reg(0x0D, 0x44); \ + __spi_write_reg(0x0D, 0x4D); \ + __spi_write_reg(0x0B, 0x06); \ + __spi_write_reg(0x40, 0xC0); \ + __spi_write_reg(0x42, 0x43); \ + __spi_write_reg(0x44, 0x28); \ + __spi_write_reg(0x0D, 0x4F); \ +} while (0) + + #define __lcd_special_off() \ + do { \ + __spi_write_reg(0x04, 0x4C); \ + } while (0) + +#endif /* CONFIG_JZLCD_AUO_A030FL01_V1 */ + +//#if defined(CONFIG_JZLCD_FOXCONN_PT035TN01) +#if defined(CONFIG_JZLCD_FOXCONN_PT035TN01) || defined(CONFIG_JZLCD_INNOLUX_PT035TN01_SERIAL) + +#if defined(CONFIG_JZLCD_FOXCONN_PT035TN01) /* board pmp */ +#define MODE 0xcd /* 24bit parellel RGB */ +#endif +#if defined(CONFIG_JZLCD_INNOLUX_PT035TN01_SERIAL) +#define MODE 0xc9 /* 8bit serial RGB */ +#endif + +#if defined(CONFIG_JZ4730_PMP) + #define SPEN 60 //LCD_SPL + #define SPCK 61 //LCD_CLS + #define SPDA 62 //LCD_PS + #define LCD_RET 63 //LCD_REV //use for lcd reset +#elif defined(CONFIG_JZ4740_LEO) /* board leo */ + #define SPEN (32*1+18) //LCD_SPL + #define SPCK (32*1+17) //LCD_CLS + #define SPDA (32*2+22) //LCD_PS + #define LCD_RET (32*2+23) //LCD_REV //use for lcd reset +#elif defined(CONFIG_JZ4740_PAVO) /* board pavo */ + #define SPEN (32*1+18) //LCD_SPL + #define SPCK (32*1+17) //LCD_CLS + #define SPDA (32*2+12) //LCD_D12 + #define LCD_RET (32*2+23) //LCD_REV, GPC23 +#if 0 /*old driver*/ + #define SPEN (32*1+18) //LCD_SPL + #define SPCK (32*1+17) //LCD_CLS + #define SPDA (32*2+12) //LCD_D12 + #define LCD_RET (32*3+27) //PWM4 //use for lcd reset +#endif +#else +#error "driver/video/Jzlcd.h, please define SPI pins on your board." +#endif + + #define __spi_write_reg1(reg, val) \ + do { \ + unsigned char no;\ + unsigned short value;\ + unsigned char a=0;\ + unsigned char b=0;\ + a=reg;\ + b=val;\ + __gpio_set_pin(SPEN);\ + __gpio_set_pin(SPCK);\ + __gpio_clear_pin(SPDA);\ + __gpio_clear_pin(SPEN);\ + udelay(25);\ + value=((a<<8)|(b&0xFF));\ + for(no=0;no<16;no++)\ + {\ + __gpio_clear_pin(SPCK);\ + if((value&0x8000)==0x8000)\ + __gpio_set_pin(SPDA);\ + else\ + __gpio_clear_pin(SPDA);\ + udelay(25);\ + __gpio_set_pin(SPCK);\ + value=(value<<1); \ + udelay(25);\ + }\ + __gpio_set_pin(SPEN);\ + udelay(100);\ + } while (0) + + #define __spi_write_reg(reg, val) \ + do {\ + __spi_write_reg1((reg<<2|2), val); \ + udelay(100); \ + }while(0) + + #define __lcd_special_pin_init() \ + do { \ + __gpio_as_output(SPEN); /* use SPDA */\ + __gpio_as_output(SPCK); /* use SPCK */\ + __gpio_as_output(SPDA); /* use SPDA */\ + __gpio_as_output(LCD_RET);\ + udelay(50);\ + __gpio_clear_pin(LCD_RET);\ + mdelay(150);\ + __gpio_set_pin(LCD_RET);\ + } while (0) + + #define __lcd_special_on() \ + do { \ + udelay(50);\ + __gpio_clear_pin(LCD_RET);\ + mdelay(150);\ + __gpio_set_pin(LCD_RET);\ + mdelay(10);\ + __spi_write_reg(0x00, 0x03); \ + __spi_write_reg(0x01, 0x40); \ + __spi_write_reg(0x02, 0x11); \ + __spi_write_reg(0x03, MODE); /* mode */ \ + __spi_write_reg(0x04, 0x32); \ + __spi_write_reg(0x05, 0x0e); \ + __spi_write_reg(0x07, 0x03); \ + __spi_write_reg(0x08, 0x08); \ + __spi_write_reg(0x09, 0x32); \ + __spi_write_reg(0x0A, 0x88); \ + __spi_write_reg(0x0B, 0xc6); \ + __spi_write_reg(0x0C, 0x20); \ + __spi_write_reg(0x0D, 0x20); \ + } while (0) //reg 0x0a is control the display direction:DB0->horizontal level DB1->vertical level + +/* __spi_write_reg(0x02, 0x03); \ + __spi_write_reg(0x06, 0x40); \ + __spi_write_reg(0x0a, 0x11); \ + __spi_write_reg(0x0e, 0xcd); \ + __spi_write_reg(0x12, 0x32); \ + __spi_write_reg(0x16, 0x0e); \ + __spi_write_reg(0x1e, 0x03); \ + __spi_write_reg(0x22, 0x08); \ + __spi_write_reg(0x26, 0x40); \ + __spi_write_reg(0x2a, 0x88); \ + __spi_write_reg(0x2e, 0x88); \ + __spi_write_reg(0x32, 0x20); \ + __spi_write_reg(0x36, 0x20); \ +*/ +// } while (0) //reg 0x0a is control the display direction:DB0->horizontal level DB1->vertical level + + #define __lcd_special_off() \ + do { \ + __spi_write_reg(0x00, 0x03); \ + } while (0) + +#endif /* CONFIG_JZLCD_FOXCONN_PT035TN01 or CONFIG_JZLCD_INNOLUX_PT035TN01_SERIAL */ + + +#ifndef __lcd_special_pin_init +#define __lcd_special_pin_init() +#endif +#ifndef __lcd_special_on +#define __lcd_special_on() +#endif +#ifndef __lcd_special_off +#define __lcd_special_off() +#endif + + +/* + * Platform specific definition + */ + +#if defined(CONFIG_JZ4730_GPS) + +#define __lcd_set_backlight_level(n) \ +do { \ + ; \ +} while (0) + +#define __lcd_display_pin_init() \ +do { \ + __lcd_special_pin_init(); \ + __gpio_as_output(94); /* PWM0 pin */ \ + __gpio_as_output(95); /* PWM1 pin */ \ +} while (0) + +#define __lcd_display_on() \ +do { \ + __lcd_special_on(); \ + __gpio_set_pin(94); /* PWM0 pin */ \ + __gpio_set_pin(95); /* PWM1 pin */ \ + __lcd_set_backlight_level(8); \ +} while (0) + +#define __lcd_display_off() \ +do { \ + __lcd_special_off(); \ +} while (0) + +#endif /* CONFIG_JZ4730_GPS */ + +#if defined(CONFIG_JZ4730_FPRINT) + +#define __lcd_set_backlight_level(n) \ +do { \ + REG_PWM_DUT(0) = n; \ + REG_PWM_PER(0) = 7; \ + REG_PWM_CTR(0) = 0x81; \ +} while (0) + +#if defined(CONFIG_JZLCD_FOXCONN_PT035TN01) + +#define __lcd_display_pin_init() \ +do { \ + __lcd_special_pin_init();\ + __gpio_as_pwm();\ + __lcd_set_backlight_level(8);\ +} while (0) + +#define __lcd_display_on() \ +do { \ + __lcd_set_backlight_level(8); \ + __lcd_special_on();\ +} while (0) + +#define __lcd_display_off() \ +do { \ + __lcd_set_backlight_level(0); \ + __lcd_special_off();\ +} while (0) + +#else + +#define __lcd_display_pin_init() \ +do { \ + __gpio_as_output(GPIO_DISP_OFF_N); \ + __gpio_as_pwm(); \ + __lcd_set_backlight_level(8); \ +} while (0) + +#define __lcd_display_on() \ +do { \ + __lcd_set_backlight_level(8); \ + __gpio_set_pin(GPIO_DISP_OFF_N); \ +} while (0) + +#define __lcd_display_off() \ +do { \ + __lcd_set_backlight_level(0); \ + __gpio_clear_pin(GPIO_DISP_OFF_N); \ +} while (0) +#endif + +#endif /* CONFIG_JZ4730_FPRINT */ + +#if defined(CONFIG_JZ4730_LIBRA) + +#define __lcd_set_backlight_level(n) \ +do { \ +} while (0) + +#define __lcd_display_pin_init() \ +do { \ + __lcd_special_pin_init(); \ + __gpio_clear_pin(100); \ + __gpio_as_output(100); \ + __gpio_as_output(94); \ + __gpio_as_output(95); \ + __lcd_set_backlight_level(8); \ +} while (0) + +#define __lcd_display_on() \ +do { \ + __lcd_special_on(); \ + __gpio_set_pin(100); \ + __gpio_set_pin(94); \ + __gpio_set_pin(95); \ +} while (0) + +#define __lcd_display_off() \ +do { \ + __lcd_special_off(); \ + __gpio_clear_pin(100); \ + __gpio_clear_pin(94); \ + __gpio_clear_pin(95); \ +} while (0) + +#endif /* CONFIG_JZ4730_LIBRA */ + +#if defined(CONFIG_JZ4730_PMP) + +#define __lcd_set_backlight_level(n) \ +do { \ + REG_PWM_DUT(0) = n; \ + REG_PWM_PER(0) = 7; \ + REG_PWM_CTR(0) = 0x81; \ +} while (0) + +#define __lcd_display_pin_init() \ +do { \ + __gpio_as_output(GPIO_DISP_OFF_N); \ + __gpio_as_pwm(); \ + __lcd_set_backlight_level(10); \ + __lcd_special_pin_init(); \ +} while (0) + +#define __lcd_display_on() \ +do { \ + __lcd_special_on(); \ + __lcd_set_backlight_level(8); \ + __gpio_set_pin(GPIO_DISP_OFF_N); \ +} while (0) + +#define __lcd_display_off() \ +do { \ + __lcd_special_off(); \ + __lcd_set_backlight_level(0); \ + __gpio_clear_pin(GPIO_DISP_OFF_N); \ +} while (0) + +#endif /* CONFIG_JZ4730_PMP */ + +/*#if defined(CONFIG_JZ4740_LEO) || defined(CONFIG_JZ4740_PAVO)*/ +#if defined(CONFIG_SOC_JZ4740) +#if defined(CONFIG_JZ4740_PAVO) || defined(CONFIG_JZ4740_LYRA) +#define GPIO_PWM 123 /* GP_D27 */ +#define PWM_CHN 4 /* pwm channel */ +#define PWM_FULL 101 +/* 100 level: 0,1,...,100 */ +#define __lcd_set_backlight_level(n)\ +do { \ +__gpio_as_output(32*3+27); \ +__gpio_set_pin(32*3+27); \ +} while (0) + +#define __lcd_close_backlight() \ +do { \ +__gpio_as_output(GPIO_PWM); \ +__gpio_clear_pin(GPIO_PWM); \ +} while (0) + +#elif defined(CONFIG_JZ4720_VIRGO) +#define GPIO_PWM 119 /* GP_D23 */ +#define PWM_CHN 0 /* pwm channel */ +#define PWM_FULL 101 +/* 100 level: 0,1,...,100 */ +/*#define __lcd_set_backlight_level(n) \ +do { \ + __gpio_as_pwm(0); \ + __tcu_disable_pwm_output(PWM_CHN); \ + __tcu_stop_counter(PWM_CHN); \ + __tcu_init_pwm_output_high(PWM_CHN); \ + __tcu_set_pwm_output_shutdown_abrupt(PWM_CHN); \ + __tcu_select_clk_div1(PWM_CHN); \ + __tcu_mask_full_match_irq(PWM_CHN); \ + __tcu_mask_half_match_irq(PWM_CHN); \ + __tcu_set_count(PWM_CHN,0); \ + __tcu_set_full_data(PWM_CHN,__cpm_get_extalclk()/1000); \ + __tcu_set_half_data(PWM_CHN,__cpm_get_extalclk()/1000*n/100); \ + __tcu_enable_pwm_output(PWM_CHN); \ + __tcu_select_extalclk(PWM_CHN); \ + __tcu_start_counter(PWM_CHN); \ +} while (0) +*/ + +#define __lcd_set_backlight_level(n) \ +do { \ + __gpio_as_output(GPIO_PWM); \ + __gpio_set_pin(GPIO_PWM); \ +} while (0) + +#define __lcd_close_backlight() \ +do { \ +__gpio_as_output(GPIO_PWM); \ +__gpio_clear_pin(GPIO_PWM); \ +} while (0) + +#else +#define __lcd_set_backlight_level(n) +#define __lcd_close_backlight() + +#endif /* #if defined(CONFIG_MIPS_JZ4740_PAVO) */ + +#define __lcd_display_pin_init() \ +do { \ + __gpio_as_output(GPIO_DISP_OFF_N); \ + __cpm_start_tcu(); \ + __lcd_special_pin_init(); \ +} while (0) +/* __lcd_set_backlight_level(100); \*/ +#define __lcd_display_on() \ +do { \ + __gpio_set_pin(GPIO_DISP_OFF_N); \ + __lcd_special_on(); \ + __lcd_set_backlight_level(80); \ +} while (0) + +#define __lcd_display_off() \ +do { \ + __lcd_special_off(); \ + __lcd_close_backlight(); \ + __gpio_clear_pin(GPIO_DISP_OFF_N); \ +} while (0) + +#endif /* CONFIG_MIPS_JZ4740_LEO */ + +#if defined(CONFIG_JZLCD_MSTN_240x128) + +#if 0 /* The final version does not use software emulation of VCOM. */ + +#define GPIO_VSYNC 59 +#define GPIO_VCOM 90 + +#define REG_VCOM REG_GPIO_GPDR((GPIO_VCOM>>5)) +#define VCOM_BIT (1 << (GPIO_VCOM & 0x1f)) +static unsigned int vcom_static; +static void vsync_irq(int irq, void *dev_id, struct pt_regs *reg) +{ + vcom_static = REG_VCOM; + vcom_static ^= VCOM_BIT; + REG_VCOM = vcom_static; +} + +#define __lcd_display_pin_init() \ + __gpio_as_irq_rise_edge(GPIO_VSYNC); \ + __gpio_as_output(GPIO_VCOM); \ + { \ + static int inited = 0; \ + if (!inited) { \ + inited = 1; \ + if (request_irq(IRQ_GPIO_0 + GPIO_VSYNC, vsync_irq, SA_INTERRUPT, \ + "vsync", 0)) { \ + err = -EBUSY; \ + goto failed; \ + }}} + +#endif + +/* We uses AC BIAs pin to generate VCOM signal, so above code should be removed. + */ +#endif +/***************************************************************************** + * LCD display pin dummy macros + *****************************************************************************/ +#ifndef __lcd_display_pin_init +#define __lcd_display_pin_init() +#endif +#ifndef __lcd_display_on +#define __lcd_display_on() +#endif +#ifndef __lcd_display_off +#define __lcd_display_off() +#endif +#ifndef __lcd_set_backlight_level +#define __lcd_set_backlight_level(n) +#endif + +#endif /* __JZLCD_H__ */ --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -45,6 +45,15 @@ comment "Watchdog Device Drivers" # Architecture Independent +config JZ_WDT + bool 'JzSoC On-Chip watchdog' + help + Watchdog timer embedded into JZSOC chips. This will reboot your + system when the timeout is reached. + + To compile this driver as a module, choose M here: the + module will be called jz_wdt. + config SOFT_WATCHDOG tristate "Software watchdog" help --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -10,6 +10,9 @@ # that also fails then you can fall back to the software watchdog # to give you some cover. +# JZ-watchdog timer +obj-$(CONFIG_JZ_WDT) += jz_wdt.o + # ISA-based Watchdog Cards obj-$(CONFIG_PCWATCHDOG) += pcwd.o obj-$(CONFIG_MIXCOMWD) += mixcomwd.o --- /dev/null +++ b/drivers/watchdog/jz_wdt.c @@ -0,0 +1,203 @@ +/* + * linux/drivers/char/jz_wdt.c + * + * Watchdog driver for the Ingenic JzSOC + * + * Author: Wei Jianli + * + * 2005 (c) Ingenic Semiconductor. This file is licensed under the + * terms of the GNU General Public License version 2. This program is + * licensed "as is" without any warranty of any kind, whether express + * or implied. + */ + +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TIMER_MARGIN 60 /* (secs) Default is 1 minute */ + +static unsigned int timer_margin = TIMER_MARGIN; /* in seconds */ +static unsigned int timer_rate; +static unsigned int pre_margin; +static unsigned long jz_wdt_users = 0; + +#ifdef MODULE +MODULE_PARM(timer_margin, "i"); +#endif + +static void +jz_wdt_ping(void) +{ + printk("jz_wdt_ping\n"); + /* reload counter with (new) margin */ +#ifdef CONFIG_SOC_JZ4730 + pre_margin = 0xffffffff - timer_rate * timer_margin; + __wdt_set_count(pre_margin); +#endif +#ifdef CONFIG_SOC_JZ4740 + pre_margin = timer_rate * timer_margin; + __wdt_set_count(0); + __wdt_set_data(pre_margin); +#endif +} + +/* + * Allow only one person to hold it open + */ + +static int +jz_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(1, &jz_wdt_users)) + return -EBUSY; + + printk("jz_wdt_open\n"); +#ifdef CONFIG_SOC_JZ4730 + if (REG_CPM_OCR & CPM_OCR_EXT_RTC_CLK) + timer_rate = 32768; + else + timer_rate = JZ_EXTAL/128; +#endif + +#ifdef CONFIG_SOC_JZ4740 + /* Initialize the wdt clocks */ + __wdt_select_rtcclk(); + __wdt_select_clk_div1024(); + __tcu_start_wdt_clock(); + timer_rate = 32; /* 32768 / 1024 */ +#endif + + jz_wdt_ping(); + __wdt_start(); + + return 0; +} + +static int +jz_wdt_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + * Lock it in if it's a module and we defined ...NOWAYOUT + */ + jz_wdt_ping(); +#ifndef CONFIG_WATCHDOG_NOWAYOUT + /* SW can't stop wdt once it was started */ +#endif + jz_wdt_users = 0; + return 0; +} + +static ssize_t +jz_wdt_write(struct file *file, const char *data, size_t len, loff_t * ppos) +{ + /* Can't seek (pwrite) on this device */ + if (ppos != &file->f_pos) + return -ESPIPE; + + printk("jz_wdt_write\n"); + + /* Refresh counter */ + if (len) { + jz_wdt_ping(); + return 1; + } + return 0; +} + +static int +jz_wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int new_margin; + static struct watchdog_info ident = { + .identity = "JzSOC Watchdog", + .options = WDIOF_SETTIMEOUT, + .firmware_version = 0, + }; + + switch (cmd) { + default: + return -ENOIOCTLCMD; + case WDIOC_GETSUPPORT: + return copy_to_user((struct watchdog_info *) arg, &ident, + sizeof (ident)); + case WDIOC_GETSTATUS: + return put_user(0, (int *) arg); + case WDIOC_GETBOOTSTATUS: +#ifdef CONFIG_SOC_JZ4730 + return put_user(REG_CPM_RSTR, (int *) arg); +#endif +#ifdef CONFIG_SOC_JZ4740 + return put_user(REG_RTC_HWRSR, (int *) arg); +#endif + case WDIOC_KEEPALIVE: + jz_wdt_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, (int *) arg)) + return -EFAULT; + if (new_margin < 1) + return -EINVAL; + timer_margin = new_margin; + jz_wdt_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(timer_margin, (int *) arg); + } +} + +static struct file_operations jz_wdt_fops = { + .owner = THIS_MODULE, + .write = jz_wdt_write, + .ioctl = jz_wdt_ioctl, + .open = jz_wdt_open, + .release = jz_wdt_release, +}; + +static struct miscdevice jz_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "jz_wdt", + .fops = &jz_wdt_fops +}; + +static int __init +jz_wdt_init(void) +{ + int ret; + + ret = misc_register(&jz_wdt_miscdev); + + if (ret) + return ret; + + printk("JzSOC Watchdog Timer: timer margin %d sec\n", timer_margin); + + return 0; +} + +static void __exit +jz_wdt_exit(void) +{ + misc_deregister(&jz_wdt_miscdev); +} + +module_init(jz_wdt_init); +module_exit(jz_wdt_exit); + +MODULE_AUTHOR("Wei Jianli"); +MODULE_LICENSE("GPL");