| /* |
| * drivers/video/pnx4008/sdum.c |
| * |
| * Display Update Master support |
| * |
| * Authors: Grigory Tolstolytkin <gtolstolytkin@ru.mvista.com> |
| * Vitaly Wool <vitalywool@gmail.com> |
| * Based on Philips Semiconductors's code |
| * |
| * Copyrght (c) 2005-2006 MontaVista Software, Inc. |
| * Copyright (c) 2005 Philips Semiconductors |
| * This file is licensed under the terms of the GNU General Public License |
| * version 2. This program is licensed "as is" without any warranty of any |
| * kind, whether express or implied. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/string.h> |
| #include <linux/mm.h> |
| #include <linux/tty.h> |
| #include <linux/vmalloc.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/platform_device.h> |
| #include <linux/fb.h> |
| #include <linux/init.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/clk.h> |
| #include <linux/gfp.h> |
| #include <asm/uaccess.h> |
| #include <mach/gpio.h> |
| |
| #include "sdum.h" |
| #include "fbcommon.h" |
| #include "dum.h" |
| |
| /* Framebuffers we have */ |
| |
| static struct pnx4008_fb_addr { |
| int fb_type; |
| long addr_offset; |
| long fb_length; |
| } fb_addr[] = { |
| [0] = { |
| FB_TYPE_YUV, 0, 0xB0000 |
| }, |
| [1] = { |
| FB_TYPE_RGB, 0xB0000, 0x50000 |
| }, |
| }; |
| |
| static struct dum_data { |
| u32 lcd_phys_start; |
| u32 lcd_virt_start; |
| u32 slave_phys_base; |
| u32 *slave_virt_base; |
| int fb_owning_channel[MAX_DUM_CHANNELS]; |
| struct dumchannel_uf chan_uf_store[MAX_DUM_CHANNELS]; |
| } dum_data; |
| |
| /* Different local helper functions */ |
| |
| static u32 nof_pixels_dx(struct dum_ch_setup *ch_setup) |
| { |
| return (ch_setup->xmax - ch_setup->xmin + 1); |
| } |
| |
| static u32 nof_pixels_dy(struct dum_ch_setup *ch_setup) |
| { |
| return (ch_setup->ymax - ch_setup->ymin + 1); |
| } |
| |
| static u32 nof_pixels_dxy(struct dum_ch_setup *ch_setup) |
| { |
| return (nof_pixels_dx(ch_setup) * nof_pixels_dy(ch_setup)); |
| } |
| |
| static u32 nof_bytes(struct dum_ch_setup *ch_setup) |
| { |
| u32 r = nof_pixels_dxy(ch_setup); |
| switch (ch_setup->format) { |
| case RGB888: |
| case RGB666: |
| r *= 4; |
| break; |
| |
| default: |
| r *= 2; |
| break; |
| } |
| return r; |
| } |
| |
| static u32 build_command(int disp_no, u32 reg, u32 val) |
| { |
| return ((disp_no << 26) | BIT(25) | (val << 16) | (disp_no << 10) | |
| (reg << 0)); |
| } |
| |
| static u32 build_double_index(int disp_no, u32 val) |
| { |
| return ((disp_no << 26) | (val << 16) | (disp_no << 10) | (val << 0)); |
| } |
| |
| static void build_disp_window(struct dum_ch_setup * ch_setup, struct disp_window * dw) |
| { |
| dw->ymin = ch_setup->ymin; |
| dw->ymax = ch_setup->ymax; |
| dw->xmin_l = ch_setup->xmin & 0xFF; |
| dw->xmin_h = (ch_setup->xmin & BIT(8)) >> 8; |
| dw->xmax_l = ch_setup->xmax & 0xFF; |
| dw->xmax_h = (ch_setup->xmax & BIT(8)) >> 8; |
| } |
| |
| static int put_channel(struct dumchannel chan) |
| { |
| int i = chan.channelnr; |
| |
| if (i < 0 || i > MAX_DUM_CHANNELS) |
| return -EINVAL; |
| else { |
| DUM_CH_MIN(i) = chan.dum_ch_min; |
| DUM_CH_MAX(i) = chan.dum_ch_max; |
| DUM_CH_CONF(i) = chan.dum_ch_conf; |
| DUM_CH_CTRL(i) = chan.dum_ch_ctrl; |
| } |
| |
| return 0; |
| } |
| |
| static void clear_channel(int channr) |
| { |
| struct dumchannel chan; |
| |
| chan.channelnr = channr; |
| chan.dum_ch_min = 0; |
| chan.dum_ch_max = 0; |
| chan.dum_ch_conf = 0; |
| chan.dum_ch_ctrl = 0; |
| |
| put_channel(chan); |
| } |
| |
| static int put_cmd_string(struct cmdstring cmds) |
| { |
| u16 *cmd_str_virtaddr; |
| u32 *cmd_ptr0_virtaddr; |
| u32 cmd_str_physaddr; |
| |
| int i = cmds.channelnr; |
| |
| if (i < 0 || i > MAX_DUM_CHANNELS) |
| return -EINVAL; |
| else if ((cmd_ptr0_virtaddr = |
| (int *)ioremap_nocache(DUM_COM_BASE, |
| sizeof(int) * MAX_DUM_CHANNELS)) == |
| NULL) |
| return -EIOREMAPFAILED; |
| else { |
| cmd_str_physaddr = ioread32(&cmd_ptr0_virtaddr[cmds.channelnr]); |
| if ((cmd_str_virtaddr = |
| (u16 *) ioremap_nocache(cmd_str_physaddr, |
| sizeof(cmds))) == NULL) { |
| iounmap(cmd_ptr0_virtaddr); |
| return -EIOREMAPFAILED; |
| } else { |
| int t; |
| for (t = 0; t < 8; t++) |
| iowrite16(*((u16 *)&cmds.prestringlen + t), |
| cmd_str_virtaddr + t); |
| |
| for (t = 0; t < cmds.prestringlen / 2; t++) |
| iowrite16(*((u16 *)&cmds.precmd + t), |
| cmd_str_virtaddr + t + 8); |
| |
| for (t = 0; t < cmds.poststringlen / 2; t++) |
| iowrite16(*((u16 *)&cmds.postcmd + t), |
| cmd_str_virtaddr + t + 8 + |
| cmds.prestringlen / 2); |
| |
| iounmap(cmd_ptr0_virtaddr); |
| iounmap(cmd_str_virtaddr); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static u32 dum_ch_setup(int ch_no, struct dum_ch_setup * ch_setup) |
| { |
| struct cmdstring cmds_c; |
| struct cmdstring *cmds = &cmds_c; |
| struct disp_window dw; |
| int standard; |
| u32 orientation = 0; |
| struct dumchannel chan = { 0 }; |
| int ret; |
| |
| if ((ch_setup->xmirror) || (ch_setup->ymirror) || (ch_setup->rotate)) { |
| standard = 0; |
| |
| orientation = BIT(1); /* always set 9-bit-bus */ |
| if (ch_setup->xmirror) |
| orientation |= BIT(4); |
| if (ch_setup->ymirror) |
| orientation |= BIT(3); |
| if (ch_setup->rotate) |
| orientation |= BIT(0); |
| } else |
| standard = 1; |
| |
| cmds->channelnr = ch_no; |
| |
| /* build command string header */ |
| if (standard) { |
| cmds->prestringlen = 32; |
| cmds->poststringlen = 0; |
| } else { |
| cmds->prestringlen = 48; |
| cmds->poststringlen = 16; |
| } |
| |
| cmds->format = |
| (u16) ((ch_setup->disp_no << 4) | (BIT(3)) | (ch_setup->format)); |
| cmds->reserved = 0x0; |
| cmds->startaddr_low = (ch_setup->minadr & 0xFFFF); |
| cmds->startaddr_high = (ch_setup->minadr >> 16); |
| |
| if ((ch_setup->minadr == 0) && (ch_setup->maxadr == 0) |
| && (ch_setup->xmin == 0) |
| && (ch_setup->ymin == 0) && (ch_setup->xmax == 0) |
| && (ch_setup->ymax == 0)) { |
| cmds->pixdatlen_low = 0; |
| cmds->pixdatlen_high = 0; |
| } else { |
| u32 nbytes = nof_bytes(ch_setup); |
| cmds->pixdatlen_low = (nbytes & 0xFFFF); |
| cmds->pixdatlen_high = (nbytes >> 16); |
| } |
| |
| if (ch_setup->slave_trans) |
| cmds->pixdatlen_high |= BIT(15); |
| |
| /* build pre-string */ |
| build_disp_window(ch_setup, &dw); |
| |
| if (standard) { |
| cmds->precmd[0] = |
| build_command(ch_setup->disp_no, DISP_XMIN_L_REG, 0x99); |
| cmds->precmd[1] = |
| build_command(ch_setup->disp_no, DISP_XMIN_L_REG, |
| dw.xmin_l); |
| cmds->precmd[2] = |
| build_command(ch_setup->disp_no, DISP_XMIN_H_REG, |
| dw.xmin_h); |
| cmds->precmd[3] = |
| build_command(ch_setup->disp_no, DISP_YMIN_REG, dw.ymin); |
| cmds->precmd[4] = |
| build_command(ch_setup->disp_no, DISP_XMAX_L_REG, |
| dw.xmax_l); |
| cmds->precmd[5] = |
| build_command(ch_setup->disp_no, DISP_XMAX_H_REG, |
| dw.xmax_h); |
| cmds->precmd[6] = |
| build_command(ch_setup->disp_no, DISP_YMAX_REG, dw.ymax); |
| cmds->precmd[7] = |
| build_double_index(ch_setup->disp_no, DISP_PIXEL_REG); |
| } else { |
| if (dw.xmin_l == ch_no) |
| cmds->precmd[0] = |
| build_command(ch_setup->disp_no, DISP_XMIN_L_REG, |
| 0x99); |
| else |
| cmds->precmd[0] = |
| build_command(ch_setup->disp_no, DISP_XMIN_L_REG, |
| ch_no); |
| |
| cmds->precmd[1] = |
| build_command(ch_setup->disp_no, DISP_XMIN_L_REG, |
| dw.xmin_l); |
| cmds->precmd[2] = |
| build_command(ch_setup->disp_no, DISP_XMIN_H_REG, |
| dw.xmin_h); |
| cmds->precmd[3] = |
| build_command(ch_setup->disp_no, DISP_YMIN_REG, dw.ymin); |
| cmds->precmd[4] = |
| build_command(ch_setup->disp_no, DISP_XMAX_L_REG, |
| dw.xmax_l); |
| cmds->precmd[5] = |
| build_command(ch_setup->disp_no, DISP_XMAX_H_REG, |
| dw.xmax_h); |
| cmds->precmd[6] = |
| build_command(ch_setup->disp_no, DISP_YMAX_REG, dw.ymax); |
| cmds->precmd[7] = |
| build_command(ch_setup->disp_no, DISP_1_REG, orientation); |
| cmds->precmd[8] = |
| build_double_index(ch_setup->disp_no, DISP_PIXEL_REG); |
| cmds->precmd[9] = |
| build_double_index(ch_setup->disp_no, DISP_PIXEL_REG); |
| cmds->precmd[0xA] = |
| build_double_index(ch_setup->disp_no, DISP_PIXEL_REG); |
| cmds->precmd[0xB] = |
| build_double_index(ch_setup->disp_no, DISP_PIXEL_REG); |
| cmds->postcmd[0] = |
| build_command(ch_setup->disp_no, DISP_1_REG, BIT(1)); |
| cmds->postcmd[1] = |
| build_command(ch_setup->disp_no, DISP_DUMMY1_REG, 1); |
| cmds->postcmd[2] = |
| build_command(ch_setup->disp_no, DISP_DUMMY1_REG, 2); |
| cmds->postcmd[3] = |
| build_command(ch_setup->disp_no, DISP_DUMMY1_REG, 3); |
| } |
| |
| if ((ret = put_cmd_string(cmds_c)) != 0) { |
| return ret; |
| } |
| |
| chan.channelnr = cmds->channelnr; |
| chan.dum_ch_min = ch_setup->dirtybuffer + ch_setup->minadr; |
| chan.dum_ch_max = ch_setup->dirtybuffer + ch_setup->maxadr; |
| chan.dum_ch_conf = 0x002; |
| chan.dum_ch_ctrl = 0x04; |
| |
| put_channel(chan); |
| |
| return 0; |
| } |
| |
| static u32 display_open(int ch_no, int auto_update, u32 * dirty_buffer, |
| u32 * frame_buffer, u32 xpos, u32 ypos, u32 w, u32 h) |
| { |
| |
| struct dum_ch_setup k; |
| int ret; |
| |
| /* keep width & height within display area */ |
| if ((xpos + w) > DISP_MAX_X_SIZE) |
| w = DISP_MAX_X_SIZE - xpos; |
| |
| if ((ypos + h) > DISP_MAX_Y_SIZE) |
| h = DISP_MAX_Y_SIZE - ypos; |
| |
| /* assume 1 display only */ |
| k.disp_no = 0; |
| k.xmin = xpos; |
| k.ymin = ypos; |
| k.xmax = xpos + (w - 1); |
| k.ymax = ypos + (h - 1); |
| |
| /* adjust min and max values if necessary */ |
| if (k.xmin > DISP_MAX_X_SIZE - 1) |
| k.xmin = DISP_MAX_X_SIZE - 1; |
| if (k.ymin > DISP_MAX_Y_SIZE - 1) |
| k.ymin = DISP_MAX_Y_SIZE - 1; |
| |
| if (k.xmax > DISP_MAX_X_SIZE - 1) |
| k.xmax = DISP_MAX_X_SIZE - 1; |
| if (k.ymax > DISP_MAX_Y_SIZE - 1) |
| k.ymax = DISP_MAX_Y_SIZE - 1; |
| |
| k.xmirror = 0; |
| k.ymirror = 0; |
| k.rotate = 0; |
| k.minadr = (u32) frame_buffer; |
| k.maxadr = (u32) frame_buffer + (((w - 1) << 10) | ((h << 2) - 2)); |
| k.pad = PAD_1024; |
| k.dirtybuffer = (u32) dirty_buffer; |
| k.format = RGB888; |
| k.hwdirty = 0; |
| k.slave_trans = 0; |
| |
| ret = dum_ch_setup(ch_no, &k); |
| |
| return ret; |
| } |
| |
| static void lcd_reset(void) |
| { |
| u32 *dum_pio_base = (u32 *)IO_ADDRESS(PNX4008_PIO_BASE); |
| |
| udelay(1); |
| iowrite32(BIT(19), &dum_pio_base[2]); |
| udelay(1); |
| iowrite32(BIT(19), &dum_pio_base[1]); |
| udelay(1); |
| } |
| |
| static int dum_init(struct platform_device *pdev) |
| { |
| struct clk *clk; |
| |
| /* enable DUM clock */ |
| clk = clk_get(&pdev->dev, "dum_ck"); |
| if (IS_ERR(clk)) { |
| printk(KERN_ERR "pnx4008_dum: Unable to access DUM clock\n"); |
| return PTR_ERR(clk); |
| } |
| |
| clk_set_rate(clk, 1); |
| clk_put(clk); |
| |
| DUM_CTRL = V_DUM_RESET; |
| |
| /* set priority to "round-robin". All other params to "false" */ |
| DUM_CONF = BIT(9); |
| |
| /* Display 1 */ |
| DUM_WTCFG1 = PNX4008_DUM_WT_CFG; |
| DUM_RTCFG1 = PNX4008_DUM_RT_CFG; |
| DUM_TCFG = PNX4008_DUM_T_CFG; |
| |
| return 0; |
| } |
| |
| static void dum_chan_init(void) |
| { |
| int i = 0, ch = 0; |
| u32 *cmdptrs; |
| u32 *cmdstrings; |
| |
| DUM_COM_BASE = |
| CMDSTRING_BASEADDR + BYTES_PER_CMDSTRING * NR_OF_CMDSTRINGS; |
| |
| if ((cmdptrs = |
| (u32 *) ioremap_nocache(DUM_COM_BASE, |
| sizeof(u32) * NR_OF_CMDSTRINGS)) == NULL) |
| return; |
| |
| for (ch = 0; ch < NR_OF_CMDSTRINGS; ch++) |
| iowrite32(CMDSTRING_BASEADDR + BYTES_PER_CMDSTRING * ch, |
| cmdptrs + ch); |
| |
| for (ch = 0; ch < MAX_DUM_CHANNELS; ch++) |
| clear_channel(ch); |
| |
| /* Clear the cmdstrings */ |
| cmdstrings = |
| (u32 *)ioremap_nocache(*cmdptrs, |
| BYTES_PER_CMDSTRING * NR_OF_CMDSTRINGS); |
| |
| if (!cmdstrings) |
| goto out; |
| |
| for (i = 0; i < NR_OF_CMDSTRINGS * BYTES_PER_CMDSTRING / sizeof(u32); |
| i++) |
| iowrite32(0, cmdstrings + i); |
| |
| iounmap((u32 *)cmdstrings); |
| |
| out: |
| iounmap((u32 *)cmdptrs); |
| } |
| |
| static void lcd_init(void) |
| { |
| lcd_reset(); |
| |
| DUM_OUTP_FORMAT1 = 0; /* RGB666 */ |
| |
| udelay(1); |
| iowrite32(V_LCD_STANDBY_OFF, dum_data.slave_virt_base); |
| udelay(1); |
| iowrite32(V_LCD_USE_9BIT_BUS, dum_data.slave_virt_base); |
| udelay(1); |
| iowrite32(V_LCD_SYNC_RISE_L, dum_data.slave_virt_base); |
| udelay(1); |
| iowrite32(V_LCD_SYNC_RISE_H, dum_data.slave_virt_base); |
| udelay(1); |
| iowrite32(V_LCD_SYNC_FALL_L, dum_data.slave_virt_base); |
| udelay(1); |
| iowrite32(V_LCD_SYNC_FALL_H, dum_data.slave_virt_base); |
| udelay(1); |
| iowrite32(V_LCD_SYNC_ENABLE, dum_data.slave_virt_base); |
| udelay(1); |
| iowrite32(V_LCD_DISPLAY_ON, dum_data.slave_virt_base); |
| udelay(1); |
| } |
| |
| /* Interface exported to framebuffer drivers */ |
| |
| int pnx4008_get_fb_addresses(int fb_type, void **virt_addr, |
| dma_addr_t *phys_addr, int *fb_length) |
| { |
| int i; |
| int ret = -1; |
| for (i = 0; i < ARRAY_SIZE(fb_addr); i++) |
| if (fb_addr[i].fb_type == fb_type) { |
| *virt_addr = (void *)(dum_data.lcd_virt_start + |
| fb_addr[i].addr_offset); |
| *phys_addr = |
| dum_data.lcd_phys_start + fb_addr[i].addr_offset; |
| *fb_length = fb_addr[i].fb_length; |
| ret = 0; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| EXPORT_SYMBOL(pnx4008_get_fb_addresses); |
| |
| int pnx4008_alloc_dum_channel(int dev_id) |
| { |
| int i = 0; |
| |
| while ((i < MAX_DUM_CHANNELS) && (dum_data.fb_owning_channel[i] != -1)) |
| i++; |
| |
| if (i == MAX_DUM_CHANNELS) |
| return -ENORESOURCESLEFT; |
| else { |
| dum_data.fb_owning_channel[i] = dev_id; |
| return i; |
| } |
| } |
| |
| EXPORT_SYMBOL(pnx4008_alloc_dum_channel); |
| |
| int pnx4008_free_dum_channel(int channr, int dev_id) |
| { |
| if (channr < 0 || channr > MAX_DUM_CHANNELS) |
| return -EINVAL; |
| else if (dum_data.fb_owning_channel[channr] != dev_id) |
| return -EFBNOTOWNER; |
| else { |
| clear_channel(channr); |
| dum_data.fb_owning_channel[channr] = -1; |
| } |
| |
| return 0; |
| } |
| |
| EXPORT_SYMBOL(pnx4008_free_dum_channel); |
| |
| int pnx4008_put_dum_channel_uf(struct dumchannel_uf chan_uf, int dev_id) |
| { |
| int i = chan_uf.channelnr; |
| int ret; |
| |
| if (i < 0 || i > MAX_DUM_CHANNELS) |
| return -EINVAL; |
| else if (dum_data.fb_owning_channel[i] != dev_id) |
| return -EFBNOTOWNER; |
| else if ((ret = |
| display_open(chan_uf.channelnr, 0, chan_uf.dirty, |
| chan_uf.source, chan_uf.y_offset, |
| chan_uf.x_offset, chan_uf.height, |
| chan_uf.width)) != 0) |
| return ret; |
| else { |
| dum_data.chan_uf_store[i].dirty = chan_uf.dirty; |
| dum_data.chan_uf_store[i].source = chan_uf.source; |
| dum_data.chan_uf_store[i].x_offset = chan_uf.x_offset; |
| dum_data.chan_uf_store[i].y_offset = chan_uf.y_offset; |
| dum_data.chan_uf_store[i].width = chan_uf.width; |
| dum_data.chan_uf_store[i].height = chan_uf.height; |
| } |
| |
| return 0; |
| } |
| |
| EXPORT_SYMBOL(pnx4008_put_dum_channel_uf); |
| |
| int pnx4008_set_dum_channel_sync(int channr, int val, int dev_id) |
| { |
| if (channr < 0 || channr > MAX_DUM_CHANNELS) |
| return -EINVAL; |
| else if (dum_data.fb_owning_channel[channr] != dev_id) |
| return -EFBNOTOWNER; |
| else { |
| if (val == CONF_SYNC_ON) { |
| DUM_CH_CONF(channr) |= CONF_SYNCENABLE; |
| DUM_CH_CONF(channr) |= DUM_CHANNEL_CFG_SYNC_MASK | |
| DUM_CHANNEL_CFG_SYNC_MASK_SET; |
| } else if (val == CONF_SYNC_OFF) |
| DUM_CH_CONF(channr) &= ~CONF_SYNCENABLE; |
| else |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| EXPORT_SYMBOL(pnx4008_set_dum_channel_sync); |
| |
| int pnx4008_set_dum_channel_dirty_detect(int channr, int val, int dev_id) |
| { |
| if (channr < 0 || channr > MAX_DUM_CHANNELS) |
| return -EINVAL; |
| else if (dum_data.fb_owning_channel[channr] != dev_id) |
| return -EFBNOTOWNER; |
| else { |
| if (val == CONF_DIRTYDETECTION_ON) |
| DUM_CH_CONF(channr) |= CONF_DIRTYENABLE; |
| else if (val == CONF_DIRTYDETECTION_OFF) |
| DUM_CH_CONF(channr) &= ~CONF_DIRTYENABLE; |
| else |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| EXPORT_SYMBOL(pnx4008_set_dum_channel_dirty_detect); |
| |
| #if 0 /* Functions not used currently, but likely to be used in future */ |
| |
| static int get_channel(struct dumchannel *p_chan) |
| { |
| int i = p_chan->channelnr; |
| |
| if (i < 0 || i > MAX_DUM_CHANNELS) |
| return -EINVAL; |
| else { |
| p_chan->dum_ch_min = DUM_CH_MIN(i); |
| p_chan->dum_ch_max = DUM_CH_MAX(i); |
| p_chan->dum_ch_conf = DUM_CH_CONF(i); |
| p_chan->dum_ch_stat = DUM_CH_STAT(i); |
| p_chan->dum_ch_ctrl = 0; /* WriteOnly control register */ |
| } |
| |
| return 0; |
| } |
| |
| int pnx4008_get_dum_channel_uf(struct dumchannel_uf *p_chan_uf, int dev_id) |
| { |
| int i = p_chan_uf->channelnr; |
| |
| if (i < 0 || i > MAX_DUM_CHANNELS) |
| return -EINVAL; |
| else if (dum_data.fb_owning_channel[i] != dev_id) |
| return -EFBNOTOWNER; |
| else { |
| p_chan_uf->dirty = dum_data.chan_uf_store[i].dirty; |
| p_chan_uf->source = dum_data.chan_uf_store[i].source; |
| p_chan_uf->x_offset = dum_data.chan_uf_store[i].x_offset; |
| p_chan_uf->y_offset = dum_data.chan_uf_store[i].y_offset; |
| p_chan_uf->width = dum_data.chan_uf_store[i].width; |
| p_chan_uf->height = dum_data.chan_uf_store[i].height; |
| } |
| |
| return 0; |
| } |
| |
| EXPORT_SYMBOL(pnx4008_get_dum_channel_uf); |
| |
| int pnx4008_get_dum_channel_config(int channr, int dev_id) |
| { |
| int ret; |
| struct dumchannel chan; |
| |
| if (channr < 0 || channr > MAX_DUM_CHANNELS) |
| return -EINVAL; |
| else if (dum_data.fb_owning_channel[channr] != dev_id) |
| return -EFBNOTOWNER; |
| else { |
| chan.channelnr = channr; |
| if ((ret = get_channel(&chan)) != 0) |
| return ret; |
| } |
| |
| return (chan.dum_ch_conf & DUM_CHANNEL_CFG_MASK); |
| } |
| |
| EXPORT_SYMBOL(pnx4008_get_dum_channel_config); |
| |
| int pnx4008_force_update_dum_channel(int channr, int dev_id) |
| { |
| if (channr < 0 || channr > MAX_DUM_CHANNELS) |
| return -EINVAL; |
| |
| else if (dum_data.fb_owning_channel[channr] != dev_id) |
| return -EFBNOTOWNER; |
| else |
| DUM_CH_CTRL(channr) = CTRL_SETDIRTY; |
| |
| return 0; |
| } |
| |
| EXPORT_SYMBOL(pnx4008_force_update_dum_channel); |
| |
| #endif |
| |
| int pnx4008_sdum_mmap(struct fb_info *info, struct vm_area_struct *vma, |
| struct device *dev) |
| { |
| unsigned long off = vma->vm_pgoff << PAGE_SHIFT; |
| |
| if (off < info->fix.smem_len) { |
| vma->vm_pgoff += 1; |
| return dma_mmap_writecombine(dev, vma, |
| (void *)dum_data.lcd_virt_start, |
| dum_data.lcd_phys_start, |
| FB_DMA_SIZE); |
| } |
| return -EINVAL; |
| } |
| |
| EXPORT_SYMBOL(pnx4008_sdum_mmap); |
| |
| int pnx4008_set_dum_exit_notification(int dev_id) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_DUM_CHANNELS; i++) |
| if (dum_data.fb_owning_channel[i] == dev_id) |
| return -ERESOURCESNOTFREED; |
| |
| return 0; |
| } |
| |
| EXPORT_SYMBOL(pnx4008_set_dum_exit_notification); |
| |
| /* Platform device driver for DUM */ |
| |
| static int sdum_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| int retval = 0; |
| struct clk *clk; |
| |
| clk = clk_get(0, "dum_ck"); |
| if (!IS_ERR(clk)) { |
| clk_set_rate(clk, 0); |
| clk_put(clk); |
| } else |
| retval = PTR_ERR(clk); |
| |
| /* disable BAC */ |
| DUM_CTRL = V_BAC_DISABLE_IDLE; |
| |
| /* LCD standby & turn off display */ |
| lcd_reset(); |
| |
| return retval; |
| } |
| |
| static int sdum_resume(struct platform_device *pdev) |
| { |
| int retval = 0; |
| struct clk *clk; |
| |
| clk = clk_get(0, "dum_ck"); |
| if (!IS_ERR(clk)) { |
| clk_set_rate(clk, 1); |
| clk_put(clk); |
| } else |
| retval = PTR_ERR(clk); |
| |
| /* wait for BAC disable */ |
| DUM_CTRL = V_BAC_DISABLE_TRIG; |
| |
| while (DUM_CTRL & BAC_ENABLED) |
| udelay(10); |
| |
| /* re-init LCD */ |
| lcd_init(); |
| |
| /* enable BAC and reset MUX */ |
| DUM_CTRL = V_BAC_ENABLE; |
| udelay(1); |
| DUM_CTRL = V_MUX_RESET; |
| return 0; |
| } |
| |
| static int __devinit sdum_probe(struct platform_device *pdev) |
| { |
| int ret = 0, i = 0; |
| |
| /* map frame buffer */ |
| dum_data.lcd_virt_start = (u32) dma_alloc_writecombine(&pdev->dev, |
| FB_DMA_SIZE, |
| &dum_data.lcd_phys_start, |
| GFP_KERNEL); |
| |
| if (!dum_data.lcd_virt_start) { |
| ret = -ENOMEM; |
| goto out_3; |
| } |
| |
| /* map slave registers */ |
| dum_data.slave_phys_base = PNX4008_DUM_SLAVE_BASE; |
| dum_data.slave_virt_base = |
| (u32 *) ioremap_nocache(dum_data.slave_phys_base, sizeof(u32)); |
| |
| if (dum_data.slave_virt_base == NULL) { |
| ret = -ENOMEM; |
| goto out_2; |
| } |
| |
| /* initialize DUM and LCD display */ |
| ret = dum_init(pdev); |
| if (ret) |
| goto out_1; |
| |
| dum_chan_init(); |
| lcd_init(); |
| |
| DUM_CTRL = V_BAC_ENABLE; |
| udelay(1); |
| DUM_CTRL = V_MUX_RESET; |
| |
| /* set decode address and sync clock divider */ |
| DUM_DECODE = dum_data.lcd_phys_start & DUM_DECODE_MASK; |
| DUM_CLK_DIV = PNX4008_DUM_CLK_DIV; |
| |
| for (i = 0; i < MAX_DUM_CHANNELS; i++) |
| dum_data.fb_owning_channel[i] = -1; |
| |
| /*setup wakeup interrupt */ |
| start_int_set_rising_edge(SE_DISP_SYNC_INT); |
| start_int_ack(SE_DISP_SYNC_INT); |
| start_int_umask(SE_DISP_SYNC_INT); |
| |
| return 0; |
| |
| out_1: |
| iounmap((void *)dum_data.slave_virt_base); |
| out_2: |
| dma_free_writecombine(&pdev->dev, FB_DMA_SIZE, |
| (void *)dum_data.lcd_virt_start, |
| dum_data.lcd_phys_start); |
| out_3: |
| return ret; |
| } |
| |
| static int sdum_remove(struct platform_device *pdev) |
| { |
| struct clk *clk; |
| |
| start_int_mask(SE_DISP_SYNC_INT); |
| |
| clk = clk_get(0, "dum_ck"); |
| if (!IS_ERR(clk)) { |
| clk_set_rate(clk, 0); |
| clk_put(clk); |
| } |
| |
| iounmap((void *)dum_data.slave_virt_base); |
| |
| dma_free_writecombine(&pdev->dev, FB_DMA_SIZE, |
| (void *)dum_data.lcd_virt_start, |
| dum_data.lcd_phys_start); |
| |
| return 0; |
| } |
| |
| static struct platform_driver sdum_driver = { |
| .driver = { |
| .name = "pnx4008-sdum", |
| }, |
| .probe = sdum_probe, |
| .remove = sdum_remove, |
| .suspend = sdum_suspend, |
| .resume = sdum_resume, |
| }; |
| |
| int __init sdum_init(void) |
| { |
| return platform_driver_register(&sdum_driver); |
| } |
| |
| static void __exit sdum_exit(void) |
| { |
| platform_driver_unregister(&sdum_driver); |
| }; |
| |
| module_init(sdum_init); |
| module_exit(sdum_exit); |
| |
| MODULE_LICENSE("GPL"); |