/* * OMAP2 Remote Frame Buffer Interface support * * Copyright (C) 2005 Nokia Corporation * Author: Juha Yrj�l� <juha.yrjola@nokia.com> * Imre Deak <imre.deak@nokia.com> * * 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/delay.h> #include <linux/i2c.h> #include <linux/err.h> #include <linux/interrupt.h> #include <linux/clk.h> #include <linux/io.h> #include <asm/arch/omapfb.h> #include "dispc.h" /* To work around an RFBI transfer rate limitation */ #define OMAP_RFBI_RATE_LIMIT 1 #define RFBI_BASE 0x48050800 #define RFBI_REVISION 0x0000 #define RFBI_SYSCONFIG 0x0010 #define RFBI_SYSSTATUS 0x0014 #define RFBI_CONTROL 0x0040 #define RFBI_PIXEL_CNT 0x0044 #define RFBI_LINE_NUMBER 0x0048 #define RFBI_CMD 0x004c #define RFBI_PARAM 0x0050 #define RFBI_DATA 0x0054 #define RFBI_READ 0x0058 #define RFBI_STATUS 0x005c #define RFBI_CONFIG0 0x0060 #define RFBI_ONOFF_TIME0 0x0064 #define RFBI_CYCLE_TIME0 0x0068 #define RFBI_DATA_CYCLE1_0 0x006c #define RFBI_DATA_CYCLE2_0 0x0070 #define RFBI_DATA_CYCLE3_0 0x0074 #define RFBI_VSYNC_WIDTH 0x0090 #define RFBI_HSYNC_WIDTH 0x0094 #define DISPC_BASE 0x48050400 #define DISPC_CONTROL 0x0040 static struct { u32 base; void (*lcdc_callback)(void *data); void *lcdc_callback_data; unsigned long l4_khz; int bits_per_cycle; struct omapfb_device *fbdev; struct clk *dss_ick; struct clk *dss1_fck; unsigned tearsync_pin_cnt; unsigned tearsync_mode; } rfbi; static inline void rfbi_write_reg(int idx, u32 val) { __raw_writel(val, rfbi.base + idx); } static inline u32 rfbi_read_reg(int idx) { return __raw_readl(rfbi.base + idx); } static int rfbi_get_clocks(void) { if (IS_ERR((rfbi.dss_ick = clk_get(rfbi.fbdev->dev, "dss_ick")))) { dev_err(rfbi.fbdev->dev, "can't get dss_ick\n"); return PTR_ERR(rfbi.dss_ick); } if (IS_ERR((rfbi.dss1_fck = clk_get(rfbi.fbdev->dev, "dss1_fck")))) { dev_err(rfbi.fbdev->dev, "can't get dss1_fck\n"); clk_put(rfbi.dss_ick); return PTR_ERR(rfbi.dss1_fck); } return 0; } static void rfbi_put_clocks(void) { clk_put(rfbi.dss1_fck); clk_put(rfbi.dss_ick); } static void rfbi_enable_clocks(int enable) { if (enable) { clk_enable(rfbi.dss_ick); clk_enable(rfbi.dss1_fck); } else { clk_disable(rfbi.dss1_fck); clk_disable(rfbi.dss_ick); } } #ifdef VERBOSE static void rfbi_print_timings(void) { u32 l; u32 time; l = rfbi_read_reg(RFBI_CONFIG0); time = 1000000000 / rfbi.l4_khz; if (l & (1 << 4)) time *= 2; dev_dbg(rfbi.fbdev->dev, "Tick time %u ps\n", time); l = rfbi_read_reg(RFBI_ONOFF_TIME0); dev_dbg(rfbi.fbdev->dev, "CSONTIME %d, CSOFFTIME %d, WEONTIME %d, WEOFFTIME %d, " "REONTIME %d, REOFFTIME %d\n", l & 0x0f, (l >> 4) & 0x3f, (l >> 10) & 0x0f, (l >> 14) & 0x3f, (l >> 20) & 0x0f, (l >> 24) & 0x3f); l = rfbi_read_reg(RFBI_CYCLE_TIME0); dev_dbg(rfbi.fbdev->dev, "WECYCLETIME %d, RECYCLETIME %d, CSPULSEWIDTH %d, " "ACCESSTIME %d\n", (l & 0x3f), (l >> 6) & 0x3f, (l >> 12) & 0x3f, (l >> 22) & 0x3f); } #else static void rfbi_print_timings(void) {} #endif static void rfbi_set_timings(const struct extif_timings *t) { u32 l; BUG_ON(!t->converted); rfbi_enable_clocks(1); rfbi_write_reg(RFBI_ONOFF_TIME0, t->tim[0]); rfbi_write_reg(RFBI_CYCLE_TIME0, t->tim[1]); l = rfbi_read_reg(RFBI_CONFIG0); l &= ~(1 << 4); l |= (t->tim[2] ? 1 : 0) << 4; rfbi_write_reg(RFBI_CONFIG0, l); rfbi_print_timings(); rfbi_enable_clocks(0); } static void rfbi_get_clk_info(u32 *clk_period, u32 *max_clk_div) { *clk_period = 1000000000 / rfbi.l4_khz; *max_clk_div = 2; } static int ps_to_rfbi_ticks(int time, int div) { unsigned long tick_ps; int ret; /* Calculate in picosecs to yield more exact results */ tick_ps = 1000000000 / (rfbi.l4_khz) * div; ret = (time + tick_ps - 1) / tick_ps; return ret; } #ifdef OMAP_RFBI_RATE_LIMIT static unsigned long rfbi_get_max_tx_rate(void) { unsigned long l4_rate, dss1_rate; int min_l4_ticks = 0; int i; /* According to TI this can't be calculated so make the * adjustments for a couple of known frequencies and warn for * others. */ static const struct { unsigned long l4_clk; /* HZ */ unsigned long dss1_clk; /* HZ */ unsigned long min_l4_ticks; } ftab[] = { { 55, 132, 7, }, /* 7.86 MPix/s */ { 110, 110, 12, }, /* 9.16 MPix/s */ { 110, 132, 10, }, /* 11 Mpix/s */ { 120, 120, 10, }, /* 12 Mpix/s */ { 133, 133, 10, }, /* 13.3 Mpix/s */ }; l4_rate = rfbi.l4_khz / 1000; dss1_rate = clk_get_rate(rfbi.dss1_fck) / 1000000; for (i = 0; i < ARRAY_SIZE(ftab); i++) { /* Use a window instead of an exact match, to account * for different DPLL multiplier / divider pairs. */ if (abs(ftab[i].l4_clk - l4_rate) < 3 && abs(ftab[i].dss1_clk - dss1_rate) < 3) { min_l4_ticks = ftab[i].min_l4_ticks; break; } } if (i == ARRAY_SIZE(ftab)) { /* Can't be sure, return anyway the maximum not * rate-limited. This might cause a problem only for the * tearing synchronisation. */ dev_err(rfbi.fbdev->dev, "can't determine maximum RFBI transfer rate\n"); return rfbi.l4_khz * 1000; } return rfbi.l4_khz * 1000 / min_l4_ticks; } #else static int rfbi_get_max_tx_rate(void) { return rfbi.l4_khz * 1000; } #endif static int rfbi_convert_timings(struct extif_timings *t) { u32 l; int reon, reoff, weon, weoff, cson, csoff, cs_pulse; int actim, recyc, wecyc; int div = t->clk_div; if (div <= 0 || div > 2) return -1; /* Make sure that after conversion it still holds that: * weoff > weon, reoff > reon, recyc >= reoff, wecyc >= weoff, * csoff > cson, csoff >= max(weoff, reoff), actim > reon */ weon = ps_to_rfbi_ticks(t->we_on_time, div); weoff = ps_to_rfbi_ticks(t->we_off_time, div); if (weoff <= weon) weoff = weon + 1; if (weon > 0x0f) return -1; if (weoff > 0x3f) return -1; reon = ps_to_rfbi_ticks(t->re_on_time, div); reoff = ps_to_rfbi_ticks(t->re_off_time, div); if (reoff <= reon) reoff = reon + 1; if (reon > 0x0f) return -1; if (reoff > 0x3f) return -1; cson = ps_to_rfbi_ticks(t->cs_on_time, div); csoff = ps_to_rfbi_ticks(t->cs_off_time, div); if (csoff <= cson) csoff = cson + 1; if (csoff < max(weoff, reoff)) csoff = max(weoff, reoff); if (cson > 0x0f) return -1; if (csoff > 0x3f) return -1; l = cson; l |= csoff << 4; l |= weon << 10; l |= weoff << 14; l |= reon << 20; l |= reoff << 24; t->tim[0] = l; actim = ps_to_rfbi_ticks(t->access_time, div); if (actim <= reon) actim = reon + 1; if (actim > 0x3f) return -1; wecyc = ps_to_rfbi_ticks(t->we_cycle_time, div); if (wecyc < weoff) wecyc = weoff; if (wecyc > 0x3f) return -1; recyc = ps_to_rfbi_ticks(t->re_cycle_time, div); if (recyc < reoff) recyc = reoff; if (recyc > 0x3f) return -1; cs_pulse = ps_to_rfbi_ticks(t->cs_pulse_width, div); if (cs_pulse > 0x3f) return -1; l = wecyc; l |= recyc << 6; l |= cs_pulse << 12; l |= actim << 22; t->tim[1] = l; t->tim[2] = div - 1; t->converted = 1; return 0; } static int rfbi_setup_tearsync(unsigned pin_cnt, unsigned hs_pulse_time, unsigned vs_pulse_time, int hs_pol_inv, int vs_pol_inv, int extif_div) { int hs, vs; int min; u32 l; if (pin_cnt != 1 && pin_cnt != 2) return -EINVAL; hs = ps_to_rfbi_ticks(hs_pulse_time, 1); vs = ps_to_rfbi_ticks(vs_pulse_time, 1); if (hs < 2) return -EDOM; if (pin_cnt == 2) min = 2; else min = 4; if (vs < min) return -EDOM; if (vs == hs) return -EINVAL; rfbi.tearsync_pin_cnt = pin_cnt; dev_dbg(rfbi.fbdev->dev, "setup_tearsync: pins %d hs %d vs %d hs_inv %d vs_inv %d\n", pin_cnt, hs, vs, hs_pol_inv, vs_pol_inv); rfbi_enable_clocks(1); rfbi_write_reg(RFBI_HSYNC_WIDTH, hs); rfbi_write_reg(RFBI_VSYNC_WIDTH, vs); l = rfbi_read_reg(RFBI_CONFIG0); if (hs_pol_inv) l &= ~(1 << 21); else l |= 1 << 21; if (vs_pol_inv) l &= ~(1 << 20); else l |= 1 << 20; rfbi_enable_clocks(0); return 0; } static int rfbi_enable_tearsync(int enable, unsigned line) { u32 l; dev_dbg(rfbi.fbdev->dev, "tearsync %d line %d mode %d\n", enable, line, rfbi.tearsync_mode); if (line > (1 << 11) - 1) return -EINVAL; rfbi_enable_clocks(1); l = rfbi_read_reg(RFBI_CONFIG0); l &= ~(0x3 << 2); if (enable) { rfbi.tearsync_mode = rfbi.tearsync_pin_cnt; l |= rfbi.tearsync_mode << 2; } else rfbi.tearsync_mode = 0; rfbi_write_reg(RFBI_CONFIG0, l); rfbi_write_reg(RFBI_LINE_NUMBER, line); rfbi_enable_clocks(0); return 0; } static void rfbi_write_command(const void *buf, unsigned int len) { rfbi_enable_clocks(1); if (rfbi.bits_per_cycle == 16) { const u16 *w = buf; BUG_ON(len & 1); for (; len; len -= 2) rfbi_write_reg(RFBI_CMD, *w++); } else { const u8 *b = buf; BUG_ON(rfbi.bits_per_cycle != 8); for (; len; len--) rfbi_write_reg(RFBI_CMD, *b++); } rfbi_enable_clocks(0); } static void rfbi_read_data(void *buf, unsigned int len) { rfbi_enable_clocks(1); if (rfbi.bits_per_cycle == 16) { u16 *w = buf; BUG_ON(len & ~1); for (; len; len -= 2) { rfbi_write_reg(RFBI_READ, 0); *w++ = rfbi_read_reg(RFBI_READ); } } else { u8 *b = buf; BUG_ON(rfbi.bits_per_cycle != 8); for (; len; len--) { rfbi_write_reg(RFBI_READ, 0); *b++ = rfbi_read_reg(RFBI_READ); } } rfbi_enable_clocks(0); } static void rfbi_write_data(const void *buf, unsigned int len) { rfbi_enable_clocks(1); if (rfbi.bits_per_cycle == 16) { const u16 *w = buf; BUG_ON(len & 1); for (; len; len -= 2) rfbi_write_reg(RFBI_PARAM, *w++); } else { const u8 *b = buf; BUG_ON(rfbi.bits_per_cycle != 8); for (; len; len--) rfbi_write_reg(RFBI_PARAM, *b++); } rfbi_enable_clocks(0); } static void rfbi_transfer_area(int width, int height, void (callback)(void * data), void *data) { u32 w; BUG_ON(callback == NULL); rfbi_enable_clocks(1); omap_dispc_set_lcd_size(width, height); rfbi.lcdc_callback = callback; rfbi.lcdc_callback_data = data; rfbi_write_reg(RFBI_PIXEL_CNT, width * height); w = rfbi_read_reg(RFBI_CONTROL); w |= 1; /* enable */ if (!rfbi.tearsync_mode) w |= 1 << 4; /* internal trigger, reset by HW */ rfbi_write_reg(RFBI_CONTROL, w); omap_dispc_enable_lcd_out(1); } static inline void _stop_transfer(void) { u32 w; w = rfbi_read_reg(RFBI_CONTROL); rfbi_write_reg(RFBI_CONTROL, w & ~(1 << 0)); rfbi_enable_clocks(0); } static void rfbi_dma_callback(void *data) { _stop_transfer(); rfbi.lcdc_callback(rfbi.lcdc_callback_data); } static void rfbi_set_bits_per_cycle(int bpc) { u32 l; rfbi_enable_clocks(1); l = rfbi_read_reg(RFBI_CONFIG0); l &= ~(0x03 << 0); switch (bpc) { case 8: break; case 16: l |= 3; break; default: BUG(); } rfbi_write_reg(RFBI_CONFIG0, l); rfbi.bits_per_cycle = bpc; rfbi_enable_clocks(0); } static int rfbi_init(struct omapfb_device *fbdev) { u32 l; int r; rfbi.fbdev = fbdev; rfbi.base = io_p2v(RFBI_BASE); if ((r = rfbi_get_clocks()) < 0) return r; rfbi_enable_clocks(1); rfbi.l4_khz = clk_get_rate(rfbi.dss_ick) / 1000; /* Reset */ rfbi_write_reg(RFBI_SYSCONFIG, 1 << 1); while (!(rfbi_read_reg(RFBI_SYSSTATUS) & (1 << 0))); l = rfbi_read_reg(RFBI_SYSCONFIG); /* Enable autoidle and smart-idle */ l |= (1 << 0) | (2 << 3); rfbi_write_reg(RFBI_SYSCONFIG, l); /* 16-bit interface, ITE trigger mode, 16-bit data */ l = (0x03 << 0) | (0x00 << 2) | (0x01 << 5) | (0x02 << 7); l |= (0 << 9) | (1 << 20) | (1 << 21); rfbi_write_reg(RFBI_CONFIG0, l); rfbi_write_reg(RFBI_DATA_CYCLE1_0, 0x00000010); l = rfbi_read_reg(RFBI_CONTROL); /* Select CS0, clear bypass mode */ l = (0x01 << 2); rfbi_write_reg(RFBI_CONTROL, l); if ((r = omap_dispc_request_irq(rfbi_dma_callback, NULL)) < 0) { dev_err(fbdev->dev, "can't get DISPC irq\n"); rfbi_enable_clocks(0); return r; } l = rfbi_read_reg(RFBI_REVISION); pr_info("omapfb: RFBI version %d.%d initialized\n", (l >> 4) & 0x0f, l & 0x0f); rfbi_enable_clocks(0); return 0; } static void rfbi_cleanup(void) { omap_dispc_free_irq(); rfbi_put_clocks(); } const struct lcd_ctrl_extif omap2_ext_if = { .init = rfbi_init, .cleanup = rfbi_cleanup, .get_clk_info = rfbi_get_clk_info, .get_max_tx_rate = rfbi_get_max_tx_rate, .set_bits_per_cycle = rfbi_set_bits_per_cycle, .convert_timings = rfbi_convert_timings, .set_timings = rfbi_set_timings, .write_command = rfbi_write_command, .read_data = rfbi_read_data, .write_data = rfbi_write_data, .transfer_area = rfbi_transfer_area, .setup_tearsync = rfbi_setup_tearsync, .enable_tearsync = rfbi_enable_tearsync, .max_transmit_size = (u32) ~0, };