192261343SJaya Kumar /* 292261343SJaya Kumar * am200epd.c -- Platform device for AM200 EPD kit 392261343SJaya Kumar * 492261343SJaya Kumar * Copyright (C) 2008, Jaya Kumar 592261343SJaya Kumar * 692261343SJaya Kumar * This file is subject to the terms and conditions of the GNU General Public 792261343SJaya Kumar * License. See the file COPYING in the main directory of this archive for 892261343SJaya Kumar * more details. 992261343SJaya Kumar * 1092261343SJaya Kumar * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven. 1192261343SJaya Kumar * 1292261343SJaya Kumar * This work was made possible by help and equipment support from E-Ink 1392261343SJaya Kumar * Corporation. http://support.eink.com/community 1492261343SJaya Kumar * 1592261343SJaya Kumar * This driver is written to be used with the Metronome display controller. 1692261343SJaya Kumar * on the AM200 EPD prototype kit/development kit with an E-Ink 800x600 1792261343SJaya Kumar * Vizplex EPD on a Gumstix board using the Lyre interface board. 1892261343SJaya Kumar * 1992261343SJaya Kumar */ 2092261343SJaya Kumar 2192261343SJaya Kumar #include <linux/module.h> 2292261343SJaya Kumar #include <linux/kernel.h> 2392261343SJaya Kumar #include <linux/errno.h> 2492261343SJaya Kumar #include <linux/string.h> 2592261343SJaya Kumar #include <linux/delay.h> 2692261343SJaya Kumar #include <linux/interrupt.h> 2792261343SJaya Kumar #include <linux/fb.h> 2892261343SJaya Kumar #include <linux/init.h> 2992261343SJaya Kumar #include <linux/platform_device.h> 3092261343SJaya Kumar #include <linux/irq.h> 3192261343SJaya Kumar #include <linux/gpio.h> 3292261343SJaya Kumar 334c25c5d2SArnd Bergmann #include "pxa25x.h" 344c25c5d2SArnd Bergmann #include "gumstix.h" 35293b2da1SArnd Bergmann #include <linux/platform_data/video-pxafb.h> 3692261343SJaya Kumar 37854feaedSJaya Kumar #include "generic.h" 38854feaedSJaya Kumar 3992261343SJaya Kumar #include <video/metronomefb.h> 4092261343SJaya Kumar 4192261343SJaya Kumar static unsigned int panel_type = 6; 4292261343SJaya Kumar static struct platform_device *am200_device; 4392261343SJaya Kumar static struct metronome_board am200_board; 4492261343SJaya Kumar 4592261343SJaya Kumar static struct pxafb_mode_info am200_fb_mode_9inch7 = { 4692261343SJaya Kumar .pixclock = 40000, 4792261343SJaya Kumar .xres = 1200, 4892261343SJaya Kumar .yres = 842, 4992261343SJaya Kumar .bpp = 16, 5092261343SJaya Kumar .hsync_len = 2, 5192261343SJaya Kumar .left_margin = 2, 5292261343SJaya Kumar .right_margin = 2, 5392261343SJaya Kumar .vsync_len = 1, 5492261343SJaya Kumar .upper_margin = 2, 5592261343SJaya Kumar .lower_margin = 25, 5692261343SJaya Kumar .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 5792261343SJaya Kumar }; 5892261343SJaya Kumar 5992261343SJaya Kumar static struct pxafb_mode_info am200_fb_mode_8inch = { 6092261343SJaya Kumar .pixclock = 40000, 6192261343SJaya Kumar .xres = 1088, 6292261343SJaya Kumar .yres = 791, 6392261343SJaya Kumar .bpp = 16, 6492261343SJaya Kumar .hsync_len = 28, 6592261343SJaya Kumar .left_margin = 8, 6692261343SJaya Kumar .right_margin = 30, 6792261343SJaya Kumar .vsync_len = 8, 6892261343SJaya Kumar .upper_margin = 10, 6992261343SJaya Kumar .lower_margin = 8, 7092261343SJaya Kumar .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 7192261343SJaya Kumar }; 7292261343SJaya Kumar 7392261343SJaya Kumar static struct pxafb_mode_info am200_fb_mode_6inch = { 7492261343SJaya Kumar .pixclock = 40189, 7592261343SJaya Kumar .xres = 832, 7692261343SJaya Kumar .yres = 622, 7792261343SJaya Kumar .bpp = 16, 7892261343SJaya Kumar .hsync_len = 28, 7992261343SJaya Kumar .left_margin = 34, 8092261343SJaya Kumar .right_margin = 34, 8192261343SJaya Kumar .vsync_len = 25, 8292261343SJaya Kumar .upper_margin = 0, 8392261343SJaya Kumar .lower_margin = 2, 8492261343SJaya Kumar .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 8592261343SJaya Kumar }; 8692261343SJaya Kumar 8792261343SJaya Kumar static struct pxafb_mach_info am200_fb_info = { 8892261343SJaya Kumar .modes = &am200_fb_mode_6inch, 8992261343SJaya Kumar .num_modes = 1, 9092261343SJaya Kumar .lcd_conn = LCD_TYPE_COLOR_TFT | LCD_PCLK_EDGE_FALL | 9192261343SJaya Kumar LCD_AC_BIAS_FREQ(24), 9292261343SJaya Kumar }; 9392261343SJaya Kumar 9492261343SJaya Kumar /* register offsets for gpio control */ 9592261343SJaya Kumar #define LED_GPIO_PIN 51 9692261343SJaya Kumar #define STDBY_GPIO_PIN 48 9792261343SJaya Kumar #define RST_GPIO_PIN 49 9892261343SJaya Kumar #define RDY_GPIO_PIN 32 9992261343SJaya Kumar #define ERR_GPIO_PIN 17 10092261343SJaya Kumar #define PCBPWR_GPIO_PIN 16 10192261343SJaya Kumar static int gpios[] = { LED_GPIO_PIN , STDBY_GPIO_PIN , RST_GPIO_PIN, 10292261343SJaya Kumar RDY_GPIO_PIN, ERR_GPIO_PIN, PCBPWR_GPIO_PIN }; 10392261343SJaya Kumar static char *gpio_names[] = { "LED" , "STDBY" , "RST", "RDY", "ERR", "PCBPWR" }; 10492261343SJaya Kumar 10592261343SJaya Kumar static int am200_init_gpio_regs(struct metronomefb_par *par) 10692261343SJaya Kumar { 10792261343SJaya Kumar int i; 10892261343SJaya Kumar int err; 10992261343SJaya Kumar 11092261343SJaya Kumar for (i = 0; i < ARRAY_SIZE(gpios); i++) { 11192261343SJaya Kumar err = gpio_request(gpios[i], gpio_names[i]); 11292261343SJaya Kumar if (err) { 11392261343SJaya Kumar dev_err(&am200_device->dev, "failed requesting " 11492261343SJaya Kumar "gpio %s, err=%d\n", gpio_names[i], err); 11592261343SJaya Kumar goto err_req_gpio; 11692261343SJaya Kumar } 11792261343SJaya Kumar } 11892261343SJaya Kumar 11992261343SJaya Kumar gpio_direction_output(LED_GPIO_PIN, 0); 12092261343SJaya Kumar gpio_direction_output(STDBY_GPIO_PIN, 0); 12192261343SJaya Kumar gpio_direction_output(RST_GPIO_PIN, 0); 12292261343SJaya Kumar 12392261343SJaya Kumar gpio_direction_input(RDY_GPIO_PIN); 12492261343SJaya Kumar gpio_direction_input(ERR_GPIO_PIN); 12592261343SJaya Kumar 12692261343SJaya Kumar gpio_direction_output(PCBPWR_GPIO_PIN, 0); 12792261343SJaya Kumar 12892261343SJaya Kumar return 0; 12992261343SJaya Kumar 13092261343SJaya Kumar err_req_gpio: 1318aad172eSAxel Lin while (--i >= 0) 1328aad172eSAxel Lin gpio_free(gpios[i]); 13392261343SJaya Kumar 13492261343SJaya Kumar return err; 13592261343SJaya Kumar } 13692261343SJaya Kumar 13792261343SJaya Kumar static void am200_cleanup(struct metronomefb_par *par) 13892261343SJaya Kumar { 13992261343SJaya Kumar int i; 14092261343SJaya Kumar 1416384fdadSHaojian Zhuang free_irq(PXA_GPIO_TO_IRQ(RDY_GPIO_PIN), par); 14292261343SJaya Kumar 14392261343SJaya Kumar for (i = 0; i < ARRAY_SIZE(gpios); i++) 14492261343SJaya Kumar gpio_free(gpios[i]); 14592261343SJaya Kumar } 14692261343SJaya Kumar 14792261343SJaya Kumar static int am200_share_video_mem(struct fb_info *info) 14892261343SJaya Kumar { 14992261343SJaya Kumar /* rough check if this is our desired fb and not something else */ 15092261343SJaya Kumar if ((info->var.xres != am200_fb_info.modes->xres) 15192261343SJaya Kumar || (info->var.yres != am200_fb_info.modes->yres)) 15292261343SJaya Kumar return 0; 15392261343SJaya Kumar 15492261343SJaya Kumar /* we've now been notified that we have our new fb */ 15592261343SJaya Kumar am200_board.metromem = info->screen_base; 15692261343SJaya Kumar am200_board.host_fbinfo = info; 15792261343SJaya Kumar 15892261343SJaya Kumar /* try to refcount host drv since we are the consumer after this */ 15992261343SJaya Kumar if (!try_module_get(info->fbops->owner)) 16092261343SJaya Kumar return -ENODEV; 16192261343SJaya Kumar 16292261343SJaya Kumar return 0; 16392261343SJaya Kumar } 16492261343SJaya Kumar 16592261343SJaya Kumar static int am200_unshare_video_mem(struct fb_info *info) 16692261343SJaya Kumar { 16792261343SJaya Kumar dev_dbg(&am200_device->dev, "ENTER %s\n", __func__); 16892261343SJaya Kumar 16992261343SJaya Kumar if (info != am200_board.host_fbinfo) 17092261343SJaya Kumar return 0; 17192261343SJaya Kumar 17292261343SJaya Kumar module_put(am200_board.host_fbinfo->fbops->owner); 17392261343SJaya Kumar return 0; 17492261343SJaya Kumar } 17592261343SJaya Kumar 17692261343SJaya Kumar static int am200_fb_notifier_callback(struct notifier_block *self, 17792261343SJaya Kumar unsigned long event, void *data) 17892261343SJaya Kumar { 17992261343SJaya Kumar struct fb_event *evdata = data; 18092261343SJaya Kumar struct fb_info *info = evdata->info; 18192261343SJaya Kumar 18292261343SJaya Kumar dev_dbg(&am200_device->dev, "ENTER %s\n", __func__); 18392261343SJaya Kumar 18492261343SJaya Kumar if (event == FB_EVENT_FB_REGISTERED) 18592261343SJaya Kumar return am200_share_video_mem(info); 18692261343SJaya Kumar else if (event == FB_EVENT_FB_UNREGISTERED) 18792261343SJaya Kumar return am200_unshare_video_mem(info); 18892261343SJaya Kumar 18992261343SJaya Kumar return 0; 19092261343SJaya Kumar } 19192261343SJaya Kumar 19292261343SJaya Kumar static struct notifier_block am200_fb_notif = { 19392261343SJaya Kumar .notifier_call = am200_fb_notifier_callback, 19492261343SJaya Kumar }; 19592261343SJaya Kumar 19692261343SJaya Kumar /* this gets called as part of our init. these steps must be done now so 1974321e1a1SRussell King - ARM Linux * that we can use pxa_set_fb_info */ 19892261343SJaya Kumar static void __init am200_presetup_fb(void) 19992261343SJaya Kumar { 20092261343SJaya Kumar int fw; 20192261343SJaya Kumar int fh; 20292261343SJaya Kumar int padding_size; 20392261343SJaya Kumar int totalsize; 20492261343SJaya Kumar 20592261343SJaya Kumar switch (panel_type) { 20692261343SJaya Kumar case 6: 20792261343SJaya Kumar am200_fb_info.modes = &am200_fb_mode_6inch; 20892261343SJaya Kumar break; 20992261343SJaya Kumar case 8: 21092261343SJaya Kumar am200_fb_info.modes = &am200_fb_mode_8inch; 21192261343SJaya Kumar break; 21292261343SJaya Kumar case 97: 21392261343SJaya Kumar am200_fb_info.modes = &am200_fb_mode_9inch7; 21492261343SJaya Kumar break; 21592261343SJaya Kumar default: 21692261343SJaya Kumar dev_err(&am200_device->dev, "invalid panel_type selection," 21792261343SJaya Kumar " setting to 6\n"); 21892261343SJaya Kumar am200_fb_info.modes = &am200_fb_mode_6inch; 21992261343SJaya Kumar break; 22092261343SJaya Kumar } 22192261343SJaya Kumar 22292261343SJaya Kumar /* the frame buffer is divided as follows: 22392261343SJaya Kumar command | CRC | padding 22492261343SJaya Kumar 16kb waveform data | CRC | padding 22592261343SJaya Kumar image data | CRC 22692261343SJaya Kumar */ 22792261343SJaya Kumar 22892261343SJaya Kumar fw = am200_fb_info.modes->xres; 22992261343SJaya Kumar fh = am200_fb_info.modes->yres; 23092261343SJaya Kumar 23192261343SJaya Kumar /* waveform must be 16k + 2 for checksum */ 23292261343SJaya Kumar am200_board.wfm_size = roundup(16*1024 + 2, fw); 23392261343SJaya Kumar 23492261343SJaya Kumar padding_size = PAGE_SIZE + (4 * fw); 23592261343SJaya Kumar 23692261343SJaya Kumar /* total is 1 cmd , 1 wfm, padding and image */ 23792261343SJaya Kumar totalsize = fw + am200_board.wfm_size + padding_size + (fw*fh); 23892261343SJaya Kumar 23992261343SJaya Kumar /* save this off because we're manipulating fw after this and 24092261343SJaya Kumar * we'll need it when we're ready to setup the framebuffer */ 24192261343SJaya Kumar am200_board.fw = fw; 24292261343SJaya Kumar am200_board.fh = fh; 24392261343SJaya Kumar 24492261343SJaya Kumar /* the reason we do this adjustment is because we want to acquire 24592261343SJaya Kumar * more framebuffer memory without imposing custom awareness on the 24692261343SJaya Kumar * underlying pxafb driver */ 24792261343SJaya Kumar am200_fb_info.modes->yres = DIV_ROUND_UP(totalsize, fw); 24892261343SJaya Kumar 24992261343SJaya Kumar /* we divide since we told the LCD controller we're 16bpp */ 25092261343SJaya Kumar am200_fb_info.modes->xres /= 2; 25192261343SJaya Kumar 2524321e1a1SRussell King - ARM Linux pxa_set_fb_info(NULL, &am200_fb_info); 25392261343SJaya Kumar 25492261343SJaya Kumar } 25592261343SJaya Kumar 25692261343SJaya Kumar /* this gets called by metronomefb as part of its init, in our case, we 25792261343SJaya Kumar * have already completed initial framebuffer init in presetup_fb so we 25892261343SJaya Kumar * can just setup the fb access pointers */ 25992261343SJaya Kumar static int am200_setup_fb(struct metronomefb_par *par) 26092261343SJaya Kumar { 26192261343SJaya Kumar int fw; 26292261343SJaya Kumar int fh; 26392261343SJaya Kumar 26492261343SJaya Kumar fw = am200_board.fw; 26592261343SJaya Kumar fh = am200_board.fh; 26692261343SJaya Kumar 26792261343SJaya Kumar /* metromem was set up by the notifier in share_video_mem so now 26892261343SJaya Kumar * we can use its value to calculate the other entries */ 26992261343SJaya Kumar par->metromem_cmd = (struct metromem_cmd *) am200_board.metromem; 27092261343SJaya Kumar par->metromem_wfm = am200_board.metromem + fw; 27192261343SJaya Kumar par->metromem_img = par->metromem_wfm + am200_board.wfm_size; 27292261343SJaya Kumar par->metromem_img_csum = (u16 *) (par->metromem_img + (fw * fh)); 27392261343SJaya Kumar par->metromem_dma = am200_board.host_fbinfo->fix.smem_start; 27492261343SJaya Kumar 27592261343SJaya Kumar return 0; 27692261343SJaya Kumar } 27792261343SJaya Kumar 27892261343SJaya Kumar static int am200_get_panel_type(void) 27992261343SJaya Kumar { 28092261343SJaya Kumar return panel_type; 28192261343SJaya Kumar } 28292261343SJaya Kumar 28392261343SJaya Kumar static irqreturn_t am200_handle_irq(int irq, void *dev_id) 28492261343SJaya Kumar { 28592261343SJaya Kumar struct metronomefb_par *par = dev_id; 28692261343SJaya Kumar 28792261343SJaya Kumar wake_up_interruptible(&par->waitq); 28892261343SJaya Kumar return IRQ_HANDLED; 28992261343SJaya Kumar } 29092261343SJaya Kumar 29192261343SJaya Kumar static int am200_setup_irq(struct fb_info *info) 29292261343SJaya Kumar { 29392261343SJaya Kumar int ret; 29492261343SJaya Kumar 2956384fdadSHaojian Zhuang ret = request_irq(PXA_GPIO_TO_IRQ(RDY_GPIO_PIN), am200_handle_irq, 296ed7936f9SMichael Opdenacker IRQF_TRIGGER_FALLING, "AM200", info->par); 29792261343SJaya Kumar if (ret) 29892261343SJaya Kumar dev_err(&am200_device->dev, "request_irq failed: %d\n", ret); 29992261343SJaya Kumar 30092261343SJaya Kumar return ret; 30192261343SJaya Kumar } 30292261343SJaya Kumar 30392261343SJaya Kumar static void am200_set_rst(struct metronomefb_par *par, int state) 30492261343SJaya Kumar { 30592261343SJaya Kumar gpio_set_value(RST_GPIO_PIN, state); 30692261343SJaya Kumar } 30792261343SJaya Kumar 30892261343SJaya Kumar static void am200_set_stdby(struct metronomefb_par *par, int state) 30992261343SJaya Kumar { 31092261343SJaya Kumar gpio_set_value(STDBY_GPIO_PIN, state); 31192261343SJaya Kumar } 31292261343SJaya Kumar 31392261343SJaya Kumar static int am200_wait_event(struct metronomefb_par *par) 31492261343SJaya Kumar { 31592261343SJaya Kumar return wait_event_timeout(par->waitq, gpio_get_value(RDY_GPIO_PIN), HZ); 31692261343SJaya Kumar } 31792261343SJaya Kumar 31892261343SJaya Kumar static int am200_wait_event_intr(struct metronomefb_par *par) 31992261343SJaya Kumar { 32092261343SJaya Kumar return wait_event_interruptible_timeout(par->waitq, 32192261343SJaya Kumar gpio_get_value(RDY_GPIO_PIN), HZ); 32292261343SJaya Kumar } 32392261343SJaya Kumar 32492261343SJaya Kumar static struct metronome_board am200_board = { 32592261343SJaya Kumar .owner = THIS_MODULE, 32692261343SJaya Kumar .setup_irq = am200_setup_irq, 32792261343SJaya Kumar .setup_io = am200_init_gpio_regs, 32892261343SJaya Kumar .setup_fb = am200_setup_fb, 32992261343SJaya Kumar .set_rst = am200_set_rst, 33092261343SJaya Kumar .set_stdby = am200_set_stdby, 33192261343SJaya Kumar .met_wait_event = am200_wait_event, 33292261343SJaya Kumar .met_wait_event_intr = am200_wait_event_intr, 33392261343SJaya Kumar .get_panel_type = am200_get_panel_type, 33492261343SJaya Kumar .cleanup = am200_cleanup, 33592261343SJaya Kumar }; 33692261343SJaya Kumar 337854feaedSJaya Kumar static unsigned long am200_pin_config[] __initdata = { 338854feaedSJaya Kumar GPIO51_GPIO, 339854feaedSJaya Kumar GPIO49_GPIO, 340854feaedSJaya Kumar GPIO48_GPIO, 341854feaedSJaya Kumar GPIO32_GPIO, 342854feaedSJaya Kumar GPIO17_GPIO, 343854feaedSJaya Kumar GPIO16_GPIO, 344854feaedSJaya Kumar }; 345854feaedSJaya Kumar 3463332b0c1SJaya Kumar int __init am200_init(void) 34792261343SJaya Kumar { 34892261343SJaya Kumar int ret; 34992261343SJaya Kumar 350*97b67986SDaniel Vetter /* 351*97b67986SDaniel Vetter * Before anything else, we request notification for any fb 352*97b67986SDaniel Vetter * creation events. 353*97b67986SDaniel Vetter * 354*97b67986SDaniel Vetter * FIXME: This is terrible and needs to be nuked. The notifier is used 355*97b67986SDaniel Vetter * to get at the fb base address from the boot splash fb driver, which 356*97b67986SDaniel Vetter * is then passed to metronomefb. Instaed of metronomfb or this board 357*97b67986SDaniel Vetter * support file here figuring this out on their own. 358*97b67986SDaniel Vetter * 359*97b67986SDaniel Vetter * See also the #ifdef in fbmem.c. 360*97b67986SDaniel Vetter */ 36192261343SJaya Kumar fb_register_client(&am200_fb_notif); 36292261343SJaya Kumar 363854feaedSJaya Kumar pxa2xx_mfp_config(ARRAY_AND_SIZE(am200_pin_config)); 364854feaedSJaya Kumar 36592261343SJaya Kumar /* request our platform independent driver */ 36692261343SJaya Kumar request_module("metronomefb"); 36792261343SJaya Kumar 36892261343SJaya Kumar am200_device = platform_device_alloc("metronomefb", -1); 36992261343SJaya Kumar if (!am200_device) 37092261343SJaya Kumar return -ENOMEM; 37192261343SJaya Kumar 37292261343SJaya Kumar /* the am200_board that will be seen by metronomefb is a copy */ 37392261343SJaya Kumar platform_device_add_data(am200_device, &am200_board, 37492261343SJaya Kumar sizeof(am200_board)); 37592261343SJaya Kumar 37692261343SJaya Kumar /* this _add binds metronomefb to am200. metronomefb refcounts am200 */ 37792261343SJaya Kumar ret = platform_device_add(am200_device); 37892261343SJaya Kumar 37992261343SJaya Kumar if (ret) { 38092261343SJaya Kumar platform_device_put(am200_device); 38192261343SJaya Kumar fb_unregister_client(&am200_fb_notif); 38292261343SJaya Kumar return ret; 38392261343SJaya Kumar } 38492261343SJaya Kumar 38592261343SJaya Kumar am200_presetup_fb(); 38692261343SJaya Kumar 38792261343SJaya Kumar return 0; 38892261343SJaya Kumar } 38992261343SJaya Kumar 39092261343SJaya Kumar module_param(panel_type, uint, 0); 39192261343SJaya Kumar MODULE_PARM_DESC(panel_type, "Select the panel type: 6, 8, 97"); 39292261343SJaya Kumar 39392261343SJaya Kumar MODULE_DESCRIPTION("board driver for am200 metronome epd kit"); 39492261343SJaya Kumar MODULE_AUTHOR("Jaya Kumar"); 39592261343SJaya Kumar MODULE_LICENSE("GPL"); 396