diff options
Diffstat (limited to 'drivers')
28 files changed, 8042 insertions, 1 deletions
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 8cc453c85ea..ac11fd783a7 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -11,6 +11,69 @@ menuconfig INPUT_TOUCHSCREEN if INPUT_TOUCHSCREEN +menuconfig TOUCHSCREEN_FILTER + boolean "Touchscreen Filtering" + depends on INPUT_TOUCHSCREEN + select TOUCHSCREEN_FILTER_GROUP + select TOUCHSCREEN_FILTER_MEDIAN + select TOUCHSCREEN_FILTER_MEAN + select TOUCHSCREEN_FILTER_LINEAR + help + Select this to include kernel touchscreen filter support. The filters + can be combined in any order in your machine init and the parameters + for them can also be set there. + +config TOUCHSCREEN_FILTER_GROUP + bool "Group Touchscreen Filter" + depends on INPUT_TOUCHSCREEN && TOUCHSCREEN_FILTER + default Y + help + Say Y here if you want to use the Group touchscreen filter, it + avoids using atypical samples. + +config TOUCHSCREEN_FILTER_MEDIAN + bool "Median Average Touchscreen Filter" + depends on INPUT_TOUCHSCREEN && TOUCHSCREEN_FILTER + default Y + help + Say Y here if you want to use the Median touchscreen filter, it's + highly effective if you data is noisy with occasional excursions. + +config TOUCHSCREEN_FILTER_MEAN + bool "Mean Average Touchscreen Filter" + depends on INPUT_TOUCHSCREEN && TOUCHSCREEN_FILTER + default Y + help + Say Y here if you want to use the Mean touchscreen filter, it + can further improve decent quality data by removing jitter + +config TOUCHSCREEN_FILTER_LINEAR + bool "Linear Touchscreen Filter" + depends on INPUT_TOUCHSCREEN && TOUCHSCREEN_FILTER + default Y + help + Say Y here if you want to use the Linear touchscreen filter, it + enables the use of calibration data for the touchscreen. + +config TOUCHSCREEN_S3C2410 + tristate "Samsung S3C2410 touchscreen input driver" + depends on ARCH_S3C2410 && INPUT && INPUT_TOUCHSCREEN + select S3C24XX_ADC + select SERIO + help + Say Y here if you have the s3c2410 touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called s3c2410_ts. + +config TOUCHSCREEN_S3C2410_DEBUG + boolean "Samsung S3C2410 touchscreen debug messages" + depends on TOUCHSCREEN_S3C2410 + help + Select this if you want debug messages + config TOUCHSCREEN_ADS7846 tristate "ADS7846/TSC2046 and ADS7843 based touchscreens" depends on SPI_MASTER diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 15fa62cffc7..453477fe012 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -42,3 +42,9 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o +obj-$(CONFIG_TOUCHSCREEN_FILTER) += ts_filter_chain.o +obj-$(CONFIG_TOUCHSCREEN_FILTER_GROUP) += ts_filter_group.o +obj-$(CONFIG_TOUCHSCREEN_FILTER_LINEAR) += ts_filter_linear.o +obj-$(CONFIG_TOUCHSCREEN_FILTER_MEDIAN) += ts_filter_median.o +obj-$(CONFIG_TOUCHSCREEN_FILTER_MEAN) += ts_filter_mean.o +obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o diff --git a/drivers/input/touchscreen/s3c2410_ts.c b/drivers/input/touchscreen/s3c2410_ts.c new file mode 100644 index 00000000000..db083a16284 --- /dev/null +++ b/drivers/input/touchscreen/s3c2410_ts.c @@ -0,0 +1,552 @@ +/* + * 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 + * + * Copyright (c) 2004 Arnaud Patard <arnaud.patard@rtp-net.org> + * iPAQ H1940 touchscreen support + * + * ChangeLog + * + * 2004-09-05: Herbert Pƶtzl <herbert@13thfloor.at> + * - Added clock (de-)allocation code + * + * 2005-03-06: Arnaud Patard <arnaud.patard@rtp-net.org> + * - h1940_ -> s3c2410 (this driver is now also used on the n30 + * machines :P) + * - Debug messages are now enabled with the config option + * TOUCHSCREEN_S3C2410_DEBUG + * - Changed the way the value are read + * - Input subsystem should now work + * - Use ioremap and readl/writel + * + * 2005-03-23: Arnaud Patard <arnaud.patard@rtp-net.org> + * - Make use of some undocumented features of the touchscreen + * controller + * + * 2007-05-23: Harald Welte <laforge@openmoko.org> + * - Add proper support for S32440 + * + * 2008-06-23: Andy Green <andy@warmcat.com> + * - Removed averaging system + * - Added generic Touchscreen filter stuff + * + * 2008-11-27: Nelson Castillo <arhuaco@freaks-unidos.net> + * - Improve interrupt handling + * + * 2009-04-09: Nelson Castillo <arhuaco@freaks-unidos.net> + * - Use s3c-adc API (Vasily Khoruzhick <anarsoul@gmail.com> provided + * a working example for a simpler version of this driver). + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/timer.h> +#include <linux/kfifo.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <asm/irq.h> + +#include <mach/regs-gpio.h> +#include <mach/gpio-nrs.h> +#include <mach/gpio-fns.h> +#include <mach/ts.h> +#include <mach/hardware.h> +#include <plat/regs-adc.h> +#include <plat/adc.h> + +#include <linux/input/touchscreen/ts_filter_chain.h> + +/* For ts.dev.id.version */ +#define S3C2410TSVERSION 0x0101 + +#define WAIT4INT(x) (((x)<<8) | \ + S3C2410_ADCTSC_YM_SEN | \ + S3C2410_ADCTSC_YP_SEN | \ + S3C2410_ADCTSC_XP_SEN | \ + S3C2410_ADCTSC_XY_PST(3)) + +#define TSPRINTK(fmt, args...) \ + printk(KERN_DEBUG "%s: " fmt, __func__ , ## args) +#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG +# define DPRINTK TSPRINTK +#else +# define DPRINTK(fmt, args...) +#endif + + +MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>"); +MODULE_DESCRIPTION("s3c2410 touchscreen driver"); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +static char *s3c2410ts_name = "s3c2410 TouchScreen"; + +#define TS_RELEASE_TIMEOUT (HZ >> 7 ? HZ >> 7 : 1) /* 8ms (5ms if HZ is 200) */ +#define TS_EVENT_FIFO_SIZE (2 << 6) /* Must be a power of 2. */ + +/* + * Per-touchscreen data. + */ + +enum ts_state {TS_STATE_STANDBY, TS_STATE_PRESSED, TS_STATE_RELEASE_PENDING, + TS_STATE_RELEASE}; + +struct s3c2410ts { + struct input_dev *dev; + struct ts_filter_chain *chain; + enum ts_state state; + int is_down; + struct kfifo *event_fifo; + struct s3c_adc_client *adc_client; + unsigned adc_selected; +}; + +static struct s3c2410ts ts; + +static void __iomem *base_addr; + +/* + * A few low level functions. + */ + +static inline void s3c2410_ts_connect(void) +{ + s3c2410_gpio_cfgpin(S3C2410_GPG(12), S3C2410_GPG12_XMON); + s3c2410_gpio_cfgpin(S3C2410_GPG(13), S3C2410_GPG13_nXPON); + s3c2410_gpio_cfgpin(S3C2410_GPG(14), S3C2410_GPG14_YMON); + s3c2410_gpio_cfgpin(S3C2410_GPG(15), S3C2410_GPG15_nYPON); +} + +/* + * Code that starts ADC conversions. + */ + +static void ts_adc_timer_f(unsigned long data); +static struct timer_list ts_adc_timer = TIMER_INITIALIZER(ts_adc_timer_f, 0, 0); + +static void ts_adc_timer_f(unsigned long data) +{ + if (s3c_adc_start(ts.adc_client, 0, 1)) + mod_timer(&ts_adc_timer, jiffies + 1); +} + +static void s3c2410_ts_start_adc_conversion(void) +{ + if (ts.adc_selected) + mod_timer(&ts_adc_timer, jiffies + 1); + else + ts_adc_timer_f(0); +} + +/* Callback for the s3c-adc API. */ +void adc_selected_f(unsigned selected) +{ + ts.adc_selected = selected; +} + +/* + * Just send the input events. + */ + +enum ts_input_event {IE_DOWN = 0, IE_UP}; + +static void ts_input_report(int event, int coords[]) +{ +#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG + static char *s[] = {"down", "up"}; + struct timeval tv; + + do_gettimeofday(&tv); +#endif + + if (event == IE_DOWN) { + input_report_abs(ts.dev, ABS_X, coords[0]); + input_report_abs(ts.dev, ABS_Y, coords[1]); + input_report_key(ts.dev, BTN_TOUCH, 1); + input_report_abs(ts.dev, ABS_PRESSURE, 1); + + DPRINTK("T:%06d %6s (X:%03d, Y:%03d)\n", + (int)tv.tv_usec, s[event], coords[0], coords[1]); + } else { + input_report_key(ts.dev, BTN_TOUCH, 0); + input_report_abs(ts.dev, ABS_PRESSURE, 0); + + DPRINTK("T:%06d %6s\n", + (int)tv.tv_usec, s[event]); + } + + input_sync(ts.dev); +} + +/* + * Manage the state of the touchscreen. + */ + +static void event_send_timer_f(unsigned long data); + +static struct timer_list event_send_timer = + TIMER_INITIALIZER(event_send_timer_f, 0, 0); + +static void event_send_timer_f(unsigned long data) +{ + static int noop_counter; + int event_type; + + while (__kfifo_get(ts.event_fifo, (unsigned char *)&event_type, + sizeof(int))) { + int buf[2]; + + switch (event_type) { + case 'D': + if (ts.state == TS_STATE_RELEASE_PENDING) + /* Ignore short UP event. */ + ts.state = TS_STATE_PRESSED; + break; + + case 'U': + ts.state = TS_STATE_RELEASE_PENDING; + break; + + case 'P': + if (ts.is_down) /* Stylus_action needs a conversion. */ + s3c2410_ts_start_adc_conversion(); + + if (unlikely(__kfifo_get(ts.event_fifo, + (unsigned char *)buf, + sizeof(int) * 2) + != sizeof(int) * 2)) { + /* This will only happen if we have a bug. */ + TSPRINTK("Invalid packet\n"); + return; + } + + ts_input_report(IE_DOWN, buf); + ts.state = TS_STATE_PRESSED; + break; + } + + noop_counter = 0; + } + + if (noop_counter++ >= 1) { + noop_counter = 0; + if (ts.state == TS_STATE_RELEASE_PENDING) { + /* + * We delay the UP event for a while to avoid jitter. + * If we get a DOWN event we do not send it. + */ + ts_input_report(IE_UP, NULL); + ts.state = TS_STATE_STANDBY; + + ts_filter_chain_clear(ts.chain); + } + } else { + mod_timer(&event_send_timer, jiffies + TS_RELEASE_TIMEOUT); + } +} + +/* + * Manage interrupts. + */ + +static irqreturn_t stylus_updown(int irq, void *dev_id) +{ + unsigned long data0; + unsigned long data1; + int event_type; + + data0 = readl(base_addr + S3C2410_ADCDAT0); + data1 = readl(base_addr + S3C2410_ADCDAT1); + + ts.is_down = !(data0 & S3C2410_ADCDAT0_UPDOWN) && + !(data1 & S3C2410_ADCDAT0_UPDOWN); + + event_type = ts.is_down ? 'D' : 'U'; + + if (unlikely(__kfifo_put(ts.event_fifo, (unsigned char *)&event_type, + sizeof(int)) != sizeof(int))) + /* Only happens if we have a bug. */ + TSPRINTK("FIFO full\n"); + + if (ts.is_down) + s3c2410_ts_start_adc_conversion(); + else + writel(WAIT4INT(0), base_addr + S3C2410_ADCTSC); + + mod_timer(&event_send_timer, jiffies + 1); + + return IRQ_HANDLED; +} + +static void stylus_adc_action(unsigned p0, unsigned p1, unsigned *conv_left) +{ + int buf[3]; + + /* TODO: Do we really need this? */ + if (p0 & S3C2410_ADCDAT0_AUTO_PST || + p1 & S3C2410_ADCDAT1_AUTO_PST) { + *conv_left = 1; + return; + } + + buf[1] = p0; + buf[2] = p1; + + switch (ts_filter_chain_feed(ts.chain, &buf[1])) { + case 0: + /* The filter wants more points. */ + *conv_left = 1; + return; + case 1: + /* We have a point from the filters or no filtering enabled. */ + buf[0] = 'P'; + break; + default: + TSPRINTK("invalid return value\n"); + case -1: + /* Too much noise. Ignore the event. */ + ts_filter_chain_clear(ts.chain); + writel(WAIT4INT(1), base_addr + S3C2410_ADCTSC); + return; + }; + + if (unlikely(__kfifo_put(ts.event_fifo, (unsigned char *)buf, + sizeof(int) * 3) != sizeof(int) * 3)) + /* This will only happen if we have a bug. */ + TSPRINTK("FIFO full\n"); + + writel(WAIT4INT(1), base_addr + S3C2410_ADCTSC); + mod_timer(&event_send_timer, jiffies + 1); + + return; +} + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init s3c2410ts_probe(struct platform_device *pdev) +{ + int rc; + struct s3c2410_ts_mach_info *info; + struct input_dev *input_dev; + int ret = 0; + + dev_info(&pdev->dev, "Starting\n"); + + info = (struct s3c2410_ts_mach_info *)pdev->dev.platform_data; + + if (!info) { + dev_err(&pdev->dev, "No platform data\n"); + return -EINVAL; + } + + base_addr = ioremap(S3C2410_PA_ADC, 0x20); + if (base_addr == NULL) { + dev_err(&pdev->dev, "Failed to remap register block\n"); + ret = -ENOMEM; + goto bail0; + } + + /* If we acutally are a S3C2410: Configure GPIOs */ + if (!strcmp(pdev->name, "s3c2410-ts")) + s3c2410_ts_connect(); + + writel(WAIT4INT(0), base_addr + S3C2410_ADCTSC); + + /* Initialise input stuff */ + memset(&ts, 0, sizeof(struct s3c2410ts)); + + ts.adc_client = + s3c_adc_register(pdev, adc_selected_f, stylus_adc_action, 1); + if (!ts.adc_client) { + dev_err(&pdev->dev, + "Unable to register s3c2410_ts as s3_adc client\n"); + iounmap(base_addr); + ret = -EIO; + goto bail0; + } + + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&pdev->dev, "Unable to allocate the input device\n"); + ret = -ENOMEM; + goto bail1; + } + + ts.dev = input_dev; + ts.dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | + BIT_MASK(EV_ABS); + ts.dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(ts.dev, ABS_X, 0, 0x3FF, 0, 0); + input_set_abs_params(ts.dev, ABS_Y, 0, 0x3FF, 0, 0); + input_set_abs_params(ts.dev, ABS_PRESSURE, 0, 1, 0, 0); + + ts.dev->name = s3c2410ts_name; + ts.dev->id.bustype = BUS_RS232; + ts.dev->id.vendor = 0xDEAD; + ts.dev->id.product = 0xBEEF; + ts.dev->id.version = S3C2410TSVERSION; + ts.state = TS_STATE_STANDBY; + ts.event_fifo = kfifo_alloc(TS_EVENT_FIFO_SIZE, GFP_KERNEL, NULL); + if (IS_ERR(ts.event_fifo)) { + ret = -EIO; + goto bail2; + } + + /* Create the filter chain set up for the 2 coordinates we produce. */ + ts.chain = ts_filter_chain_create(pdev, info->filter_config, 2); + + if (IS_ERR(ts.chain)) + goto bail2; + + ts_filter_chain_clear(ts.chain); + + /* Get IRQ. */ + if (request_irq(IRQ_TC, stylus_updown, 0, "s3c2410_action", ts.dev)) { + dev_err(&pdev->dev, "Could not allocate ts IRQ_TC !\n"); + iounmap(base_addr); + ret = -EIO; + goto bail3; + } + + dev_info(&pdev->dev, "Successfully loaded\n"); + + /* All went ok. Register to the input system. */ + rc = input_register_device(ts.dev); + if (rc) { + dev_info(&pdev->dev, "Could not register input device\n"); + ret = -EIO; + goto bail4; + } + + return 0; + +bail4: + free_irq(IRQ_TC, ts.dev); + iounmap(base_addr); + disable_irq(IRQ_TC); +bail3: + ts_filter_chain_destroy(ts.chain); + kfifo_free(ts.event_fifo); +bail2: + input_unregister_device(ts.dev); +bail1: + iounmap(base_addr); +bail0: + + return ret; +} + +static int s3c2410ts_remove(struct platform_device *pdev) +{ + disable_irq(IRQ_TC); + free_irq(IRQ_TC, ts.dev); + + s3c_adc_release(ts.adc_client); + input_unregister_device(ts.dev); + iounmap(base_addr); + + ts_filter_chain_destroy(ts.chain); + + kfifo_free(ts.event_fifo); + + return 0; +} + +#ifdef CONFIG_PM + +#define TSC_SLEEP (S3C2410_ADCTSC_PULL_UP_DISABLE | \ + S3C2410_ADCTSC_XY_PST(0)) + +static int s3c2410ts_suspend(struct platform_device *pdev, pm_message_t state) +{ + writel(TSC_SLEEP, base_addr + S3C2410_ADCTSC); + writel(readl(base_addr + S3C2410_ADCCON) | S3C2410_ADCCON_STDBM, + base_addr + S3C2410_ADCCON); + disable_irq(IRQ_TC); + + return 0; +} + +static int s3c2410ts_resume(struct platform_device *pdev) +{ + ts_filter_chain_clear(ts.chain); + enable_irq(IRQ_TC); + writel(WAIT4INT(0), base_addr + S3C2410_ADCTSC); + + return 0; +} + +#else +#define s3c2410ts_suspend NULL +#define s3c2410ts_resume NULL +#endif + +static struct platform_driver s3c2410ts_driver = { + .driver = { + .name = "s3c2410-ts", + .owner = THIS_MODULE, + }, + .probe = s3c2410ts_probe, + .remove = s3c2410ts_remove, + .suspend = s3c2410ts_suspend, + .resume = s3c2410ts_resume, +}; + +static struct platform_driver s3c2440ts_driver = { + .driver = { + .name = "s3c2440-ts", + .owner = THIS_MODULE, + }, + .probe = s3c2410ts_probe, + .remove = s3c2410ts_remove, + .suspend = s3c2410ts_suspend, + .resume = s3c2410ts_resume, +}; + +static int __init s3c2410ts_init(void) +{ + int rc; + + rc = platform_driver_register(&s3c2410ts_driver); + if (rc < 0) + return rc; + + rc = platform_driver_register(&s3c2440ts_driver); + if (rc < 0) + platform_driver_unregister(&s3c2410ts_driver); + + return rc; +} + +static void __exit s3c2410ts_exit(void) +{ + platform_driver_unregister(&s3c2440ts_driver); + platform_driver_unregister(&s3c2410ts_driver); +} + +module_init(s3c2410ts_init); +module_exit(s3c2410ts_exit); + diff --git a/drivers/input/touchscreen/ts_filter_chain.c b/drivers/input/touchscreen/ts_filter_chain.c new file mode 100644 index 00000000000..8b5e2145b27 --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_chain.c @@ -0,0 +1,183 @@ +/* + * 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 + * + * Copyright (c) 2008,2009 Andy Green <andy@warmcat.com> + */ + +#include <linux/kernel.h> +#include <linux/device.h> + +#include <linux/input/touchscreen/ts_filter_chain.h> +#include <linux/input/touchscreen/ts_filter.h> + +/* + * Tux, would you like the following function in /lib? + * It helps us avoid silly code. + */ + +/** + * sptrlen - Count how many non-null pointers are in a pointer array + * @arr: The array of pointers + */ +static int sptrlen(const void *arr) +{ + /* All pointers have the same size. */ + const int **p = (const int **)arr; + int len = 0; + + while (*(p++)) + len++; + + return len; +} + + +struct ts_filter_chain { + /* All of the filters. */ + struct ts_filter **arr; + /* Filters that can propagate values in the chain. */ + struct ts_filter **pchain; + /* Length of the pchain array. */ + int pchain_len; + /* FIXME: Add a spinlock and use it. */ +}; + +struct ts_filter_chain *ts_filter_chain_create( + struct platform_device *pdev, + const struct ts_filter_chain_configuration conf[], + int count_coords) +{ + struct ts_filter_chain *c; + int count = 0; + int len; + + BUG_ON((count_coords < 1)); + BUG_ON(count_coords > MAX_TS_FILTER_COORDS); + + c = kzalloc(sizeof(struct ts_filter_chain), GFP_KERNEL); + if (!c) + goto create_err_1; + + len = (sptrlen(conf) + 1); + /* Memory for two null-terminated arrays of filters. */ + c->arr = kzalloc(2 * sizeof(struct ts_filter *) * len, GFP_KERNEL); + if (!c->arr) + goto create_err_1; + c->pchain = c->arr + len; + + while (conf->api) { + /* TODO: Can we get away with only sending pdev->dev? */ + struct ts_filter *f = + (conf->api->create)(pdev, conf->config, count_coords); + if (!f) { + dev_info(&pdev->dev, "Filter %d creation failed\n", + count); + goto create_err_2; + } + + f->api = conf->api; + c->arr[count++] = f; + + if (f->api->haspoint && f->api->getpoint && f->api->process) + c->pchain[c->pchain_len++] = f; + + conf++; + } + + dev_info(&pdev->dev, "%d filter(s) initialized\n", count); + + return c; + +create_err_2: + ts_filter_chain_destroy(c); /* Also frees c. */ +create_err_1: + dev_info(&pdev->dev, "Error in filter chain initialization\n"); + /* + * FIXME: Individual filters have to return errors this way. + * We only have to forward the errors we find. + */ + return ERR_PTR(-ENOMEM); +} +EXPORT_SYMBOL_GPL(ts_filter_chain_create); + +void ts_filter_chain_destroy(struct ts_filter_chain *c) +{ + if (c->arr) { + struct ts_filter **a = c->arr; + while (*a) { + ((*a)->api->destroy)(*a); + a++; + } + kfree(c->arr); + } + kfree(c); +} +EXPORT_SYMBOL_GPL(ts_filter_chain_destroy); + +void ts_filter_chain_clear(struct ts_filter_chain *c) +{ + struct ts_filter **a = c->arr; + + while (*a) { + if ((*a)->api->clear) + ((*a)->api->clear)(*a); + a++; + } +} +EXPORT_SYMBOL_GPL(ts_filter_chain_clear); + +static void ts_filter_chain_scale(struct ts_filter_chain *c, int *coords) +{ + struct ts_filter **a = c->arr; + while (*a) { + if ((*a)->api->scale) + ((*a)->api->scale)(*a, coords); + a++; + } +} + +int ts_filter_chain_feed(struct ts_filter_chain *c, int *coords) +{ + int len = c->pchain_len; + int i = len - 1; + + if (!c->pchain[0]) + return 1; /* Nothing to do. */ + + BUG_ON(c->pchain[0]->api->haspoint(c->pchain[0])); + + if (c->pchain[0]->api->process(c->pchain[0], coords)) + return -1; + + while (i >= 0 && i < len) { + if (c->pchain[i]->api->haspoint(c->pchain[i])) { + c->pchain[i]->api->getpoint(c->pchain[i], coords); + if (++i < len && + c->pchain[i]->api->process(c->pchain[i], coords)) + return -1; /* Error. */ + } else { + i--; + } + } + + if (i >= 0) { /* Same as i == len. */ + ts_filter_chain_scale(c, coords); + return 1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(ts_filter_chain_feed); + diff --git a/drivers/input/touchscreen/ts_filter_group.c b/drivers/input/touchscreen/ts_filter_group.c new file mode 100644 index 00000000000..9e344c5f19f --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_group.c @@ -0,0 +1,330 @@ +/* + * 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 + * + * Copyright (C) 2008,2009 by Openmoko, Inc. + * Author: Nelson Castillo <arhuaco@freaks-unidos.net> + * All rights reserved. + * + * + * This filter is useful to reject samples that are not reliable. We consider + * that a sample is not reliable if it deviates form the Majority. + * This filter mixes information from all the available dimensions. It means + * that for two dimensions we draw a rectangle where the thought-to-be good + * points can be found. + * + * The implementation would be more efficient with a double-linked list but + * let's keep it simple for now. + * + * 1) We collect S samples and keep it in sorted sets. + * - Points that are "close enough" are considered to be in the same set. + * We don't actually keep the sets but ranges of points. + * + * 2) For each dimension: + * - We choose the range with more elements. If more than "threshold" + * points are in this range we use the minimum and the maximum point + * of the range to define the valid range for this dimension [min, max], + * otherwise we discard all the points and the ranges and go to step 1. + * + * 3) We consider the unsorted S samples and try to feed them to the next + * filter in the chain. If one of the points of each sample + * is not in the allowed range for its dimension we discard the sample. + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input/touchscreen/ts_filter_group.h> + +struct coord_range { + int min; /* Minimum value of the range. */ + int max; /* Maximum value of the range */ + int N; /* Number of points in the range. */ +}; + +struct ts_filter_group { + /* Private filter configuration. */ + struct ts_filter_group_configuration *config; + /* Filter API. */ + struct ts_filter tsf; + + int N; /* How many samples we have. */ + int *samples[MAX_TS_FILTER_COORDS]; /* The samples: our input. */ + + /* Temporal values that help us compute range_min and range_max. */ + struct coord_range *ranges[MAX_TS_FILTER_COORDS]; /* Ranges. */ + int n_ranges[MAX_TS_FILTER_COORDS]; /* Number of ranges */ + + /* Computed ranges that help us filter the points. */ + int range_max[MAX_TS_FILTER_COORDS]; /* Max. computed ranges. */ + int range_min[MAX_TS_FILTER_COORDS]; /* Min. computed ranges. */ + + int tries_left; /* We finish if we can't get enough samples. */ + int ready; /* If we are ready to deliver samples. */ + int result; /* Index of the point being returned. */ +}; + +#define ts_filter_to_filter_group(f) \ + container_of(f, struct ts_filter_group, tsf) + + +static void ts_filter_group_clear_internal(struct ts_filter_group *tsfg, + int attempts) +{ + int n; + tsfg->N = 0; + tsfg->tries_left = attempts; + tsfg->ready = 0; + tsfg->result = 0; + for (n = 0; n < tsfg->tsf.count_coords; n++) + tsfg->n_ranges[n] = 0; +} + +static void ts_filter_group_clear(struct ts_filter *tsf) +{ + struct ts_filter_group *tsfg = ts_filter_to_filter_group(tsf); + + ts_filter_group_clear_internal(tsfg, tsfg->config->attempts); +} + +static struct ts_filter *ts_filter_group_create( + struct platform_device *pdev, + const struct ts_filter_configuration *conf, + int count_coords) +{ + struct ts_filter_group *tsfg; + int i; + + tsfg = kzalloc(sizeof(struct ts_filter_group), GFP_KERNEL); + if (!tsfg) + return NULL; + + tsfg->config = container_of(conf, + struct ts_filter_group_configuration, + config); + tsfg->tsf.count_coords = count_coords; + + BUG_ON(tsfg->config->attempts <= 0); + BUG_ON(tsfg->config->length < tsfg->config->threshold); + + tsfg->samples[0] = kmalloc(count_coords * sizeof(int) * + tsfg->config->length, GFP_KERNEL); + if (!tsfg->samples[0]) { + kfree(tsfg); + return NULL; + } + for (i = 1; i < count_coords; ++i) + tsfg->samples[i] = tsfg->samples[0] + i * tsfg->config->length; + + tsfg->ranges[0] = kmalloc(count_coords * sizeof(struct coord_range) * + tsfg->config->length, GFP_KERNEL); + if (!tsfg->ranges[0]) { + kfree(tsfg->samples[0]); + kfree(tsfg); + return NULL; + } + for (i = 1; i < count_coords; ++i) + tsfg->ranges[i] = tsfg->ranges[0] + i * tsfg->config->length; + + ts_filter_group_clear_internal(tsfg, tsfg->config->attempts); + + dev_info(&pdev->dev, "Created Group filter len:%d coords:%d close:%d " + "thresh:%d\n", tsfg->config->length, count_coords, + tsfg->config->close_enough, tsfg->config->threshold); + + return &tsfg->tsf; +} + +static void ts_filter_group_destroy(struct ts_filter *tsf) +{ + struct ts_filter_group *tsfg = ts_filter_to_filter_group(tsf); + + kfree(tsfg->samples[0]); + kfree(tsfg->ranges[0]); + kfree(tsf); +} + +static void ts_filter_group_prepare_next(struct ts_filter *tsf); + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define IN_RANGE(c, r) ((c) >= (r).min - tsfg->config->close_enough && \ + (c) <= (r).max + tsfg->config->close_enough) + +static void delete_spot(struct coord_range *v, int n, int size) +{ + int i; + for (i = n; i < size - 1; ++i) + v[i] = v[i + 1]; +} + +static int ts_filter_group_process(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_group *tsfg = ts_filter_to_filter_group(tsf); + int n; + int j; + + BUG_ON(tsfg->N >= tsfg->config->length); + BUG_ON(tsfg->ready); + + for (n = 0; n < tsfg->tsf.count_coords; n++) { + int i; + struct coord_range *range = tsfg->ranges[n]; + int *n_ranges = &tsfg->n_ranges[n]; + int found = 0; + + tsfg->samples[n][tsfg->N] = coords[n]; + + for (i = 0; i < *n_ranges; ++i) { + if (IN_RANGE(coords[n], range[i])) { + range[i].min = MIN(range[i].min, coords[n]); + range[i].max = MAX(range[i].max, coords[n]); + range[i].N++; + found = 1; + break; + } else if (coords[n] <= range[i].min) + break; /* We need to insert a range. */ + } + if (found) { /* We might need to melt ranges. */ + if (i && range[i - 1].max + tsfg->config->close_enough + >= range[i].min) { + BUG_ON(range[i - 1].max >= range[i].max); + range[i - 1].max = range[i].max; + range[i - 1].N += range[i].N; + delete_spot(range, i, *n_ranges); + (*n_ranges)--; + i--; + } + if (i < *n_ranges - 1 && range[i + 1].min - + tsfg->config->close_enough <= range[i].max) { + range[i].max = range[i + 1].max; + range[i].N += range[i + 1].N; + delete_spot(range, i + 1, *n_ranges); + (*n_ranges)--; + } + } else { + BUG_ON((*n_ranges) >= tsfg->config->length); + (*n_ranges)++; + for (j = *n_ranges - 1; j > i; --j) + range[j] = range[j - 1]; + range[i].N = 1; + range[i].min = coords[n]; + range[i].max = coords[n]; + } + } + + if (++tsfg->N < tsfg->config->length) + return 0; + + for (n = 0; n < tsfg->tsf.count_coords; ++n) { + int best = 0; + for (j = 1; j < tsfg->n_ranges[n]; ++j) + if (tsfg->ranges[n][best].N < tsfg->ranges[n][j].N) + best = j; + if (tsfg->ranges[n][best].N < tsfg->config->threshold) { + /* This set of points is not good enough for us. */ + if (--tsfg->tries_left) { + ts_filter_group_clear_internal + (tsfg, tsfg->tries_left); + /* No errors but we need more samples. */ + return 0; + } + return 1; /* We give up: error. */ + } + tsfg->range_min[n] = tsfg->ranges[n][best].min; + tsfg->range_max[n] = tsfg->ranges[n][best].max; + } + + ts_filter_group_prepare_next(tsf); + + return 0; +} + +/* + * This private function prepares a point that will be returned + * in ts_filter_group_getpoint if it is available. It updates + * the priv->ready state also. + */ +static void ts_filter_group_prepare_next(struct ts_filter *tsf) +{ + struct ts_filter_group *priv = ts_filter_to_filter_group(tsf); + int n; + + while (priv->result < priv->N) { + for (n = 0; n < priv->tsf.count_coords; ++n) { + if (priv->samples[n][priv->result] < + priv->range_min[n] || + priv->samples[n][priv->result] > priv->range_max[n]) + break; + } + + if (n == priv->tsf.count_coords) /* Sample is OK. */ + break; + + priv->result++; + } + + if (unlikely(priv->result >= priv->N)) { /* No sample to deliver. */ + ts_filter_group_clear_internal(priv, priv->config->attempts); + priv->ready = 0; + } else { + priv->ready = 1; + } +} + +static int ts_filter_group_haspoint(struct ts_filter *tsf) +{ + struct ts_filter_group *priv = ts_filter_to_filter_group(tsf); + + return priv->ready; +} + +static void ts_filter_group_getpoint(struct ts_filter *tsf, int *point) +{ + struct ts_filter_group *priv = ts_filter_to_filter_group(tsf); + int n; + + BUG_ON(!priv->ready); + + for (n = 0; n < priv->tsf.count_coords; n++) + point[n] = priv->samples[n][priv->result]; + + priv->result++; + + /* This call will update priv->ready. */ + ts_filter_group_prepare_next(tsf); +} + +/* + * Get ready to process the next batch of points, forget + * points we could have delivered. + */ +static void ts_filter_group_scale(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_group *priv = ts_filter_to_filter_group(tsf); + + ts_filter_group_clear_internal(priv, priv->config->attempts); +} + +const struct ts_filter_api ts_filter_group_api = { + .create = ts_filter_group_create, + .destroy = ts_filter_group_destroy, + .clear = ts_filter_group_clear, + .process = ts_filter_group_process, + .haspoint = ts_filter_group_haspoint, + .getpoint = ts_filter_group_getpoint, + .scale = ts_filter_group_scale, +}; +EXPORT_SYMBOL_GPL(ts_filter_group_api); + diff --git a/drivers/input/touchscreen/ts_filter_linear.c b/drivers/input/touchscreen/ts_filter_linear.c new file mode 100644 index 00000000000..8b496591eec --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_linear.c @@ -0,0 +1,212 @@ +/* + * 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; 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 + * + * Copyright (C) 2008,2009 by Openmoko, Inc. + * Author: Nelson Castillo <arhuaco@freaks-unidos.net> + * All rights reserved. + * + * Linearly scale touchscreen values. + * + * Expose the TS_FILTER_LINEAR_NCONSTANTS for the linear transformation + * using sysfs. + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include <linux/input/touchscreen/ts_filter_linear.h> + +struct ts_filter_linear; + +/* Sysfs code. */ + +struct const_obj { + /* The actual private object. */ + struct ts_filter_linear *tsfl; + /* Our kobject. */ + struct kobject kobj; +}; + +#define to_const_obj(x) container_of(x, struct const_obj, kobj) + +struct const_attribute { + struct attribute attr; + ssize_t (*show)(struct const_obj *const, struct const_attribute *attr, + char *buf); + ssize_t (*store)(struct const_obj *const, struct const_attribute *attr, + const char *buf, size_t count); +}; + +#define to_const_attr(x) container_of(x, struct const_attribute, attr) + + +/* Private linear filter structure. */ + +struct ts_filter_linear { + /* Private configuration for this filter. */ + struct ts_filter_linear_configuration *config; + + /* Generic filter API. */ + struct ts_filter tsf; + + /* Linear constants for the transformation. */ + int constants[TS_FILTER_LINEAR_NCONSTANTS]; + + /* Sysfs. */ + + /* Our const_object. */ + struct const_obj c_obj; + /* Our type. We will stick operations to it. */ + struct kobj_type const_ktype; + /* Attrs. of the virtual files. */ + struct const_attribute kattrs[TS_FILTER_LINEAR_NCONSTANTS]; + /* Default Attrs. Always NULL for us. */ + struct attribute *attrs[TS_FILTER_LINEAR_NCONSTANTS + 1]; + /* Storage for the name of the virtual files. */ + char attr_names[TS_FILTER_LINEAR_NCONSTANTS][2]; +}; + +#define ts_filter_to_filter_linear(f) \ + container_of(f, struct ts_filter_linear, tsf) + +/* Sysfs functions. */ + +static ssize_t const_attr_show(struct kobject *kobj, + struct attribute *attr, + char *buf) +{ + struct const_attribute *a = to_const_attr(attr); + + return a->show(to_const_obj(kobj), a, buf); +} + +static ssize_t const_attr_store(struct kobject *kobj, + struct attribute *attr, + const char *buf, size_t len) +{ + struct const_attribute *a = to_const_attr(attr); + + return a->store(to_const_obj(kobj), a, buf, len); +} + +static struct sysfs_ops const_sysfs_ops = { + .show = const_attr_show, + .store = const_attr_store, +}; + +static void const_release(struct kobject *kobj) +{ + kfree(to_const_obj(kobj)->tsfl); +} + +static ssize_t const_show(struct const_obj *obj, struct const_attribute *attr, + char *buf) +{ + int who; + + sscanf(attr->attr.name, "%d", &who); + return sprintf(buf, "%d\n", obj->tsfl->constants[who]); +} + +static ssize_t const_store(struct const_obj *obj, struct const_attribute *attr, + const char *buf, size_t count) +{ + int who; + + sscanf(attr->attr.name, "%d", &who); + sscanf(buf, "%d", &obj->tsfl->constants[who]); + return count; +} + +/* Filter functions. */ + +static struct ts_filter *ts_filter_linear_create( + struct platform_device *pdev, + const struct ts_filter_configuration *conf, + int count_coords) +{ + struct ts_filter_linear *tsfl; + int i; + int ret; + + tsfl = kzalloc(sizeof(struct ts_filter_linear), GFP_KERNEL); + if (!tsfl) + return NULL; + + tsfl->config = container_of(conf, + struct ts_filter_linear_configuration, + config); + + tsfl->tsf.count_coords = count_coords; + + for (i = 0; i < TS_FILTER_LINEAR_NCONSTANTS; ++i) { + tsfl->constants[i] = tsfl->config->constants[i]; + + /* sysfs */ + sprintf(tsfl->attr_names[i], "%d", i); + tsfl->kattrs[i].attr.name = tsfl->attr_names[i]; + tsfl->kattrs[i].attr.mode = 0666; + tsfl->kattrs[i].show = const_show; + tsfl->kattrs[i].store = const_store; + tsfl->attrs[i] = &tsfl->kattrs[i].attr; + } + tsfl->attrs[i] = NULL; + + tsfl->const_ktype.sysfs_ops = &const_sysfs_ops; + tsfl->const_ktype.release = const_release; + tsfl->const_ktype.default_attrs = tsfl->attrs; + tsfl->c_obj.tsfl = tsfl; /* kernel frees tsfl in const_release */ + + ret = kobject_init_and_add(&tsfl->c_obj.kobj, &tsfl->const_ktype, + &pdev->dev.kobj, "calibration"); + if (ret) { + kobject_put(&tsfl->c_obj.kobj); + return NULL; + } + + dev_info(&pdev->dev, "Created Linear filter coords:%d\n", count_coords); + + return &tsfl->tsf; +} + +static void ts_filter_linear_destroy(struct ts_filter *tsf) +{ + struct ts_filter_linear *tsfl = ts_filter_to_filter_linear(tsf); + + /* Kernel frees tsfl in const_release. */ + kobject_put(&tsfl->c_obj.kobj); +} + +static void ts_filter_linear_scale(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_linear *tsfl = ts_filter_to_filter_linear(tsf); + + int *k = tsfl->constants; + int c0 = coords[tsfl->config->coord0]; + int c1 = coords[tsfl->config->coord1]; + + coords[tsfl->config->coord0] = (k[2] + k[0] * c0 + k[1] * c1) / k[6]; + coords[tsfl->config->coord1] = (k[5] + k[3] * c0 + k[4] * c1) / k[6]; +} + +const struct ts_filter_api ts_filter_linear_api = { + .create = ts_filter_linear_create, + .destroy = ts_filter_linear_destroy, + .scale = ts_filter_linear_scale, +}; +EXPORT_SYMBOL_GPL(ts_filter_linear_api); + diff --git a/drivers/input/touchscreen/ts_filter_mean.c b/drivers/input/touchscreen/ts_filter_mean.c new file mode 100644 index 00000000000..ad4e9c1207c --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_mean.c @@ -0,0 +1,174 @@ +/* + * 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 + * + * Copyright (c) 2008,2009 + * Andy Green <andy@warmcat.com> + * Nelson Castillo <arhuaco@freaks-unidos.net> + * + * Simple mean filter. + * + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> + +#include <linux/input/touchscreen/ts_filter_mean.h> + +struct ts_filter_mean { + /* Copy of the private filter configuration. */ + struct ts_filter_mean_configuration *config; + /* Filter API. */ + struct ts_filter tsf; + + /* Index on a circular buffer. */ + int curr; + /* Useful to tell if the circular buffer is full(read:ready). */ + int count; + /* Sumation used to compute the mean. */ + int sum[MAX_TS_FILTER_COORDS]; + /* Keep point values and decrement them from the sum on time. */ + int *fifo[MAX_TS_FILTER_COORDS]; + /* Store the output of this filter. */ + int ready; +}; + +#define ts_filter_to_filter_mean(f) container_of(f, struct ts_filter_mean, tsf) + + +static void ts_filter_mean_clear(struct ts_filter *tsf); + +static struct ts_filter *ts_filter_mean_create( + struct platform_device *pdev, + const struct ts_filter_configuration *conf, + int count_coords) +{ + struct ts_filter_mean *priv; + int *v; + int n; + + priv = kzalloc(sizeof(struct ts_filter_mean), GFP_KERNEL); + if (!priv) + return NULL; + + priv->tsf.count_coords = count_coords; + priv->config = container_of(conf, + struct ts_filter_mean_configuration, + config); + + BUG_ON(priv->config->length <= 0); + + v = kmalloc(priv->config->length * sizeof(int) * count_coords, + GFP_KERNEL); + if (!v) + return NULL; + + for (n = 0; n < count_coords; n++) { + priv->fifo[n] = v; + v += priv->config->length; + } + + ts_filter_mean_clear(&priv->tsf); + + dev_info(&pdev->dev, "Created Mean filter len:%d coords:%d\n", + priv->config->length, count_coords); + + return &priv->tsf; +} + +static void ts_filter_mean_destroy(struct ts_filter *tsf) +{ + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + + kfree(priv->fifo[0]); /* first guy has pointer from kmalloc */ + kfree(tsf); +} + +static void ts_filter_mean_clear(struct ts_filter *tsf) +{ + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + + priv->count = 0; + priv->curr = 0; + priv->ready = 0; + memset(priv->sum, 0, tsf->count_coords * sizeof(int)); +} + +static int ts_filter_mean_process(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + int n; + + BUG_ON(priv->ready); + + for (n = 0; n < tsf->count_coords; n++) { + priv->sum[n] += coords[n]; + priv->fifo[n][priv->curr] = coords[n]; + } + + if (priv->count + 1 == priv->config->length) + priv->ready = 1; + else + priv->count++; + + priv->curr = (priv->curr + 1) % priv->config->length; + + return 0; /* No error. */ +} + +static int ts_filter_mean_haspoint(struct ts_filter *tsf) +{ + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + + return priv->ready; +} + +static void ts_filter_mean_getpoint(struct ts_filter *tsf, int *point) +{ + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + int n; + + BUG_ON(!priv->ready); + + for (n = 0; n < tsf->count_coords; n++) { + point[n] = priv->sum[n]; + priv->sum[n] -= priv->fifo[n][priv->curr]; + } + + priv->ready = 0; +} + +static void ts_filter_mean_scale(struct ts_filter *tsf, int *coords) +{ + int n; + struct ts_filter_mean *priv = ts_filter_to_filter_mean(tsf); + + for (n = 0; n < tsf->count_coords; n++) { + coords[n] += priv->config->length >> 1; /* Rounding. */ + coords[n] /= priv->config->length; + } +} + +const struct ts_filter_api ts_filter_mean_api = { + .create = ts_filter_mean_create, + .destroy = ts_filter_mean_destroy, + .clear = ts_filter_mean_clear, + .process = ts_filter_mean_process, + .scale = ts_filter_mean_scale, + .haspoint = ts_filter_mean_haspoint, + .getpoint = ts_filter_mean_getpoint, +}; +EXPORT_SYMBOL_GPL(ts_filter_mean_api); + diff --git a/drivers/input/touchscreen/ts_filter_median.c b/drivers/input/touchscreen/ts_filter_median.c new file mode 100644 index 00000000000..c608f7a9794 --- /dev/null +++ b/drivers/input/touchscreen/ts_filter_median.c @@ -0,0 +1,261 @@ +/* + * 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 + * + * Copyright (c) 2008 Andy Green <andy@warmcat.com> + * + * + * Median averaging stuff. We sort incoming raw samples into an array of + * MEDIAN_SIZE length, discarding the oldest sample each time once we are full. + * We then return the sum of the middle three samples for X and Y. It means + * the final result must be divided by (3 * scaling factor) to correct for + * avoiding the repeated /3. + * + * This strongly rejects brief excursions away from a central point that is + * sticky in time compared to the excursion duration. + * + * Thanks to Dale Schumacher (who wrote some example code) and Carl-Daniel + * Halifinger who pointed out this would be a good method. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input/touchscreen/ts_filter_median.h> + +struct ts_filter_median { + /* Private configuration. */ + struct ts_filter_median_configuration *config; + /* Generic Filter API. */ + struct ts_filter tsf; + + /* Count raw samples we get. */ + int samples_count; + /* + * Remember the last coordinates we got in order to know if + * we are moving slow or fast. + */ + int last_issued[MAX_TS_FILTER_COORDS]; + /* How many samples in the sort buffer are valid. */ + int valid; + /* Samples taken for median in sorted form. */ + int *sort[MAX_TS_FILTER_COORDS]; + /* Samples taken for median. */ + int *fifo[MAX_TS_FILTER_COORDS]; + /* Where we are in the fifo sample memory. */ + int pos; + /* Do we have a sample to deliver? */ + int ready; +}; + +#define ts_filter_to_filter_median(f) \ + container_of(f, struct ts_filter_median, tsf) + + +static void ts_filter_median_insert(int *p, int sample, int count) +{ + int n; + + /* Search through what we got so far to find where to put sample. */ + for (n = 0; n < count; n++) + if (sample < p[n]) { /* We met somebody bigger than us? */ + /* Starting from the end, push bigger guys down one. */ + for (count--; count >= n; count--) + p[count + 1] = p[count]; + p[n] = sample; /* Put us in place of first bigger. */ + return; + } + + p[count] = sample; /* Nobody was bigger than us, add us on the end. */ +} + +static void ts_filter_median_del(int *p, int value, int count) +{ + int index; + + for (index = 0; index < count; index++) + if (p[index] == value) { + for (; index < count; index++) + p[index] = p[index + 1]; + return; + } +} + + +static void ts_filter_median_clear(struct ts_filter *tsf) +{ + struct ts_filter_median *tsfm = ts_filter_to_filter_median(tsf); + + tsfm->pos = 0; + tsfm->valid = 0; + tsfm->ready = 0; + memset(&tsfm->last_issued[0], 1, tsf->count_coords * sizeof(int)); +} + +static struct ts_filter *ts_filter_median_create( + struct platform_device *pdev, + const struct ts_filter_configuration *conf, + int count_coords) +{ + int *p; + int n; + struct ts_filter_median *tsfm = kzalloc(sizeof(struct ts_filter_median), + GFP_KERNEL); + + if (!tsfm) + return NULL; + + tsfm->config = container_of(conf, + struct ts_filter_median_configuration, + config); + + tsfm->tsf.count_coords = count_coords; + + tsfm->config->midpoint = (tsfm->config->extent >> 1) + 1; + + p = kmalloc(2 * count_coords * sizeof(int) * (tsfm->config->extent + 1), + GFP_KERNEL); + if (!p) { + kfree(tsfm); + return NULL; + } + + for (n = 0; n < count_coords; n++) { + tsfm->sort[n] = p; + p += tsfm->config->extent + 1; + tsfm->fifo[n] = p; + p += tsfm->config->extent + 1; + } + + ts_filter_median_clear(&tsfm->tsf); + + dev_info(&pdev->dev, + "Created Median filter len:%d coords:%d dec_threshold:%d\n", + tsfm->config->extent, count_coords, + tsfm->config->decimation_threshold); + + return &tsfm->tsf; +} + +static void ts_filter_median_destroy(struct ts_filter *tsf) +{ + struct ts_filter_median *tsfm = ts_filter_to_filter_median(tsf); + + kfree(tsfm->sort[0]); /* First guy has pointer from kmalloc. */ + kfree(tsf); +} + +static void ts_filter_median_scale(struct ts_filter *tsf, int *coords) +{ + int n; + + for (n = 0; n < tsf->count_coords; n++) + coords[n] = (coords[n] + 2) / 3; +} + +/* + * Give us the raw sample data coords, and if we return 1 then you can + * get a filtered coordinate from coords. If we return 0 you didn't + * fill all the filters with samples yet. + */ + +static int ts_filter_median_process(struct ts_filter *tsf, int *coords) +{ + struct ts_filter_median *tsfm = ts_filter_to_filter_median(tsf); + int n; + int movement = 1; + + for (n = 0; n < tsf->count_coords; n++) { + /* Grab copy in insertion order to remove when oldest. */ + tsfm->fifo[n][tsfm->pos] = coords[n]; + /* Insert these samples in sorted order in the median arrays. */ + ts_filter_median_insert(tsfm->sort[n], coords[n], tsfm->valid); + } + /* Move us on in the fifo. */ + if (++tsfm->pos == (tsfm->config->extent + 1)) + tsfm->pos = 0; + + /* Have we finished a median sampling? */ + if (++tsfm->valid < tsfm->config->extent) + goto process_exit; /* No valid sample to use. */ + + BUG_ON(tsfm->valid != tsfm->config->extent); + + tsfm->valid--; + + /* + * Sum the middle 3 in the median sorted arrays. We don't divide back + * down which increases the sum resolution by a factor of 3 until the + * scale API function is called. + */ + for (n = 0; n < tsf->count_coords; n++) + /* Perform the deletion of the oldest sample. */ + ts_filter_median_del(tsfm->sort[n], tsfm->fifo[n][tsfm->pos], + tsfm->valid); + + tsfm->samples_count--; + if (tsfm->samples_count >= 0) + goto process_exit; + + for (n = 0; n < tsf->count_coords; n++) { + /* Give the coordinate result from summing median 3. */ + coords[n] = tsfm->sort[n][tsfm->config->midpoint - 1] + + tsfm->sort[n][tsfm->config->midpoint] + + tsfm->sort[n][tsfm->config->midpoint + 1]; + + movement += abs(tsfm->last_issued[n] - coords[n]); + } + + if (movement > tsfm->config->decimation_threshold) /* Moving fast. */ + tsfm->samples_count = tsfm->config->decimation_above; + else + tsfm->samples_count = tsfm->config->decimation_below; + + memcpy(&tsfm->last_issued[0], coords, tsf->count_coords * sizeof(int)); + + tsfm->ready = 1; + +process_exit: + return 0; +} + +static int ts_filter_median_haspoint(struct ts_filter *tsf) +{ + struct ts_filter_median *priv = ts_filter_to_filter_median(tsf); + + return priv->ready; +} + +static void ts_filter_median_getpoint(struct ts_filter *tsf, int *point) +{ + struct ts_filter_median *priv = ts_filter_to_filter_median(tsf); + + BUG_ON(!priv->ready); + + memcpy(point, &priv->last_issued[0], tsf->count_coords * sizeof(int)); + + priv->ready = 0; +} + +const struct ts_filter_api ts_filter_median_api = { + .create = ts_filter_median_create, + .destroy = ts_filter_median_destroy, + .clear = ts_filter_median_clear, + .process = ts_filter_median_process, + .scale = ts_filter_median_scale, + .haspoint = ts_filter_median_haspoint, + .getpoint = ts_filter_median_getpoint, +}; +EXPORT_SYMBOL_GPL(ts_filter_median_api); + diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 570be139f9d..f5a60b49dfb 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -305,6 +305,8 @@ config EZX_PCAP This enables the PCAP ASIC present on EZX Phones. This is needed for MMC, TouchScreen, Sound, USB, etc.. +source "drivers/mfd/glamo/Kconfig" + endmenu menu "Multimedia Capabilities Port drivers" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index f3b277b90d4..5272b0451f0 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_MFD_SM501) += sm501.o obj-$(CONFIG_MFD_ASIC3) += asic3.o +obj-$(CONFIG_MFD_GLAMO) += glamo/ obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o diff --git a/drivers/mfd/glamo/Kconfig b/drivers/mfd/glamo/Kconfig new file mode 100644 index 00000000000..3aa4831a4ff --- /dev/null +++ b/drivers/mfd/glamo/Kconfig @@ -0,0 +1,42 @@ +config MFD_GLAMO + bool "Smedia Glamo 336x/337x support" + select MFD_CORE + help + This enables the core driver for the Smedia Glamo 336x/337x + multi-function device. It includes irq_chip demultiplex as + well as clock / power management and GPIO support. + +config MFD_GLAMO_FB + tristate "Smedia Glamo 336x/337x framebuffer support" + depends on FB && MFD_GLAMO + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Frame buffer driver for the LCD controller in the Smedia Glamo + 336x/337x. + + This driver is also available as a module ( = code which can be + inserted and removed from the running kernel whenever you want). The + module will be called glamofb. If you want to compile it as a module, + say M here and read <file:Documentation/modules.txt>. + + If unsure, say N. + +config MFD_GLAMO_GPIO + tristate "Glamo GPIO support" + depends on MFD_GLAMO + + help + Enable a bitbanging SPI adapter driver for the Smedia Glamo. + +config MFD_GLAMO_MCI + tristate "Glamo S3C SD/MMC Card Interface support" + depends on MFD_GLAMO && MMC && REGULATOR + select CRC7 + help + This selects a driver for the MCI interface found in + the S-Media GLAMO chip, as used in Openmoko + neo1973 GTA-02. + + If unsure, say N. diff --git a/drivers/mfd/glamo/Makefile b/drivers/mfd/glamo/Makefile new file mode 100644 index 00000000000..ebf26f7e473 --- /dev/null +++ b/drivers/mfd/glamo/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for the Smedia Glamo framebuffer driver +# + +obj-$(CONFIG_MFD_GLAMO) += glamo-core.o +obj-$(CONFIG_MFD_GLAMO_GPIO) += glamo-gpio.o +obj-$(CONFIG_MFD_GLAMO_SPI) += glamo-spi.o + +obj-$(CONFIG_MFD_GLAMO_FB) += glamo-fb.o +obj-$(CONFIG_MFD_GLAMO_MCI) += glamo-mci.o + diff --git a/drivers/mfd/glamo/glamo-core.c b/drivers/mfd/glamo/glamo-core.c new file mode 100644 index 00000000000..e0e39409595 --- /dev/null +++ b/drivers/mfd/glamo/glamo-core.c @@ -0,0 +1,1284 @@ +/* Smedia Glamo 3362 driver + * + * (C) 2007 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * (C) 2009, Lars-Peter Clausen <lars@metafoo.de> + * All rights reserved. + * + * 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 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/kernel_stat.h> +#include <linux/spinlock.h> +#include <linux/mfd/core.h> +#include <linux/mfd/glamo.h> +#include <linux/io.h> + +#include <linux/pm.h> + +#include "glamo-regs.h" +#include "glamo-core.h" + +#define GLAMO_MEM_REFRESH_COUNT 0x100 + +#define GLAMO_NR_IRQS 9 + +#define GLAMO_IRQ_HOSTBUS 0 +#define GLAMO_IRQ_JPEG 1 +#define GLAMO_IRQ_MPEG 2 +#define GLAMO_IRQ_MPROC1 3 +#define GLAMO_IRQ_MPROC0 4 +#define GLAMO_IRQ_CMDQUEUE 5 +#define GLAMO_IRQ_2D 6 +#define GLAMO_IRQ_MMC 7 +#define GLAMO_IRQ_RISC 8 + +/* + * Glamo internal settings + * + * We run the memory interface from the faster PLLB on 2.6.28 kernels and + * above. Couple of GTA02 users report trouble with memory bus when they + * upgraded from 2.6.24. So this parameter allows reversion to 2.6.24 + * scheme if their Glamo chip needs it. + * + * you can override the faster default on kernel commandline using + * + * glamo3362.slow_memory=1 + * + * for example + */ + +static int slow_memory; +module_param(slow_memory, int, 0644); + +struct reg_range { + int start; + int count; + char *name; + unsigned dump:1; +}; + +static const struct reg_range reg_range[] = { + { 0x0000, 0x76, "General", 1 }, + { 0x0200, 0x18, "Host Bus", 1 }, + { 0x0300, 0x38, "Memory", 1 }, +/* { 0x0400, 0x100, "Sensor", 0 }, */ +/* { 0x0500, 0x300, "ISP", 0 }, */ +/* { 0x0800, 0x400, "JPEG", 0 }, */ +/* { 0x0c00, 0xcc, "MPEG", 0 }, */ + { 0x1100, 0xb2, "LCD 1", 1 }, + { 0x1200, 0x64, "LCD 2", 1 }, + { 0x1400, 0x42, "MMC", 1 }, +/* { 0x1500, 0x080, "MPU 0", 0 }, + { 0x1580, 0x080, "MPU 1", 0 }, + { 0x1600, 0x080, "Cmd Queue", 0 }, + { 0x1680, 0x080, "RISC CPU", 0 },*/ + { 0x1700, 0x400, "2D Unit", 1 }, +/* { 0x1b00, 0x900, "3D Unit", 0 }, */ +}; + +static inline void __reg_write(struct glamo_core *glamo, + uint16_t reg, uint16_t val) +{ + writew(val, glamo->base + reg); +} + +void glamo_reg_write(struct glamo_core *glamo, + uint16_t reg, uint16_t val) +{ + spin_lock(&glamo->lock); + __reg_write(glamo, reg, val); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL_GPL(glamo_reg_write); + + +static inline uint16_t __reg_read(struct glamo_core *glamo, + uint16_t reg) +{ + return readw(glamo->base + reg); +} + +uint16_t glamo_reg_read(struct glamo_core *glamo, uint16_t reg) +{ + uint16_t val; + spin_lock(&glamo->lock); + val = __reg_read(glamo, reg); + spin_unlock(&glamo->lock); + + return val; +} +EXPORT_SYMBOL_GPL(glamo_reg_read); + +static void __reg_set_bit_mask(struct glamo_core *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + uint16_t tmp; + + val &= mask; + + tmp = __reg_read(glamo, reg); + tmp &= ~mask; + tmp |= val; + __reg_write(glamo, reg, tmp); +} + +static void reg_set_bit_mask(struct glamo_core *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + spin_lock(&glamo->lock); + __reg_set_bit_mask(glamo, reg, mask, val); + spin_unlock(&glamo->lock); +} + +static inline void __reg_set_bit(struct glamo_core *glamo, + uint16_t reg, uint16_t bit) +{ + uint16_t tmp; + tmp = __reg_read(glamo, reg); + tmp |= bit; + __reg_write(glamo, reg, tmp); +} + +static inline void __reg_clear_bit(struct glamo_core *glamo, + uint16_t reg, uint16_t bit) +{ + uint16_t tmp; + tmp = __reg_read(glamo, reg); + tmp &= ~bit; + __reg_write(glamo, reg, tmp); +} + +static void __reg_write_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + uint16_t end; + for (end = reg + count * 2; reg < end; reg += 2, ++values) + __reg_write(glamo, reg, *values); +} + +static void __reg_read_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + uint16_t end; + for (end = reg + count * 2; reg < end; reg += 2, ++values) + *values = __reg_read(glamo, reg); +} + +void glamo_reg_write_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + spin_lock(&glamo->lock); + __reg_write_batch(glamo, reg, count, values); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL(glamo_reg_write_batch); + +void glamo_reg_read_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values) +{ + spin_lock(&glamo->lock); + __reg_read_batch(glamo, reg, count, values); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL(glamo_reg_read_batch); + +/*********************************************************************** + * resources of sibling devices + ***********************************************************************/ + +static struct resource glamo_fb_resources[] = { + { + .name = "glamo-fb-regs", + .start = GLAMO_REGOFS_LCD, + .end = GLAMO_REGOFS_MMC - 1, + .flags = IORESOURCE_MEM, + }, { + .name = "glamo-fb-mem", + .start = GLAMO_OFFSET_FB, + .end = GLAMO_OFFSET_FB + GLAMO_FB_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource glamo_mmc_resources[] = { + { + .name = "glamo-mmc-regs", + .start = GLAMO_REGOFS_MMC, + .end = GLAMO_REGOFS_MPROC0 - 1, + .flags = IORESOURCE_MEM + }, { + .name = "glamo-mmc-mem", + .start = GLAMO_OFFSET_FB + GLAMO_FB_SIZE, + .end = GLAMO_OFFSET_FB + GLAMO_FB_SIZE + + GLAMO_MMC_BUFFER_SIZE - 1, + .flags = IORESOURCE_MEM + }, { + .start = GLAMO_IRQ_MMC, + .end = GLAMO_IRQ_MMC, + .flags = IORESOURCE_IRQ, + }, +}; + +enum glamo_cells { + GLAMO_CELL_FB, + GLAMO_CELL_MMC, + GLAMO_CELL_GPIO, +}; + +static const struct mfd_cell glamo_cells[] = { + [GLAMO_CELL_FB] = { + .name = "glamo-fb", + .num_resources = ARRAY_SIZE(glamo_fb_resources), + .resources = glamo_fb_resources, + }, + [GLAMO_CELL_MMC] = { + .name = "glamo-mci", + .num_resources = ARRAY_SIZE(glamo_mmc_resources), + .resources = glamo_mmc_resources, + }, + [GLAMO_CELL_GPIO] = { + .name = "glamo-gpio", + }, +}; + +/*********************************************************************** + * IRQ demultiplexer + ***********************************************************************/ +#define glamo_irq_bit(glamo, x) BIT(x - glamo->irq_base) + +static inline struct glamo_core *irq_to_glamo(unsigned int irq) +{ + return (struct glamo_core *)get_irq_chip_data(irq); +} + +static void glamo_ack_irq(unsigned int irq) +{ + struct glamo_core *glamo = irq_to_glamo(irq); + /* clear interrupt source */ + __reg_write(glamo, GLAMO_REG_IRQ_CLEAR, glamo_irq_bit(glamo, irq)); +} + +static void glamo_mask_irq(unsigned int irq) +{ + struct glamo_core *glamo = irq_to_glamo(irq); + + /* clear bit in enable register */ + __reg_clear_bit(glamo, GLAMO_REG_IRQ_ENABLE, glamo_irq_bit(glamo, irq)); +} + +static void glamo_unmask_irq(unsigned int irq) +{ + struct glamo_core *glamo = irq_to_glamo(irq); + + /* set bit in enable register */ + __reg_set_bit(glamo, GLAMO_REG_IRQ_ENABLE, glamo_irq_bit(glamo, irq)); +} + +static struct irq_chip glamo_irq_chip = { + .name = "glamo", + .ack = glamo_ack_irq, + .mask = glamo_mask_irq, + .unmask = glamo_unmask_irq, +}; + +static void glamo_irq_demux_handler(unsigned int irq, struct irq_desc *desc) +{ + struct glamo_core *glamo = get_irq_desc_data(desc); + desc->status &= ~(IRQ_REPLAY | IRQ_WAITING); + + if (unlikely(desc->status & IRQ_INPROGRESS)) { + desc->status |= (IRQ_PENDING | IRQ_MASKED); + desc->chip->mask(irq); + desc->chip->ack(irq); + return; + } + kstat_incr_irqs_this_cpu(irq, desc); + + desc->chip->ack(irq); + desc->status |= IRQ_INPROGRESS; + + do { + uint16_t irqstatus; + int i; + + if (unlikely((desc->status & + (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) == + (IRQ_PENDING | IRQ_MASKED))) { + /* dealing with pending IRQ, unmasking */ + desc->chip->unmask(irq); + desc->status &= ~IRQ_MASKED; + } + + desc->status &= ~IRQ_PENDING; + + /* read IRQ status register */ + irqstatus = __reg_read(glamo, GLAMO_REG_IRQ_STATUS); + for (i = 0; i < 9; ++i) { + if (irqstatus & BIT(i)) + generic_handle_irq(glamo->irq_base + i); + } + + } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING); + + desc->status &= ~IRQ_INPROGRESS; +} + +/* +sysfs +*/ + +static ssize_t regs_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + unsigned int reg; + unsigned int val; + + sscanf(buf, "%u %u", ®, &val); + printk(KERN_INFO"reg 0x%02x <-- 0x%04x\n", + reg, val); + + glamo_reg_write(glamo, reg, val); + + return count; +} + +static ssize_t regs_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + int i, n; + char *end = buf; + const struct reg_range *rr = reg_range; + + spin_lock(&glamo->lock); + + for (i = 0; i < ARRAY_SIZE(reg_range); ++i, ++rr) { + if (!rr->dump) + continue; + end += sprintf(end, "\n%s\n", rr->name); + for (n = rr->start; n < rr->start + rr->count; n += 2) { + if ((n & 15) == 0) + end += sprintf(end, "\n%04X: ", n); + end += sprintf(end, "%04x ", __reg_read(glamo, n)); + } + end += sprintf(end, "\n"); + } + spin_unlock(&glamo->lock); + + return end - buf; +} + +static DEVICE_ATTR(regs, 0644, regs_read, regs_write); + +struct glamo_engine_reg_set { + uint16_t reg; + uint16_t mask_suspended; + uint16_t mask_enabled; +}; + +struct glamo_engine_desc { + const char *name; + uint16_t hostbus; + const struct glamo_engine_reg_set *regs; + int num_regs; +}; + +static const struct glamo_engine_reg_set glamo_lcd_regs[] = { + { GLAMO_REG_CLOCK_LCD, + GLAMO_CLOCK_LCD_EN_M5CLK | + GLAMO_CLOCK_LCD_DG_M5CLK | + GLAMO_CLOCK_LCD_EN_DMCLK, + + GLAMO_CLOCK_LCD_EN_DHCLK | + GLAMO_CLOCK_LCD_EN_DCLK + }, + { GLAMO_REG_CLOCK_GEN5_1, + GLAMO_CLOCK_GEN51_EN_DIV_DMCLK, + + GLAMO_CLOCK_GEN51_EN_DIV_DHCLK | + GLAMO_CLOCK_GEN51_EN_DIV_DCLK + } +}; + +static const struct glamo_engine_reg_set glamo_mmc_regs[] = { + { GLAMO_REG_CLOCK_MMC, + GLAMO_CLOCK_MMC_EN_M9CLK | + GLAMO_CLOCK_MMC_DG_M9CLK, + + GLAMO_CLOCK_MMC_EN_TCLK | + GLAMO_CLOCK_MMC_DG_TCLK + }, + { GLAMO_REG_CLOCK_GEN5_1, + 0, + GLAMO_CLOCK_GEN51_EN_DIV_TCLK + } +}; + +static const struct glamo_engine_reg_set glamo_2d_regs[] = { + { GLAMO_REG_CLOCK_2D, + GLAMO_CLOCK_2D_EN_M7CLK | + GLAMO_CLOCK_2D_DG_M7CLK, + + GLAMO_CLOCK_2D_EN_GCLK | + GLAMO_CLOCK_2D_DG_GCLK + }, + { GLAMO_REG_CLOCK_GEN5_1, + 0, + GLAMO_CLOCK_GEN51_EN_DIV_GCLK, + } +}; + +static const struct glamo_engine_reg_set glamo_cmdq_regs[] = { + { GLAMO_REG_CLOCK_2D, + GLAMO_CLOCK_2D_EN_M6CLK, + 0 + }, +}; + +#define GLAMO_ENGINE(xname, xhostbus, xregs) { \ + .name = xname, \ + .hostbus = xhostbus, \ + .num_regs = ARRAY_SIZE(xregs), \ + .regs = xregs, \ +} + +static const struct glamo_engine_desc glamo_engines[] = { + [GLAMO_ENGINE_LCD] = GLAMO_ENGINE("LCD", GLAMO_HOSTBUS2_MMIO_EN_LCD, + glamo_lcd_regs), + [GLAMO_ENGINE_MMC] = GLAMO_ENGINE("MMC", GLAMO_HOSTBUS2_MMIO_EN_MMC, + glamo_mmc_regs), + [GLAMO_ENGINE_2D] = GLAMO_ENGINE("2D", GLAMO_HOSTBUS2_MMIO_EN_2D, + glamo_2d_regs), + [GLAMO_ENGINE_CMDQ] = GLAMO_ENGINE("CMDQ", GLAMO_HOSTBUS2_MMIO_EN_CQ, + glamo_cmdq_regs), +}; + +static inline const char *glamo_engine_name(enum glamo_engine engine) +{ + return glamo_engines[engine].name; +} + +/*********************************************************************** + * 'engine' support + ***********************************************************************/ + +int __glamo_engine_enable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int i; + const struct glamo_engine_desc *engine_desc = &glamo_engines[engine]; + const struct glamo_engine_reg_set *reg; + + switch (engine) { + case GLAMO_ENGINE_LCD: + case GLAMO_ENGINE_MMC: + case GLAMO_ENGINE_2D: + case GLAMO_ENGINE_CMDQ: + break; + default: + return -EINVAL; + } + + reg = engine_desc->regs; + + __reg_set_bit(glamo, GLAMO_REG_HOSTBUS(2), + engine_desc->hostbus); + for (i = engine_desc->num_regs; i; --i, ++reg) + __reg_set_bit(glamo, reg->reg, + reg->mask_suspended | reg->mask_enabled); + + return 0; +} + +int glamo_engine_enable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int ret = 0; + + spin_lock(&glamo->lock); + + if (glamo->engine_state[engine] != GLAMO_ENGINE_ENABLED) { + ret = __glamo_engine_enable(glamo, engine); + if (!ret) + glamo->engine_state[engine] = GLAMO_ENGINE_ENABLED; + } + + spin_unlock(&glamo->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(glamo_engine_enable); + +int __glamo_engine_disable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int i; + const struct glamo_engine_desc *engine_desc = &glamo_engines[engine]; + const struct glamo_engine_reg_set *reg; + + switch (engine) { + case GLAMO_ENGINE_LCD: + case GLAMO_ENGINE_MMC: + case GLAMO_ENGINE_2D: + case GLAMO_ENGINE_CMDQ: + break; + default: + return -EINVAL; + } + + reg = engine_desc->regs; + + __reg_clear_bit(glamo, GLAMO_REG_HOSTBUS(2), + engine_desc->hostbus); + for (i = engine_desc->num_regs; i; --i, ++reg) + __reg_clear_bit(glamo, reg->reg, + reg->mask_suspended | reg->mask_enabled); + + return 0; +} +int glamo_engine_disable(struct glamo_core *glamo, enum glamo_engine engine) +{ + int ret = 0; + + spin_lock(&glamo->lock); + + if (glamo->engine_state[engine] != GLAMO_ENGINE_DISABLED) { + ret = __glamo_engine_disable(glamo, engine); + if (!ret) + glamo->engine_state[engine] = GLAMO_ENGINE_DISABLED; + } + + spin_unlock(&glamo->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(glamo_engine_disable); + +int __glamo_engine_suspend(struct glamo_core *glamo, enum glamo_engine engine) +{ + int i; + const struct glamo_engine_desc *engine_desc = &glamo_engines[engine]; + const struct glamo_engine_reg_set *reg; + + switch (engine) { + case GLAMO_ENGINE_LCD: + case GLAMO_ENGINE_MMC: + case GLAMO_ENGINE_2D: + case GLAMO_ENGINE_CMDQ: + break; + default: + return -EINVAL; + } + + reg = engine_desc->regs; + + __reg_set_bit(glamo, GLAMO_REG_HOSTBUS(2), + engine_desc->hostbus); + for (i = engine_desc->num_regs; i; --i, ++reg) { + __reg_set_bit(glamo, reg->reg, reg->mask_suspended); + __reg_clear_bit(glamo, reg->reg, reg->mask_enabled); + } + + return 0; +} + +int glamo_engine_suspend(struct glamo_core *glamo, enum glamo_engine engine) +{ + int ret = 0; + + spin_lock(&glamo->lock); + + if (glamo->engine_state[engine] != GLAMO_ENGINE_SUSPENDED) { + ret = __glamo_engine_suspend(glamo, engine); + if (!ret) + glamo->engine_state[engine] = GLAMO_ENGINE_SUSPENDED; + } + + spin_unlock(&glamo->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(glamo_engine_suspend); + +static const struct glamo_script reset_regs[] = { + [GLAMO_ENGINE_LCD] = { + GLAMO_REG_CLOCK_LCD, GLAMO_CLOCK_LCD_RESET + }, + [GLAMO_ENGINE_MMC] = { + GLAMO_REG_CLOCK_MMC, GLAMO_CLOCK_MMC_RESET + }, + [GLAMO_ENGINE_CMDQ] = { + GLAMO_REG_CLOCK_2D, GLAMO_CLOCK_2D_CQ_RESET + }, + [GLAMO_ENGINE_2D] = { + GLAMO_REG_CLOCK_2D, GLAMO_CLOCK_2D_RESET + }, + [GLAMO_ENGINE_JPEG] = { + GLAMO_REG_CLOCK_JPEG, GLAMO_CLOCK_JPEG_RESET + }, +}; + +void glamo_engine_reset(struct glamo_core *glamo, enum glamo_engine engine) +{ + uint16_t reg = reset_regs[engine].reg; + uint16_t val = reset_regs[engine].val; + + if (engine >= ARRAY_SIZE(reset_regs)) { + dev_warn(&glamo->pdev->dev, "unknown engine %u ", engine); + return; + } + + spin_lock(&glamo->lock); + __reg_set_bit(glamo, reg, val); + __reg_clear_bit(glamo, reg, val); + spin_unlock(&glamo->lock); +} +EXPORT_SYMBOL_GPL(glamo_engine_reset); + +int glamo_pll_rate(struct glamo_core *glamo, + enum glamo_pll pll) +{ + uint16_t reg; + unsigned int osci = glamo->pdata->osci_clock_rate; + + switch (pll) { + case GLAMO_PLL1: + reg = __reg_read(glamo, GLAMO_REG_PLL_GEN1); + break; + case GLAMO_PLL2: + reg = __reg_read(glamo, GLAMO_REG_PLL_GEN3); + break; + default: + return -EINVAL; + } + + return (int)osci * (int)reg; +} +EXPORT_SYMBOL_GPL(glamo_pll_rate); + +int glamo_engine_reclock(struct glamo_core *glamo, + enum glamo_engine engine, + int hz) +{ + int pll; + uint16_t reg, mask, div; + + if (!hz) + return -EINVAL; + + switch (engine) { + case GLAMO_ENGINE_LCD: + pll = GLAMO_PLL1; + reg = GLAMO_REG_CLOCK_GEN7; + mask = 0xff; + break; + case GLAMO_ENGINE_MMC: + pll = GLAMO_PLL1; + reg = GLAMO_REG_CLOCK_GEN8; + mask = 0xff; + break; + default: + dev_warn(&glamo->pdev->dev, + "reclock of engine 0x%x not supported\n", engine); + return -EINVAL; + break; + } + + pll = glamo_pll_rate(glamo, pll); + + div = pll / hz; + + if (div != 0 && pll / div <= hz) + --div; + + if (div > mask) + div = mask; + + dev_dbg(&glamo->pdev->dev, + "PLL %d, kHZ %d, div %d\n", pll, hz / 1000, div); + + reg_set_bit_mask(glamo, reg, mask, div); + mdelay(5); /* wait some time to stabilize */ + + return pll / (div + 1); +} +EXPORT_SYMBOL_GPL(glamo_engine_reclock); + +/*********************************************************************** + * script support + ***********************************************************************/ + +#define GLAMO_SCRIPT_END 0xffff +#define GLAMO_SCRIPT_WAIT 0xfffe +#define GLAMO_SCRIPT_LOCK_PLL 0xfffd + +/* + * couple of people reported artefacts with 2.6.28 changes, this + * allows reversion to 2.6.24 settings +*/ +static const uint16_t reg_0x200[] = { +0xe03, /* 0 waits on Async BB R & W, Use PLL 2 for mem bus */ +0xef0, /* 3 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xea0, /* 2 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xe50, /* 1 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xe00, /* 0 waits on Async BB R & W, Use PLL 1 for mem bus */ +0xef3, /* 3 waits on Async BB R & W, Use PLL 2 for mem bus */ +0xea3, /* 2 waits on Async BB R & W, Use PLL 2 for mem bus */ +0xe53, /* 1 waits on Async BB R & W, Use PLL 2 for mem bus */ +}; + +static int glamo_run_script(struct glamo_core *glamo, + const struct glamo_script *script, int len, + int may_sleep) +{ + int i; + uint16_t status; + const struct glamo_script *line = script; + + for (i = 0; i < len; ++i, ++line) { + switch (line->reg) { + case GLAMO_SCRIPT_END: + return 0; + case GLAMO_SCRIPT_WAIT: + if (may_sleep) + msleep(line->val); + else + mdelay(line->val * 4); + break; + case GLAMO_SCRIPT_LOCK_PLL: + /* spin until PLLs lock */ + do { + status = __reg_read(glamo, GLAMO_REG_PLL_GEN5); + } while ((status & 3) != 3); + break; + case 0x200: + __reg_write(glamo, line->reg, + reg_0x200[slow_memory & 0x8]); + break; + default: + __reg_write(glamo, line->reg, line->val); + break; + } + } + + return 0; +} + +static const struct glamo_script glamo_init_script[] = { + { GLAMO_REG_CLOCK_HOST, 0x1000 }, + { GLAMO_SCRIPT_WAIT, 2 }, + { GLAMO_REG_CLOCK_MEMORY, 0x1000 }, + { GLAMO_REG_CLOCK_MEMORY, 0x2000 }, + { GLAMO_REG_CLOCK_LCD, 0x1000 }, + { GLAMO_REG_CLOCK_MMC, 0x1000 }, + { GLAMO_REG_CLOCK_ISP, 0x1000 }, + { GLAMO_REG_CLOCK_ISP, 0x3000 }, + { GLAMO_REG_CLOCK_JPEG, 0x1000 }, + { GLAMO_REG_CLOCK_3D, 0x1000 }, + { GLAMO_REG_CLOCK_3D, 0x3000 }, + { GLAMO_REG_CLOCK_2D, 0x1000 }, + { GLAMO_REG_CLOCK_2D, 0x3000 }, + { GLAMO_REG_CLOCK_RISC1, 0x1000 }, + { GLAMO_REG_CLOCK_MPEG, 0x1000 }, + { GLAMO_REG_CLOCK_MPEG, 0x3000 }, + { GLAMO_REG_CLOCK_MPROC, 0x1000 /*0x100f*/ }, + { GLAMO_SCRIPT_WAIT, 2 }, + { GLAMO_REG_CLOCK_HOST, 0x0000 }, + { GLAMO_REG_CLOCK_MEMORY, 0x0000 }, + { GLAMO_REG_CLOCK_LCD, 0x0000 }, + { GLAMO_REG_CLOCK_MMC, 0x0000 }, + { GLAMO_REG_PLL_GEN1, 0x05db }, /* 48MHz */ + { GLAMO_REG_PLL_GEN3, 0x0aba }, /* 90MHz */ + { GLAMO_SCRIPT_LOCK_PLL, 0 }, + /* + * b9 of this register MUST be zero to get any interrupts on INT# + * the other set bits enable all the engine interrupt sources + */ + { GLAMO_REG_IRQ_ENABLE, 0x0100 }, + { GLAMO_REG_CLOCK_GEN6, 0x2000 }, + { GLAMO_REG_CLOCK_GEN7, 0x0101 }, + { GLAMO_REG_CLOCK_GEN8, 0x0100 }, + { GLAMO_REG_CLOCK_HOST, 0x000d }, + /* + * b7..b4 = 0 = no wait states on read or write + * b0 = 1 select PLL2 for Host interface, b1 = enable it + */ + { GLAMO_REG_HOSTBUS(0), 0x0e03 /* this is replaced by script parser */ }, + { GLAMO_REG_HOSTBUS(1), 0x07ff }, /* TODO: Disable all */ + { GLAMO_REG_HOSTBUS(10), 0x0000 }, + { GLAMO_REG_HOSTBUS(11), 0x4000 }, + { GLAMO_REG_HOSTBUS(12), 0xf00e }, + + /* S-Media recommended "set tiling mode to 512 mode for memory access + * more efficiency when 640x480" */ + { GLAMO_REG_MEM_TYPE, 0x0c74 }, /* 8MB, 16 word pg wr+rd */ + { GLAMO_REG_MEM_GEN, 0xafaf }, /* 63 grants min + max */ + + { GLAMO_REG_MEM_TIMING1, 0x0108 }, + { GLAMO_REG_MEM_TIMING2, 0x0010 }, /* Taa = 3 MCLK */ + { GLAMO_REG_MEM_TIMING3, 0x0000 }, + { GLAMO_REG_MEM_TIMING4, 0x0000 }, /* CE1# delay fall/rise */ + { GLAMO_REG_MEM_TIMING5, 0x0000 }, /* UB# LB# */ + { GLAMO_REG_MEM_TIMING6, 0x0000 }, /* OE# */ + { GLAMO_REG_MEM_TIMING7, 0x0000 }, /* WE# */ + { GLAMO_REG_MEM_TIMING8, 0x1002 }, /* MCLK delay, was 0x1000 */ + { GLAMO_REG_MEM_TIMING9, 0x6006 }, + { GLAMO_REG_MEM_TIMING10, 0x00ff }, + { GLAMO_REG_MEM_TIMING11, 0x0001 }, + { GLAMO_REG_MEM_POWER1, 0x0020 }, + { GLAMO_REG_MEM_POWER2, 0x0000 }, + { GLAMO_REG_MEM_DRAM1, 0x0000 }, + { GLAMO_SCRIPT_WAIT, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xc100 }, + { GLAMO_SCRIPT_WAIT, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xe100 }, + { GLAMO_REG_MEM_DRAM2, 0x01d6 }, + { GLAMO_REG_CLOCK_MEMORY, 0x000b }, +}; + +/* Find out if we can support this version of the Glamo chip */ +static int __devinit glamo_supported(struct glamo_core *glamo) +{ + uint16_t dev_id, rev_id; + + dev_id = __reg_read(glamo, GLAMO_REG_DEVICE_ID); + rev_id = __reg_read(glamo, GLAMO_REG_REVISION_ID); + + switch (dev_id) { + case 0x3650: + switch (rev_id) { + case GLAMO_CORE_REV_A2: + break; + case GLAMO_CORE_REV_A0: + case GLAMO_CORE_REV_A1: + case GLAMO_CORE_REV_A3: + dev_warn(&glamo->pdev->dev, "untested core revision " + "%04x, your mileage may vary\n", rev_id); + break; + default: + dev_warn(&glamo->pdev->dev, "unknown glamo revision " + "%04x, your mileage may vary\n", rev_id); + } + break; + default: + dev_err(&glamo->pdev->dev, "unsupported Glamo device %04x\n", + dev_id); + return 0; + } + + dev_dbg(&glamo->pdev->dev, "Detected Glamo core %04x Revision %04x " + "(%uHz CPU / %uHz Memory)\n", dev_id, rev_id, + glamo_pll_rate(glamo, GLAMO_PLL1), + glamo_pll_rate(glamo, GLAMO_PLL2)); + + return 1; +} + +static int __devinit glamo_probe(struct platform_device *pdev) +{ + int ret = 0, irq, irq_base; + struct glamo_core *glamo; + struct resource *mem; + + glamo = kmalloc(GFP_KERNEL, sizeof(*glamo)); + if (!glamo) + return -ENOMEM; + + spin_lock_init(&glamo->lock); + + glamo->pdev = pdev; + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + glamo->irq = platform_get_irq(pdev, 0); + glamo->irq_base = irq_base = platform_get_irq(pdev, 1); + glamo->pdata = pdev->dev.platform_data; + + if (glamo->irq < 0) { + ret = glamo->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + goto err_free; + } + + if (irq_base < 0) { + ret = glamo->irq; + dev_err(&pdev->dev, "Failed to get glamo irq base: %d\n", ret); + goto err_free; + } + + if (!mem) { + dev_err(&pdev->dev, "Failed to get platform memory\n"); + ret = -ENOENT; + goto err_free; + } + + if (!glamo->pdata) { + dev_err(&pdev->dev, "Missing platform data\n"); + ret = -ENOENT; + goto err_free; + } + + /* only request the generic, hostbus and memory controller registers */ + glamo->mem = request_mem_region(mem->start, GLAMO_REGOFS_VIDCAP, + pdev->name); + + if (!glamo->mem) { + dev_err(&pdev->dev, "Failed to request io memory region\n"); + ret = -ENOENT; + goto err_free; + } + + glamo->base = ioremap(glamo->mem->start, resource_size(glamo->mem)); + if (!glamo->base) { + dev_err(&pdev->dev, "Failed to ioremap() memory region\n"); + goto err_release_mem_region; + } + + /* confirm it isn't insane version */ + if (!glamo_supported(glamo)) { + dev_err(&pdev->dev, + "This version of the Glamo is not supported\n"); + goto err_iounmap; + } + + platform_set_drvdata(pdev, glamo); + + /* sysfs */ + ret = device_create_file(&pdev->dev, &dev_attr_regs); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to create sysfs file\n"); + goto err_iounmap; + } + + /* init the chip with canned register set */ + glamo_run_script(glamo, glamo_init_script, + ARRAY_SIZE(glamo_init_script), 1); + + /* + * finally set the mfd interrupts up + */ + for (irq = irq_base; irq < irq_base + GLAMO_NR_IRQS; ++irq) { +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + set_irq_chip_data(irq, glamo); + set_irq_chip_and_handler(irq, &glamo_irq_chip, + handle_level_irq); + } + + set_irq_type(glamo->irq, IRQ_TYPE_EDGE_FALLING); + set_irq_data(glamo->irq, glamo); + set_irq_chained_handler(glamo->irq, glamo_irq_demux_handler); + glamo->irq_works = 1; + + ret = mfd_add_devices(&pdev->dev, pdev->id, glamo_cells, + ARRAY_SIZE(glamo_cells), mem, glamo->irq_base); + + if (ret) { + dev_err(&pdev->dev, "Failed to add child devices: %d\n", ret); + goto err_free_irqs; + } + + dev_info(&glamo->pdev->dev, "Glamo core PLL1: %uHz, PLL2: %uHz\n", + glamo_pll_rate(glamo, GLAMO_PLL1), + glamo_pll_rate(glamo, GLAMO_PLL2)); + + return 0; + +err_free_irqs: + disable_irq(glamo->irq); + set_irq_chained_handler(glamo->irq, NULL); + set_irq_chip_data(glamo->irq, NULL); + + for (irq = irq_base; irq < irq_base + GLAMO_NR_IRQS; ++irq) { + set_irq_chip(irq, NULL); +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#else + set_irq_probe(irq); +#endif + set_irq_chip_data(irq, NULL); + } +err_iounmap: + iounmap(glamo->base); +err_release_mem_region: + release_mem_region(glamo->mem->start, resource_size(glamo->mem)); +err_free: + platform_set_drvdata(pdev, NULL); + kfree(glamo); + + return ret; +} + +static int __devexit glamo_remove(struct platform_device *pdev) +{ + struct glamo_core *glamo = platform_get_drvdata(pdev); + int irq; + int irq_base = glamo->irq_base; + + mfd_remove_devices(&pdev->dev); + + disable_irq(glamo->irq); + set_irq_chained_handler(glamo->irq, NULL); + set_irq_chip_data(glamo->irq, NULL); + + for (irq = irq_base; irq < irq_base + GLAMO_NR_IRQS; ++irq) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#else + set_irq_noprobe(); +#endif + set_irq_chip(irq, NULL); + set_irq_chip_data(irq, NULL); + } + + platform_set_drvdata(pdev, NULL); + iounmap(glamo->base); + release_mem_region(glamo->mem->start, resource_size(glamo->mem)); + kfree(glamo); + + return 0; +} + +#ifdef CONFIG_PM +#if 0 +static struct glamo_script glamo_resume_script[] = { + + { GLAMO_REG_PLL_GEN1, 0x05db }, /* 48MHz */ + { GLAMO_REG_PLL_GEN3, 0x0aba }, /* 90MHz */ + { GLAMO_REG_DFT_GEN6, 1 }, + { 0xfffe, 100 }, + { 0xfffd, 0 }, + { 0x200, 0x0e03 }, + + /* + * b9 of this register MUST be zero to get any interrupts on INT# + * the other set bits enable all the engine interrupt sources + */ + { GLAMO_REG_IRQ_ENABLE, 0x01ff }, + { GLAMO_REG_CLOCK_HOST, 0x0018 }, + { GLAMO_REG_CLOCK_GEN5_1, 0x18b1 }, + + { GLAMO_REG_MEM_DRAM1, 0x0000 }, + { 0xfffe, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xc100 }, + { 0xfffe, 1 }, + { GLAMO_REG_MEM_DRAM1, 0xe100 }, + { GLAMO_REG_MEM_DRAM2, 0x01d6 }, + { GLAMO_REG_CLOCK_MEMORY, 0x000b }, +}; +#endif + +#if 0 +static void glamo_power(struct glamo_core *glamo) +{ + unsigned long flags; + + spin_lock_irqsave(&glamo->lock, flags); + + /* +Power management +static const REG_VALUE_MASK_TYPE reg_powerOn[] = +{ + { REG_GEN_DFT6, REG_BIT_ALL, REG_DATA(1u << 0) }, + { REG_GEN_PLL3, 0u, REG_DATA(1u << 13) }, + { REG_GEN_MEM_CLK, REG_BIT_ALL, REG_BIT_EN_MOCACLK }, + { REG_MEM_DRAM2, 0u, REG_BIT_EN_DEEP_POWER_DOWN }, + { REG_MEM_DRAM1, 0u, REG_BIT_SELF_REFRESH } +}; + +static const REG_VALUE_MASK_TYPE reg_powerStandby[] = +{ + { REG_MEM_DRAM1, REG_BIT_ALL, REG_BIT_SELF_REFRESH }, + { REG_GEN_MEM_CLK, 0u, REG_BIT_EN_MOCACLK }, + { REG_GEN_PLL3, REG_BIT_ALL, REG_DATA(1u << 13) }, + { REG_GEN_DFT5, REG_BIT_ALL, REG_DATA(1u << 0) } +}; + +static const REG_VALUE_MASK_TYPE reg_powerSuspend[] = +{ + { REG_MEM_DRAM2, REG_BIT_ALL, REG_BIT_EN_DEEP_POWER_DOWN }, + { REG_GEN_MEM_CLK, 0u, REG_BIT_EN_MOCACLK }, + { REG_GEN_PLL3, REG_BIT_ALL, REG_DATA(1u << 13) }, + { REG_GEN_DFT5, REG_BIT_ALL, REG_DATA(1u << 0) } +}; +*/ + switch (new_state) { + case GLAMO_POWER_ON: + + /* + * glamo state on resume is nondeterministic in some + * fundamental way, it has also been observed that the + * Glamo reset pin can get asserted by, eg, touching it with + * a scope probe. So the only answer is to roll with it and + * force an external reset on the Glamo during resume. + */ + + + break; + + case GLAMO_POWER_SUSPEND: + + break; + } + spin_unlock_irqrestore(&glamo->lock, flags); +} +#endif + +static int glamo_suspend(struct device *dev) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + int n; + + spin_lock(&glamo->lock); + + /* nuke interrupts */ + __reg_write(glamo, GLAMO_REG_IRQ_ENABLE, 0x200); + + /* take down each engine before we kill mem and pll */ + for (n = 0; n < __NUM_GLAMO_ENGINES; n++) { + if (glamo->engine_state != GLAMO_ENGINE_DISABLED) + __glamo_engine_disable(glamo, n); + } + + /* enable self-refresh */ + + __reg_write(glamo, GLAMO_REG_MEM_DRAM1, + GLAMO_MEM_DRAM1_EN_DRAM_REFRESH | + GLAMO_MEM_DRAM1_EN_GATE_CKE | + GLAMO_MEM_DRAM1_SELF_REFRESH | + GLAMO_MEM_REFRESH_COUNT); + __reg_write(glamo, GLAMO_REG_MEM_DRAM1, + GLAMO_MEM_DRAM1_EN_MODEREG_SET | + GLAMO_MEM_DRAM1_EN_DRAM_REFRESH | + GLAMO_MEM_DRAM1_EN_GATE_CKE | + GLAMO_MEM_DRAM1_SELF_REFRESH | + GLAMO_MEM_REFRESH_COUNT); + + /* force RAM into deep powerdown */ + __reg_write(glamo, GLAMO_REG_MEM_DRAM2, + GLAMO_MEM_DRAM2_DEEP_PWRDOWN | + (7 << 6) | /* tRC */ + (1 << 4) | /* tRP */ + (1 << 2) | /* tRCD */ + 2); /* CAS latency */ + + /* disable clocks to memory */ + __reg_write(glamo, GLAMO_REG_CLOCK_MEMORY, 0); + + /* all dividers from OSCI */ + __reg_set_bit_mask(glamo, GLAMO_REG_CLOCK_GEN5_1, 0x400, 0x400); + + /* PLL2 into bypass */ + __reg_set_bit_mask(glamo, GLAMO_REG_PLL_GEN3, 1 << 12, 1 << 12); + + __reg_write(glamo, GLAMO_BASIC_MMC_EN_TCLK_DLYA1, 0x0e00); + + /* kill PLLS 1 then 2 */ + __reg_write(glamo, GLAMO_REG_DFT_GEN5, 0x0001); + __reg_set_bit_mask(glamo, GLAMO_REG_PLL_GEN3, 1 << 13, 1 << 13); + + spin_unlock(&glamo->lock); + + return 0; +} + +static int glamo_resume(struct device *dev) +{ + struct glamo_core *glamo = dev_get_drvdata(dev); + int n; + + (glamo->pdata->glamo_external_reset)(0); + udelay(10); + (glamo->pdata->glamo_external_reset)(1); + mdelay(5); + + spin_lock(&glamo->lock); + + glamo_run_script(glamo, glamo_init_script, + ARRAY_SIZE(glamo_init_script), 0); + + + for (n = 0; n < __NUM_GLAMO_ENGINES; n++) { + switch (glamo->engine_state[n]) { + case GLAMO_ENGINE_SUSPENDED: + __glamo_engine_suspend(glamo, n); + break; + case GLAMO_ENGINE_ENABLED: + __glamo_engine_enable(glamo, n); + break; + default: + break; + } + } + + spin_unlock(&glamo->lock); + + return 0; +} + +static struct dev_pm_ops glamo_pm_ops = { + .suspend = glamo_suspend, + .resume = glamo_resume, + .poweroff = glamo_suspend, + .restore = glamo_resume, +}; + +#define GLAMO_PM_OPS (&glamo_pm_ops) + +#else +#define GLAMO_PM_OPS NULL +#endif + +static struct platform_driver glamo_driver = { + .probe = glamo_probe, + .remove = __devexit_p(glamo_remove), + .driver = { + .name = "glamo3362", + .owner = THIS_MODULE, + .pm = GLAMO_PM_OPS, + }, +}; + +static int __devinit glamo_init(void) +{ + return platform_driver_register(&glamo_driver); +} +module_init(glamo_init); + +static void __exit glamo_exit(void) +{ + platform_driver_unregister(&glamo_driver); +} +module_exit(glamo_exit); + +MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Smedia Glamo 3362 core/resource driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:glamo3362"); diff --git a/drivers/mfd/glamo/glamo-core.h b/drivers/mfd/glamo/glamo-core.h new file mode 100644 index 00000000000..e5b1a35518a --- /dev/null +++ b/drivers/mfd/glamo/glamo-core.h @@ -0,0 +1,60 @@ +#ifndef __GLAMO_CORE_H +#define __GLAMO_CORE_H + +#include <linux/mfd/glamo.h> + +/* for the time being, we put the on-screen framebuffer into the lowest + * VRAM space. This should make the code easily compatible with the various + * 2MB/4MB/8MB variants of the Smedia chips */ +#define GLAMO_OFFSET_VRAM 0x800000 +#define GLAMO_OFFSET_FB (GLAMO_OFFSET_VRAM) + +/* we only allocate the minimum possible size for the framebuffer to make + * sure we have sufficient memory for other functions of the chip */ +/*#define GLAMO_FB_SIZE (640*480*4) *//* == 0x12c000 */ +#define GLAMO_INTERNAL_RAM_SIZE 0x800000 +#define GLAMO_MMC_BUFFER_SIZE (64 * 1024) +#define GLAMO_FB_SIZE (GLAMO_INTERNAL_RAM_SIZE - GLAMO_MMC_BUFFER_SIZE) + +enum glamo_pll { + GLAMO_PLL1, + GLAMO_PLL2, +}; + +enum glamo_engine_state { + GLAMO_ENGINE_DISABLED, + GLAMO_ENGINE_SUSPENDED, + GLAMO_ENGINE_ENABLED, +}; + +struct glamo_core { + int irq; + int irq_base; + int irq_works; /* 0 means PCB does not support Glamo IRQ */ + struct resource *mem; + void __iomem *base; + struct platform_device *pdev; + struct glamo_platform_data *pdata; + enum glamo_engine_state engine_state[__NUM_GLAMO_ENGINES]; + spinlock_t lock; +}; + +struct glamo_script { + uint16_t reg; + uint16_t val; +}; + +int glamo_pll_rate(struct glamo_core *glamo, enum glamo_pll pll); + +int glamo_engine_enable(struct glamo_core *glamo, enum glamo_engine engine); +int glamo_engine_suspend(struct glamo_core *glamo, enum glamo_engine engine); +int glamo_engine_disable(struct glamo_core *glamo, enum glamo_engine engine); +void glamo_engine_reset(struct glamo_core *glamo, enum glamo_engine engine); +int glamo_engine_reclock(struct glamo_core *glamo, + enum glamo_engine engine, int ps); + +void glamo_reg_read_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values); +void glamo_reg_write_batch(struct glamo_core *glamo, uint16_t reg, + uint16_t count, uint16_t *values); +#endif /* __GLAMO_CORE_H */ diff --git a/drivers/mfd/glamo/glamo-fb.c b/drivers/mfd/glamo/glamo-fb.c new file mode 100644 index 00000000000..61a457760f1 --- /dev/null +++ b/drivers/mfd/glamo/glamo-fb.c @@ -0,0 +1,981 @@ +/* Smedia Glamo 336x/337x driver + * + * (C) 2007-2008 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * All rights reserved. + * + * 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 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/console.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/mfd/glamo.h> + +#include <asm/div64.h> + +#ifdef CONFIG_PM +#include <linux/pm.h> +#endif + +#include <linux/glamofb.h> + +#include "glamo-regs.h" +#include "glamo-core.h" + +static void glamofb_program_mode(struct glamofb_handle *glamo); + +struct glamofb_handle { + struct glamo_core *core; + struct fb_info *fb; + struct device *dev; + struct resource *reg; + struct resource *fb_res; + char __iomem *base; + struct glamo_fb_platform_data *mach_info; + char __iomem *cursor_addr; + int cursor_on; + u_int32_t pseudo_pal[16]; + spinlock_t lock_cmd; + int blank_mode; + int mode_set; /* 0 if the current display mode hasn't been set on the glamo */ + int output_enabled; /* 0 if the video output is disabled */ +}; + +static void glamo_output_enable(struct glamofb_handle *gfb) +{ + struct glamo_core *gcore = gfb->core; + + if (gfb->output_enabled) + return; + + /* enable the pixel clock if off */ + glamo_engine_enable(gcore, GLAMO_ENGINE_LCD); + + gfb->output_enabled = 1; + if (!gfb->mode_set) + glamofb_program_mode(gfb); +} + +static void glamo_output_disable(struct glamofb_handle *gfb) +{ + struct glamo_core *gcore = gfb->core; + + if (!gfb->output_enabled) + return; + + /* enable the pixel clock if off */ + glamo_engine_suspend(gcore, GLAMO_ENGINE_LCD); + + gfb->output_enabled = 0; +} + + +static int reg_read(struct glamofb_handle *glamo, + u_int16_t reg) +{ + int i = 0; + + for (i = 0; i != 2; i++) + nop(); + + return readw(glamo->base + reg); +} + +static void reg_write(struct glamofb_handle *glamo, + uint16_t reg, uint16_t val) +{ + int i = 0; + + for (i = 0; i != 2; i++) + nop(); + + writew(val, glamo->base + reg); +} + +static struct glamo_script glamo_regs[] = { + { GLAMO_REG_LCD_MODE1, 0x0020 }, + /* no display rotation, no hardware cursor, no dither, no gamma, + * no retrace flip, vsync low-active, hsync low active, + * no TVCLK, no partial display, hw dest color from fb, + * no partial display mode, LCD1, software flip, */ + { GLAMO_REG_LCD_MODE2, 0x9020 }, + /* video flip, no ptr, no ptr, dhclk off, + * normal mode, no cpuif, + * res, serial msb first, single fb, no fr ctrl, + * cpu if bits all zero, no crc + * 0000 0000 0010 0000 */ + { GLAMO_REG_LCD_MODE3, 0x0b40 }, + /* src data rgb565, res, 18bit rgb666 + * 000 01 011 0100 0000 */ + { GLAMO_REG_LCD_POLARITY, 0x440c }, + /* DE high active, no cpu/lcd if, cs0 force low, a0 low active, + * np cpu if, 9bit serial data, sclk rising edge latch data + * 01 00 0 100 0 000 01 0 0 */ + /* The following values assume 640*480@16bpp */ + { GLAMO_REG_LCD_A_BASE1, 0x0000 }, /* display A base address 15:0 */ + { GLAMO_REG_LCD_A_BASE2, 0x0000 }, /* display A base address 22:16 */ + { GLAMO_REG_LCD_CURSOR_BASE1, 0xC000 }, /* cursor base address 15:0 */ + { GLAMO_REG_LCD_CURSOR_BASE2, 0x0012 }, /* cursor base address 22:16 */ + { GLAMO_REG_LCD_COMMAND2, 0x0000 }, /* display page A */ +}; + +static int glamofb_run_script(struct glamofb_handle *glamo, + struct glamo_script *script, int len) +{ + int i; + + for (i = 0; i < len; i++) { + struct glamo_script *line = &script[i]; + + if (line->reg == 0xffff) + return 0; + else if (line->reg == 0xfffe) + msleep(line->val); + else + reg_write(glamo, script[i].reg, script[i].val); + } + + return 0; +} + +static int glamofb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct glamofb_handle *glamo = info->par; + + if (var->bits_per_pixel != 16) + var->bits_per_pixel = 16; + + var->height = glamo->mach_info->height; + var->width = glamo->mach_info->width; + + /* FIXME: set rgb positions */ + switch (var->bits_per_pixel) { + case 16: + switch (reg_read(glamo, GLAMO_REG_LCD_MODE3) & 0xc000) { + case GLAMO_LCD_SRC_RGB565: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + var->transp.length = 0; + break; + case GLAMO_LCD_SRC_ARGB1555: + var->transp.offset = 15; + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->transp.length = 1; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + break; + case GLAMO_LCD_SRC_ARGB4444: + var->transp.offset = 12; + var->red.offset = 8; + var->green.offset = 4; + var->blue.offset = 0; + var->transp.length = 4; + var->red.length = 4; + var->green.length = 4; + var->blue.length = 4; + break; + } + break; + case 24: + case 32: + default: + /* The Smedia Glamo doesn't support anything but 16bit color */ + printk(KERN_ERR + "Smedia driver does not [yet?] support 24/32bpp\n"); + return -EINVAL; + } + + return 0; +} + +static void reg_set_bit_mask(struct glamofb_handle *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + u_int16_t tmp; + + val &= mask; + + tmp = reg_read(glamo, reg); + tmp &= ~mask; + tmp |= val; + reg_write(glamo, reg, tmp); +} + +#define GLAMO_LCD_WIDTH_MASK 0x03FF +#define GLAMO_LCD_HEIGHT_MASK 0x03FF +#define GLAMO_LCD_PITCH_MASK 0x07FE +#define GLAMO_LCD_HV_TOTAL_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_START_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_END_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_DISP_START_MASK 0x03FF +#define GLAMO_LCD_HV_RETR_DISP_END_MASK 0x03FF + +/* the caller has to enxure lock_cmd is held and we are in cmd mode */ +static void __rotate_lcd(struct glamofb_handle *glamo, __u32 rotation) +{ + int glamo_rot; + + switch (rotation) { + case FB_ROTATE_CW: + glamo_rot = GLAMO_LCD_ROT_MODE_90; + break; + case FB_ROTATE_UD: + glamo_rot = GLAMO_LCD_ROT_MODE_180; + break; + case FB_ROTATE_CCW: + glamo_rot = GLAMO_LCD_ROT_MODE_270; + break; + default: + glamo_rot = GLAMO_LCD_ROT_MODE_0; + break; + } + + reg_set_bit_mask(glamo, + GLAMO_REG_LCD_WIDTH, + GLAMO_LCD_ROT_MODE_MASK, + glamo_rot); + reg_set_bit_mask(glamo, + GLAMO_REG_LCD_MODE1, + GLAMO_LCD_MODE1_ROTATE_EN, + (glamo_rot != GLAMO_LCD_ROT_MODE_0) ? + GLAMO_LCD_MODE1_ROTATE_EN : 0); +} + +static void glamofb_program_mode(struct glamofb_handle *gfb) +{ + int sync, bp, disp, fp, total; + unsigned long flags; + struct glamo_core *gcore = gfb->core; + struct fb_var_screeninfo *var = &gfb->fb->var; + + dev_dbg(&gcore->pdev->dev, + "glamofb_program_mode spin_lock_irqsave\n"); + spin_lock_irqsave(&gfb->lock_cmd, flags); + + if (glamofb_cmd_mode(gfb, 1)) + goto out_unlock; + + if (var->pixclock) + glamo_engine_reclock(gcore, GLAMO_ENGINE_LCD, + (1000000000UL / gfb->fb->var.pixclock) * 1000); + + reg_set_bit_mask(gfb, + GLAMO_REG_LCD_WIDTH, + GLAMO_LCD_WIDTH_MASK, + var->xres); + reg_set_bit_mask(gfb, + GLAMO_REG_LCD_HEIGHT, + GLAMO_LCD_HEIGHT_MASK, + var->yres); + reg_set_bit_mask(gfb, + GLAMO_REG_LCD_PITCH, + GLAMO_LCD_PITCH_MASK, + gfb->fb->fix.line_length); + + /* honour the rotation request */ + __rotate_lcd(gfb, var->rotate); + + /* update scannout timings */ + sync = 0; + bp = sync + var->hsync_len; + disp = bp + var->left_margin; + fp = disp + var->xres; + total = fp + var->right_margin; + + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_TOTAL, + GLAMO_LCD_HV_TOTAL_MASK, total); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_RETR_START, + GLAMO_LCD_HV_RETR_START_MASK, sync); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_RETR_END, + GLAMO_LCD_HV_RETR_END_MASK, bp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_DISP_START, + GLAMO_LCD_HV_RETR_DISP_START_MASK, disp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_HORIZ_DISP_END, + GLAMO_LCD_HV_RETR_DISP_END_MASK, fp); + + sync = 0; + bp = sync + var->vsync_len; + disp = bp + var->upper_margin; + fp = disp + var->yres; + total = fp + var->lower_margin; + + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_TOTAL, + GLAMO_LCD_HV_TOTAL_MASK, total); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_RETR_START, + GLAMO_LCD_HV_RETR_START_MASK, sync); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_RETR_END, + GLAMO_LCD_HV_RETR_END_MASK, bp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_DISP_START, + GLAMO_LCD_HV_RETR_DISP_START_MASK, disp); + reg_set_bit_mask(gfb, GLAMO_REG_LCD_VERT_DISP_END, + GLAMO_LCD_HV_RETR_DISP_END_MASK, fp); + + glamofb_cmd_mode(gfb, 0); + + gfb->mode_set = 1; + +out_unlock: + dev_dbg(&gcore->pdev->dev, + "glamofb_program_mode spin_unlock_irqrestore\n"); + spin_unlock_irqrestore(&gfb->lock_cmd, flags); +} + + +static int glamofb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + return 0; +} + +static struct fb_videomode *glamofb_find_mode(struct fb_info *info, + struct fb_var_screeninfo *var) +{ + struct glamofb_handle *glamo = info->par; + struct glamo_fb_platform_data *pdata = glamo->mach_info; + struct fb_videomode *mode; + int i; + + for (i = pdata->num_modes, mode = pdata->modes; i; --i, ++mode) { + if (mode->xres == var->xres && + mode->yres == var->yres) + return mode; + } + + return NULL; +} + +static int glamofb_set_par(struct fb_info *info) +{ + struct glamofb_handle *glamo = info->par; + struct fb_var_screeninfo *var = &info->var; + struct fb_videomode *mode; + + mode = glamofb_find_mode(info, var); + if (!mode) + return -EINVAL; + + fb_videomode_to_var(var, mode); + + info->mode = mode; + + glamo->mode_set = 0; + + switch (var->rotate) { + case FB_ROTATE_CW: + case FB_ROTATE_CCW: + info->fix.line_length = (var->yres * var->bits_per_pixel) / 8; + /* FIXME: Limit pixelclock */ + var->pixclock *= 2; + break; + default: + info->fix.line_length = (var->xres * var->bits_per_pixel) / 8; + break; + } + + if (glamo->output_enabled) + glamofb_program_mode(glamo); + + return 0; +} + +static int glamofb_blank(int blank_mode, struct fb_info *info) +{ + struct glamofb_handle *gfb = info->par; + + dev_dbg(gfb->dev, "glamofb_blank(%u)\n", blank_mode); + + switch (blank_mode) { + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + /* FIXME: add pdata hook/flag to indicate whether + * we should already switch off pixel clock here */ + break; + case FB_BLANK_POWERDOWN: + /* disable the pixel clock */ + glamo_output_disable(gfb); + gfb->blank_mode = blank_mode; + break; + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + glamo_output_enable(gfb); + gfb->blank_mode = blank_mode; + break; + } + + /* FIXME: once we have proper clock management in glamo-core, + * we can determine if other units need MCLK1 or the PLL, and + * disable it if not used. */ + return 0; +} + +static inline unsigned int chan_to_field(unsigned int chan, + struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int glamofb_setcolreg(unsigned regno, + unsigned red, unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + struct glamofb_handle *glamo = info->par; + unsigned int val; + + switch (glamo->fb->fix.visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_DIRECTCOLOR: + /* true-colour, use pseuo-palette */ + + if (regno < 16) { + u32 *pal = glamo->fb->pseudo_palette; + + val = chan_to_field(red, &glamo->fb->var.red); + val |= chan_to_field(green, &glamo->fb->var.green); + val |= chan_to_field(blue, &glamo->fb->var.blue); + + pal[regno] = val; + }; + break; + default: + return 1; /* unknown type */ + } + + return 0; +} + +static int glamofb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct glamofb_handle *gfb = (struct glamofb_handle *)info->par; + struct glamo_core *gcore = gfb->core; + int retval = -ENOTTY; + + switch (cmd) { + case GLAMOFB_ENGINE_ENABLE: + retval = glamo_engine_enable(gcore, arg); + break; + case GLAMOFB_ENGINE_DISABLE: + retval = glamo_engine_disable(gcore, arg); + break; + case GLAMOFB_ENGINE_RESET: + glamo_engine_reset(gcore, arg); + retval = 0; + break; + default: + break; + } + + return retval; +} + + +#ifdef CONFIG_MFD_GLAMO_HWACCEL +static inline void glamofb_vsync_wait(struct glamofb_handle *glamo, + int line, int size, int range) +{ + int count[2]; + + do { + count[0] = reg_read(glamo, GLAMO_REG_LCD_STATUS2) & 0x3ff; + count[1] = reg_read(glamo, GLAMO_REG_LCD_STATUS2) & 0x3ff; + } while (count[0] != count[1] || + (line < count[0] + range && + size > count[0] - range) || + count[0] < range * 2); +} + +/* + * Enable/disable the hardware cursor mode altogether + * (for blinking and such, use glamofb_cursor()). + */ +static void glamofb_cursor_onoff(struct glamofb_handle *glamo, int on) +{ + int y, size; + + if (glamo->cursor_on) { + y = reg_read(glamo, GLAMO_REG_LCD_CURSOR_Y_POS); + size = reg_read(glamo, GLAMO_REG_LCD_CURSOR_Y_SIZE); + + glamofb_vsync_wait(glamo, y, size, 30); + } + + reg_set_bit_mask(glamo, GLAMO_REG_LCD_MODE1, + GLAMO_LCD_MODE1_CURSOR_EN, + on ? GLAMO_LCD_MODE1_CURSOR_EN : 0); + glamo->cursor_on = on; + + /* Hide the cursor by default */ + reg_write(glamo, GLAMO_REG_LCD_CURSOR_X_SIZE, 0); +} + +static int glamofb_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct glamofb_handle *glamo = info->par; + unsigned long flags; + + spin_lock_irqsave(&glamo->lock_cmd, flags); + + reg_write(glamo, GLAMO_REG_LCD_CURSOR_X_SIZE, + cursor->enable ? cursor->image.width : 0); + + if (cursor->set & FB_CUR_SETPOS) { + reg_write(glamo, GLAMO_REG_LCD_CURSOR_X_POS, + cursor->image.dx); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_Y_POS, + cursor->image.dy); + } + + if (cursor->set & FB_CUR_SETCMAP) { + uint16_t fg = glamo->pseudo_pal[cursor->image.fg_color]; + uint16_t bg = glamo->pseudo_pal[cursor->image.bg_color]; + + reg_write(glamo, GLAMO_REG_LCD_CURSOR_FG_COLOR, fg); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_BG_COLOR, bg); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_DST_COLOR, fg); + } + + if (cursor->set & FB_CUR_SETHOT) + reg_write(glamo, GLAMO_REG_LCD_CURSOR_PRESET, + (cursor->hot.x << 8) | cursor->hot.y); + + if ((cursor->set & FB_CUR_SETSIZE) || + (cursor->set & (FB_CUR_SETIMAGE | FB_CUR_SETSHAPE))) { + int x, y, pitch, op; + const uint8_t *pcol = cursor->image.data; + const uint8_t *pmsk = cursor->mask; + uint8_t __iomem *dst = glamo->cursor_addr; + uint8_t dcol = 0; + uint8_t dmsk = 0; + uint8_t byte = 0; + + if (cursor->image.depth > 1) { + spin_unlock_irqrestore(&glamo->lock_cmd, flags); + return -EINVAL; + } + + pitch = ((cursor->image.width + 7) >> 2) & ~1; + reg_write(glamo, GLAMO_REG_LCD_CURSOR_PITCH, + pitch); + reg_write(glamo, GLAMO_REG_LCD_CURSOR_Y_SIZE, + cursor->image.height); + + for (y = 0; y < cursor->image.height; y++) { + byte = 0; + for (x = 0; x < cursor->image.width; x++) { + if ((x % 8) == 0) { + dcol = *pcol++; + dmsk = *pmsk++; + } else { + dcol >>= 1; + dmsk >>= 1; + } + + if (cursor->rop == ROP_COPY) + op = (dmsk & 1) ? + (dcol & 1) ? 1 : 3 : 0; + else + op = ((dmsk & 1) << 1) | + ((dcol & 1) << 0); + byte |= op << ((x & 3) << 1); + + if (x % 4 == 3) { + writeb(byte, dst + x / 4); + byte = 0; + } + } + if (x % 4) { + writeb(byte, dst + x / 4); + byte = 0; + } + + dst += pitch; + } + } + + spin_unlock_irqrestore(&glamo->lock_cmd, flags); + + return 0; +} +#endif + +static inline int glamofb_cmdq_empty(struct glamofb_handle *gfb) +{ + /* DGCMdQempty -- 1 == command queue is empty */ + return reg_read(gfb, GLAMO_REG_LCD_STATUS1) & (1 << 15); +} + +/* call holding gfb->lock_cmd when locking, until you unlock */ +int glamofb_cmd_mode(struct glamofb_handle *gfb, int on) +{ + int timeout = 2000000; + + dev_dbg(gfb->dev, "glamofb_cmd_mode(gfb=%p, on=%d)\n", gfb, on); + if (on) { + dev_dbg(gfb->dev, "%s: waiting for cmdq empty: ", + __func__); + while (!glamofb_cmdq_empty(gfb) && (timeout--)) + cpu_relax(); + if (timeout < 0) { + printk(KERN_ERR "glamofb cmd_queue never got empty\n"); + return -EIO; + } + dev_dbg(gfb->dev, "empty!\n"); + + /* display the entire frame then switch to command */ + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, + GLAMO_LCD_CMD_TYPE_DISP | + GLAMO_LCD_CMD_DATA_FIRE_VSYNC); + + /* wait until lcd idle */ + dev_dbg(gfb->dev, "waiting for lcd idle: "); + timeout = 2000000; + while (!(reg_read(gfb, GLAMO_REG_LCD_STATUS2) & (1 << 12)) && + (timeout--)) + cpu_relax(); + if (timeout < 0) { + printk(KERN_ERR"*************" + "glamofb lcd never idle" + "*************\n"); + return -EIO; + } + + mdelay(100); + + dev_dbg(gfb->dev, "cmd mode entered\n"); + + } else { + /* RGB interface needs vsync/hsync */ + if (reg_read(gfb, GLAMO_REG_LCD_MODE3) & GLAMO_LCD_MODE3_RGB) + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, + GLAMO_LCD_CMD_TYPE_DISP | + GLAMO_LCD_CMD_DATA_DISP_SYNC); + + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, + GLAMO_LCD_CMD_TYPE_DISP | + GLAMO_LCD_CMD_DATA_DISP_FIRE); + } + + return 0; +} +EXPORT_SYMBOL_GPL(glamofb_cmd_mode); + + +int glamofb_cmd_write(struct glamofb_handle *gfb, u_int16_t val) +{ + int timeout = 200000; + + dev_dbg(gfb->dev, "%s: waiting for cmdq empty\n", __func__); + while ((!glamofb_cmdq_empty(gfb)) && (timeout--)) + yield(); + if (timeout < 0) { + printk(KERN_ERR"*************" + "glamofb cmd_queue never got empty" + "*************\n"); + return 1; + } + dev_dbg(gfb->dev, "idle, writing 0x%04x\n", val); + + reg_write(gfb, GLAMO_REG_LCD_COMMAND1, val); + + return 0; +} +EXPORT_SYMBOL_GPL(glamofb_cmd_write); + +static struct fb_ops glamofb_ops = { + .owner = THIS_MODULE, + .fb_check_var = glamofb_check_var, + .fb_pan_display = glamofb_pan_display, + .fb_set_par = glamofb_set_par, + .fb_blank = glamofb_blank, + .fb_setcolreg = glamofb_setcolreg, + .fb_ioctl = glamofb_ioctl, +#ifdef CONFIG_MFD_GLAMO_HWACCEL + .fb_cursor = glamofb_cursor, +#endif + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +static int glamofb_init_regs(struct glamofb_handle *glamo) +{ + struct fb_info *info = glamo->fb; + + glamofb_check_var(&info->var, info); + glamofb_run_script(glamo, glamo_regs, ARRAY_SIZE(glamo_regs)); + glamofb_set_par(info); + + return 0; +} + +static int __init glamofb_probe(struct platform_device *pdev) +{ + int rc = -EIO; + struct fb_info *fbinfo; + struct glamofb_handle *glamofb; + struct glamo_core *core = dev_get_drvdata(pdev->dev.parent); + struct glamo_fb_platform_data *mach_info; + + printk(KERN_INFO "SMEDIA Glamo frame buffer driver (C) 2007 " + "Openmoko, Inc.\n"); + + if (!core->pdata || !core->pdata->fb_data) + return -ENOENT; + + + fbinfo = framebuffer_alloc(sizeof(struct glamofb_handle), &pdev->dev); + if (!fbinfo) + return -ENOMEM; + + + glamofb = fbinfo->par; + glamofb->fb = fbinfo; + glamofb->dev = &pdev->dev; + + glamofb->blank_mode = FB_BLANK_POWERDOWN; + + strcpy(fbinfo->fix.id, "SMedia Glamo"); + + glamofb->reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "glamo-fb-regs"); + if (!glamofb->reg) { + dev_err(&pdev->dev, "platform device with no registers?\n"); + rc = -ENOENT; + goto out_free; + } + + glamofb->fb_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "glamo-fb-mem"); + if (!glamofb->fb_res) { + dev_err(&pdev->dev, "platform device with no memory ?\n"); + rc = -ENOENT; + goto out_free; + } + + glamofb->reg = request_mem_region(glamofb->reg->start, + resource_size(glamofb->reg), + pdev->name); + if (!glamofb->reg) { + dev_err(&pdev->dev, "failed to request mmio region\n"); + goto out_free; + } + + glamofb->fb_res = request_mem_region(glamofb->fb_res->start, + resource_size(glamofb->fb_res), + pdev->name); + if (!glamofb->fb_res) { + dev_err(&pdev->dev, "failed to request vram region\n"); + goto out_release_reg; + } + + /* we want to remap only the registers required for this core + * driver. */ + glamofb->base = ioremap_nocache(glamofb->reg->start, + resource_size(glamofb->reg)); + if (!glamofb->base) { + dev_err(&pdev->dev, "failed to ioremap() mmio memory\n"); + goto out_release_fb; + } + + fbinfo->fix.smem_start = (unsigned long) glamofb->fb_res->start; + fbinfo->fix.smem_len = (__u32) resource_size(glamofb->fb_res); + + fbinfo->screen_base = ioremap(glamofb->fb_res->start, + resource_size(glamofb->fb_res)); + if (!fbinfo->screen_base) { + dev_err(&pdev->dev, "failed to ioremap() vram memory\n"); + goto out_release_fb; + } + glamofb->cursor_addr = fbinfo->screen_base + 0x12C000; + + platform_set_drvdata(pdev, glamofb); + + mach_info = core->pdata->fb_data; + glamofb->core = core; + glamofb->mach_info = mach_info; + + fbinfo->fix.visual = FB_VISUAL_TRUECOLOR; + fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; + fbinfo->fix.type_aux = 0; + fbinfo->fix.xpanstep = 0; + fbinfo->fix.ypanstep = 0; + fbinfo->fix.ywrapstep = 0; + fbinfo->fix.accel = FB_ACCEL_GLAMO; + + + fbinfo->fbops = &glamofb_ops; + fbinfo->flags = FBINFO_FLAG_DEFAULT; + fbinfo->pseudo_palette = &glamofb->pseudo_pal; + + fbinfo->mode = mach_info->modes; + fb_videomode_to_var(&fbinfo->var, fbinfo->mode); + fbinfo->var.bits_per_pixel = 16; + fbinfo->var.nonstd = 0; + fbinfo->var.activate = FB_ACTIVATE_NOW; + fbinfo->var.height = mach_info->height; + fbinfo->var.width = mach_info->width; + fbinfo->var.accel_flags = 0; + fbinfo->var.vmode = FB_VMODE_NONINTERLACED; + + glamo_engine_enable(core, GLAMO_ENGINE_LCD); + glamo_engine_reset(core, GLAMO_ENGINE_LCD); + glamofb->output_enabled = 1; + glamofb->mode_set = 1; + + dev_info(&pdev->dev, "spin_lock_init\n"); + spin_lock_init(&glamofb->lock_cmd); + glamofb_init_regs(glamofb); +#ifdef CONFIG_MFD_GLAMO_HWACCEL + glamofb_cursor_onoff(glamofb, 1); +#endif + + fb_videomode_to_modelist(mach_info->modes, mach_info->num_modes, + &fbinfo->modelist); + + rc = register_framebuffer(fbinfo); + if (rc < 0) { + dev_err(&pdev->dev, "failed to register framebuffer\n"); + goto out_unmap_fb; + } + + printk(KERN_INFO "fb%d: %s frame buffer device\n", + fbinfo->node, fbinfo->fix.id); + + return 0; + +out_unmap_fb: + iounmap(fbinfo->screen_base); + iounmap(glamofb->base); +out_release_fb: + release_mem_region(glamofb->fb_res->start, + resource_size(glamofb->fb_res)); +out_release_reg: + release_mem_region(glamofb->reg->start, + resource_size(glamofb->reg)); +out_free: + framebuffer_release(fbinfo); + return rc; +} + +static int glamofb_remove(struct platform_device *pdev) +{ + struct glamofb_handle *glamofb = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + iounmap(glamofb->base); + release_mem_region(glamofb->reg->start, resource_size(glamofb->reg)); + kfree(glamofb); + + return 0; +} + +#ifdef CONFIG_PM + +static int glamofb_suspend(struct device *dev) +{ + struct glamofb_handle *gfb = dev_get_drvdata(dev); + + acquire_console_sem(); + fb_set_suspend(gfb->fb, 1); + release_console_sem(); + + /* seriously -- nobody is allowed to touch glamo memory when we + * are suspended or we lock on nWAIT + */ + /* iounmap(gfb->fb->screen_base); */ + + return 0; +} + +static int glamofb_resume(struct device *dev) +{ + struct glamofb_handle *gfb = dev_get_drvdata(dev); + + /* OK let's allow framebuffer ops again */ + /* gfb->fb->screen_base = ioremap(gfb->fb_res->start, + resource_size(gfb->fb_res)); */ + glamo_engine_enable(gfb->core, GLAMO_ENGINE_LCD); + glamo_engine_reset(gfb->core, GLAMO_ENGINE_LCD); + + glamofb_init_regs(gfb); +#ifdef CONFIG_MFD_GLAMO_HWACCEL + glamofb_cursor_onoff(gfb, 1); +#endif + + acquire_console_sem(); + fb_set_suspend(gfb->fb, 0); + release_console_sem(); + + return 0; +} + +static struct dev_pm_ops glamofb_pm_ops = { + .suspend = glamofb_suspend, + .resume = glamofb_resume, +}; + +#define GLAMOFB_PM_OPS (&glamofb_pm_ops) + +#else +#define GLAMOFB_PM_OPS NULL +#endif + +static struct platform_driver glamofb_driver = { + .probe = glamofb_probe, + .remove = glamofb_remove, + .driver = { + .name = "glamo-fb", + .owner = THIS_MODULE, + .pm = GLAMOFB_PM_OPS + }, +}; + +static int __devinit glamofb_init(void) +{ + return platform_driver_register(&glamofb_driver); +} + +static void __exit glamofb_cleanup(void) +{ + platform_driver_unregister(&glamofb_driver); +} + +module_init(glamofb_init); +module_exit(glamofb_cleanup); + +MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); +MODULE_DESCRIPTION("Smedia Glamo 336x/337x framebuffer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/glamo/glamo-gpio.c b/drivers/mfd/glamo/glamo-gpio.c new file mode 100644 index 00000000000..1bcec99de41 --- /dev/null +++ b/drivers/mfd/glamo/glamo-gpio.c @@ -0,0 +1,284 @@ +/* Smedia Glamo 336x/337x gpio driver + * + * (C) 2009 Lars-Peter Clausen + * + * 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 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/platform_device.h> + +#include <linux/gpio.h> +#include <linux/mfd/glamo.h> + +#include "glamo-core.h" +#include "glamo-regs.h" + +#define GLAMO_NR_GPIO 21 +#define GLAMO_NR_GPIO_REGS DIV_ROUND_UP(GLAMO_NR_GPIO, 4) + +#define GLAMO_REG_GPIO(x) (((x) * 2) + GLAMO_REG_GPIO_GEN1) + +struct glamo_gpio { + struct glamo_core *glamo; + struct gpio_chip chip; + uint16_t saved_regs[GLAMO_NR_GPIO_REGS]; +}; + +#define REG_OF_GPIO(gpio) (GLAMO_REG_GPIO(gpio >> 2)) +#define NUM_OF_GPIO(gpio) (gpio & 0x3) +#define DIRECTION_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 0)) +#define OUTPUT_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 4)) +#define INPUT_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 8)) +#define FUNC_BIT(gpio) (1 << (NUM_OF_GPIO(gpio) + 12)) + + +static inline struct glamo_core *chip_to_glamo(struct gpio_chip *chip) +{ + return container_of(chip, struct glamo_gpio, chip)->glamo; +} + +static void glamo_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if (value) + tmp |= OUTPUT_BIT(offset); + else + tmp &= ~OUTPUT_BIT(offset); + writew(tmp, glamo->base + reg); + spin_unlock(&glamo->lock); +} + +static int glamo_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + return !!(readw(glamo->base + REG_OF_GPIO(offset)) & INPUT_BIT(offset)); +} + +static int glamo_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if ((tmp & FUNC_BIT(offset)) == 0) { + tmp |= FUNC_BIT(offset); + writew(tmp, glamo->base + reg); + } + spin_unlock(&glamo->lock); + + return 0; +} + +static void glamo_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if ((tmp & FUNC_BIT(offset)) == 1) { + tmp &= ~FUNC_BIT(offset); + writew(tmp, glamo->base + reg); + } + spin_unlock(&glamo->lock); +} + +static int glamo_gpio_direction_output(struct gpio_chip *chip, unsigned offset, + int value) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + tmp &= ~DIRECTION_BIT(offset); + + if (value) + tmp |= OUTPUT_BIT(offset); + else + tmp &= ~OUTPUT_BIT(offset); + + writew(tmp, glamo->base + reg); + spin_unlock(&glamo->lock); + + return 0; +} + +static int glamo_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct glamo_core *glamo = chip_to_glamo(chip); + unsigned int reg = REG_OF_GPIO(offset); + u_int16_t tmp; + + spin_lock(&glamo->lock); + tmp = readw(glamo->base + reg); + if ((tmp & DIRECTION_BIT(offset)) == 0) { + tmp |= DIRECTION_BIT(offset); + writew(tmp, glamo->base + reg); + } + spin_unlock(&glamo->lock); + + return 0; +} + +static const struct __devinit gpio_chip glamo_gpio_chip = { + .label = "glamo", + .request = glamo_gpio_request, + .free = glamo_gpio_free, + .direction_input = glamo_gpio_direction_input, + .get = glamo_gpio_get, + .direction_output = glamo_gpio_direction_output, + .set = glamo_gpio_set, + .base = -1, + .ngpio = GLAMO_NR_GPIO, + .can_sleep = 0, + .owner = THIS_MODULE, +}; + +static int __devinit glamo_gpio_probe(struct platform_device *pdev) +{ + struct glamo_platform_data *pdata = pdev->dev.parent->platform_data; + struct glamo_gpio *glamo_gpio; + int ret; + + glamo_gpio = kzalloc(sizeof(struct glamo_gpio), GFP_KERNEL); + if (!glamo_gpio) + return -ENOMEM; + + glamo_gpio->glamo = dev_get_drvdata(pdev->dev.parent); + glamo_gpio->chip = glamo_gpio_chip; + glamo_gpio->chip.dev = &pdev->dev; + if (pdata && pdata->gpio_data) + glamo_gpio->chip.base = pdata->gpio_data->base; + + ret = gpiochip_add(&glamo_gpio->chip); + + if (ret) { + dev_err(&pdev->dev, "Could not register gpio chip: %d\n", ret); + goto err; + } + + platform_set_drvdata(pdev, glamo_gpio); + + if (pdata && pdata->gpio_data && pdata->gpio_data->registered) + pdata->gpio_data->registered(&pdev->dev); + + return 0; +err: + kfree(glamo_gpio); + return ret; +} + +static int __devexit glamo_gpio_remove(struct platform_device *pdev) +{ + struct glamo_gpio *glamo_gpio = platform_get_drvdata(pdev); + int ret; + + ret = gpiochip_remove(&glamo_gpio->chip); + if (!ret) + goto done; + + platform_set_drvdata(pdev, NULL); + kfree(glamo_gpio); + +done: + return ret; +} + +#ifdef CONFIG_PM + +static int glamo_gpio_suspend(struct device *dev) +{ + struct glamo_gpio *glamo_gpio = dev_get_drvdata(dev); + struct glamo_core *glamo = glamo_gpio->glamo; + uint16_t *saved_regs = glamo_gpio->saved_regs; + int i; + + spin_lock(&glamo->lock); + for (i = 0; i < GLAMO_NR_GPIO / 4; ++i) + saved_regs[i] = readw(glamo->base + GLAMO_REG_GPIO(i)); + spin_unlock(&glamo->lock); + + return 0; +} + +static int glamo_gpio_resume(struct device *dev) +{ + struct glamo_gpio *glamo_gpio = dev_get_drvdata(dev); + struct glamo_core *glamo = glamo_gpio->glamo; + uint16_t *saved_regs = glamo_gpio->saved_regs; + int i; + + spin_lock(&glamo->lock); + for (i = 0; i < GLAMO_NR_GPIO_REGS; ++i) + writew(saved_regs[i], glamo->base + GLAMO_REG_GPIO(i)); + spin_unlock(&glamo->lock); + return 0; +} + +static const struct dev_pm_ops glamo_pm_ops = { + .suspend = glamo_gpio_suspend, + .resume = glamo_gpio_resume, + .freeze = glamo_gpio_suspend, + .thaw = glamo_gpio_resume, +}; + +#define GLAMO_GPIO_PM_OPS (&glamo_pm_ops) + +#else +#define GLAMO_GPIO_PM_OPS NULL +#endif + +static struct platform_driver glamo_gpio_driver = { + .driver = { + .name = "glamo-gpio", + .owner = THIS_MODULE, + .pm = GLAMO_GPIO_PM_OPS, + }, + .probe = glamo_gpio_probe, + .remove = __devexit_p(glamo_gpio_remove), +}; + +static int __devinit glamo_gpio_init(void) +{ + return platform_driver_register(&glamo_gpio_driver); +} +module_init(glamo_gpio_init); + +static void __exit glamo_gpio_exit(void) +{ + platform_driver_unregister(&glamo_gpio_driver); +} +module_exit(glamo_gpio_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("GPIO interface for the Glamo multimedia device"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:glamo-gpio"); diff --git a/drivers/mfd/glamo/glamo-mci.c b/drivers/mfd/glamo/glamo-mci.c new file mode 100644 index 00000000000..faeb885c553 --- /dev/null +++ b/drivers/mfd/glamo/glamo-mci.c @@ -0,0 +1,1025 @@ +/* + * linux/drivers/mmc/host/glamo-mmc.c - Glamo MMC driver + * + * Copyright (C) 2007 Openmoko, Inc, Andy Green <andy@openmoko.com> + * Copyright (C) 2009, Lars-Peter Clausen <lars@metafoo.de> + * Based on S3C MMC driver that was: + * Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel <tk@maintech.de> + * + * 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 <linux/module.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sd.h> +#include <linux/mmc/host.h> +#include <linux/platform_device.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/crc7.h> +#include <linux/scatterlist.h> +#include <linux/io.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/mfd/glamo.h> + +#include "glamo-core.h" +#include "glamo-regs.h" + +struct glamo_mci_host { + struct glamo_mmc_platform_data *pdata; + struct platform_device *pdev; + struct glamo_core *core; + struct mmc_host *mmc; + struct resource *mmio_mem; + struct resource *data_mem; + void __iomem *mmio_base; + uint16_t __iomem *data_base; + + unsigned int irq; + + struct regulator *regulator; + struct mmc_request *mrq; + + unsigned int clk_rate; + + unsigned short vdd; + char power_mode; + + unsigned char request_counter; + + struct timer_list disable_timer; + + struct work_struct irq_work; + struct work_struct read_work; + + unsigned clk_enabled:1; +}; + +static void glamo_mci_send_request(struct mmc_host *mmc, + struct mmc_request *mrq); +static void glamo_mci_send_command(struct glamo_mci_host *host, + struct mmc_command *cmd); + +/* + * Max SD clock rate + * + * held at /(3 + 1) due to concerns of 100R recommended series resistor + * allows 16MHz @ 4-bit --> 8MBytes/sec raw + * + * you can override this on kernel commandline using + * + * glamo_mci.sd_max_clk=10000000 + * + * for example + */ + +static int sd_max_clk = 21000000; +module_param(sd_max_clk, int, 0644); + +/* + * Slow SD clock rate + * + * you can override this on kernel commandline using + * + * glamo_mci.sd_slow_ratio=8 + * + * for example + * + * platform callback is used to decide effective clock rate, if not + * defined then max is used, if defined and returns nonzero, rate is + * divided by this factor + */ + +static int sd_slow_ratio = 8; +module_param(sd_slow_ratio, int, 0644); + +/* + * Post-power SD clock rate + * + * you can override this on kernel commandline using + * + * glamo_mci.sd_post_power_clock=1000000 + * + * for example + * + * After changing power to card, clock is held at this rate until first bulk + * transfer completes + */ + +static int sd_post_power_clock = 1000000; +module_param(sd_post_power_clock, int, 0644); + + +static inline void glamo_reg_write(struct glamo_mci_host *glamo, + uint16_t reg, uint16_t val) +{ + writew(val, glamo->mmio_base + reg); +} + +static inline uint16_t glamo_reg_read(struct glamo_mci_host *glamo, + uint16_t reg) +{ + return readw(glamo->mmio_base + reg); +} + +static void glamo_reg_set_bit_mask(struct glamo_mci_host *glamo, + uint16_t reg, uint16_t mask, + uint16_t val) +{ + uint16_t tmp; + + val &= mask; + + tmp = glamo_reg_read(glamo, reg); + tmp &= ~mask; + tmp |= val; + glamo_reg_write(glamo, reg, tmp); +} + +static void glamo_mci_reset(struct glamo_mci_host *host) +{ + glamo_engine_reset(host->core, GLAMO_ENGINE_MMC); + + glamo_reg_write(host, GLAMO_REG_MMC_WDATADS1, + (uint16_t)(host->data_mem->start)); + glamo_reg_write(host, GLAMO_REG_MMC_WDATADS2, + (uint16_t)(host->data_mem->start >> 16)); + + glamo_reg_write(host, GLAMO_REG_MMC_RDATADS1, + (uint16_t)(host->data_mem->start)); + glamo_reg_write(host, GLAMO_REG_MMC_RDATADS2, + (uint16_t)(host->data_mem->start >> 16)); + +} + +static void glamo_mci_clock_disable(struct glamo_mci_host *host) +{ + glamo_engine_suspend(host->core, GLAMO_ENGINE_MMC); +} + +static void glamo_mci_clock_enable(struct glamo_mci_host *host) +{ + del_timer_sync(&host->disable_timer); + + glamo_engine_enable(host->core, GLAMO_ENGINE_MMC); +} + +static void glamo_mci_disable_timer(unsigned long data) +{ + struct glamo_mci_host *host = (struct glamo_mci_host *)data; + glamo_mci_clock_disable(host); +} + + +static void do_pio_read(struct glamo_mci_host *host, struct mmc_data *data) +{ + struct sg_mapping_iter miter; + uint16_t __iomem *from_ptr = host->data_base; + + dev_dbg(&host->pdev->dev, "pio_read():\n"); + + sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_TO_SG); + + while (sg_miter_next(&miter)) { + memcpy(miter.addr, from_ptr, miter.length); + from_ptr += miter.length >> 1; + + data->bytes_xfered += miter.length; + } + + sg_miter_stop(&miter); + + dev_dbg(&host->pdev->dev, "pio_read(): " + "complete (no more data).\n"); +} + +static void do_pio_write(struct glamo_mci_host *host, struct mmc_data *data) +{ + struct sg_mapping_iter miter; + uint16_t __iomem *to_ptr = host->data_base; + + dev_dbg(&host->pdev->dev, "pio_write():\n"); + sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_FROM_SG); + + while (sg_miter_next(&miter)) { + memcpy(to_ptr, miter.addr, miter.length); + to_ptr += miter.length >> 1; + + data->bytes_xfered += miter.length; + } + + sg_miter_stop(&miter); + dev_dbg(&host->pdev->dev, "pio_write(): complete\n"); +} + +static int glamo_mci_set_card_clock(struct glamo_mci_host *host, int freq) +{ + int real_rate = 0; + + if (freq) { + glamo_mci_clock_enable(host); + real_rate = glamo_engine_reclock(host->core, GLAMO_ENGINE_MMC, + freq); + } else { + glamo_mci_clock_disable(host); + } + + return real_rate; +} + +static int glamo_mci_wait_idle(struct glamo_mci_host *host, + unsigned long timeout) +{ + uint16_t status; + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + } while (!(status & GLAMO_STAT1_MMC_IDLE) && + time_is_after_jiffies(timeout)); + + if (time_is_before_eq_jiffies(timeout)) { + glamo_mci_reset(host); + return -ETIMEDOUT; + } + + return 0; +} + +static void glamo_mci_request_done(struct glamo_mci_host *host, struct +mmc_request *mrq) +{ + mod_timer(&host->disable_timer, jiffies + HZ / 16); + mmc_request_done(host->mmc, mrq); +} + + +static void glamo_mci_irq_worker(struct work_struct *work) +{ + struct glamo_mci_host *host = container_of(work, struct glamo_mci_host, + irq_work); + struct mmc_request *mrq; + struct mmc_command *cmd; + uint16_t status; + + if (!host->mrq || !host->mrq->cmd) + return; + + mrq = host->mrq; + cmd = mrq->cmd; + +#if 0 + if (cmd->data->flags & MMC_DATA_READ) + return; +#endif + + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + dev_dbg(&host->pdev->dev, "status = 0x%04x\n", status); + + /* we ignore a data timeout report if we are also told the data came */ + if (status & GLAMO_STAT1_MMC_RB_DRDY) + status &= ~GLAMO_STAT1_MMC_DTOUT; + + if (status & (GLAMO_STAT1_MMC_RTOUT | GLAMO_STAT1_MMC_DTOUT)) + cmd->error = -ETIMEDOUT; + else if (status & (GLAMO_STAT1_MMC_BWERR | GLAMO_STAT1_MMC_BRERR)) + cmd->error = -EILSEQ; + + if (cmd->error) { + dev_info(&host->pdev->dev, "Error after cmd: 0x%x\n", status); + goto done; + } + + /* issue STOP if we have been given one to use */ + if (mrq->stop) + glamo_mci_send_command(host, mrq->stop); + + if (cmd->data->flags & MMC_DATA_READ) + do_pio_read(host, cmd->data); + + if (mrq->stop) + mrq->stop->error = glamo_mci_wait_idle(host, jiffies + HZ); + +done: + host->mrq = NULL; + glamo_mci_request_done(host, cmd->mrq); +} + +static void glamo_mci_read_worker(struct work_struct *work) +{ + struct glamo_mci_host *host = container_of(work, struct glamo_mci_host, + read_work); + struct mmc_command *cmd; + uint16_t status; + uint16_t blocks_ready; + size_t data_read = 0; + size_t data_ready; + struct scatterlist *sg; + uint16_t __iomem *from_ptr = host->data_base; + void *sg_pointer; + + + cmd = host->mrq->cmd; + sg = cmd->data->sg; + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + + if (status & (GLAMO_STAT1_MMC_RTOUT | GLAMO_STAT1_MMC_DTOUT)) + cmd->error = -ETIMEDOUT; + if (status & (GLAMO_STAT1_MMC_BWERR | GLAMO_STAT1_MMC_BRERR)) + cmd->error = -EILSEQ; + if (cmd->error) { + dev_info(&host->pdev->dev, "Error after cmd: 0x%x\n", + status); + goto done; + } + + blocks_ready = glamo_reg_read(host, GLAMO_REG_MMC_RB_BLKCNT); + data_ready = blocks_ready * cmd->data->blksz; + + if (data_ready == data_read) + yield(); + + while (sg && data_read + sg->length <= data_ready) { + sg_pointer = page_address(sg_page(sg)) + sg->offset; + memcpy(sg_pointer, from_ptr, sg->length); + from_ptr += sg->length >> 1; + + data_read += sg->length; + sg = sg_next(sg); + } + + } while (sg); + cmd->data->bytes_xfered = data_read; + + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + } while (!(status & GLAMO_STAT1_MMC_IDLE)); + + if (host->mrq->stop) + glamo_mci_send_command(host, host->mrq->stop); + + do { + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + } while (!(status & GLAMO_STAT1_MMC_IDLE)); +done: + host->mrq = NULL; + glamo_mci_request_done(host, cmd->mrq); +} + +static irqreturn_t glamo_mci_irq(int irq, void *devid) +{ + struct glamo_mci_host *host = (struct glamo_mci_host *)devid; + schedule_work(&host->irq_work); + + return IRQ_HANDLED; +} + +static void glamo_mci_send_command(struct glamo_mci_host *host, + struct mmc_command *cmd) +{ + uint8_t u8a[6]; + uint16_t fire = 0; + unsigned int timeout = 1000000; + uint16_t *reg_resp = (uint16_t *)(host->mmio_base + GLAMO_REG_MMC_CMD_RSP1); + uint16_t status; + int triggers_int = 1; + + /* if we can't do it, reject as busy */ + if (!(glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1) & + GLAMO_STAT1_MMC_IDLE)) { + cmd->error = -EBUSY; + return; + } + + /* create an array in wire order for CRC computation */ + u8a[0] = 0x40 | (cmd->opcode & 0x3f); + u8a[1] = (uint8_t)(cmd->arg >> 24); + u8a[2] = (uint8_t)(cmd->arg >> 16); + u8a[3] = (uint8_t)(cmd->arg >> 8); + u8a[4] = (uint8_t)cmd->arg; + u8a[5] = (crc7(0, u8a, 5) << 1) | 0x01; + + /* issue the wire-order array including CRC in register order */ + glamo_reg_write(host, GLAMO_REG_MMC_CMD_REG1, ((u8a[4] << 8) | u8a[5])); + glamo_reg_write(host, GLAMO_REG_MMC_CMD_REG2, ((u8a[2] << 8) | u8a[3])); + glamo_reg_write(host, GLAMO_REG_MMC_CMD_REG3, ((u8a[0] << 8) | u8a[1])); + + /* command index toggle */ + fire |= (host->request_counter & 1) << 12; + + /* set type of command */ + switch (mmc_cmd_type(cmd)) { + case MMC_CMD_BC: + fire |= GLAMO_FIRE_MMC_CMDT_BNR; + break; + case MMC_CMD_BCR: + fire |= GLAMO_FIRE_MMC_CMDT_BR; + break; + case MMC_CMD_AC: + fire |= GLAMO_FIRE_MMC_CMDT_AND; + break; + case MMC_CMD_ADTC: + fire |= GLAMO_FIRE_MMC_CMDT_AD; + break; + } + /* + * if it expects a response, set the type expected + * + * R1, Length : 48bit, Normal response + * R1b, Length : 48bit, same R1, but added card busy status + * R2, Length : 136bit (really 128 bits with CRC snipped) + * R3, Length : 48bit (OCR register value) + * R4, Length : 48bit, SDIO_OP_CONDITION, Reverse SDIO Card + * R5, Length : 48bit, IO_RW_DIRECTION, Reverse SDIO Card + * R6, Length : 48bit (RCA register) + * R7, Length : 48bit (interface condition, VHS(voltage supplied), + * check pattern, CRC7) + */ + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1: /* same index as R6 and R7 */ + fire |= GLAMO_FIRE_MMC_RSPT_R1; + break; + case MMC_RSP_R1B: + fire |= GLAMO_FIRE_MMC_RSPT_R1b; + break; + case MMC_RSP_R2: + fire |= GLAMO_FIRE_MMC_RSPT_R2; + break; + case MMC_RSP_R3: + fire |= GLAMO_FIRE_MMC_RSPT_R3; + break; + /* R4 and R5 supported by chip not defined in linux/mmc/core.h (sdio) */ + } + /* + * From the command index, set up the command class in the host ctrllr + * + * missing guys present on chip but couldn't figure out how to use yet: + * 0x0 "stream read" + * 0x9 "cancel running command" + */ + switch (cmd->opcode) { + case MMC_READ_SINGLE_BLOCK: + fire |= GLAMO_FIRE_MMC_CC_SBR; /* single block read */ + break; + case MMC_SWITCH: /* 64 byte payload */ + case SD_APP_SEND_SCR: + case MMC_READ_MULTIPLE_BLOCK: + /* we will get an interrupt off this */ + if (!cmd->mrq->stop) { + /* multiblock no stop */ + fire |= GLAMO_FIRE_MMC_CC_MBRNS; + } else { + /* multiblock with stop */ + fire |= GLAMO_FIRE_MMC_CC_MBRS; + } + break; + case MMC_WRITE_BLOCK: + fire |= GLAMO_FIRE_MMC_CC_SBW; /* single block write */ + break; + case MMC_WRITE_MULTIPLE_BLOCK: + if (cmd->mrq->stop) { + /* multiblock with stop */ + fire |= GLAMO_FIRE_MMC_CC_MBWS; + } else { + /* multiblock NO stop-- 'RESERVED'? */ + fire |= GLAMO_FIRE_MMC_CC_MBWNS; + } + break; + case MMC_STOP_TRANSMISSION: + fire |= GLAMO_FIRE_MMC_CC_STOP; /* STOP */ + triggers_int = 0; + break; + default: + fire |= GLAMO_FIRE_MMC_CC_BASIC; /* "basic command" */ + triggers_int = 0; + break; + } + + if (cmd->data) + host->mrq = cmd->mrq; + + /* always largest timeout */ + glamo_reg_write(host, GLAMO_REG_MMC_TIMEOUT, 0xfff); + + /* Generate interrupt on txfer */ + glamo_reg_set_bit_mask(host, GLAMO_REG_MMC_BASIC, 0xff36, + 0x0800 | + GLAMO_BASIC_MMC_NO_CLK_RD_WAIT | + GLAMO_BASIC_MMC_EN_COMPL_INT | + GLAMO_BASIC_MMC_EN_DATA_PUPS | + GLAMO_BASIC_MMC_EN_CMD_PUP); + + /* send the command out on the wire */ + /* dev_info(&host->pdev->dev, "Using FIRE %04X\n", fire); */ + glamo_reg_write(host, GLAMO_REG_MMC_CMD_FIRE, fire); + + /* we are deselecting card? because it isn't going to ack then... */ + if ((cmd->opcode == 7) && (cmd->arg == 0)) + return; + + /* + * we must spin until response is ready or timed out + * -- we don't get interrupts unless there is a bulk rx + */ + do + status = glamo_reg_read(host, GLAMO_REG_MMC_RB_STAT1); + while (((((status >> 15) & 1) != (host->request_counter & 1)) || + (!(status & (GLAMO_STAT1_MMC_RB_RRDY | + GLAMO_STAT1_MMC_RTOUT | + GLAMO_STAT1_MMC_DTOUT | + GLAMO_STAT1_MMC_BWERR | + GLAMO_STAT1_MMC_BRERR)))) && (timeout--)); + + if ((status & (GLAMO_STAT1_MMC_RTOUT | + GLAMO_STAT1_MMC_DTOUT)) || + (timeout == 0)) { + cmd->error = -ETIMEDOUT; + } else if (status & (GLAMO_STAT1_MMC_BWERR | GLAMO_STAT1_MMC_BRERR)) { + cmd->error = -EILSEQ; + } + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) { + cmd->resp[3] = readw(®_resp[0]) | + (readw(®_resp[1]) << 16); + cmd->resp[2] = readw(®_resp[2]) | + (readw(®_resp[3]) << 16); + cmd->resp[1] = readw(®_resp[4]) | + (readw(®_resp[5]) << 16); + cmd->resp[0] = readw(®_resp[6]) | + (readw(®_resp[7]) << 16); + } else { + cmd->resp[0] = (readw(®_resp[0]) >> 8) | + (readw(®_resp[1]) << 8) | + (readw(®_resp[2]) << 24); + } + } + +#if 0 + /* We'll only get an interrupt when all data has been transfered. + By starting to copy data when it's avaiable we can increase + throughput by up to 30%. */ + if (cmd->data && (cmd->data->flags & MMC_DATA_READ)) + schedule_work(&host->read_work); +#endif + +} + +static int glamo_mci_prepare_pio(struct glamo_mci_host *host, + struct mmc_data *data) +{ + /* set up the block info */ + glamo_reg_write(host, GLAMO_REG_MMC_DATBLKLEN, data->blksz); + glamo_reg_write(host, GLAMO_REG_MMC_DATBLKCNT, data->blocks); + + data->bytes_xfered = 0; + + /* if write, prep the write into the shared RAM before the command */ + if (data->flags & MMC_DATA_WRITE) + do_pio_write(host, data); + + dev_dbg(&host->pdev->dev, "(blksz=%d, count=%d)\n", + data->blksz, data->blocks); + return 0; +} + +static int glamo_mci_irq_poll(struct glamo_mci_host *host, + struct mmc_command *cmd) +{ + int timeout = 1000000; + uint16_t status; + /* + * if the glamo INT# line isn't wired (*cough* it can happen) + * I'm afraid we have to spin on the IRQ status bit and "be + * our own INT# line" + */ + /* + * we have faith we will get an "interrupt"... + * but something insane like suspend problems can mean + * we spin here forever, so we timeout after a LONG time + */ + do { + status = glamo_reg_read(host, GLAMO_REG_IRQ_STATUS); + } while ((--timeout) && !(status & GLAMO_IRQ_MMC)); + + if (timeout <= 0) { + if (cmd->data->error) + cmd->data->error = -ETIMEDOUT; + dev_err(&host->pdev->dev, "Payload timeout\n"); + return -ETIMEDOUT; + } + /* ack this interrupt source */ + writew(GLAMO_IRQ_MMC, host->core->base + + GLAMO_REG_IRQ_CLEAR); + + /* yay we are an interrupt controller! -- call the ISR + * it will stop clock to card + */ + glamo_mci_irq(host->irq, host); + + return 0; +} + +static void glamo_mci_send_request(struct mmc_host *mmc, + struct mmc_request *mrq) +{ + struct glamo_mci_host *host = mmc_priv(mmc); + struct mmc_command *cmd = mrq->cmd; + + glamo_mci_clock_enable(host); + host->request_counter++; + if (cmd->data) { + if (glamo_mci_prepare_pio(host, cmd->data)) { + cmd->error = -EIO; + cmd->data->error = -EIO; + goto done; + } + } + + dev_dbg(&host->pdev->dev, "cmd 0x%x, " + "arg 0x%x data=%p mrq->stop=%p flags 0x%x\n", + cmd->opcode, cmd->arg, cmd->data, cmd->mrq->stop, + cmd->flags); + + glamo_mci_send_command(host, cmd); + + /* + * if we don't have bulk data to take care of, we're done + */ + if (!cmd->data || cmd->error) + goto done; + + + if (!host->core->irq_works) { + if (glamo_mci_irq_poll(host, mrq->cmd)) + goto done; + } + + /* + * Otherwise can can use the interrupt as async completion -- + * if there is read data coming, or we wait for write data to complete, + * exit without mmc_request_done() as the payload interrupt + * will service it + */ + dev_dbg(&host->pdev->dev, "Waiting for payload data\n"); + return; +done: + if (!cmd->error) + cmd->error = glamo_mci_wait_idle(host, jiffies + 2 * HZ); + glamo_mci_request_done(host, mrq); +} + +static void glamo_mci_set_power_mode(struct glamo_mci_host *host, + unsigned char power_mode) +{ + int ret; + + if (power_mode == host->power_mode) + return; + + switch (power_mode) { + case MMC_POWER_UP: + if (host->power_mode == MMC_POWER_OFF) { + printk("ENABLE\n"); + ret = regulator_enable(host->regulator); + if (ret) + dev_err(&host->pdev->dev, + "Failed to enable regulator: %d\n", + ret); + } + break; + case MMC_POWER_ON: + break; + case MMC_POWER_OFF: + default: + printk("DISABLE\n"); + glamo_engine_disable(host->core, GLAMO_ENGINE_MMC); + + ret = regulator_disable(host->regulator); + if (ret) + dev_warn(&host->pdev->dev, + "Failed to disable regulator: %d\n", + ret); + break; + } + host->power_mode = power_mode; +} + +static void glamo_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct glamo_mci_host *host = mmc_priv(mmc); + int bus_width = 0; + int rate; + int sd_drive; + int ret; + + /* Set power */ + glamo_mci_set_power_mode(host, ios->power_mode); + + if (host->vdd != ios->vdd) { + ret = mmc_regulator_set_ocr(host->regulator, ios->vdd); + if (ret) + dev_err(&host->pdev->dev, + "Failed to set regulator voltage: %d\n", ret); + else + host->vdd = ios->vdd; + } + rate = glamo_mci_set_card_clock(host, ios->clock); + + if ((ios->power_mode == MMC_POWER_ON) || + (ios->power_mode == MMC_POWER_UP)) { + dev_info(&host->pdev->dev, + "powered (vdd = %hu) clk: %dkHz div=%hu (req: %ukHz). " + "Bus width=%d\n", ios->vdd, + rate / 1000, 0, + ios->clock / 1000, (int)ios->bus_width); + } else { + dev_info(&host->pdev->dev, "glamo_mci_set_ios: power down.\n"); + } + + /* set bus width */ + if (ios->bus_width == MMC_BUS_WIDTH_4) + bus_width = GLAMO_BASIC_MMC_EN_4BIT_DATA; + + sd_drive = (rate * 4) / host->clk_rate; + if (sd_drive > 3) + sd_drive = 3; + + glamo_reg_set_bit_mask(host, GLAMO_REG_MMC_BASIC, + GLAMO_BASIC_MMC_EN_4BIT_DATA | 0xb0, + bus_width | sd_drive << 6); +} + + +/* + * no physical write protect supported by us + */ +static int glamo_mci_get_ro(struct mmc_host *mmc) +{ + return 0; +} + +static struct mmc_host_ops glamo_mci_ops = { + .request = glamo_mci_send_request, + .set_ios = glamo_mci_set_ios, + .get_ro = glamo_mci_get_ro, +}; + +static int glamo_mci_probe(struct platform_device *pdev) +{ + struct mmc_host *mmc; + struct glamo_mci_host *host; + struct glamo_core *core = dev_get_drvdata(pdev->dev.parent); + int ret; + + dev_info(&pdev->dev, "glamo_mci driver (C)2007 Openmoko, Inc\n"); + + mmc = mmc_alloc_host(sizeof(struct glamo_mci_host), &pdev->dev); + if (!mmc) { + ret = -ENOMEM; + goto probe_out; + } + + host = mmc_priv(mmc); + host->mmc = mmc; + host->pdev = pdev; + if (core->pdata) + host->pdata = core->pdata->mmc_data; + host->power_mode = MMC_POWER_OFF; + host->clk_enabled = 0; + host->core = core; + host->irq = platform_get_irq(pdev, 0); + + INIT_WORK(&host->irq_work, glamo_mci_irq_worker); + INIT_WORK(&host->read_work, glamo_mci_read_worker); + + host->regulator = regulator_get(pdev->dev.parent, "SD_3V3"); + if (IS_ERR(host->regulator)) { + dev_err(&pdev->dev, "Cannot proceed without regulator.\n"); + ret = PTR_ERR(host->regulator); + goto probe_free_host; + } + + host->mmio_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!host->mmio_mem) { + dev_err(&pdev->dev, + "failed to get io memory region resouce.\n"); + ret = -ENOENT; + goto probe_regulator_put; + } + + host->mmio_mem = request_mem_region(host->mmio_mem->start, + resource_size(host->mmio_mem), + pdev->name); + + if (!host->mmio_mem) { + dev_err(&pdev->dev, "failed to request io memory region.\n"); + ret = -ENOENT; + goto probe_regulator_put; + } + + host->mmio_base = ioremap(host->mmio_mem->start, + resource_size(host->mmio_mem)); + if (!host->mmio_base) { + dev_err(&pdev->dev, "failed to ioremap() io memory region.\n"); + ret = -EINVAL; + goto probe_free_mem_region_mmio; + } + + + /* Get ahold of our data buffer we use for data in and out on MMC */ + host->data_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!host->data_mem) { + dev_err(&pdev->dev, + "failed to get io memory region resource.\n"); + ret = -ENOENT; + goto probe_iounmap_mmio; + } + + host->data_mem = request_mem_region(host->data_mem->start, + resource_size(host->data_mem), + pdev->name); + + if (!host->data_mem) { + dev_err(&pdev->dev, "failed to request io memory region.\n"); + ret = -ENOENT; + goto probe_iounmap_mmio; + } + host->data_base = ioremap(host->data_mem->start, + resource_size(host->data_mem)); + + if (host->data_base == 0) { + dev_err(&pdev->dev, "failed to ioremap() io memory region.\n"); + ret = -EINVAL; + goto probe_free_mem_region_data; + } + + ret = request_irq(host->irq, glamo_mci_irq, IRQF_SHARED, + pdev->name, host); + if (ret) { + dev_err(&pdev->dev, "failed to register irq.\n"); + goto probe_iounmap_data; + } + + + host->vdd = 0; + host->clk_rate = glamo_pll_rate(host->core, GLAMO_PLL1); + + /* explain our host controller capabilities */ + mmc->ops = &glamo_mci_ops; + mmc->ocr_avail = mmc_regulator_get_ocrmask(host->regulator); + mmc->caps = MMC_CAP_4_BIT_DATA | + MMC_CAP_MMC_HIGHSPEED | + MMC_CAP_SD_HIGHSPEED; + mmc->f_min = host->clk_rate / 256; + mmc->f_max = sd_max_clk; + + mmc->max_blk_count = (1 << 16) - 1; /* GLAMO_REG_MMC_RB_BLKCNT */ + mmc->max_blk_size = (1 << 12) - 1; /* GLAMO_REG_MMC_RB_BLKLEN */ + mmc->max_req_size = resource_size(host->data_mem); + mmc->max_seg_size = mmc->max_req_size; + mmc->max_phys_segs = 128; + mmc->max_hw_segs = 128; + + if (mmc->ocr_avail < 0) { + dev_warn(&pdev->dev, + "Failed to get ocr list for regulator: %d.\n", + mmc->ocr_avail); + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + } + + platform_set_drvdata(pdev, mmc); + + glamo_engine_enable(host->core, GLAMO_ENGINE_MMC); + glamo_mci_reset(host); + glamo_engine_disable(host->core, GLAMO_ENGINE_MMC); + + setup_timer(&host->disable_timer, glamo_mci_disable_timer, + (unsigned long)host); + + ret = mmc_add_host(mmc); + if (ret) { + dev_err(&pdev->dev, "failed to add mmc host.\n"); + goto probe_freeirq; + } + + return 0; + +probe_freeirq: + free_irq(host->irq, host); +probe_iounmap_data: + iounmap(host->data_base); +probe_free_mem_region_data: + release_mem_region(host->data_mem->start, + resource_size(host->data_mem)); +probe_iounmap_mmio: + iounmap(host->mmio_base); +probe_free_mem_region_mmio: + release_mem_region(host->mmio_mem->start, + resource_size(host->mmio_mem)); +probe_regulator_put: + regulator_put(host->regulator); +probe_free_host: + mmc_free_host(mmc); +probe_out: + return ret; +} + +static int glamo_mci_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct glamo_mci_host *host = mmc_priv(mmc); + + free_irq(host->irq, host); + + mmc_remove_host(mmc); + iounmap(host->mmio_base); + iounmap(host->data_base); + release_mem_region(host->mmio_mem->start, + resource_size(host->mmio_mem)); + release_mem_region(host->data_mem->start, + resource_size(host->data_mem)); + + regulator_put(host->regulator); + + mmc_free_host(mmc); + + glamo_engine_disable(host->core, GLAMO_ENGINE_MMC); + return 0; +} + + +#ifdef CONFIG_PM + +static int glamo_mci_suspend(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct glamo_mci_host *host = mmc_priv(mmc); + int ret; + + cancel_work_sync(&host->irq_work); + + ret = mmc_suspend_host(mmc, PMSG_SUSPEND); + + return ret; +} + +static int glamo_mci_resume(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct glamo_mci_host *host = mmc_priv(mmc); + int ret; + + + glamo_mci_reset(host); + glamo_engine_enable(host->core, GLAMO_ENGINE_MMC); + mdelay(10); + + ret = mmc_resume_host(host->mmc); + + return 0; +} + +static struct dev_pm_ops glamo_mci_pm_ops = { + .suspend = glamo_mci_suspend, + .resume = glamo_mci_resume, + .freeze = glamo_mci_suspend, + .thaw = glamo_mci_resume, +}; +#define GLAMO_MCI_PM_OPS (&glamo_mci_pm_ops) + +#else /* CONFIG_PM */ +#define GLAMO_MCI_PM_OPS NULL +#endif /* CONFIG_PM */ + + +static struct platform_driver glamo_mci_driver = { + .probe = glamo_mci_probe, + .remove = glamo_mci_remove, + .driver = { + .name = "glamo-mci", + .owner = THIS_MODULE, + .pm = GLAMO_MCI_PM_OPS, + }, +}; + +static int __init glamo_mci_init(void) +{ + platform_driver_register(&glamo_mci_driver); + return 0; +} +module_init(glamo_mci_init); + +static void __exit glamo_mci_exit(void) +{ + platform_driver_unregister(&glamo_mci_driver); +} +module_exit(glamo_mci_exit); + +MODULE_DESCRIPTION("Glamo MMC/SD Card Interface driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andy Green <andy@openmoko.com>"); diff --git a/drivers/mfd/glamo/glamo-regs.h b/drivers/mfd/glamo/glamo-regs.h new file mode 100644 index 00000000000..59848e1122e --- /dev/null +++ b/drivers/mfd/glamo/glamo-regs.h @@ -0,0 +1,630 @@ +#ifndef _GLAMO_REGS_H +#define _GLAMO_REGS_H + +/* Smedia Glamo 336x/337x driver + * + * (C) 2007 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * All rights reserved. + * + * 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 + */ + +enum glamo_regster_offsets { + GLAMO_REGOFS_GENERIC = 0x0000, + GLAMO_REGOFS_HOSTBUS = 0x0200, + GLAMO_REGOFS_MEMORY = 0x0300, + GLAMO_REGOFS_VIDCAP = 0x0400, + GLAMO_REGOFS_ISP = 0x0500, + GLAMO_REGOFS_JPEG = 0x0800, + GLAMO_REGOFS_MPEG = 0x0c00, + GLAMO_REGOFS_LCD = 0x1100, + GLAMO_REGOFS_MMC = 0x1400, + GLAMO_REGOFS_MPROC0 = 0x1500, + GLAMO_REGOFS_MPROC1 = 0x1580, + GLAMO_REGOFS_CMDQUEUE = 0x1600, + GLAMO_REGOFS_RISC = 0x1680, + GLAMO_REGOFS_2D = 0x1700, + GLAMO_REGOFS_3D = 0x1b00, + GLAMO_REGOFS_END = 0x2400, +}; + + +enum glamo_register_generic { + GLAMO_REG_GCONF1 = 0x0000, + GLAMO_REG_GCONF2 = 0x0002, +#define GLAMO_REG_DEVICE_ID GLAMO_REG_GCONF2 + GLAMO_REG_GCONF3 = 0x0004, +#define GLAMO_REG_REVISION_ID GLAMO_REG_GCONF3 + GLAMO_REG_IRQ_GEN1 = 0x0006, +#define GLAMO_REG_IRQ_ENABLE GLAMO_REG_IRQ_GEN1 + GLAMO_REG_IRQ_GEN2 = 0x0008, +#define GLAMO_REG_IRQ_SET GLAMO_REG_IRQ_GEN2 + GLAMO_REG_IRQ_GEN3 = 0x000a, +#define GLAMO_REG_IRQ_CLEAR GLAMO_REG_IRQ_GEN3 + GLAMO_REG_IRQ_GEN4 = 0x000c, +#define GLAMO_REG_IRQ_STATUS GLAMO_REG_IRQ_GEN4 + GLAMO_REG_CLOCK_HOST = 0x0010, + GLAMO_REG_CLOCK_MEMORY = 0x0012, + GLAMO_REG_CLOCK_LCD = 0x0014, + GLAMO_REG_CLOCK_MMC = 0x0016, + GLAMO_REG_CLOCK_ISP = 0x0018, + GLAMO_REG_CLOCK_JPEG = 0x001a, + GLAMO_REG_CLOCK_3D = 0x001c, + GLAMO_REG_CLOCK_2D = 0x001e, + GLAMO_REG_CLOCK_RISC1 = 0x0020, /* 3365 only? */ + GLAMO_REG_CLOCK_RISC2 = 0x0022, /* 3365 only? */ + GLAMO_REG_CLOCK_MPEG = 0x0024, + GLAMO_REG_CLOCK_MPROC = 0x0026, + + GLAMO_REG_CLOCK_GEN5_1 = 0x0030, + GLAMO_REG_CLOCK_GEN5_2 = 0x0032, + GLAMO_REG_CLOCK_GEN6 = 0x0034, + GLAMO_REG_CLOCK_GEN7 = 0x0036, + GLAMO_REG_CLOCK_GEN8 = 0x0038, + GLAMO_REG_CLOCK_GEN9 = 0x003a, + GLAMO_REG_CLOCK_GEN10 = 0x003c, + GLAMO_REG_CLOCK_GEN11 = 0x003e, + GLAMO_REG_PLL_GEN1 = 0x0040, + GLAMO_REG_PLL_GEN2 = 0x0042, + GLAMO_REG_PLL_GEN3 = 0x0044, + GLAMO_REG_PLL_GEN4 = 0x0046, + GLAMO_REG_PLL_GEN5 = 0x0048, + GLAMO_REG_GPIO_GEN1 = 0x0050, + GLAMO_REG_GPIO_GEN2 = 0x0052, + GLAMO_REG_GPIO_GEN3 = 0x0054, + GLAMO_REG_GPIO_GEN4 = 0x0056, + GLAMO_REG_GPIO_GEN5 = 0x0058, + GLAMO_REG_GPIO_GEN6 = 0x005a, + GLAMO_REG_GPIO_GEN7 = 0x005c, + GLAMO_REG_GPIO_GEN8 = 0x005e, + GLAMO_REG_GPIO_GEN9 = 0x0060, + GLAMO_REG_GPIO_GEN10 = 0x0062, + GLAMO_REG_DFT_GEN1 = 0x0070, + GLAMO_REG_DFT_GEN2 = 0x0072, + GLAMO_REG_DFT_GEN3 = 0x0074, + GLAMO_REG_DFT_GEN4 = 0x0076, + + GLAMO_REG_DFT_GEN5 = 0x01e0, + GLAMO_REG_DFT_GEN6 = 0x01f0, +}; + +#define GLAMO_REG_HOSTBUS(x) (GLAMO_REGOFS_HOSTBUS-2+(x*2)) + +#define REG_MEM(x) (GLAMO_REGOFS_MEMORY+(x)) +#define GLAMO_REG_MEM_TIMING(x) (GLAMO_REG_MEM_TIMING1-2+(x*2)) + +enum glamo_register_mem { + GLAMO_REG_MEM_TYPE = REG_MEM(0x00), + GLAMO_REG_MEM_GEN = REG_MEM(0x02), + GLAMO_REG_MEM_TIMING1 = REG_MEM(0x04), + GLAMO_REG_MEM_TIMING2 = REG_MEM(0x06), + GLAMO_REG_MEM_TIMING3 = REG_MEM(0x08), + GLAMO_REG_MEM_TIMING4 = REG_MEM(0x0a), + GLAMO_REG_MEM_TIMING5 = REG_MEM(0x0c), + GLAMO_REG_MEM_TIMING6 = REG_MEM(0x0e), + GLAMO_REG_MEM_TIMING7 = REG_MEM(0x10), + GLAMO_REG_MEM_TIMING8 = REG_MEM(0x12), + GLAMO_REG_MEM_TIMING9 = REG_MEM(0x14), + GLAMO_REG_MEM_TIMING10 = REG_MEM(0x16), + GLAMO_REG_MEM_TIMING11 = REG_MEM(0x18), + GLAMO_REG_MEM_POWER1 = REG_MEM(0x1a), + GLAMO_REG_MEM_POWER2 = REG_MEM(0x1c), + GLAMO_REG_MEM_LCD_BUF1 = REG_MEM(0x1e), + GLAMO_REG_MEM_LCD_BUF2 = REG_MEM(0x20), + GLAMO_REG_MEM_LCD_BUF3 = REG_MEM(0x22), + GLAMO_REG_MEM_LCD_BUF4 = REG_MEM(0x24), + GLAMO_REG_MEM_BIST1 = REG_MEM(0x26), + GLAMO_REG_MEM_BIST2 = REG_MEM(0x28), + GLAMO_REG_MEM_BIST3 = REG_MEM(0x2a), + GLAMO_REG_MEM_BIST4 = REG_MEM(0x2c), + GLAMO_REG_MEM_BIST5 = REG_MEM(0x2e), + GLAMO_REG_MEM_MAH1 = REG_MEM(0x30), + GLAMO_REG_MEM_MAH2 = REG_MEM(0x32), + GLAMO_REG_MEM_DRAM1 = REG_MEM(0x34), + GLAMO_REG_MEM_DRAM2 = REG_MEM(0x36), + GLAMO_REG_MEM_CRC = REG_MEM(0x38), +}; + +#define GLAMO_MEM_TYPE_MASK 0x03 + +enum glamo_reg_mem_dram1 { + /* b0 - b10 == refresh period, 1 -> 2048 clocks */ + GLAMO_MEM_DRAM1_EN_GATE_CLK = (1 << 11), + GLAMO_MEM_DRAM1_SELF_REFRESH = (1 << 12), + GLAMO_MEM_DRAM1_EN_GATE_CKE = (1 << 13), + GLAMO_MEM_DRAM1_EN_DRAM_REFRESH = (1 << 14), + GLAMO_MEM_DRAM1_EN_MODEREG_SET = (1 << 15), +}; + +enum glamo_reg_mem_dram2 { + GLAMO_MEM_DRAM2_DEEP_PWRDOWN = (1 << 12), +}; + +enum glamo_irq_index { + GLAMO_IRQIDX_HOSTBUS = 0, + GLAMO_IRQIDX_JPEG = 1, + GLAMO_IRQIDX_MPEG = 2, + GLAMO_IRQIDX_MPROC1 = 3, + GLAMO_IRQIDX_MPROC0 = 4, + GLAMO_IRQIDX_CMDQUEUE = 5, + GLAMO_IRQIDX_2D = 6, + GLAMO_IRQIDX_MMC = 7, + GLAMO_IRQIDX_RISC = 8, +}; + +enum glamo_irq { + GLAMO_IRQ_HOSTBUS = (1 << GLAMO_IRQIDX_HOSTBUS), + GLAMO_IRQ_JPEG = (1 << GLAMO_IRQIDX_JPEG), + GLAMO_IRQ_MPEG = (1 << GLAMO_IRQIDX_MPEG), + GLAMO_IRQ_MPROC1 = (1 << GLAMO_IRQIDX_MPROC1), + GLAMO_IRQ_MPROC0 = (1 << GLAMO_IRQIDX_MPROC0), + GLAMO_IRQ_CMDQUEUE = (1 << GLAMO_IRQIDX_CMDQUEUE), + GLAMO_IRQ_2D = (1 << GLAMO_IRQIDX_2D), + GLAMO_IRQ_MMC = (1 << GLAMO_IRQIDX_MMC), + GLAMO_IRQ_RISC = (1 << GLAMO_IRQIDX_RISC), +}; + +enum glamo_reg_clock_host { + GLAMO_CLOCK_HOST_DG_BCLK = 0x0001, + GLAMO_CLOCK_HOST_DG_M0CLK = 0x0004, + GLAMO_CLOCK_HOST_RESET = 0x1000, +}; + +enum glamo_reg_clock_mem { + GLAMO_CLOCK_MEM_DG_M1CLK = 0x0001, + GLAMO_CLOCK_MEM_EN_M1CLK = 0x0002, + GLAMO_CLOCK_MEM_DG_MOCACLK = 0x0004, + GLAMO_CLOCK_MEM_EN_MOCACLK = 0x0008, + GLAMO_CLOCK_MEM_RESET = 0x1000, + GLAMO_CLOCK_MOCA_RESET = 0x2000, +}; + +enum glamo_reg_clock_lcd { + GLAMO_CLOCK_LCD_DG_DCLK = 0x0001, + GLAMO_CLOCK_LCD_EN_DCLK = 0x0002, + GLAMO_CLOCK_LCD_DG_DMCLK = 0x0004, + GLAMO_CLOCK_LCD_EN_DMCLK = 0x0008, + GLAMO_CLOCK_LCD_EN_DHCLK = 0x0020, + GLAMO_CLOCK_LCD_DG_M5CLK = 0x0040, + GLAMO_CLOCK_LCD_EN_M5CLK = 0x0080, + GLAMO_CLOCK_LCD_RESET = 0x1000, +}; + +enum glamo_reg_clock_mmc { + GLAMO_CLOCK_MMC_DG_TCLK = 0x0001, + GLAMO_CLOCK_MMC_EN_TCLK = 0x0002, + GLAMO_CLOCK_MMC_DG_M9CLK = 0x0004, + GLAMO_CLOCK_MMC_EN_M9CLK = 0x0008, + GLAMO_CLOCK_MMC_RESET = 0x1000, +}; + +enum glamo_reg_basic_mmc { + /* set to disable CRC error rejection */ + GLAMO_BASIC_MMC_DISABLE_CRC = 0x0001, + /* enable completion interrupt */ + GLAMO_BASIC_MMC_EN_COMPL_INT = 0x0002, + /* stop MMC clock while enforced idle waiting for data from card */ + GLAMO_BASIC_MMC_NO_CLK_RD_WAIT = 0x0004, + /* 0 = 1-bit bus to card, 1 = use 4-bit bus (has to be negotiated) */ + GLAMO_BASIC_MMC_EN_4BIT_DATA = 0x0008, + /* enable 75K pullups on D3..D0 */ + GLAMO_BASIC_MMC_EN_DATA_PUPS = 0x0010, + /* enable 75K pullup on CMD */ + GLAMO_BASIC_MMC_EN_CMD_PUP = 0x0020, + /* IO drive strength 00=weak -> 11=strongest */ + GLAMO_BASIC_MMC_EN_DR_STR0 = 0x0040, + GLAMO_BASIC_MMC_EN_DR_STR1 = 0x0080, + /* TCLK delay stage A, 0000 = 500ps --> 1111 = 8ns */ + GLAMO_BASIC_MMC_EN_TCLK_DLYA0 = 0x0100, + GLAMO_BASIC_MMC_EN_TCLK_DLYA1 = 0x0200, + GLAMO_BASIC_MMC_EN_TCLK_DLYA2 = 0x0400, + GLAMO_BASIC_MMC_EN_TCLK_DLYA3 = 0x0800, + /* TCLK delay stage B (cumulative), 0000 = 500ps --> 1111 = 8ns */ + GLAMO_BASIC_MMC_EN_TCLK_DLYB0 = 0x1000, + GLAMO_BASIC_MMC_EN_TCLK_DLYB1 = 0x2000, + GLAMO_BASIC_MMC_EN_TCLK_DLYB2 = 0x4000, + GLAMO_BASIC_MMC_EN_TCLK_DLYB3 = 0x8000, +}; + +enum glamo_reg_stat1_mmc { + /* command "counter" (really: toggle) */ + GLAMO_STAT1_MMC_CMD_CTR = 0x8000, + /* engine is idle */ + GLAMO_STAT1_MMC_IDLE = 0x4000, + /* readback response is ready */ + GLAMO_STAT1_MMC_RB_RRDY = 0x0200, + /* readback data is ready */ + GLAMO_STAT1_MMC_RB_DRDY = 0x0100, + /* no response timeout */ + GLAMO_STAT1_MMC_RTOUT = 0x0020, + /* no data timeout */ + GLAMO_STAT1_MMC_DTOUT = 0x0010, + /* CRC error on block write */ + GLAMO_STAT1_MMC_BWERR = 0x0004, + /* CRC error on block read */ + GLAMO_STAT1_MMC_BRERR = 0x0002 +}; + +enum glamo_reg_fire_mmc { + /* command "counter" (really: toggle) + * the STAT1 register reflects this so you can ensure you don't look + * at status for previous command + */ + GLAMO_FIRE_MMC_CMD_CTR = 0x8000, + /* sets kind of response expected */ + GLAMO_FIRE_MMC_RES_MASK = 0x0700, + /* sets command type */ + GLAMO_FIRE_MMC_TYP_MASK = 0x00C0, + /* sets command class */ + GLAMO_FIRE_MMC_CLS_MASK = 0x000F, +}; + +enum glamo_fire_mmc_response_types { + GLAMO_FIRE_MMC_RSPT_R1 = 0x0000, + GLAMO_FIRE_MMC_RSPT_R1b = 0x0100, + GLAMO_FIRE_MMC_RSPT_R2 = 0x0200, + GLAMO_FIRE_MMC_RSPT_R3 = 0x0300, + GLAMO_FIRE_MMC_RSPT_R4 = 0x0400, + GLAMO_FIRE_MMC_RSPT_R5 = 0x0500, +}; + +enum glamo_fire_mmc_command_types { + /* broadcast, no response */ + GLAMO_FIRE_MMC_CMDT_BNR = 0x0000, + /* broadcast, with response */ + GLAMO_FIRE_MMC_CMDT_BR = 0x0040, + /* addressed, no data */ + GLAMO_FIRE_MMC_CMDT_AND = 0x0080, + /* addressed, with data */ + GLAMO_FIRE_MMC_CMDT_AD = 0x00C0, +}; + +enum glamo_fire_mmc_command_class { + /* "Stream Read" */ + GLAMO_FIRE_MMC_CC_STRR = 0x0000, + /* Single Block Read */ + GLAMO_FIRE_MMC_CC_SBR = 0x0001, + /* Multiple Block Read With Stop */ + GLAMO_FIRE_MMC_CC_MBRS = 0x0002, + /* Multiple Block Read No Stop */ + GLAMO_FIRE_MMC_CC_MBRNS = 0x0003, + /* RESERVED for "Stream Write" */ + GLAMO_FIRE_MMC_CC_STRW = 0x0004, + /* "Stream Write" */ + GLAMO_FIRE_MMC_CC_SBW = 0x0005, + /* RESERVED for Multiple Block Write With Stop */ + GLAMO_FIRE_MMC_CC_MBWS = 0x0006, + /* Multiple Block Write No Stop */ + GLAMO_FIRE_MMC_CC_MBWNS = 0x0007, + /* STOP command */ + GLAMO_FIRE_MMC_CC_STOP = 0x0008, + /* Cancel on Running Command */ + GLAMO_FIRE_MMC_CC_CANCL = 0x0009, + /* "Basic Command" */ + GLAMO_FIRE_MMC_CC_BASIC = 0x000a, +}; + +/* these are offsets from the start of the MMC register region */ +enum glamo_register_mmc { + /* MMC command, b15..8 = cmd arg b7..0; b7..1 = CRC; b0 = end bit */ + GLAMO_REG_MMC_CMD_REG1 = 0x00, + /* MMC command, b15..0 = cmd arg b23 .. 8 */ + GLAMO_REG_MMC_CMD_REG2 = 0x02, + /* MMC command, b15=start, b14=transmission, + * b13..8=cmd idx, b7..0=cmd arg b31..24 + */ + GLAMO_REG_MMC_CMD_REG3 = 0x04, + GLAMO_REG_MMC_CMD_FIRE = 0x06, + GLAMO_REG_MMC_CMD_RSP1 = 0x10, + GLAMO_REG_MMC_CMD_RSP2 = 0x12, + GLAMO_REG_MMC_CMD_RSP3 = 0x14, + GLAMO_REG_MMC_CMD_RSP4 = 0x16, + GLAMO_REG_MMC_CMD_RSP5 = 0x18, + GLAMO_REG_MMC_CMD_RSP6 = 0x1a, + GLAMO_REG_MMC_CMD_RSP7 = 0x1c, + GLAMO_REG_MMC_CMD_RSP8 = 0x1e, + GLAMO_REG_MMC_RB_STAT1 = 0x20, + GLAMO_REG_MMC_RB_BLKCNT = 0x22, + GLAMO_REG_MMC_RB_BLKLEN = 0x24, + GLAMO_REG_MMC_BASIC = 0x30, + GLAMO_REG_MMC_RDATADS1 = 0x34, + GLAMO_REG_MMC_RDATADS2 = 0x36, + GLAMO_REG_MMC_WDATADS1 = 0x38, + GLAMO_REG_MMC_WDATADS2 = 0x3a, + GLAMO_REG_MMC_DATBLKCNT = 0x3c, + GLAMO_REG_MMC_DATBLKLEN = 0x3e, + GLAMO_REG_MMC_TIMEOUT = 0x40, + +}; + +enum glamo_reg_clock_isp { + GLAMO_CLOCK_ISP_DG_I1CLK = 0x0001, + GLAMO_CLOCK_ISP_EN_I1CLK = 0x0002, + GLAMO_CLOCK_ISP_DG_CCLK = 0x0004, + GLAMO_CLOCK_ISP_EN_CCLK = 0x0008, + GLAMO_CLOCK_ISP_EN_SCLK = 0x0020, + GLAMO_CLOCK_ISP_DG_M2CLK = 0x0040, + GLAMO_CLOCK_ISP_EN_M2CLK = 0x0080, + GLAMO_CLOCK_ISP_DG_M15CLK = 0x0100, + GLAMO_CLOCK_ISP_EN_M15CLK = 0x0200, + GLAMO_CLOCK_ISP1_RESET = 0x1000, + GLAMO_CLOCK_ISP2_RESET = 0x2000, +}; + +enum glamo_reg_clock_jpeg { + GLAMO_CLOCK_JPEG_DG_JCLK = 0x0001, + GLAMO_CLOCK_JPEG_EN_JCLK = 0x0002, + GLAMO_CLOCK_JPEG_DG_M3CLK = 0x0004, + GLAMO_CLOCK_JPEG_EN_M3CLK = 0x0008, + GLAMO_CLOCK_JPEG_RESET = 0x1000, +}; + +enum glamo_reg_clock_2d { + GLAMO_CLOCK_2D_DG_GCLK = 0x0001, + GLAMO_CLOCK_2D_EN_GCLK = 0x0002, + GLAMO_CLOCK_2D_DG_M7CLK = 0x0004, + GLAMO_CLOCK_2D_EN_M7CLK = 0x0008, + GLAMO_CLOCK_2D_DG_M6CLK = 0x0010, + GLAMO_CLOCK_2D_EN_M6CLK = 0x0020, + GLAMO_CLOCK_2D_RESET = 0x1000, + GLAMO_CLOCK_2D_CQ_RESET = 0x2000, +}; + +enum glamo_reg_clock_3d { + GLAMO_CLOCK_3D_DG_ECLK = 0x0001, + GLAMO_CLOCK_3D_EN_ECLK = 0x0002, + GLAMO_CLOCK_3D_DG_RCLK = 0x0004, + GLAMO_CLOCK_3D_EN_RCLK = 0x0008, + GLAMO_CLOCK_3D_DG_M8CLK = 0x0010, + GLAMO_CLOCK_3D_EN_M8CLK = 0x0020, + GLAMO_CLOCK_3D_BACK_RESET = 0x1000, + GLAMO_CLOCK_3D_FRONT_RESET = 0x2000, +}; + +enum glamo_reg_clock_mpeg { + GLAMO_CLOCK_MPEG_DG_X0CLK = 0x0001, + GLAMO_CLOCK_MPEG_EN_X0CLK = 0x0002, + GLAMO_CLOCK_MPEG_DG_X1CLK = 0x0004, + GLAMO_CLOCK_MPEG_EN_X1CLK = 0x0008, + GLAMO_CLOCK_MPEG_DG_X2CLK = 0x0010, + GLAMO_CLOCK_MPEG_EN_X2CLK = 0x0020, + GLAMO_CLOCK_MPEG_DG_X3CLK = 0x0040, + GLAMO_CLOCK_MPEG_EN_X3CLK = 0x0080, + GLAMO_CLOCK_MPEG_DG_X4CLK = 0x0100, + GLAMO_CLOCK_MPEG_EN_X4CLK = 0x0200, + GLAMO_CLOCK_MPEG_DG_X6CLK = 0x0400, + GLAMO_CLOCK_MPEG_EN_X6CLK = 0x0800, + GLAMO_CLOCK_MPEG_ENC_RESET = 0x1000, + GLAMO_CLOCK_MPEG_DEC_RESET = 0x2000, +}; + +enum glamo_reg_clock51 { + GLAMO_CLOCK_GEN51_EN_DIV_MCLK = 0x0001, + GLAMO_CLOCK_GEN51_EN_DIV_SCLK = 0x0002, + GLAMO_CLOCK_GEN51_EN_DIV_JCLK = 0x0004, + GLAMO_CLOCK_GEN51_EN_DIV_DCLK = 0x0008, + GLAMO_CLOCK_GEN51_EN_DIV_DMCLK = 0x0010, + GLAMO_CLOCK_GEN51_EN_DIV_DHCLK = 0x0020, + GLAMO_CLOCK_GEN51_EN_DIV_GCLK = 0x0040, + GLAMO_CLOCK_GEN51_EN_DIV_TCLK = 0x0080, + /* FIXME: higher bits */ +}; + +enum glamo_reg_hostbus2 { + GLAMO_HOSTBUS2_MMIO_EN_ISP = 0x0001, + GLAMO_HOSTBUS2_MMIO_EN_JPEG = 0x0002, + GLAMO_HOSTBUS2_MMIO_EN_MPEG = 0x0004, + GLAMO_HOSTBUS2_MMIO_EN_LCD = 0x0008, + GLAMO_HOSTBUS2_MMIO_EN_MMC = 0x0010, + GLAMO_HOSTBUS2_MMIO_EN_MICROP0 = 0x0020, + GLAMO_HOSTBUS2_MMIO_EN_MICROP1 = 0x0040, + GLAMO_HOSTBUS2_MMIO_EN_CQ = 0x0080, + GLAMO_HOSTBUS2_MMIO_EN_RISC = 0x0100, + GLAMO_HOSTBUS2_MMIO_EN_2D = 0x0200, + GLAMO_HOSTBUS2_MMIO_EN_3D = 0x0400, +}; + +/* LCD Controller */ + +#define REG_LCD(x) (x) +enum glamo_reg_lcd { + GLAMO_REG_LCD_MODE1 = REG_LCD(0x00), + GLAMO_REG_LCD_MODE2 = REG_LCD(0x02), + GLAMO_REG_LCD_MODE3 = REG_LCD(0x04), + GLAMO_REG_LCD_WIDTH = REG_LCD(0x06), + GLAMO_REG_LCD_HEIGHT = REG_LCD(0x08), + GLAMO_REG_LCD_POLARITY = REG_LCD(0x0a), + GLAMO_REG_LCD_A_BASE1 = REG_LCD(0x0c), + GLAMO_REG_LCD_A_BASE2 = REG_LCD(0x0e), + GLAMO_REG_LCD_B_BASE1 = REG_LCD(0x10), + GLAMO_REG_LCD_B_BASE2 = REG_LCD(0x12), + GLAMO_REG_LCD_C_BASE1 = REG_LCD(0x14), + GLAMO_REG_LCD_C_BASE2 = REG_LCD(0x16), + GLAMO_REG_LCD_PITCH = REG_LCD(0x18), + /* RES */ + GLAMO_REG_LCD_HORIZ_TOTAL = REG_LCD(0x1c), + /* RES */ + GLAMO_REG_LCD_HORIZ_RETR_START = REG_LCD(0x20), + /* RES */ + GLAMO_REG_LCD_HORIZ_RETR_END = REG_LCD(0x24), + /* RES */ + GLAMO_REG_LCD_HORIZ_DISP_START = REG_LCD(0x28), + /* RES */ + GLAMO_REG_LCD_HORIZ_DISP_END = REG_LCD(0x2c), + /* RES */ + GLAMO_REG_LCD_VERT_TOTAL = REG_LCD(0x30), + /* RES */ + GLAMO_REG_LCD_VERT_RETR_START = REG_LCD(0x34), + /* RES */ + GLAMO_REG_LCD_VERT_RETR_END = REG_LCD(0x38), + /* RES */ + GLAMO_REG_LCD_VERT_DISP_START = REG_LCD(0x3c), + /* RES */ + GLAMO_REG_LCD_VERT_DISP_END = REG_LCD(0x40), + /* RES */ + GLAMO_REG_LCD_POL = REG_LCD(0x44), + GLAMO_REG_LCD_DATA_START = REG_LCD(0x46), + GLAMO_REG_LCD_FRATE_CONTRO = REG_LCD(0x48), + GLAMO_REG_LCD_DATA_CMD_HDR = REG_LCD(0x4a), + GLAMO_REG_LCD_SP_START = REG_LCD(0x4c), + GLAMO_REG_LCD_SP_END = REG_LCD(0x4e), + GLAMO_REG_LCD_CURSOR_BASE1 = REG_LCD(0x50), + GLAMO_REG_LCD_CURSOR_BASE2 = REG_LCD(0x52), + GLAMO_REG_LCD_CURSOR_PITCH = REG_LCD(0x54), + GLAMO_REG_LCD_CURSOR_X_SIZE = REG_LCD(0x56), + GLAMO_REG_LCD_CURSOR_Y_SIZE = REG_LCD(0x58), + GLAMO_REG_LCD_CURSOR_X_POS = REG_LCD(0x5a), + GLAMO_REG_LCD_CURSOR_Y_POS = REG_LCD(0x5c), + GLAMO_REG_LCD_CURSOR_PRESET = REG_LCD(0x5e), + GLAMO_REG_LCD_CURSOR_FG_COLOR = REG_LCD(0x60), + /* RES */ + GLAMO_REG_LCD_CURSOR_BG_COLOR = REG_LCD(0x64), + /* RES */ + GLAMO_REG_LCD_CURSOR_DST_COLOR = REG_LCD(0x68), + /* RES */ + GLAMO_REG_LCD_STATUS1 = REG_LCD(0x80), + GLAMO_REG_LCD_STATUS2 = REG_LCD(0x82), + GLAMO_REG_LCD_STATUS3 = REG_LCD(0x84), + GLAMO_REG_LCD_STATUS4 = REG_LCD(0x86), + /* RES */ + GLAMO_REG_LCD_COMMAND1 = REG_LCD(0xa0), + GLAMO_REG_LCD_COMMAND2 = REG_LCD(0xa2), + /* RES */ + GLAMO_REG_LCD_WFORM_DELAY1 = REG_LCD(0xb0), + GLAMO_REG_LCD_WFORM_DELAY2 = REG_LCD(0xb2), + /* RES */ + GLAMO_REG_LCD_GAMMA_CORR = REG_LCD(0x100), + /* RES */ + GLAMO_REG_LCD_GAMMA_R_ENTRY01 = REG_LCD(0x110), + GLAMO_REG_LCD_GAMMA_R_ENTRY23 = REG_LCD(0x112), + GLAMO_REG_LCD_GAMMA_R_ENTRY45 = REG_LCD(0x114), + GLAMO_REG_LCD_GAMMA_R_ENTRY67 = REG_LCD(0x116), + GLAMO_REG_LCD_GAMMA_R_ENTRY8 = REG_LCD(0x118), + /* RES */ + GLAMO_REG_LCD_GAMMA_G_ENTRY01 = REG_LCD(0x130), + GLAMO_REG_LCD_GAMMA_G_ENTRY23 = REG_LCD(0x132), + GLAMO_REG_LCD_GAMMA_G_ENTRY45 = REG_LCD(0x134), + GLAMO_REG_LCD_GAMMA_G_ENTRY67 = REG_LCD(0x136), + GLAMO_REG_LCD_GAMMA_G_ENTRY8 = REG_LCD(0x138), + /* RES */ + GLAMO_REG_LCD_GAMMA_B_ENTRY01 = REG_LCD(0x150), + GLAMO_REG_LCD_GAMMA_B_ENTRY23 = REG_LCD(0x152), + GLAMO_REG_LCD_GAMMA_B_ENTRY45 = REG_LCD(0x154), + GLAMO_REG_LCD_GAMMA_B_ENTRY67 = REG_LCD(0x156), + GLAMO_REG_LCD_GAMMA_B_ENTRY8 = REG_LCD(0x158), + /* RES */ + GLAMO_REG_LCD_SRAM_DRIVING1 = REG_LCD(0x160), + GLAMO_REG_LCD_SRAM_DRIVING2 = REG_LCD(0x162), + GLAMO_REG_LCD_SRAM_DRIVING3 = REG_LCD(0x164), +}; + +enum glamo_reg_lcd_mode1 { + GLAMO_LCD_MODE1_PWRSAVE = 0x0001, + GLAMO_LCD_MODE1_PARTIAL_PRT = 0x0002, + GLAMO_LCD_MODE1_HWFLIP = 0x0004, + GLAMO_LCD_MODE1_LCD2 = 0x0008, + /* RES */ + GLAMO_LCD_MODE1_PARTIAL_MODE = 0x0020, + GLAMO_LCD_MODE1_CURSOR_DSTCOLOR = 0x0040, + GLAMO_LCD_MODE1_PARTIAL_ENABLE = 0x0080, + GLAMO_LCD_MODE1_TVCLK_IN_ENABLE = 0x0100, + GLAMO_LCD_MODE1_HSYNC_HIGH_ACT = 0x0200, + GLAMO_LCD_MODE1_VSYNC_HIGH_ACT = 0x0400, + GLAMO_LCD_MODE1_HSYNC_FLIP = 0x0800, + GLAMO_LCD_MODE1_GAMMA_COR_EN = 0x1000, + GLAMO_LCD_MODE1_DITHER_EN = 0x2000, + GLAMO_LCD_MODE1_CURSOR_EN = 0x4000, + GLAMO_LCD_MODE1_ROTATE_EN = 0x8000, +}; + +enum glamo_reg_lcd_mode2 { + GLAMO_LCD_MODE2_CRC_CHECK_EN = 0x0001, + GLAMO_LCD_MODE2_DCMD_PER_LINE = 0x0002, + GLAMO_LCD_MODE2_NOUSE_BDEF = 0x0004, + GLAMO_LCD_MODE2_OUT_POS_MODE = 0x0008, + GLAMO_LCD_MODE2_FRATE_CTRL_EN = 0x0010, + GLAMO_LCD_MODE2_SINGLE_BUFFER = 0x0020, + GLAMO_LCD_MODE2_SER_LSB_TO_MSB = 0x0040, + /* FIXME */ +}; + +enum glamo_reg_lcd_mode3 { + /* LCD color source data format */ + GLAMO_LCD_SRC_RGB565 = 0x0000, + GLAMO_LCD_SRC_ARGB1555 = 0x4000, + GLAMO_LCD_SRC_ARGB4444 = 0x8000, + /* interface type */ + GLAMO_LCD_MODE3_LCD = 0x1000, + GLAMO_LCD_MODE3_RGB = 0x0800, + GLAMO_LCD_MODE3_CPU = 0x0000, + /* mode */ + GLAMO_LCD_MODE3_RGB332 = 0x0000, + GLAMO_LCD_MODE3_RGB444 = 0x0100, + GLAMO_LCD_MODE3_RGB565 = 0x0200, + GLAMO_LCD_MODE3_RGB666 = 0x0300, + /* depth */ + GLAMO_LCD_MODE3_6BITS = 0x0000, + GLAMO_LCD_MODE3_8BITS = 0x0010, + GLAMO_LCD_MODE3_9BITS = 0x0020, + GLAMO_LCD_MODE3_16BITS = 0x0030, + GLAMO_LCD_MODE3_18BITS = 0x0040, +}; + +enum glamo_lcd_rot_mode { + GLAMO_LCD_ROT_MODE_0 = 0x0000, + GLAMO_LCD_ROT_MODE_180 = 0x2000, + GLAMO_LCD_ROT_MODE_MIRROR = 0x4000, + GLAMO_LCD_ROT_MODE_FLIP = 0x6000, + GLAMO_LCD_ROT_MODE_90 = 0x8000, + GLAMO_LCD_ROT_MODE_270 = 0xa000, +}; +#define GLAMO_LCD_ROT_MODE_MASK 0xe000 + +enum glamo_lcd_cmd_type { + GLAMO_LCD_CMD_TYPE_DISP = 0x0000, + GLAMO_LCD_CMD_TYPE_PARALLEL = 0x4000, + GLAMO_LCD_CMD_TYPE_SERIAL = 0x8000, + GLAMO_LCD_CMD_TYPE_SERIAL_DIRECT = 0xc000, +}; +#define GLAMO_LCD_CMD_TYPE_MASK 0xc000 + +enum glamo_lcd_cmds { + GLAMO_LCD_CMD_DATA_DISP_FIRE = 0x00, + GLAMO_LCD_CMD_DATA_DISP_SYNC = 0x01, /* RGB only */ + /* switch to command mode, no display */ + GLAMO_LCD_CMD_DATA_FIRE_NO_DISP = 0x02, + /* display until VSYNC, switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_VSYNC = 0x11, + /* display until HSYNC, switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_HSYNC = 0x12, + /* display until VSYNC, 1 black frame, VSYNC, switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_VSYNC_B = 0x13, + /* don't care about display and switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_FREE = 0x14, /* RGB only */ + /* don't care about display, keep data display but disable data, + * and switch to command */ + GLAMO_LCD_CMD_DATA_FIRE_FREE_D = 0x15, /* RGB only */ +}; + +enum glamo_core_revisions { + GLAMO_CORE_REV_A0 = 0x0000, + GLAMO_CORE_REV_A1 = 0x0001, + GLAMO_CORE_REV_A2 = 0x0002, + GLAMO_CORE_REV_A3 = 0x0003, +}; + +#endif /* _GLAMO_REGS_H */ diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c index 11dc7e69c4f..7dc854c3fd8 100644 --- a/drivers/mtd/nand/s3c2410.c +++ b/drivers/mtd/nand/s3c2410.c @@ -816,7 +816,7 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, nmtd->mtd.owner = THIS_MODULE; nmtd->set = set; - if (hardware_ecc) { + if (!(info->platform && info->platform->software_ecc) && hardware_ecc) { chip->ecc.calculate = s3c2410_nand_calculate_ecc; chip->ecc.correct = s3c2410_nand_correct_data; chip->ecc.mode = NAND_ECC_HW; diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index cea6cef27e8..9f6a8275e12 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -110,4 +110,16 @@ config CHARGER_PCF50633 help Say Y to include support for NXP PCF50633 Main Battery Charger. +config BATTERY_BQ27000_HDQ + tristate "BQ27000 HDQ battery monitor driver" + help + Say Y to enable support for the battery on the Neo Freerunner + +config HDQ_GPIO_BITBANG + bool "Generic gpio based HDQ bitbang" + help + Say Y to enable supoort for generic gpio based HDQ bitbang driver. + This can not be built as a module. + endif # POWER_SUPPLY + diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b96f29d91c2..225abe32627 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -29,3 +29,6 @@ obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o +obj-$(CONFIG_BATTERY_BQ27000_HDQ) += bq27000_battery.o + +obj-$(CONFIG_HDQ_GPIO_BITBANG) += hdq.o diff --git a/drivers/power/bq27000_battery.c b/drivers/power/bq27000_battery.c new file mode 100644 index 00000000000..32655dce1f3 --- /dev/null +++ b/drivers/power/bq27000_battery.c @@ -0,0 +1,477 @@ +/* + * Driver for batteries with bq27000 chips inside via HDQ + * + * Copyright 2008 Openmoko, Inc + * Andy Green <andy@openmoko.com> + * + * based on ds2760 driver, original copyright notice for that ---> + * + * Copyright Ā© 2007 Anton Vorontsov + * 2004-2007 Matt Reimer + * 2004 Szabolcs Gyurko + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + * + * Author: Anton Vorontsov <cbou@mail.ru> + * February 2007 + * + * Matt Reimer <mreimer@vpop.net> + * April 2004, 2005, 2007 + * + * Szabolcs Gyurko <szabolcs.gyurko@tlt.hu> + * September 2004 + */ + +#include <linux/module.h> +#include <linux/param.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/bq27000_battery.h> + +enum bq27000_regs { + /* RAM regs */ + /* read-write after this */ + BQ27000_CTRL = 0, /* Device Control Register */ + BQ27000_MODE, /* Device Mode Register */ + BQ27000_AR_L, /* At-Rate H L */ + BQ27000_AR_H, + /* read-only after this */ + BQ27000_ARTTE_L, /* At-Rate Time To Empty H L */ + BQ27000_ARTTE_H, + BQ27000_TEMP_L, /* Reported Temperature H L */ + BQ27000_TEMP_H, + BQ27000_VOLT_L, /* Reported Voltage H L */ + BQ27000_VOLT_H, + BQ27000_FLAGS, /* Status Flags */ + BQ27000_RSOC, /* Relative State of Charge */ + BQ27000_NAC_L, /* Nominal Available Capacity H L */ + BQ27000_NAC_H, + BQ27000_CACD_L, /* Discharge Compensated H L */ + BQ27000_CACD_H, + BQ27000_CACT_L, /* Temperature Compensated H L */ + BQ27000_CACT_H, + BQ27000_LMD_L, /* Last measured discharge H L */ + BQ27000_LMD_H, + BQ27000_AI_L, /* Average Current H L */ + BQ27000_AI_H, + BQ27000_TTE_L, /* Time to Empty H L */ + BQ27000_TTE_H, + BQ27000_TTF_L, /* Time to Full H L */ + BQ27000_TTF_H, + BQ27000_SI_L, /* Standby Current H L */ + BQ27000_SI_H, + BQ27000_STTE_L, /* Standby Time To Empty H L */ + BQ27000_STTE_H, + BQ27000_MLI_L, /* Max Load Current H L */ + BQ27000_MLI_H, + BQ27000_MLTTE_L, /* Max Load Time To Empty H L */ + BQ27000_MLTTE_H, + BQ27000_SAE_L, /* Available Energy H L */ + BQ27000_SAE_H, + BQ27000_AP_L, /* Available Power H L */ + BQ27000_AP_H, + BQ27000_TTECP_L, /* Time to Empty at Constant Power H L */ + BQ27000_TTECP_H, + BQ27000_CYCL_L, /* Cycle count since learning cycle H L */ + BQ27000_CYCL_H, + BQ27000_CYCT_L, /* Cycle Count Total H L */ + BQ27000_CYCT_H, + BQ27000_CSOC, /* Compensated State Of Charge */ + /* EEPROM regs */ + /* read-write after this */ + BQ27000_EE_EE_EN = 0x6e, /* EEPROM Program Enable */ + BQ27000_EE_ILMD = 0x76, /* Initial Last Measured Discharge High Byte */ + BQ27000_EE_SEDVF, /* Scaled EDVF Threshold */ + BQ27000_EE_SEDV1, /* Scaled EDV1 Threshold */ + BQ27000_EE_ISLC, /* Initial Standby Load Current */ + BQ27000_EE_DMFSD, /* Digital Magnitude Filter and Self Discharge */ + BQ27000_EE_TAPER, /* Aging Estimate Enable, Charge Termination Taper */ + BQ27000_EE_PKCFG, /* Pack Configuration Values */ + BQ27000_EE_IMLC, /* Initial Max Load Current or ID #3 */ + BQ27000_EE_DCOMP, /* Discharge rate compensation constants or ID #2 */ + BQ27000_EE_TCOMP, /* Temperature Compensation constants or ID #1 */ +}; + +enum bq27000_status_flags { + BQ27000_STATUS_CHGS = 0x80, /* 1 = being charged */ + BQ27000_STATUS_NOACT = 0x40, /* 1 = no activity */ + BQ27000_STATUS_IMIN = 0x20, /* 1 = Lion taper current mode */ + BQ27000_STATUS_CI = 0x10, /* 1 = capacity likely innacurate */ + BQ27000_STATUS_CALIP = 0x08, /* 1 = calibration in progress */ + BQ27000_STATUS_VDQ = 0x04, /* 1 = capacity should be accurate */ + BQ27000_STATUS_EDV1 = 0x02, /* 1 = end of discharge.. <6% left */ + BQ27000_STATUS_EDVF = 0x01, /* 1 = no, it's really empty now */ +}; + +#define NANOVOLTS_UNIT 3750 + +struct bq27000_bat_regs { + int ai; + int flags; + int lmd; + int rsoc; + int temp; + int tte; + int ttf; + int volt; +}; + +struct bq27000_device_info { + struct device *dev; + struct power_supply bat; + struct power_supply ac; + struct power_supply usb; + struct delayed_work work; + struct bq27000_platform_data *pdata; + + struct bq27000_bat_regs regs; +}; + +static unsigned int cache_time = 5000; +module_param(cache_time, uint, 0644); +MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); + +/* + * reading 16 bit values over HDQ has a special hazard where the + * hdq device firmware can update the 16-bit register during the time we + * read the two halves. TI document SLUS556D recommends the algorithm here + * to avoid trouble + */ + +static int hdq_read16(struct bq27000_device_info *di, int address) +{ + int acc; + int high; + int retries = 3; + + while (retries--) { + + high = (di->pdata->hdq_read)(address + 1); /* high part */ + + if (high < 0) + return high; + acc = (di->pdata->hdq_read)(address); + if (acc < 0) + return acc; + + /* confirm high didn't change between reading it and low */ + if (high == (di->pdata->hdq_read)(address + 1)) + return (high << 8) | acc; + } + + return -ETIME; +} + +static void bq27000_battery_external_power_changed(struct power_supply *psy) +{ + struct bq27000_device_info *di = container_of(psy, struct bq27000_device_info, bat); + + dev_dbg(di->dev, "%s\n", __FUNCTION__); + schedule_delayed_work(&di->work, 0); +} + +static int bq27000_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int n; + struct bq27000_device_info *di = container_of(psy, struct bq27000_device_info, bat); + + if (di->regs.rsoc < 0 && psp != POWER_SUPPLY_PROP_PRESENT) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + + if (!di->pdata->get_charger_online_status) + goto use_bat; + if ((di->pdata->get_charger_online_status)()) { + /* + * charger is definitively present + * we report our state in terms of what it says it + * is doing + */ + if (!di->pdata->get_charger_active_status) + goto use_bat; + + if ((di->pdata->get_charger_active_status)()) { + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + } + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + + /* + * platform provided definite indication of charger presence, + * and it is telling us it isn't there... but we are on so we + * must be running from battery ---> + */ + + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + +use_bat: + /* + * either the charger is not connected, or the + * platform doesn't give info about charger, use battery state + * but... battery state can be out of date by 4 seconds or + * so... use the platform callbacks if possible. + */ + + /* no real activity on the battery */ + if (di->regs.ai < 2) { + if (!di->regs.ttf) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + /* power is actually going in or out... */ + if (di->regs.flags < 0) + return di->regs.flags; + if (di->regs.flags & BQ27000_STATUS_CHGS) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + /* Do we have accurate readings... */ + if (di->regs.flags < 0) + return di->regs.flags; + if (di->regs.flags & BQ27000_STATUS_VDQ) + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (di->regs.volt < 0) + return di->regs.volt; + /* mV -> uV */ + val->intval = di->regs.volt * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (di->regs.flags < 0) + return di->regs.flags; + if (di->regs.flags & BQ27000_STATUS_CHGS) + n = -NANOVOLTS_UNIT; + else + n = NANOVOLTS_UNIT; + if (di->regs.ai < 0) + return di->regs.ai; + val->intval = (di->regs.ai * n) / di->pdata->rsense_mohms; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (di->regs.lmd < 0) + return di->regs.lmd; + val->intval = (di->regs.lmd * 3570) / di->pdata->rsense_mohms; + break; + case POWER_SUPPLY_PROP_TEMP: + if (di->regs.temp < 0) + return di->regs.temp; + /* K (in 0.25K units) is 273.15 up from C (in 0.1C)*/ + /* 10926 = 27315 * 4 / 10 */ + val->intval = (((long)di->regs.temp * 10l) - 10926) / 4; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = di->regs.rsoc; + if (val->intval < 0) + return val->intval; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !(di->regs.rsoc < 0); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + if (di->regs.tte < 0) + return di->regs.tte; + val->intval = 60 * di->regs.tte; + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + if (di->regs.ttf < 0) + return di->regs.ttf; + val->intval = 60 * di->regs.ttf; + break; + case POWER_SUPPLY_PROP_ONLINE: + if (di->pdata->get_charger_online_status) + val->intval = (di->pdata->get_charger_online_status)(); + else + return -EINVAL; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void bq27000_battery_work(struct work_struct *work) +{ + struct bq27000_device_info *di = + container_of(work, struct bq27000_device_info, work.work); + + if ((di->pdata->hdq_initialized)()) { + struct bq27000_bat_regs regs; + + regs.ai = hdq_read16(di, BQ27000_AI_L); + regs.flags = (di->pdata->hdq_read)(BQ27000_FLAGS); + regs.lmd = hdq_read16(di, BQ27000_LMD_L); + regs.rsoc = (di->pdata->hdq_read)(BQ27000_RSOC); + regs.temp = hdq_read16(di, BQ27000_TEMP_L); + regs.tte = hdq_read16(di, BQ27000_TTE_L); + regs.ttf = hdq_read16(di, BQ27000_TTF_L); + regs.volt = hdq_read16(di, BQ27000_VOLT_L); + + if (memcmp (®s, &di->regs, sizeof(regs)) != 0) { + di->regs = regs; + power_supply_changed(&di->bat); + } + } + + if (!schedule_delayed_work(&di->work, cache_time)) + dev_err(di->dev, "battery service reschedule failed\n"); +} + +static enum power_supply_property bq27000_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_ONLINE +}; + +static int bq27000_battery_probe(struct platform_device *pdev) +{ + int retval = 0; + struct bq27000_device_info *di; + struct bq27000_platform_data *pdata; + + dev_info(&pdev->dev, "BQ27000 Battery Driver (C) 2008 Openmoko, Inc\n"); + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + retval = -ENOMEM; + goto di_alloc_failed; + } + + platform_set_drvdata(pdev, di); + + pdata = pdev->dev.platform_data; + di->dev = &pdev->dev; + /* di->w1_dev = pdev->dev.parent; */ + di->bat.name = pdata->name; + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = bq27000_battery_props; + di->bat.num_properties = ARRAY_SIZE(bq27000_battery_props); + di->bat.get_property = bq27000_battery_get_property; + di->bat.external_power_changed = + bq27000_battery_external_power_changed; + di->bat.use_for_apm = 1; + di->pdata = pdata; + + retval = power_supply_register(&pdev->dev, &di->bat); + if (retval) { + dev_err(di->dev, "failed to register battery\n"); + goto batt_failed; + } + + INIT_DELAYED_WORK(&di->work, bq27000_battery_work); + + if (!schedule_delayed_work(&di->work, 0)) + dev_err(di->dev, "failed to schedule bq27000_battery_work\n"); + + return 0; + +batt_failed: + kfree(di); +di_alloc_failed: + return retval; +} + +static int bq27000_battery_remove(struct platform_device *pdev) +{ + struct bq27000_device_info *di = platform_get_drvdata(pdev); + + cancel_delayed_work(&di->work); + + power_supply_unregister(&di->bat); + + return 0; +} + +void bq27000_charging_state_change(struct platform_device *pdev) +{ + struct bq27000_device_info *di = platform_get_drvdata(pdev); + + if (!di) + return; +} +EXPORT_SYMBOL_GPL(bq27000_charging_state_change); + +#ifdef CONFIG_PM + +static int bq27000_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct bq27000_device_info *di = platform_get_drvdata(pdev); + + cancel_delayed_work(&di->work); + return 0; +} + +static int bq27000_battery_resume(struct platform_device *pdev) +{ + struct bq27000_device_info *di = platform_get_drvdata(pdev); + + schedule_delayed_work(&di->work, 0); + return 0; +} + +#else + +#define bq27000_battery_suspend NULL +#define bq27000_battery_resume NULL + +#endif /* CONFIG_PM */ + +static struct platform_driver bq27000_battery_driver = { + .driver = { + .name = "bq27000-battery", + }, + .probe = bq27000_battery_probe, + .remove = bq27000_battery_remove, + .suspend = bq27000_battery_suspend, + .resume = bq27000_battery_resume, +}; + +static int __init bq27000_battery_init(void) +{ + return platform_driver_register(&bq27000_battery_driver); +} + +static void __exit bq27000_battery_exit(void) +{ + platform_driver_unregister(&bq27000_battery_driver); +} + +module_init(bq27000_battery_init); +module_exit(bq27000_battery_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andy Green <andy@openmoko.com>"); +MODULE_DESCRIPTION("bq27000 battery driver"); diff --git a/drivers/power/hdq.c b/drivers/power/hdq.c new file mode 100644 index 00000000000..f03fabfb1aa --- /dev/null +++ b/drivers/power/hdq.c @@ -0,0 +1,515 @@ +/* + * HDQ generic GPIO bitbang driver using FIQ + * + * (C) 2006-2007 by Openmoko, Inc. + * Author: Andy Green <andy@openmoko.com> + * All rights reserved. + * + * 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 <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/hdq.h> + +#define HDQ_READ 0 +#define HDQ_WRITE 0x80 + +enum hdq_bitbang_states { + HDQB_IDLE = 0, + HDQB_TX_BREAK, + HDQB_TX_BREAK_RECOVERY, + HDQB_ADS_CALC, + HDQB_ADS_LOW, + HDQB_ADS_HIGH, + HDQB_WAIT_RX, + HDQB_DATA_RX_LOW, + HDQB_DATA_RX_HIGH, + HDQB_WAIT_TX, +}; + +static struct hdq_priv { + u8 hdq_probed; /* nonzero after HDQ driver probed */ + struct mutex hdq_lock; /* if you want to use hdq, you have to take lock */ + unsigned long hdq_gpio_pin; /* GTA02 = GPD14 which pin to meddle with */ + u8 hdq_ads; /* b7..b6 = register address, b0 = r/w */ + u8 hdq_tx_data; /* data to tx for write action */ + u8 hdq_rx_data; /* data received in read action */ + u8 hdq_request_ctr; /* incremented by "user" to request a transfer */ + u8 hdq_transaction_ctr; /* incremented after each transfer */ + u8 hdq_error; /* 0 = no error */ + u8 hdq_ctr; + u8 hdq_ctr2; + u8 hdq_bit; + u8 hdq_shifter; + u8 hdq_tx_data_done; + enum hdq_bitbang_states hdq_state; + int reported_error; + + struct hdq_platform_data *pdata; +} hdq_priv; + + +static void hdq_bad(void) +{ + if (!hdq_priv.reported_error) + printk(KERN_ERR "HDQ error: %d\n", hdq_priv.hdq_error); + hdq_priv.reported_error = 1; +} + +static void hdq_good(void) +{ + if (hdq_priv.reported_error) + printk(KERN_INFO "HDQ responds again\n"); + hdq_priv.reported_error = 0; +} + +int hdq_fiq_handler(void) +{ + if (!hdq_priv.hdq_probed) + return 0; + + switch (hdq_priv.hdq_state) { + case HDQB_IDLE: + if (hdq_priv.hdq_request_ctr == hdq_priv.hdq_transaction_ctr) + break; + hdq_priv.hdq_ctr = 250 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.pdata->gpio_set(0); + hdq_priv.pdata->gpio_dir_out(); + hdq_priv.hdq_tx_data_done = 0; + hdq_priv.hdq_state = HDQB_TX_BREAK; + break; + + case HDQB_TX_BREAK: /* issue low for > 190us */ + if (--hdq_priv.hdq_ctr == 0) { + hdq_priv.hdq_ctr = 60 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_state = HDQB_TX_BREAK_RECOVERY; + hdq_priv.pdata->gpio_set(1); + } + break; + + case HDQB_TX_BREAK_RECOVERY: /* issue low for > 40us */ + if (--hdq_priv.hdq_ctr) + break; + hdq_priv.hdq_shifter = hdq_priv.hdq_ads; + hdq_priv.hdq_bit = 8; /* 8 bits of ads / rw */ + hdq_priv.hdq_tx_data_done = 0; /* doing ads */ + /* fallthru on last one */ + case HDQB_ADS_CALC: + if (hdq_priv.hdq_shifter & 1) + hdq_priv.hdq_ctr = 50 / HDQ_SAMPLE_PERIOD_US; + else + hdq_priv.hdq_ctr = 120 / HDQ_SAMPLE_PERIOD_US; + /* carefully precompute the other phase length */ + hdq_priv.hdq_ctr2 = (210 - (hdq_priv.hdq_ctr * HDQ_SAMPLE_PERIOD_US)) / + HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_state = HDQB_ADS_LOW; + hdq_priv.hdq_shifter >>= 1; + hdq_priv.hdq_bit--; + hdq_priv.pdata->gpio_set(0); + break; + + case HDQB_ADS_LOW: + if (--hdq_priv.hdq_ctr) + break; + hdq_priv.pdata->gpio_set(1); + hdq_priv.hdq_state = HDQB_ADS_HIGH; + break; + + case HDQB_ADS_HIGH: + if (--hdq_priv.hdq_ctr2 > 1) /* account for HDQB_ADS_CALC */ + break; + if (hdq_priv.hdq_bit) { /* more bits to do */ + hdq_priv.hdq_state = HDQB_ADS_CALC; + break; + } + /* no more bits, wait it out until hdq_priv.hdq_ctr2 exhausted */ + if (hdq_priv.hdq_ctr2) + break; + /* ok no more bits and very last state */ + hdq_priv.hdq_ctr = 60 / HDQ_SAMPLE_PERIOD_US; + /* FIXME 0 = read */ + if (hdq_priv.hdq_ads & 0x80) { /* write the byte out */ + /* set delay before payload */ + hdq_priv.hdq_ctr = 300 / HDQ_SAMPLE_PERIOD_US; + /* already high, no need to write */ + hdq_priv.hdq_state = HDQB_WAIT_TX; + break; + } + /* read the next byte */ + hdq_priv.hdq_bit = 8; /* 8 bits of data */ + hdq_priv.hdq_ctr = 2500 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_state = HDQB_WAIT_RX; + hdq_priv.pdata->gpio_dir_in(); + break; + + case HDQB_WAIT_TX: /* issue low for > 40us */ + if (--hdq_priv.hdq_ctr) + break; + if (!hdq_priv.hdq_tx_data_done) { /* was that the data sent? */ + hdq_priv.hdq_tx_data_done++; + hdq_priv.hdq_shifter = hdq_priv.hdq_tx_data; + hdq_priv.hdq_bit = 8; /* 8 bits of data */ + hdq_priv.hdq_state = HDQB_ADS_CALC; /* start sending */ + break; + } + hdq_priv.hdq_error = 0; + hdq_priv.hdq_transaction_ctr = hdq_priv.hdq_request_ctr; + hdq_priv.hdq_state = HDQB_IDLE; /* all tx is done */ + /* idle in input mode, it's pulled up by 10K */ + hdq_priv.pdata->gpio_dir_in(); + break; + + case HDQB_WAIT_RX: /* wait for battery to talk to us */ + if (hdq_priv.pdata->gpio_get() == 0) { + /* it talks to us! */ + hdq_priv.hdq_ctr2 = 1; + hdq_priv.hdq_bit = 8; /* 8 bits of data */ + /* timeout */ + hdq_priv.hdq_ctr = 500 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_state = HDQB_DATA_RX_LOW; + break; + } + if (--hdq_priv.hdq_ctr == 0) { /* timed out, error */ + hdq_priv.hdq_error = 1; + hdq_priv.hdq_transaction_ctr = hdq_priv.hdq_request_ctr; + hdq_priv.hdq_state = HDQB_IDLE; /* abort */ + } + break; + + /* + * HDQ basically works by measuring the low time of the bit cell + * 32-50us --> '1', 80 - 145us --> '0' + */ + + case HDQB_DATA_RX_LOW: + if (hdq_priv.pdata->gpio_get()) { + hdq_priv.hdq_rx_data >>= 1; + if (hdq_priv.hdq_ctr2 <= (65 / HDQ_SAMPLE_PERIOD_US)) + hdq_priv.hdq_rx_data |= 0x80; + + if (--hdq_priv.hdq_bit == 0) { + hdq_priv.hdq_error = 0; + hdq_priv.hdq_transaction_ctr = + hdq_priv.hdq_request_ctr; + + hdq_priv.hdq_state = HDQB_IDLE; + } else + hdq_priv.hdq_state = HDQB_DATA_RX_HIGH; + /* timeout */ + hdq_priv.hdq_ctr = 1000 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_ctr2 = 1; + break; + } + hdq_priv.hdq_ctr2++; + if (--hdq_priv.hdq_ctr) + break; + /* timed out, error */ + hdq_priv.hdq_error = 2; + hdq_priv.hdq_transaction_ctr = hdq_priv.hdq_request_ctr; + hdq_priv.hdq_state = HDQB_IDLE; /* abort */ + break; + + case HDQB_DATA_RX_HIGH: + if (!hdq_priv.pdata->gpio_get()) { + /* it talks to us! */ + hdq_priv.hdq_ctr2 = 1; + /* timeout */ + hdq_priv.hdq_ctr = 400 / HDQ_SAMPLE_PERIOD_US; + hdq_priv.hdq_state = HDQB_DATA_RX_LOW; + break; + } + if (--hdq_priv.hdq_ctr) + break; + /* timed out, error */ + hdq_priv.hdq_error = 3; + hdq_priv.hdq_transaction_ctr = hdq_priv.hdq_request_ctr; + + /* we're in input mode already */ + hdq_priv.hdq_state = HDQB_IDLE; /* abort */ + break; + } + + /* Are we interested in keeping the FIQ source alive ? */ + if (hdq_priv.hdq_state != HDQB_IDLE) + return 1; + else + return 0; +} +static int fiq_busy(void) +{ + int request = (volatile u8)hdq_priv.hdq_request_ctr; + int transact = (volatile u8)hdq_priv.hdq_transaction_ctr; + + + return (request != transact); +} + +int hdq_initialized(void) +{ + return hdq_priv.hdq_probed; +} +EXPORT_SYMBOL_GPL(hdq_initialized); + +int hdq_read(int address) +{ + int count_sleeps = 5; + int ret = -ETIME; + + if (!hdq_priv.hdq_probed) + return -EINVAL; + + mutex_lock(&hdq_priv.hdq_lock); + + hdq_priv.hdq_error = 0; + hdq_priv.hdq_ads = address | HDQ_READ; + hdq_priv.hdq_request_ctr++; + hdq_priv.pdata->kick_fiq(); + /* + * FIQ takes care of it while we block our calling process + * But we're not spinning -- other processes run normally while + * we wait for the result + */ + while (count_sleeps--) { + msleep(10); /* valid transaction always completes in < 10ms */ + + if (fiq_busy()) + continue; + + if (hdq_priv.hdq_error) { + hdq_bad(); + goto done; /* didn't see a response in good time */ + } + hdq_good(); + + ret = hdq_priv.hdq_rx_data; + goto done; + } + +done: + mutex_unlock(&hdq_priv.hdq_lock); + return ret; +} +EXPORT_SYMBOL_GPL(hdq_read); + +int hdq_write(int address, u8 data) +{ + int count_sleeps = 5; + int ret = -ETIME; + + if (!hdq_priv.hdq_probed) + return -EINVAL; + + mutex_lock(&hdq_priv.hdq_lock); + + hdq_priv.hdq_error = 0; + hdq_priv.hdq_ads = address | HDQ_WRITE; + hdq_priv.hdq_tx_data = data; + hdq_priv.hdq_request_ctr++; + hdq_priv.pdata->kick_fiq(); + /* + * FIQ takes care of it while we block our calling process + * But we're not spinning -- other processes run normally while + * we wait for the result + */ + while (count_sleeps--) { + msleep(10); /* valid transaction always completes in < 10ms */ + + if (fiq_busy()) + continue; /* something bad with FIQ */ + + if (hdq_priv.hdq_error) { + hdq_bad(); + goto done; /* didn't see a response in good time */ + } + hdq_good(); + + ret = 0; + goto done; + } + +done: + mutex_unlock(&hdq_priv.hdq_lock); + return ret; +} +EXPORT_SYMBOL_GPL(hdq_write); + +/* sysfs */ + +static ssize_t hdq_sysfs_dump(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int n; + int v; + u8 u8a[128]; /* whole address space for HDQ */ + char *end = buf; + + if (!hdq_priv.hdq_probed) + return -EINVAL; + + /* the dump does not take care about 16 bit regs, because at this + * bus level we don't know about the chip details + */ + for (n = 0; n < sizeof(u8a); n++) { + v = hdq_read(n); + if (v < 0) + goto bail; + u8a[n] = v; + } + + for (n = 0; n < sizeof(u8a); n += 16) { + hex_dump_to_buffer(u8a + n, sizeof(u8a), 16, 1, end, 4096, 0); + end += strlen(end); + *end++ = '\n'; + *end = '\0'; + } + return (end - buf); + +bail: + return sprintf(buf, "ERROR %d\n", v); +} + +/* you write by <address> <data>, eg, "34 128" */ + +#define atoi(str) simple_strtoul(((str != NULL) ? str : ""), NULL, 0) + +static ssize_t hdq_sysfs_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + const char *end = buf + count; + int address = atoi(buf); + + if (!hdq_priv.hdq_probed) + return -EINVAL; + + while ((buf != end) && (*buf != ' ')) + buf++; + if (buf >= end) + return 0; + while ((buf < end) && (*buf == ' ')) + buf++; + if (buf >= end) + return 0; + + hdq_write(address, atoi(buf)); + + return count; +} + +static DEVICE_ATTR(dump, 0400, hdq_sysfs_dump, NULL); +static DEVICE_ATTR(write, 0600, NULL, hdq_sysfs_write); + +static struct attribute *hdq_sysfs_entries[] = { + &dev_attr_dump.attr, + &dev_attr_write.attr, + NULL +}; + +static struct attribute_group hdq_attr_group = { + .name = "hdq", + .attrs = hdq_sysfs_entries, +}; + + +#ifdef CONFIG_PM +static int hdq_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* after 18s of this, the battery monitor will also go to sleep */ + hdq_priv.pdata->gpio_dir_in(); + hdq_priv.pdata->disable_fiq(); + return 0; +} + +static int hdq_resume(struct platform_device *pdev) +{ + hdq_priv.pdata->gpio_set(1); + hdq_priv.pdata->gpio_dir_out(); + hdq_priv.pdata->enable_fiq(); + return 0; +} +#endif + +static int __init hdq_probe(struct platform_device *pdev) +{ + struct resource *r = platform_get_resource(pdev, 0, 0); + int ret; + struct hdq_platform_data *pdata = pdev->dev.platform_data; + + if (!r || !pdata) + return -EINVAL; + + platform_set_drvdata(pdev, NULL); + + mutex_init(&hdq_priv.hdq_lock); + + /* set our HDQ comms pin from the platform data */ + hdq_priv.hdq_gpio_pin = r->start; + hdq_priv.pdata = pdata; + + hdq_priv.pdata->gpio_set(1); + hdq_priv.pdata->gpio_dir_out(); + + /* Initialize FIQ */ + if (hdq_priv.pdata->enable_fiq() < 0) { + dev_err(&pdev->dev, "Could not enable FIQ source\n"); + return -EINVAL; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &hdq_attr_group); + if (ret) + return ret; + + hdq_priv.hdq_probed = 1; /* we are ready to do stuff now */ + + /* + * if wanted, users can defer registration of devices + * that depend on HDQ until after we register, and can use our + * device as parent so suspend-resume ordering is correct + */ + if (pdata->attach_child_devices) + (pdata->attach_child_devices)(&pdev->dev); + + hdq_priv.pdata = pdata; + + return 0; +} + +static int hdq_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, &hdq_attr_group); + return 0; +} + +static struct platform_driver hdq_driver = { + .probe = hdq_probe, + .remove = hdq_remove, +#ifdef CONFIG_PM + .suspend = hdq_suspend, + .resume = hdq_resume, +#endif + .driver = { + .name = "hdq", + }, +}; + +static int __init hdq_init(void) +{ + return platform_driver_register(&hdq_driver); +} + +static void __exit hdq_exit(void) +{ + platform_driver_unregister(&hdq_driver); +} + +module_init(hdq_init); +module_exit(hdq_exit); + +MODULE_AUTHOR("Andy Green <andy@openmoko.com>"); +MODULE_DESCRIPTION("HDQ driver"); diff --git a/drivers/serial/samsung.c b/drivers/serial/samsung.c index 1523e8d9ae7..e0062b5626c 100644 --- a/drivers/serial/samsung.c +++ b/drivers/serial/samsung.c @@ -1263,6 +1263,13 @@ module_exit(s3c24xx_serial_modexit); #ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE static struct uart_port *cons_uart; +static int cons_silenced; + +void s3c24xx_serial_console_set_silence(int silenced) +{ + cons_silenced = silenced; +} +EXPORT_SYMBOL(s3c24xx_serial_console_set_silence); static int s3c24xx_serial_console_txrdy(struct uart_port *port, unsigned int ufcon) @@ -1287,9 +1294,21 @@ static void s3c24xx_serial_console_putchar(struct uart_port *port, int ch) { unsigned int ufcon = rd_regl(cons_uart, S3C2410_UFCON); + unsigned int umcon = rd_regl(cons_uart, S3C2410_UMCON); + + if (cons_silenced) + return; + + /* If auto HW flow control enabled, temporarily turn it off */ + if (umcon & S3C2410_UMCOM_AFC) + wr_regl(port, S3C2410_UMCON, (umcon & !S3C2410_UMCOM_AFC)); + while (!s3c24xx_serial_console_txrdy(port, ufcon)) barrier(); wr_regb(cons_uart, S3C2410_UTXH, ch); + + if (umcon & S3C2410_UMCOM_AFC) + wr_regl(port, S3C2410_UMCON, umcon); } static void diff --git a/drivers/usb/gadget/s3c2410_udc.c b/drivers/usb/gadget/s3c2410_udc.c index d5f4c1d45c9..e6b76d812c3 100644 --- a/drivers/usb/gadget/s3c2410_udc.c +++ b/drivers/usb/gadget/s3c2410_udc.c @@ -842,6 +842,7 @@ static void s3c2410_udc_handle_ep(struct s3c2410_ep *ep) u32 ep_csr1; u32 idx; +handle_ep_again: if (likely (!list_empty(&ep->queue))) req = list_entry(ep->queue.next, struct s3c2410_request, queue); @@ -881,6 +882,8 @@ static void s3c2410_udc_handle_ep(struct s3c2410_ep *ep) if ((ep_csr1 & S3C2410_UDC_OCSR1_PKTRDY) && req) { s3c2410_udc_read_fifo(ep,req); + if (s3c2410_udc_fifo_count_out()) + goto handle_ep_again; } } } diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 55eeb7fc94f..ced9bff3a6b 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -100,6 +100,20 @@ config LCD_HP700 If you have an HP Jornada 700 series handheld (710/720/728) say Y to enable LCD control driver. +config LCD_JBT6K74 + tristate "TPO JBT6K74-AS TFT display ASIC control interface" + depends on SPI_MASTER && SYSFS && LCD_CLASS_DEVICE + help + SPI driver for the control interface of TFT panels containing + the TPO JBT6K74-AS controller ASIC, such as the TPO TD028TTEC1 + TFT diplay module used in the Openmoko Freerunner GSM phone. + + The control interface is required for display operation, as it + controls power management, display timing and gamma calibration. + + This driver can also be build as a module, if so it will be called + jbt6k74. + # # Backlight # diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 8d17f13ab8d..c2ceb18c0b1 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_LCD_PLATFORM) += platform_lcd.o obj-$(CONFIG_LCD_VGG2432A4) += vgg2432a4.o obj-$(CONFIG_LCD_TDO24M) += tdo24m.o obj-$(CONFIG_LCD_TOSA) += tosa_lcd.o +obj-$(CONFIG_LCD_JBT6K74) += jbt6k74.o obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o diff --git a/drivers/video/backlight/jbt6k74.c b/drivers/video/backlight/jbt6k74.c new file mode 100644 index 00000000000..8450904d585 --- /dev/null +++ b/drivers/video/backlight/jbt6k74.c @@ -0,0 +1,896 @@ +/* Linux kernel driver for the tpo JBT6K74-AS LCM ASIC + * + * Copyright (C) 2006-2007 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org>, + * Stefan Schmidt <stefan@openmoko.org> + * Copyright (C) 2008 by Harald Welte <laforge@openmoko.org> + * Copyright (C) 2009 by Lars-Peter Clausen <lars@metafoo.de> + * All rights reserved. + * + * 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 + * + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/jbt6k74.h> +#include <linux/fb.h> +#include <linux/lcd.h> +#include <linux/time.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio.h> + +enum jbt_register { + JBT_REG_SLEEP_IN = 0x10, + JBT_REG_SLEEP_OUT = 0x11, + + JBT_REG_DISPLAY_OFF = 0x28, + JBT_REG_DISPLAY_ON = 0x29, + + JBT_REG_RGB_FORMAT = 0x3a, + JBT_REG_QUAD_RATE = 0x3b, + + JBT_REG_POWER_ON_OFF = 0xb0, + JBT_REG_BOOSTER_OP = 0xb1, + JBT_REG_BOOSTER_MODE = 0xb2, + JBT_REG_BOOSTER_FREQ = 0xb3, + JBT_REG_OPAMP_SYSCLK = 0xb4, + JBT_REG_VSC_VOLTAGE = 0xb5, + JBT_REG_VCOM_VOLTAGE = 0xb6, + JBT_REG_EXT_DISPL = 0xb7, + JBT_REG_OUTPUT_CONTROL = 0xb8, + JBT_REG_DCCLK_DCEV = 0xb9, + JBT_REG_DISPLAY_MODE1 = 0xba, + JBT_REG_DISPLAY_MODE2 = 0xbb, + JBT_REG_DISPLAY_MODE = 0xbc, + JBT_REG_ASW_SLEW = 0xbd, + JBT_REG_DUMMY_DISPLAY = 0xbe, + JBT_REG_DRIVE_SYSTEM = 0xbf, + + JBT_REG_SLEEP_OUT_FR_A = 0xc0, + JBT_REG_SLEEP_OUT_FR_B = 0xc1, + JBT_REG_SLEEP_OUT_FR_C = 0xc2, + JBT_REG_SLEEP_IN_LCCNT_D = 0xc3, + JBT_REG_SLEEP_IN_LCCNT_E = 0xc4, + JBT_REG_SLEEP_IN_LCCNT_F = 0xc5, + JBT_REG_SLEEP_IN_LCCNT_G = 0xc6, + + JBT_REG_GAMMA1_FINE_1 = 0xc7, + JBT_REG_GAMMA1_FINE_2 = 0xc8, + JBT_REG_GAMMA1_INCLINATION = 0xc9, + JBT_REG_GAMMA1_BLUE_OFFSET = 0xca, + + /* VGA */ + JBT_REG_BLANK_CONTROL = 0xcf, + JBT_REG_BLANK_TH_TV = 0xd0, + JBT_REG_CKV_ON_OFF = 0xd1, + JBT_REG_CKV_1_2 = 0xd2, + JBT_REG_OEV_TIMING = 0xd3, + JBT_REG_ASW_TIMING_1 = 0xd4, + JBT_REG_ASW_TIMING_2 = 0xd5, + + /* QVGA */ + JBT_REG_BLANK_CONTROL_QVGA = 0xd6, + JBT_REG_BLANK_TH_TV_QVGA = 0xd7, + JBT_REG_CKV_ON_OFF_QVGA = 0xd8, + JBT_REG_CKV_1_2_QVGA = 0xd9, + JBT_REG_OEV_TIMING_QVGA = 0xde, + JBT_REG_ASW_TIMING_1_QVGA = 0xdf, + JBT_REG_ASW_TIMING_2_QVGA = 0xe0, + + + JBT_REG_HCLOCK_VGA = 0xec, + JBT_REG_HCLOCK_QVGA = 0xed, +}; + +enum jbt_resolution { + JBT_RESOLUTION_VGA, + JBT_RESOLUTION_QVGA, +}; + +enum jbt_power_mode { + JBT_POWER_MODE_DEEP_STANDBY, + JBT_POWER_MODE_SLEEP, + JBT_POWER_MODE_NORMAL, +}; + +static const char *jbt_power_mode_names[] = { + [JBT_POWER_MODE_DEEP_STANDBY] = "deep-standby", + [JBT_POWER_MODE_SLEEP] = "sleep", + [JBT_POWER_MODE_NORMAL] = "normal", +}; + +static const char *jbt_resolution_names[] = { + [JBT_RESOLUTION_VGA] = "vga", + [JBT_RESOLUTION_QVGA] = "qvga", +}; + +struct jbt_info { + struct mutex lock; /* protects this structure */ + enum jbt_resolution resolution; + enum jbt_power_mode power_mode; + enum jbt_power_mode suspend_mode; + int suspended; + + struct spi_device *spi; + struct lcd_device *lcd_dev; + + unsigned long next_sleep; + struct delayed_work blank_work; + int blank_mode; + struct regulator_bulk_data supplies[2]; + uint16_t tx_buf[3]; + uint16_t reg_cache[0xEE]; +}; + +#define JBT_COMMAND 0x000 +#define JBT_DATA 0x100 + +static int jbt_reg_write_nodata(struct jbt_info *jbt, uint8_t reg) +{ + int ret; + + jbt->tx_buf[0] = JBT_COMMAND | reg; + ret = spi_write(jbt->spi, (uint8_t *)jbt->tx_buf, + sizeof(uint16_t)); + if (ret == 0) + jbt->reg_cache[reg] = 0; + else + dev_err(&jbt->spi->dev, "Write failed: %d\n", ret); + + return ret; +} + + +static int jbt_reg_write(struct jbt_info *jbt, uint8_t reg, uint8_t data) +{ + int ret; + + jbt->tx_buf[0] = JBT_COMMAND | reg; + jbt->tx_buf[1] = JBT_DATA | data; + ret = spi_write(jbt->spi, (uint8_t *)jbt->tx_buf, + 2*sizeof(uint16_t)); + if (ret == 0) + jbt->reg_cache[reg] = data; + else + dev_err(&jbt->spi->dev, "Write failed: %d\n", ret); + + return ret; +} + +static int jbt_reg_write16(struct jbt_info *jbt, uint8_t reg, uint16_t data) +{ + int ret; + + jbt->tx_buf[0] = JBT_COMMAND | reg; + jbt->tx_buf[1] = JBT_DATA | (data >> 8); + jbt->tx_buf[2] = JBT_DATA | (data & 0xff); + + ret = spi_write(jbt->spi, (uint8_t *)jbt->tx_buf, + 3*sizeof(uint16_t)); + if (ret == 0) + jbt->reg_cache[reg] = data; + else + dev_err(&jbt->spi->dev, "Write failed: %d\n", ret); + + return ret; +} + +static int jbt_init_regs(struct jbt_info *jbt) +{ + int ret; + + dev_dbg(&jbt->spi->dev, "entering %cVGA mode\n", + jbt->resolution == JBT_RESOLUTION_QVGA ? 'Q' : ' '); + + ret = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE1, 0x01); + ret |= jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE2, 0x00); + ret |= jbt_reg_write(jbt, JBT_REG_RGB_FORMAT, 0x60); + ret |= jbt_reg_write(jbt, JBT_REG_DRIVE_SYSTEM, 0x10); + ret |= jbt_reg_write(jbt, JBT_REG_BOOSTER_OP, 0x56); + ret |= jbt_reg_write(jbt, JBT_REG_BOOSTER_MODE, 0x33); + ret |= jbt_reg_write(jbt, JBT_REG_BOOSTER_FREQ, 0x11); + ret |= jbt_reg_write(jbt, JBT_REG_OPAMP_SYSCLK, 0x02); + ret |= jbt_reg_write(jbt, JBT_REG_VSC_VOLTAGE, 0x2b); + ret |= jbt_reg_write(jbt, JBT_REG_VCOM_VOLTAGE, 0x40); + ret |= jbt_reg_write(jbt, JBT_REG_EXT_DISPL, 0x03); + ret |= jbt_reg_write(jbt, JBT_REG_DCCLK_DCEV, 0x04); + /* + * default of 0x02 in JBT_REG_ASW_SLEW responsible for 72Hz requirement + * to avoid red / blue flicker + */ + ret |= jbt_reg_write(jbt, JBT_REG_ASW_SLEW, 0x00 | (1 << 5)); + ret |= jbt_reg_write(jbt, JBT_REG_DUMMY_DISPLAY, 0x00); + + ret |= jbt_reg_write(jbt, JBT_REG_SLEEP_OUT_FR_A, 0x11); + ret |= jbt_reg_write(jbt, JBT_REG_SLEEP_OUT_FR_B, 0x11); + ret |= jbt_reg_write(jbt, JBT_REG_SLEEP_OUT_FR_C, 0x11); + ret |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_D, 0x2040); + ret |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_E, 0x60c0); + ret |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_F, 0x1020); + ret |= jbt_reg_write16(jbt, JBT_REG_SLEEP_IN_LCCNT_G, 0x60c0); + + ret |= jbt_reg_write16(jbt, JBT_REG_GAMMA1_FINE_1, 0x5533); + ret |= jbt_reg_write(jbt, JBT_REG_GAMMA1_FINE_2, 0x00); + ret |= jbt_reg_write(jbt, JBT_REG_GAMMA1_INCLINATION, 0x00); + ret |= jbt_reg_write(jbt, JBT_REG_GAMMA1_BLUE_OFFSET, 0x00); + + if (jbt->resolution != JBT_RESOLUTION_QVGA) { + ret |= jbt_reg_write16(jbt, JBT_REG_HCLOCK_VGA, 0x1f0); + ret |= jbt_reg_write(jbt, JBT_REG_BLANK_CONTROL, 0x02); + ret |= jbt_reg_write16(jbt, JBT_REG_BLANK_TH_TV, 0x0804); + + ret |= jbt_reg_write(jbt, JBT_REG_CKV_ON_OFF, 0x01); + ret |= jbt_reg_write16(jbt, JBT_REG_CKV_1_2, 0x0000); + + ret |= jbt_reg_write16(jbt, JBT_REG_OEV_TIMING, 0x0d0e); + ret |= jbt_reg_write16(jbt, JBT_REG_ASW_TIMING_1, 0x11a4); + ret |= jbt_reg_write(jbt, JBT_REG_ASW_TIMING_2, 0x0e); + } else { + ret |= jbt_reg_write16(jbt, JBT_REG_HCLOCK_QVGA, 0x00ff); + ret |= jbt_reg_write(jbt, JBT_REG_BLANK_CONTROL_QVGA, 0x02); + ret |= jbt_reg_write16(jbt, JBT_REG_BLANK_TH_TV_QVGA, 0x0804); + + ret |= jbt_reg_write(jbt, JBT_REG_CKV_ON_OFF_QVGA, 0x01); + ret |= jbt_reg_write16(jbt, JBT_REG_CKV_1_2_QVGA, 0x0008); + + ret |= jbt_reg_write16(jbt, JBT_REG_OEV_TIMING_QVGA, 0x050a); + ret |= jbt_reg_write16(jbt, JBT_REG_ASW_TIMING_1_QVGA, 0x0a19); + ret |= jbt_reg_write(jbt, JBT_REG_ASW_TIMING_2_QVGA, 0x0a); + } + + return ret ? -EIO : 0; +} + +static int jbt_standby_to_sleep(struct jbt_info *jbt) +{ + int ret; + struct jbt6k74_platform_data *pdata = jbt->spi->dev.platform_data; + + gpio_set_value_cansleep(pdata->gpio_reset, 1); + ret = regulator_bulk_enable(ARRAY_SIZE(jbt->supplies), jbt->supplies); + + /* three times command zero */ + ret |= jbt_reg_write_nodata(jbt, 0x00); + mdelay(1); + ret |= jbt_reg_write_nodata(jbt, 0x00); + mdelay(1); + ret |= jbt_reg_write_nodata(jbt, 0x00); + mdelay(1); + + /* deep standby out */ + ret |= jbt_reg_write(jbt, JBT_REG_POWER_ON_OFF, 0x11); + mdelay(1); + ret = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE, 0x28); + + /* (re)initialize register set */ + ret |= jbt_init_regs(jbt); + + return ret ? -EIO : 0; +} + +static int jbt_sleep_to_normal(struct jbt_info *jbt) +{ + int ret; + + /* Make sure we are 120 ms after SLEEP_OUT */ + if (time_before(jiffies, jbt->next_sleep)) + mdelay(jiffies_to_msecs(jbt->next_sleep - jiffies)); + + if (jbt->resolution == JBT_RESOLUTION_VGA) { + /* RGB I/F on, RAM wirte off, QVGA through, SIGCON enable */ + ret = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE, 0x80); + + /* Quad mode off */ + ret |= jbt_reg_write(jbt, JBT_REG_QUAD_RATE, 0x00); + } else { + /* RGB I/F on, RAM wirte off, QVGA through, SIGCON enable */ + ret = jbt_reg_write(jbt, JBT_REG_DISPLAY_MODE, 0x81); + + /* Quad mode on */ + ret |= jbt_reg_write(jbt, JBT_REG_QUAD_RATE, 0x22); + } + + /* AVDD on, XVDD on */ + ret |= jbt_reg_write(jbt, JBT_REG_POWER_ON_OFF, 0x16); + + /* Output control */ + ret |= jbt_reg_write16(jbt, JBT_REG_OUTPUT_CONTROL, 0xdff9); + + /* Turn on display */ + ret |= jbt_reg_write_nodata(jbt, JBT_REG_DISPLAY_ON); + + /* Sleep mode off */ + ret |= jbt_reg_write_nodata(jbt, JBT_REG_SLEEP_OUT); + jbt->next_sleep = jiffies + msecs_to_jiffies(120); + + /* Allow the booster and display controller to restart stably */ + mdelay(5); + + return ret ? -EIO : 0; +} + +static int jbt_normal_to_sleep(struct jbt_info *jbt) +{ + int ret; + + /* Make sure we are 120 ms after SLEEP_OUT */ + while (time_before(jiffies, jbt->next_sleep)) + cpu_relax(); + + ret = jbt_reg_write_nodata(jbt, JBT_REG_DISPLAY_OFF); + ret |= jbt_reg_write16(jbt, JBT_REG_OUTPUT_CONTROL, 0x8000 | 1 << 3); + ret |= jbt_reg_write_nodata(jbt, JBT_REG_SLEEP_IN); + jbt->next_sleep = jiffies + msecs_to_jiffies(120); + + /* Allow the internal circuits to stop automatically */ + mdelay(5); + + return ret ? -EIO : 0; +} + +static int jbt_sleep_to_standby(struct jbt_info *jbt) +{ + int ret; + struct jbt6k74_platform_data *pdata = jbt->spi->dev.platform_data; + + ret = jbt_reg_write(jbt, JBT_REG_POWER_ON_OFF, 0x00); + + if (!ret) + ret = regulator_bulk_disable(ARRAY_SIZE(jbt->supplies), jbt->supplies); + + if (!ret) + gpio_set_value_cansleep(pdata->gpio_reset, 0); + + return ret; +} + +static int jbt6k74_enter_power_mode(struct jbt_info *jbt, + enum jbt_power_mode new_mode) +{ + struct jbt6k74_platform_data *pdata = jbt->spi->dev.platform_data; + int ret = -EINVAL; + + dev_dbg(&jbt->spi->dev, "entering (old_state=%s, new_state=%s)\n", + jbt_power_mode_names[jbt->power_mode], + jbt_power_mode_names[new_mode]); + + mutex_lock(&jbt->lock); + + if (jbt->suspended) { + switch (new_mode) { + case JBT_POWER_MODE_DEEP_STANDBY: + case JBT_POWER_MODE_SLEEP: + case JBT_POWER_MODE_NORMAL: + ret = 0; + jbt->suspend_mode = new_mode; + break; + default: + break; + } + } else if (new_mode == JBT_POWER_MODE_NORMAL && + pdata->enable_pixel_clock) { + pdata->enable_pixel_clock(&jbt->spi->dev, 1); + } + + switch (jbt->power_mode) { + case JBT_POWER_MODE_DEEP_STANDBY: + switch (new_mode) { + case JBT_POWER_MODE_DEEP_STANDBY: + ret = 0; + break; + case JBT_POWER_MODE_SLEEP: + ret = jbt_standby_to_sleep(jbt); + break; + case JBT_POWER_MODE_NORMAL: + /* first transition into sleep */ + ret = jbt_standby_to_sleep(jbt); + /* then transition into normal */ + ret |= jbt_sleep_to_normal(jbt); + break; + } + break; + case JBT_POWER_MODE_SLEEP: + switch (new_mode) { + case JBT_POWER_MODE_SLEEP: + ret = 0; + break; + case JBT_POWER_MODE_DEEP_STANDBY: + ret = jbt_sleep_to_standby(jbt); + break; + case JBT_POWER_MODE_NORMAL: + ret = jbt_sleep_to_normal(jbt); + break; + } + break; + case JBT_POWER_MODE_NORMAL: + switch (new_mode) { + case JBT_POWER_MODE_NORMAL: + ret = 0; + break; + case JBT_POWER_MODE_DEEP_STANDBY: + /* first transition into sleep */ + ret = jbt_normal_to_sleep(jbt); + /* then transition into deep standby */ + ret |= jbt_sleep_to_standby(jbt); + break; + case JBT_POWER_MODE_SLEEP: + ret = jbt_normal_to_sleep(jbt); + break; + } + } + + if (ret == 0) { + jbt->power_mode = new_mode; + if (new_mode != JBT_POWER_MODE_NORMAL && + pdata->enable_pixel_clock) + pdata->enable_pixel_clock(&jbt->spi->dev, 0); + } else { + dev_err(&jbt->spi->dev, "Failed enter state '%s': %d\n", + jbt_power_mode_names[new_mode], ret); + } + + mutex_unlock(&jbt->lock); + + return ret; +} + +static int jbt6k74_set_resolution(struct jbt_info *jbt, + enum jbt_resolution new_resolution) +{ + int ret = 0; + enum jbt_resolution old_resolution; + + mutex_lock(&jbt->lock); + + if (jbt->resolution == new_resolution) + goto out_unlock; + + old_resolution = jbt->resolution; + jbt->resolution = new_resolution; + + if (jbt->power_mode == JBT_POWER_MODE_NORMAL) { + + /* first transition into sleep */ + ret = jbt_normal_to_sleep(jbt); + ret |= jbt_sleep_to_normal(jbt); + + if (ret) { + jbt->resolution = old_resolution; + dev_err(&jbt->spi->dev, "Failed to set resolution '%s')\n", + jbt_resolution_names[new_resolution]); + } + } + +out_unlock: + mutex_unlock(&jbt->lock); + + return ret; +} + +static ssize_t resolution_read(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct jbt_info *jbt = dev_get_drvdata(dev); + + if (jbt->resolution >= ARRAY_SIZE(jbt_resolution_names)) + return -EIO; + + return sprintf(buf, "%s\n", jbt_resolution_names[jbt->resolution]); +} + +static ssize_t resolution_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct jbt_info *jbt = dev_get_drvdata(dev); + int i, ret; + + for (i = 0; i < ARRAY_SIZE(jbt_resolution_names); i++) { + if (!strncmp(buf, jbt_resolution_names[i], + strlen(jbt_resolution_names[i]))) { + ret = jbt6k74_set_resolution(jbt, i); + if (ret) + return ret; + return count; + } + } + + return -EINVAL; +} + +static DEVICE_ATTR(resolution, 0644, resolution_read, resolution_write); + +static int reg_by_string(const char *name) +{ + if (!strcmp(name, "gamma_fine1")) + return JBT_REG_GAMMA1_FINE_1; + else if (!strcmp(name, "gamma_fine2")) + return JBT_REG_GAMMA1_FINE_2; + else if (!strcmp(name, "gamma_inclination")) + return JBT_REG_GAMMA1_INCLINATION; + else + return JBT_REG_GAMMA1_BLUE_OFFSET; +} + +static ssize_t gamma_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct jbt_info *jbt = dev_get_drvdata(dev); + int reg = reg_by_string(attr->attr.name); + uint16_t val; + + mutex_lock(&jbt->lock); + val = jbt->reg_cache[reg]; + mutex_unlock(&jbt->lock); + + return sprintf(buf, "0x%04x\n", val); +} + +static ssize_t gamma_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct jbt_info *jbt = dev_get_drvdata(dev); + int reg = reg_by_string(attr->attr.name); + unsigned long val; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&jbt->lock); + jbt_reg_write(jbt, reg, val & 0xff); + mutex_unlock(&jbt->lock); + + return count; +} + +static ssize_t reset_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct jbt_info *jbt = dev_get_drvdata(dev); + struct jbt6k74_platform_data *pdata = jbt->spi->dev.platform_data; + enum jbt_power_mode old_power_mode = jbt->power_mode; + + mutex_lock(&jbt->lock); + + if (gpio_is_valid(pdata->gpio_reset)) { + /* hard reset the jbt6k74 */ + gpio_set_value_cansleep(pdata->gpio_reset, 0); + mdelay(120); + gpio_set_value_cansleep(pdata->gpio_reset, 1); + mdelay(120); + } + + ret = jbt_reg_write_nodata(jbt, 0x01); + if (ret < 0) + dev_err(&jbt->spi->dev, "cannot soft reset\n"); + mdelay(120); + + mutex_unlock(&jbt->lock); + + jbt->power_mode = JBT_POWER_MODE_DEEP_STANDBY; + jbt6k74_enter_power_mode(jbt, old_power_mode); + + return count; +} + +static DEVICE_ATTR(gamma_fine1, 0644, gamma_read, gamma_write); +static DEVICE_ATTR(gamma_fine2, 0644, gamma_read, gamma_write); +static DEVICE_ATTR(gamma_inclination, 0644, gamma_read, gamma_write); +static DEVICE_ATTR(gamma_blue_offset, 0644, gamma_read, gamma_write); +static DEVICE_ATTR(reset, 0600, NULL, reset_write); + +static struct attribute *jbt_sysfs_entries[] = { + &dev_attr_resolution.attr, + &dev_attr_gamma_fine1.attr, + &dev_attr_gamma_fine2.attr, + &dev_attr_gamma_inclination.attr, + &dev_attr_gamma_blue_offset.attr, + &dev_attr_reset.attr, + NULL, +}; + +static struct attribute_group jbt_attr_group = { + .name = NULL, + .attrs = jbt_sysfs_entries, +}; + +/* FIXME: This in an ugly hack to delay display blanking. + When the jbt is in sleep mode it displays an all white screen and thus one + will a see a short flash. + By delaying the blanking we will give the backlight a chance to turn off and + thus avoid getting the flash */ +static void jbt_blank_worker(struct work_struct *work) +{ + struct jbt_info *jbt = container_of(work, struct jbt_info, + blank_work.work); + + switch (jbt->blank_mode) { + case FB_BLANK_NORMAL: + jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_SLEEP); + break; + case FB_BLANK_POWERDOWN: + jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_DEEP_STANDBY); + break; + default: + break; + } +} + +static int jbt6k74_set_mode(struct lcd_device *ld, struct fb_videomode *m) +{ + int ret = -EINVAL; + struct jbt_info *jbt = dev_get_drvdata(&ld->dev); + + if (m->xres == 240 && m->yres == 320) { + ret = jbt6k74_set_resolution(jbt, JBT_RESOLUTION_QVGA); + } else if (m->xres == 480 && m->yres == 640) { + ret = jbt6k74_set_resolution(jbt, JBT_RESOLUTION_VGA); + } else { + dev_err(&jbt->spi->dev, "Unknown resolution.\n"); + jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_SLEEP); + } + + return ret; +} + +static int jbt6k74_set_power(struct lcd_device *ld, int power) +{ + int ret = -EINVAL; + struct jbt_info *jbt = dev_get_drvdata(&ld->dev); + + jbt->blank_mode = power; + cancel_rearming_delayed_work(&jbt->blank_work); + + switch (power) { + case FB_BLANK_UNBLANK: + dev_dbg(&jbt->spi->dev, "unblank\n"); + mdelay(20); + ret = jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL); + break; + case FB_BLANK_NORMAL: + dev_dbg(&jbt->spi->dev, "blank\n"); + ret = schedule_delayed_work(&jbt->blank_work, HZ); + break; + case FB_BLANK_POWERDOWN: + dev_dbg(&jbt->spi->dev, "powerdown\n"); + ret = schedule_delayed_work(&jbt->blank_work, HZ); + break; + default: + break; + } + + return ret; +} + +static int jbt6k74_get_power(struct lcd_device *ld) +{ + struct jbt_info *jbt = dev_get_drvdata(&ld->dev); + + switch (jbt->power_mode) { + case JBT_POWER_MODE_NORMAL: + return FB_BLANK_UNBLANK; + case JBT_POWER_MODE_SLEEP: + return FB_BLANK_NORMAL; + default: + return JBT_POWER_MODE_DEEP_STANDBY; + } +} + +struct lcd_ops jbt6k74_lcd_ops = { + .set_power = jbt6k74_set_power, + .get_power = jbt6k74_get_power, + .set_mode = jbt6k74_set_mode, +}; + +/* linux device model infrastructure */ + +static int __devinit jbt_probe(struct spi_device *spi) +{ + int ret; + struct jbt_info *jbt; + struct jbt6k74_platform_data *pdata = spi->dev.platform_data; + + /* the controller doesn't have a MISO pin; we can't do detection */ + + spi->mode = SPI_CPOL | SPI_CPHA; + spi->bits_per_word = 9; + + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, + "Failed to setup spi\n"); + return ret; + } + + jbt = kzalloc(sizeof(*jbt), GFP_KERNEL); + if (!jbt) + return -ENOMEM; + + jbt->spi = spi; + + jbt->lcd_dev = lcd_device_register("jbt6k74-lcd", &spi->dev, jbt, + &jbt6k74_lcd_ops); + + if (IS_ERR(jbt->lcd_dev)) { + ret = PTR_ERR(jbt->lcd_dev); + goto err_free_drvdata; + } + + INIT_DELAYED_WORK(&jbt->blank_work, jbt_blank_worker); + + jbt->resolution = JBT_RESOLUTION_VGA; + jbt->power_mode = JBT_POWER_MODE_DEEP_STANDBY; + jbt->next_sleep = jiffies + msecs_to_jiffies(120); + mutex_init(&jbt->lock); + + dev_set_drvdata(&spi->dev, jbt); + + jbt->supplies[0].supply = "VDC"; + jbt->supplies[1].supply = "VDDIO"; + + ret = regulator_bulk_get(&spi->dev, ARRAY_SIZE(jbt->supplies), + jbt->supplies); + if (ret) { + dev_err(&spi->dev, "Failed to power get supplies: %d\n", ret); + goto err_unregister_lcd; + } + + if (gpio_is_valid(pdata->gpio_reset)) { + ret = gpio_request(pdata->gpio_reset, "jbt6k74 reset"); + if (ret) { + dev_err(&spi->dev, "Failed to request reset gpio: %d\n", + ret); + goto err_free_supplies; + } + + ret = gpio_direction_output(pdata->gpio_reset, 1); + if (ret) { + dev_err(&spi->dev, "Failed to set reset gpio direction: %d\n", + ret); + goto err_gpio_free; + } + } + + ret = sysfs_create_group(&spi->dev.kobj, &jbt_attr_group); + if (ret < 0) { + dev_err(&spi->dev, "cannot create sysfs group\n"); + goto err_gpio_free; + } + + mdelay(50); + ret = jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL); + if (ret < 0) { + dev_err(&spi->dev, "cannot enter NORMAL state\n"); + goto err_sysfs_remove; + } + + + if (pdata->probe_completed) + (pdata->probe_completed)(&spi->dev); + + return 0; + +err_sysfs_remove: + sysfs_remove_group(&spi->dev.kobj, &jbt_attr_group); +err_gpio_free: + gpio_free(pdata->gpio_reset); +err_free_supplies: + regulator_bulk_free(ARRAY_SIZE(jbt->supplies), jbt->supplies); +err_unregister_lcd: + lcd_device_unregister(jbt->lcd_dev); +err_free_drvdata: + dev_set_drvdata(&spi->dev, NULL); + kfree(jbt); + + return ret; +} + +static int __devexit jbt_remove(struct spi_device *spi) +{ + struct jbt_info *jbt = dev_get_drvdata(&spi->dev); + struct jbt6k74_platform_data *pdata = jbt->spi->dev.platform_data; + + /* We don't want to switch off the display in case the user + * accidentially unloads the module (whose use count normally is 0) */ + jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_NORMAL); + + sysfs_remove_group(&spi->dev.kobj, &jbt_attr_group); + + if (gpio_is_valid(pdata->gpio_reset)) + gpio_free(pdata->gpio_reset); + + lcd_device_unregister(jbt->lcd_dev); + + dev_set_drvdata(&spi->dev, NULL); + + regulator_bulk_free(ARRAY_SIZE(jbt->supplies), jbt->supplies); + kfree(jbt); + + return 0; +} + +#ifdef CONFIG_PM +static int jbt_suspend(struct spi_device *spi, pm_message_t state) +{ + struct jbt_info *jbt = dev_get_drvdata(&spi->dev); + + jbt->suspend_mode = jbt->power_mode; + + jbt6k74_enter_power_mode(jbt, JBT_POWER_MODE_DEEP_STANDBY); + jbt->suspended = 1; + + dev_info(&spi->dev, "suspended\n"); + + return 0; +} + +int jbt6k74_resume(struct spi_device *spi) +{ + struct jbt_info *jbt = dev_get_drvdata(&spi->dev); + dev_info(&spi->dev, "starting resume: %d\n", jbt->suspend_mode); + + mdelay(20); + + jbt->suspended = 0; + jbt6k74_enter_power_mode(jbt, jbt->suspend_mode); + + dev_info(&spi->dev, "resumed: %d\n", jbt->suspend_mode); + + return 0; +} +EXPORT_SYMBOL_GPL(jbt6k74_resume); + +#else +#define jbt_suspend NULL +#define jbt6k74_resume NULL +#endif + +static struct spi_driver jbt6k74_driver = { + .driver = { + .name = "jbt6k74", + .owner = THIS_MODULE, + }, + + .probe = jbt_probe, + .remove = __devexit_p(jbt_remove), + .suspend = jbt_suspend, + .resume = jbt6k74_resume, +}; + +static int __init jbt_init(void) +{ + return spi_register_driver(&jbt6k74_driver); +} +module_init(jbt_init); + +static void __exit jbt_exit(void) +{ + spi_unregister_driver(&jbt6k74_driver); +} +module_exit(jbt_exit); + +MODULE_DESCRIPTION("SPI driver for tpo JBT6K74-AS LCM control interface"); +MODULE_AUTHOR("Harald Welte <laforge@openmoko.org>"); +MODULE_LICENSE("GPL"); |