/* * Copyright Ondrej Jirman */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // {{{ Registry defines #define SUN4I_TCON_GCTL_REG 0x0 #define SUN4I_TCON_GCTL_TCON_ENABLE BIT(31) #define SUN4I_TCON_GCTL_IOMAP_MASK BIT(0) #define SUN4I_TCON_GCTL_IOMAP_TCON1 (1 << 0) #define SUN4I_TCON_GCTL_IOMAP_TCON0 (0 << 0) #define SUN4I_TCON_GINT0_REG 0x4 #define SUN4I_TCON_GINT0_VBLANK_ENABLE(pipe) BIT(31 - (pipe)) #define SUN4I_TCON_GINT0_VBLANK_INT(pipe) BIT(15 - (pipe)) #define SUN4I_TCON_GINT0_LINE_ENABLE(pipe) BIT(29 - (pipe)) #define SUN4I_TCON_GINT0_LINE_INT(pipe) BIT(13 - (pipe)) #define SUN4I_TCON_GINT1_REG 0x8 #define SUN4I_TCON0_CTL_REG 0x40 #define SUN4I_TCON0_CTL_TCON_ENABLE BIT(31) #define SUN4I_TCON0_CTL_IF_MASK GENMASK(25, 24) #define SUN4I_TCON0_CTL_IF_8080 (1 << 24) #define SUN4I_TCON0_CTL_CLK_DELAY_MASK GENMASK(8, 4) #define SUN4I_TCON0_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON0_CTL_CLK_DELAY_MASK) #define SUN4I_TCON0_CTL_SRC_SEL_MASK GENMASK(2, 0) #define SUN4I_TCON0_DCLK_REG 0x44 #define SUN4I_TCON0_DCLK_GATE_BIT (31) #define SUN4I_TCON0_DCLK_DIV_SHIFT (0) #define SUN4I_TCON0_DCLK_DIV_WIDTH (7) #define SUN4I_TCON0_BASIC0_REG 0x48 #define SUN4I_TCON0_BASIC0_X(width) ((((width) - 1) & 0xfff) << 16) #define SUN4I_TCON0_BASIC0_Y(height) (((height) - 1) & 0xfff) #define SUN4I_TCON0_BASIC1_REG 0x4c #define SUN4I_TCON0_BASIC1_H_TOTAL(total) ((((total) - 1) & 0x1fff) << 16) #define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp) (((bp) - 1) & 0xfff) #define SUN4I_TCON0_BASIC2_REG 0x50 #define SUN4I_TCON0_BASIC2_V_TOTAL(total) (((total) & 0x1fff) << 16) #define SUN4I_TCON0_BASIC2_V_BACKPORCH(bp) (((bp) - 1) & 0xfff) #define SUN4I_TCON0_BASIC3_REG 0x54 #define SUN4I_TCON0_BASIC3_H_SYNC(width) ((((width) - 1) & 0x7ff) << 16) #define SUN4I_TCON0_BASIC3_V_SYNC(height) (((height) - 1) & 0x7ff) #define SUN4I_TCON0_HV_IF_REG 0x58 #define SUN4I_TCON0_IO_POL_REG 0x88 #define SUN4I_TCON0_IO_POL_DCLK_PHASE(phase) ((phase & 3) << 28) #define SUN4I_TCON0_IO_POL_DE_NEGATIVE BIT(27) #define SUN4I_TCON0_IO_POL_DCLK_NEGATIVE BIT(26) #define SUN4I_TCON0_IO_POL_HSYNC_POSITIVE BIT(25) #define SUN4I_TCON0_IO_POL_VSYNC_POSITIVE BIT(24) #define SUN4I_TCON0_IO_TRI_REG 0x8c #define SUN4I_TCON0_IO_TRI_HSYNC_DISABLE BIT(25) #define SUN4I_TCON0_IO_TRI_VSYNC_DISABLE BIT(24) #define SUN4I_TCON0_IO_TRI_DATA_PINS_DISABLE(pins) GENMASK(pins, 0) #define SUN4I_BACKEND_MODCTL_REG 0x800 #define SUN4I_BACKEND_MODCTL_LINE_SEL BIT(29) #define SUN4I_BACKEND_MODCTL_ITLMOD_EN BIT(28) #define SUN4I_BACKEND_MODCTL_OUT_SEL GENMASK(22, 20) #define SUN4I_BACKEND_MODCTL_OUT_LCD0 (0 << 20) #define SUN4I_BACKEND_MODCTL_OUT_LCD1 (1 << 20) #define SUN4I_BACKEND_MODCTL_OUT_FE0 (6 << 20) #define SUN4I_BACKEND_MODCTL_OUT_FE1 (7 << 20) #define SUN4I_BACKEND_MODCTL_HWC_EN BIT(16) #define SUN4I_BACKEND_MODCTL_LAY_EN(l) BIT(8 + l) #define SUN4I_BACKEND_MODCTL_OCSC_EN BIT(5) #define SUN4I_BACKEND_MODCTL_DFLK_EN BIT(4) #define SUN4I_BACKEND_MODCTL_DLP_START_CTL BIT(2) #define SUN4I_BACKEND_MODCTL_START_CTL BIT(1) #define SUN4I_BACKEND_MODCTL_DEBE_EN BIT(0) #define SUN4I_BACKEND_BACKCOLOR_REG 0x804 #define SUN4I_BACKEND_BACKCOLOR(r, g, b) (((r) << 16) | ((g) << 8) | (b)) #define SUN4I_BACKEND_DISSIZE_REG 0x808 #define SUN4I_BACKEND_DISSIZE(w, h) (((((h) - 1) & 0xffff) << 16) | \ (((w) - 1) & 0xffff)) #define SUN4I_BACKEND_LAYSIZE_REG(l) (0x810 + (0x4 * (l))) #define SUN4I_BACKEND_LAYSIZE(w, h) (((((h) - 1) & 0x1fff) << 16) | \ (((w) - 1) & 0x1fff)) #define SUN4I_BACKEND_LAYCOOR_REG(l) (0x820 + (0x4 * (l))) #define SUN4I_BACKEND_LAYCOOR(x, y) ((((u32)(y) & 0xffff) << 16) | \ ((u32)(x) & 0xffff)) #define SUN4I_BACKEND_LAYLINEWIDTH_REG(l) (0x840 + (0x4 * (l))) #define SUN4I_BACKEND_LAYFB_L32ADD_REG(l) (0x850 + (0x4 * (l))) #define SUN4I_BACKEND_LAYFB_H4ADD_REG 0x860 #define SUN4I_BACKEND_LAYFB_H4ADD_MSK(l) GENMASK(3 + ((l) * 8), (l) * 8) #define SUN4I_BACKEND_LAYFB_H4ADD(l, val) ((val) << ((l) * 8)) #define SUN4I_BACKEND_REGBUFFCTL_REG 0x870 #define SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS BIT(1) #define SUN4I_BACKEND_REGBUFFCTL_LOADCTL BIT(0) #define SUN4I_BACKEND_CKMAX_REG 0x880 #define SUN4I_BACKEND_CKMIN_REG 0x884 #define SUN4I_BACKEND_CKCFG_REG 0x888 #define SUN4I_BACKEND_ATTCTL_REG0(l) (0x890 + (0x4 * (l))) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_GLBALPHA_MASK GENMASK(31, 24) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_GLBALPHA(x) ((x) << 24) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK BIT(15) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(x) ((x) << 15) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL_MASK GENMASK(11, 10) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL(x) ((x) << 10) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_WORKMOD_MASK GENMASK(23, 22) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_WORKMOD(x) ((x) << 22) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_YUVEN BIT(2) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_VDOEN BIT(1) #define SUN4I_BACKEND_ATTCTL_REG0_LAY_GLBALPHA_EN BIT(0) #define SUN4I_BACKEND_ATTCTL_REG1(l) (0x8a0 + (0x4 * (l))) #define SUN4I_BACKEND_ATTCTL_REG1_LAY_HSCAFCT GENMASK(15, 14) #define SUN4I_BACKEND_ATTCTL_REG1_LAY_WSCAFCT GENMASK(13, 12) #define SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT GENMASK(11, 8) #define SUN4I_BACKEND_LAY_FBFMT_1BPP (0 << 8) #define SUN4I_BACKEND_LAY_FBFMT_2BPP (1 << 8) #define SUN4I_BACKEND_LAY_FBFMT_4BPP (2 << 8) #define SUN4I_BACKEND_LAY_FBFMT_8BPP (3 << 8) #define SUN4I_BACKEND_LAY_FBFMT_RGB655 (4 << 8) #define SUN4I_BACKEND_LAY_FBFMT_RGB565 (5 << 8) #define SUN4I_BACKEND_LAY_FBFMT_RGB556 (6 << 8) #define SUN4I_BACKEND_LAY_FBFMT_ARGB1555 (7 << 8) #define SUN4I_BACKEND_LAY_FBFMT_RGBA5551 (8 << 8) #define SUN4I_BACKEND_LAY_FBFMT_XRGB8888 (9 << 8) #define SUN4I_BACKEND_LAY_FBFMT_ARGB8888 (10 << 8) #define SUN4I_BACKEND_LAY_FBFMT_RGB888 (11 << 8) #define SUN4I_BACKEND_LAY_FBFMT_ARGB4444 (12 << 8) #define SUN4I_BACKEND_LAY_FBFMT_RGBA4444 (13 << 8) // }}} #define EINK_IOCTL_UPDATE _IO('A', 0) //#define EINK_IOCTL_STATUS _IOR('A', 2, int) enum { EINK_REQ_NONE = 0, EINK_REQ_UPDATE, EINK_REQ_POWERDOWN, }; enum { EINK_WORKING, EINK_OPEN, EINK_WAITING_INT, EINK_BE_SETUP, }; struct eink_dev { struct device *dev; // io struct pinctrl* pinctrl; struct pinctrl_state *pinctrl_active; struct pinctrl_state *pinctrl_idle; struct gpio_descs* gpios; // power struct regulator *panel_supply; struct timer_list powerdown_timer; bool powered; // tcon0 struct clk_bulk_data *clks; int num_clks; struct reset_control *rstc; struct regmap *tcon_regs; struct regmap *be_regs; int irq; // framebuffers u8* framebuffers[2]; u32* ctlstream; dma_addr_t ctlstream_paddr; size_t ctlstream_size; // byte size // control device struct cdev cdev; dev_t major; bool is_open; // device can be used by only one user // rendering work spinlock_t lock; wait_queue_head_t waitqueue; struct workqueue_struct *wq; struct work_struct work; unsigned long flags[1]; int last_request; const struct eink_panel_config* panel; }; struct eink_panel_config { int sources; int gates; int h_lead; int h_trail; int v_extra; }; static const struct eink_panel_config eink_ed060xd4_panel = { .sources = 1024, .gates = 758, .h_lead = 40, // 1000ns (should be multiple of 8 for 256-bit alignment) .h_trail = 8, // should be multiple of 8 for 256-bit alignment .v_extra = 9, }; // buf format is 1 byte per pixel, len needs to be multiple of 4 bytes /* PocketBook 626(2) (Touch Lux 3) port D <-> Panel pin map */ #define D0 BIT(3) #define D1 BIT(4) #define D2 BIT(5) #define D3 BIT(6) #define D4 BIT(7) #define D5 BIT(10) #define D6 BIT(11) #define D7 BIT(12) #define S_LE BIT(13) #define G_MODE1 BIT(15) #define S_OE BIT(20) #define S_SP BIT(21) #define G_CLK BIT(22) #define G_SP BIT(23) /* * we need to encode stream of control signals for various operations * into separate rows: * * - sequence for starting a frame * - three leading G_CLK pulses * - sequence for all data rows * - wait time with OE for previous row * - wait time for the last row without shifting in more data * - trailing row with a sequence for ending a frame * * So we have to have: * - 6 special rows with no data + all the data rows * - all of these have to have the same width * - we want to use TCON to manage timing of the OE via HT/HBP settings * * Timings: * - we use 20MHz clock (50ns period) * - we have to spend 256 clocks per row, pushing data * - we want to have 85Hz frame refresh rate * - 11765us budget per frame * - minimum G_CLK low time is 500ns, minimum G_CLK cycle time is 5us * - G_SP 100ns setup (to G_CLK H->L)/hold time(after G_CLK H->L) * - LE pulse width >150ns * - LE delay before more data 200ns * - 12us time the output reacts to LE (this should be hsync time/hbp) * - at 20MHz, time to load data is 13us (about 100 FPS) */ static void eink_ctlstream_prepare(struct eink_dev* eink) { const struct eink_panel_config* pc = eink->panel; u32* p = eink->ctlstream; int row_width = pc->sources / 4 + pc->h_lead + pc->h_trail; // ~262 int i, j, t, r; u32 base; #define SET_PINS(t, v) { \ for (i = 0; i < t; i++) \ *p++ = v; \ r -= t; \ } // prepare a startup row t = row_width / 3; r = row_width; SET_PINS(20, G_MODE1 | G_SP | S_SP ); SET_PINS(2*t,G_MODE1 | G_SP | S_SP | G_CLK ); SET_PINS(r, G_MODE1 | G_SP | S_SP ); r = row_width; SET_PINS(t, G_MODE1 | G_SP | S_SP | G_CLK ); SET_PINS(t, G_MODE1 | S_SP | G_CLK ); SET_PINS(r, G_MODE1 | S_SP ); r = row_width; SET_PINS(t, G_MODE1 | S_SP | G_CLK ); SET_PINS(t, G_MODE1 | G_SP | S_SP | G_CLK ); SET_PINS(r, G_MODE1 | G_SP | S_SP ); // three empty rows for (j = 0; j < 3; j++) { r = row_width; SET_PINS(2*t, G_MODE1 | G_SP | S_SP | G_CLK ); SET_PINS(r, G_MODE1 | G_SP | S_SP ); } // extra row is there to keep S_OE high for the previous last data // row that was shifted in for (j = 0; j < pc->gates + 1; j++) { base = G_MODE1 | G_SP; // We have 40 periods here to work with here (h_lead). r = pc->h_lead; // S_SP goes high after the last data and there's 2us // delay (it may even go high during the last data period?) SET_PINS(20, base | S_SP); // 1000ns for tLEdly base &= ~S_OE; SET_PINS(2, base | S_SP); SET_PINS(6, base | S_SP | (j > 0 ? S_LE : 0)); // 300ns for tLEw if (j > 0) base |= S_OE; SET_PINS(2, base | S_SP | (j > 0 ? S_LE : 0)); // S_OE goes high SET_PINS(r-2,base | S_SP); // 400ns for tLEoff SET_PINS(r, base | S_SP | G_CLK); // 400ns for tLEoff if (j >= pc->gates) base |= S_SP; // start loading data immediately at S_SP going low r = pc->sources / 4; t = r / 3; SET_PINS(t, base | G_CLK); SET_PINS(r, base); r = pc->h_trail; SET_PINS(r, base | S_SP); } r = row_width; SET_PINS(r, G_SP | S_SP ); // this last row will be stopped by an interrupt before finishing // completely, so it's a dummy one r = row_width; SET_PINS(r, G_SP | S_SP ); } void eink_ctlstream_fill_data_neon(u32* cmd, u8* fb, int stride, int sources, int gates, u32 masks[4]); static void eink_ctlstream_fill_data(struct eink_dev* eink, u8* fb) { const struct eink_panel_config* pc = eink->panel; int row_width = pc->sources / 4 + pc->h_lead + pc->h_trail; u32 masks[4] = { G_MODE1 | G_SP | G_CLK, G_MODE1 | G_SP, G_MODE1 | G_SP | S_OE | G_CLK, G_MODE1 | G_SP | S_OE, }; kernel_neon_begin(); eink_ctlstream_fill_data_neon(eink->ctlstream + 6 * row_width + pc->h_lead, fb, row_width, pc->sources, pc->gates, masks); kernel_neon_end(); } static int eink_set_power(struct eink_dev* eink, bool en) { int ret; if (!en != !eink->powered) { dev_warn(eink->dev, "%sable power supply", en ? "en" : "dis"); if (en) ret = regulator_enable(eink->panel_supply); else ret = regulator_disable(eink->panel_supply); if (ret) { dev_err(eink->dev, "can't %sable power supply (%d)", en ? "en" : "dis", ret); return ret; } } if (en) mod_timer(&eink->powerdown_timer, jiffies + msecs_to_jiffies(5000)); else del_timer_sync(&eink->powerdown_timer); eink->powered = en; return 0; } __maybe_unused static void print_time(struct device* dev, u64 ts, char *buf) { do_div(ts, 1000); dev_info(dev, "[%s] %6lu us", buf, (unsigned long)ts); } // DCLK = 20MHz static int eink_update(struct eink_dev* eink) { struct regmap *tcon = eink->tcon_regs; struct regmap *be = eink->be_regs; unsigned long sclk_rate; u32 ddiv = 6, w, h; u32 lo_paddr, hi_paddr; u32 vbp = 1, hbp = 40; int ret; ret = eink_set_power(eink, 1); if (ret) return ret; sclk_rate = clk_get_rate(eink->clks[1].clk); if (sclk_rate == 0) return -EINVAL; spin_lock(&eink->lock); eink_ctlstream_fill_data(eink, eink->framebuffers[0]); spin_unlock(&eink->lock); w = eink->panel->sources / 4 + eink->panel->h_lead + eink->panel->h_trail; h = eink->panel->gates + eink->panel->v_extra; // Setup DEBE if (!test_bit(EINK_BE_SETUP, eink->flags)) { regmap_write(be, SUN4I_BACKEND_DISSIZE_REG, SUN4I_BACKEND_DISSIZE(w, h)); regmap_write(be, SUN4I_BACKEND_LAYSIZE_REG(0), SUN4I_BACKEND_LAYSIZE(w, h)); regmap_write(be, SUN4I_BACKEND_LAYCOOR_REG(0), SUN4I_BACKEND_LAYCOOR(0, 0)); regmap_write(be, SUN4I_BACKEND_LAYLINEWIDTH_REG(0), w * 4 * 8); /* input format has 4 bytes per pixel */ lo_paddr = eink->ctlstream_paddr << 3; hi_paddr = eink->ctlstream_paddr >> 29; regmap_write(be, SUN4I_BACKEND_LAYFB_L32ADD_REG(0), lo_paddr); regmap_update_bits(be, SUN4I_BACKEND_LAYFB_H4ADD_REG, SUN4I_BACKEND_LAYFB_H4ADD_MSK(0), SUN4I_BACKEND_LAYFB_H4ADD(0, hi_paddr)); regmap_write(be, SUN4I_BACKEND_ATTCTL_REG0(0), SUN4I_BACKEND_ATTCTL_REG0_LAY_WORKMOD(0) | SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(0) | SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL(3)); regmap_write(be, SUN4I_BACKEND_ATTCTL_REG1(0), SUN4I_BACKEND_LAY_FBFMT_XRGB8888); /* load BE buffers */ regmap_write(be, SUN4I_BACKEND_REGBUFFCTL_REG, SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS | SUN4I_BACKEND_REGBUFFCTL_LOADCTL); set_bit(EINK_BE_SETUP, eink->flags); } // Setup TCON/TCON0 // // TCON0 timings, allwinner style: // // ht - horizontal total time // - total number of clock cycles in a row // hbp - horizontal back porch // - number of dclk cycles between start of hsync and data // vt - vertical total time // - number of rows in two frames // vbp - vertical back porch // - number of rows between vsync start and a valid data line // hspw // - width of hsync signal (in # of dclk cycles) // vspw // - width of vsync signal (in # of rows) // // Notes: // hbp(aw)=hbp(panel)+hspw(panel) (same for vbp) /* Enable tcon module */ regmap_write(tcon, SUN4I_TCON_GCTL_REG, SUN4I_TCON_GCTL_TCON_ENABLE | SUN4I_TCON_GCTL_IOMAP_TCON0); /* Set the resolution */ regmap_write(tcon, SUN4I_TCON0_BASIC0_REG, SUN4I_TCON0_BASIC0_X(w) | SUN4I_TCON0_BASIC0_Y(h)); /* Set horizontal display timings */ regmap_write(tcon, SUN4I_TCON0_BASIC1_REG, SUN4I_TCON0_BASIC1_H_TOTAL(w + hbp + 2) | SUN4I_TCON0_BASIC1_H_BACKPORCH(hbp)); /* Set vertical display timings */ regmap_write(tcon, SUN4I_TCON0_BASIC2_REG, SUN4I_TCON0_BASIC2_V_TOTAL((h + vbp + 2) * 2) | SUN4I_TCON0_BASIC2_V_BACKPORCH(vbp)); /* Set Hsync and Vsync length */ regmap_write(tcon, SUN4I_TCON0_BASIC3_REG, SUN4I_TCON0_BASIC3_V_SYNC(1) | SUN4I_TCON0_BASIC3_H_SYNC(1)); /* Setup output mode/pins */ regmap_write(tcon, SUN4I_TCON0_HV_IF_REG, 0); // 24bit parallel mode regmap_write(tcon, SUN4I_TCON0_IO_POL_REG, SUN4I_TCON0_IO_POL_DCLK_NEGATIVE); regmap_write(tcon, SUN4I_TCON0_IO_TRI_REG, 0); ret = pinctrl_select_state(eink->pinctrl, eink->pinctrl_active); if (ret) { dev_err(eink->dev, "can't switch to active pinctrl state (%d)", ret); } set_bit(EINK_WAITING_INT, eink->flags); /* Interrupt after h lines */ regmap_write(tcon, SUN4I_TCON_GINT0_REG, 0); regmap_write(tcon, SUN4I_TCON_GINT1_REG, (h + vbp + 2) << 16); //regmap_write(tcon, SUN4I_TCON_GINT0_REG, //SUN4I_TCON_GINT0_VBLANK_ENABLE(0)); regmap_write(tcon, SUN4I_TCON_GINT0_REG, SUN4I_TCON_GINT0_LINE_ENABLE(0)); /* Configure and enable the dot clock */ regmap_write(tcon, SUN4I_TCON0_DCLK_REG, BIT(SUN4I_TCON0_DCLK_GATE_BIT) | (ddiv << SUN4I_TCON0_DCLK_DIV_SHIFT)); /* Enable tcon 0, set clk delay */ regmap_write(tcon, SUN4I_TCON0_CTL_REG, SUN4I_TCON0_CTL_CLK_DELAY(3)); // up to 30 regmap_update_bits(tcon, SUN4I_TCON0_CTL_REG, SUN4I_TCON0_CTL_TCON_ENABLE, SUN4I_TCON0_CTL_TCON_ENABLE); ret = wait_event_timeout(eink->waitqueue, !test_bit(EINK_WAITING_INT, eink->flags), msecs_to_jiffies(100)); if (ret == 0) dev_warn(eink->dev, "Timeout waiting for TCON transfer\n"); // stop everything, cleanup //regmap_write(tcon, SUN4I_TCON0_DCLK_REG, 0); regmap_update_bits(tcon, SUN4I_TCON0_CTL_REG, SUN4I_TCON0_CTL_TCON_ENABLE, 0); //regmap_write(tcon, SUN4I_TCON_GCTL_REG, 0); regmap_write(tcon, SUN4I_TCON_GINT0_REG, 0); regmap_write(tcon, SUN4I_TCON_GINT1_REG, 0); ret = pinctrl_select_state(eink->pinctrl, eink->pinctrl_idle); if (ret) { dev_err(eink->dev, "can't switch to idle pinctrl state (%d)", ret); } regmap_write(tcon, SUN4I_TCON0_IO_TRI_REG, ~0); return ret; } static void eink_supply_powerdown_timer(struct timer_list *t) { struct eink_dev *eink = from_timer(eink, t, powerdown_timer); //dev_info(eink->dev, "powering down"); spin_lock(&eink->lock); eink->last_request = EINK_REQ_POWERDOWN; spin_unlock(&eink->lock); queue_work(eink->wq, &eink->work); } static void eink_work_handler(struct work_struct *work) { struct eink_dev *eink = container_of(work, struct eink_dev, work); int last_request; spin_lock(&eink->lock); last_request = eink->last_request; eink->last_request = 0; spin_unlock(&eink->lock); if (last_request == EINK_REQ_UPDATE) { eink_update(eink); } else if (last_request == EINK_REQ_POWERDOWN) { eink_set_power(eink, 0); } clear_bit(EINK_WORKING, eink->flags); wake_up(&eink->waitqueue); } static ssize_t eink_read(struct file *fp, char __user *buf, size_t len, loff_t *off) { struct eink_dev* eink = fp->private_data; int ret, fb_size; fb_size = eink->panel->sources / 4 * eink->panel->gates; if (len != fb_size) return -EINVAL; spin_lock(&eink->lock); if (copy_to_user(buf, eink->framebuffers[0], fb_size)) ret = -EFAULT; else ret = fb_size; spin_unlock(&eink->lock); return ret; } static ssize_t eink_write(struct file *fp, const char __user *buf, size_t len, loff_t *off) { struct eink_dev* eink = fp->private_data; int non_blocking = fp->f_flags & O_NONBLOCK; int ret, fb_size; fb_size = eink->panel->sources / 4 * eink->panel->gates; if (len != fb_size) return -EINVAL; if (test_and_set_bit(EINK_WORKING, eink->flags)) return -EBUSY; spin_lock(&eink->lock); ret = copy_from_user(eink->framebuffers[0], buf, len); if (ret) { clear_bit(EINK_WORKING, eink->flags); spin_unlock(&eink->lock); return -EFAULT; } eink->last_request = EINK_REQ_UPDATE; spin_unlock(&eink->lock); queue_work(eink->wq, &eink->work); // first handle non-blocking path if (non_blocking && test_bit(EINK_WORKING, eink->flags)) return -EWOULDBLOCK; // wait for availability of wakeup wait_event(eink->waitqueue, !test_bit(EINK_WORKING, eink->flags)); //u64 ts = ktime_get_boottime_ns(); //print_time(eink->dev, ktime_get_boottime() - ts, "one frame"); return len; } static unsigned int eink_poll(struct file *fp, poll_table *wait) { struct eink_dev* eink = fp->private_data; int ret = 0; poll_wait(fp, &eink->waitqueue, wait); if (!test_bit(EINK_WORKING, eink->flags)) ret |= POLLIN | POLLRDNORM; return ret; } static long eink_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { struct eink_dev* eink = fp->private_data; unsigned long flags; int ret = -ENOSYS; //void __user *parg = (void __user *)arg; if (!capable(CAP_SYS_ADMIN)) return -EACCES; if (test_and_set_bit(EINK_WORKING, eink->flags)) return -EBUSY; spin_lock_irqsave(&eink->lock, flags); switch (cmd) { case EINK_IOCTL_UPDATE: eink->last_request = EINK_REQ_UPDATE; ret = 0; break; /* case EINK_IOCTL_BLANK: eink->last_request = EINK_REQ_BLANK; ret = 0; break; case EINK_IOCTL_STATUS: if (copy_to_user(parg, &powered, sizeof powered)) return -EFAULT; return 0; */ } spin_unlock_irqrestore(&eink->lock, flags); if (ret == 0) queue_work(eink->wq, &eink->work); else clear_bit(EINK_WORKING, eink->flags); return ret; } static int eink_release(struct inode *ip, struct file *fp) { struct eink_dev* eink = fp->private_data; clear_bit(EINK_OPEN, eink->flags); return 0; } static int eink_open(struct inode *ip, struct file *fp) { struct eink_dev* eink = container_of(ip->i_cdev, struct eink_dev, cdev); if (test_and_set_bit(EINK_OPEN, eink->flags)) return -EBUSY; fp->private_data = eink; nonseekable_open(ip, fp); return 0; } static const struct file_operations eink_fops = { .owner = THIS_MODULE, .read = eink_read, .write = eink_write, .poll = eink_poll, .unlocked_ioctl = eink_ioctl, .open = eink_open, .release = eink_release, .llseek = noop_llseek, }; static struct regmap_config eink_tcon_regmap_config = { .name = "tcon", .reg_bits = 32, .val_bits = 32, .reg_stride = 4, .max_register = 0x7ff, .cache_type = REGCACHE_NONE, }; static struct regmap_config eink_be_regmap_config = { .name = "be", .reg_bits = 32, .val_bits = 32, .reg_stride = 4, .max_register = 0x57ff, .cache_type = REGCACHE_NONE, }; static struct class* eink_class; static irqreturn_t eink_tcon0_irq_handler(int irq, void *private) { struct eink_dev *eink = private; struct regmap *tcon = eink->tcon_regs; unsigned int status; regmap_read(eink->tcon_regs, SUN4I_TCON_GINT0_REG, &status); if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) | SUN4I_TCON_GINT0_LINE_INT(0)))) return IRQ_NONE; // stop everything //regmap_write(tcon, SUN4I_TCON_GCTL_REG, 0); regmap_update_bits(tcon, SUN4I_TCON0_CTL_REG, SUN4I_TCON0_CTL_TCON_ENABLE, 0); regmap_write(tcon, SUN4I_TCON_GINT0_REG, 0); regmap_write(tcon, SUN4I_TCON_GINT1_REG, 0); //regmap_write(tcon, SUN4I_TCON0_DCLK_REG, 0); //regmap_write(tcon, SUN4I_TCON0_IO_TRI_REG, ~0); clear_bit(EINK_WAITING_INT, eink->flags); wake_up(&eink->waitqueue); /* regmap_update_bits(eink->tcon_regs, SUN4I_TCON_GINT0_REG, SUN4I_TCON_GINT0_VBLANK_INT(0) | SUN4I_TCON_GINT0_LINE_INT(0), 0); */ return IRQ_HANDLED; } static const struct clk_bulk_data eink_clocks[] = { { .id = "tcon_bus" }, { .id = "tcon_mod" }, { .id = "be_bus" }, { .id = "be_mod" }, { .id = "be_ram" }, }; static int eink_probe(struct platform_device *pdev) { struct eink_dev *eink; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct device *sdev; const char* cdev_name = NULL; void __iomem *tcon_regs, *be_regs; int ret, i, fb_size, cs_size; eink = devm_kzalloc(dev, sizeof(*eink), GFP_KERNEL); if (!eink) return -ENOMEM; eink->panel = &eink_ed060xd4_panel; fb_size = eink->panel->sources / 4 * eink->panel->gates; // we shift four 2-bit source values per clock cycle cs_size = ((eink->panel->sources / 4 + eink->panel->h_lead + eink->panel->h_trail) * (eink->panel->gates + eink->panel->v_extra)) * 4; eink->framebuffers[0] = devm_kzalloc(dev, fb_size, GFP_KERNEL); if (!eink->framebuffers[0]) return -ENOMEM; eink->framebuffers[1] = devm_kzalloc(dev, fb_size, GFP_KERNEL); if (!eink->framebuffers[1]) return -ENOMEM; eink->dev = dev; platform_set_drvdata(pdev, eink); init_waitqueue_head(&eink->waitqueue); spin_lock_init(&eink->lock); INIT_WORK(&eink->work, &eink_work_handler); timer_setup(&eink->powerdown_timer, eink_supply_powerdown_timer, 0); ret = of_dma_configure(dev, dev->of_node, true); if (ret) { dev_err(dev, "failed to configure dma (%d)\n", ret); return ret; } tcon_regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(tcon_regs)) return PTR_ERR(tcon_regs); be_regs = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(be_regs)) return PTR_ERR(be_regs); eink->pinctrl = devm_pinctrl_get(dev); if (IS_ERR(eink->pinctrl)) { ret = PTR_ERR(eink->pinctrl); dev_err(dev, "can't get pinctrl (%d)\n", ret); return ret; } eink->pinctrl_active = pinctrl_lookup_state(eink->pinctrl, "active"); if (!eink->pinctrl_active) { dev_err(dev, "missing active pinctrl state"); return -EINVAL; } eink->pinctrl_idle = pinctrl_lookup_state(eink->pinctrl, "idle"); if (!eink->pinctrl_idle) { dev_err(dev, "missing idle pinctrl state"); return -EINVAL; } ret = pinctrl_select_state(eink->pinctrl, eink->pinctrl_idle); if (ret) { dev_err(eink->dev, "can't switch to idle pinctrl state (%d)", ret); return ret; } eink->gpios = devm_gpiod_get_array(dev, "all", GPIOD_OUT_LOW); if (IS_ERR(eink->gpios)) { ret = PTR_ERR(eink->gpios); dev_err(dev, "can't get all gpios (%d)\n", ret); return ret; } eink->irq = platform_get_irq(pdev, 0); if (eink->irq < 0) { dev_err(dev, "Couldn't retrieve the TCON interrupt\n"); return eink->irq; } ret = of_property_read_string(np, "control-device-name", &cdev_name); if (ret) { dev_err(dev, "char-device-name is not configured (%d)\n", ret); return ret; } eink->panel_supply = devm_regulator_get(dev, "panel"); if (IS_ERR(eink->panel_supply)) { ret = PTR_ERR(eink->panel_supply); dev_err(dev, "can't get panel supply (%d)\n", ret); return ret; } eink->clks = devm_kmemdup(dev, eink_clocks, sizeof(eink_clocks), GFP_KERNEL); if (!eink->clks) return -ENOMEM; eink->num_clks = ARRAY_SIZE(eink_clocks); ret = devm_clk_bulk_get(dev, eink->num_clks, eink->clks); if (ret) return ret; eink->rstc = devm_reset_control_array_get(dev, false, false); if (IS_ERR(eink->rstc)) { ret = PTR_ERR(eink->rstc); dev_err(dev, "Couldn't get our reset line (%d)\n", ret); return ret; } eink->tcon_regs = devm_regmap_init_mmio(dev, tcon_regs, &eink_tcon_regmap_config); if (IS_ERR(eink->tcon_regs)) { ret = PTR_ERR(eink->tcon_regs); dev_err(dev, "Couldn't create the TCON regmap (%d)\n", ret); return ret; } eink->be_regs = devm_regmap_init_mmio(dev, be_regs, &eink_be_regmap_config); if (IS_ERR(eink->be_regs)) { ret = PTR_ERR(eink->be_regs); dev_err(dev, "Couldn't create the BE regmap (%d)\n", ret); return ret; } // init the actual hardware ret = reset_control_deassert(eink->rstc); if (ret) { dev_err(dev, "Couldn't deassert our reset line (%d)\n", ret); return ret; } // possible to set 27-381MHz in 3MHz steps ret = clk_set_rate(eink->clks[1].clk, 120000000); if (ret) { dev_err(dev, "Couldn't set tcon0 rate to (%d)\n", ret); goto err_reset; } ret = clk_bulk_prepare_enable(eink->num_clks, eink->clks); if (ret) { dev_err(dev, "Couldn't enable clocks (%d)\n", ret); goto err_reset; } /* Clear DEBE registers */ for (i = 0x800; i < 0x1000; i += 4) regmap_write(eink->be_regs, i, 0); /* Disable registers autoloading */ regmap_write(eink->be_regs, SUN4I_BACKEND_REGBUFFCTL_REG, SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS); regmap_write(eink->be_regs, SUN4I_BACKEND_MODCTL_REG, SUN4I_BACKEND_MODCTL_START_CTL); /* Enable the backend */ regmap_write(eink->be_regs, SUN4I_BACKEND_MODCTL_REG, SUN4I_BACKEND_MODCTL_DEBE_EN | SUN4I_BACKEND_MODCTL_START_CTL | SUN4I_BACKEND_MODCTL_OUT_LCD0 | SUN4I_BACKEND_MODCTL_LAY_EN(0)); /* Make sure the TCON is disabled and all IRQs are off */ regmap_write(eink->tcon_regs, SUN4I_TCON_GCTL_REG, 0); regmap_write(eink->tcon_regs, SUN4I_TCON_GINT0_REG, 0); regmap_write(eink->tcon_regs, SUN4I_TCON_GINT1_REG, 0); /* Disable IO lines and set them to tristate */ regmap_write(eink->tcon_regs, SUN4I_TCON0_IO_TRI_REG, ~0); ret = devm_request_irq(dev, eink->irq, eink_tcon0_irq_handler, 0, dev_name(dev), eink); if (ret) { dev_err(dev, "Couldn't request the IRQ\n"); goto err_disable_hw; } // create char device ret = alloc_chrdev_region(&eink->major, 0, 1, "eink"); if (ret) { dev_err(dev, "can't allocate chrdev region"); goto err_disable_hw; } cdev_init(&eink->cdev, &eink_fops); eink->cdev.owner = THIS_MODULE; ret = cdev_add(&eink->cdev, eink->major, 1); if (ret) { dev_err(dev, "can't add cdev"); goto err_unreg_chrev_region; } sdev = device_create(eink_class, dev, eink->major, eink, cdev_name); if (IS_ERR(sdev)) { ret = PTR_ERR(sdev); goto err_del_cdev; } eink->ctlstream_size = round_up(cs_size, PAGE_SIZE); eink->ctlstream = dma_alloc_wc(dev, eink->ctlstream_size, &eink->ctlstream_paddr, GFP_KERNEL | __GFP_NOWARN); if (!eink->ctlstream) { ret = -ENOMEM; dev_err(dev, "failed to allocate buffer with size %zu\n", eink->ctlstream_size); goto err_destroy_device; } eink_ctlstream_prepare(eink); eink->wq = alloc_workqueue("eink", WQ_SYSFS | WQ_HIGHPRI, 0); if (!eink->wq) { ret = -ENOMEM; dev_err(dev, "failed to allocate workqueue\n"); goto err_free_dma; } dev_info(dev, "eink-panel driver ready!\n"); return 0; err_free_dma: dma_free_wc(eink->dev, eink->ctlstream_size, eink->ctlstream, eink->ctlstream_paddr); err_destroy_device: device_destroy(eink_class, eink->major); err_del_cdev: cdev_del(&eink->cdev); err_unreg_chrev_region: unregister_chrdev(eink->major, "eink"); err_disable_hw: clk_bulk_disable_unprepare(eink->num_clks, eink->clks); err_reset: reset_control_assert(eink->rstc); cancel_work_sync(&eink->work); return ret; } static int eink_remove(struct platform_device *pdev) { struct eink_dev *eink = platform_get_drvdata(pdev); eink_set_power(eink, 0); del_timer_sync(&eink->powerdown_timer); cancel_work_sync(&eink->work); destroy_workqueue(eink->wq); dma_free_wc(eink->dev, eink->ctlstream_size, eink->ctlstream, eink->ctlstream_paddr); clk_bulk_disable_unprepare(eink->num_clks, eink->clks); reset_control_assert(eink->rstc); device_destroy(eink_class, eink->major); cdev_del(&eink->cdev); unregister_chrdev(eink->major, "eink"); return 0; } static const struct of_device_id eink_of_match[] = { { .compatible = "custom,pocketbook-touch-lux-3-tcon0-ed060xd4-display" }, {}, }; MODULE_DEVICE_TABLE(of, eink_of_match); static struct platform_driver eink_platform_driver = { .probe = eink_probe, .remove = eink_remove, .driver = { .name = "eink_tcon0", .of_match_table = eink_of_match, }, }; static int __init eink_driver_init(void) { int ret; eink_class = class_create("eink-panel"); ret = platform_driver_register(&eink_platform_driver); if (ret) class_destroy(eink_class); return ret; } static void __exit eink_driver_exit(void) { platform_driver_unregister(&eink_platform_driver); class_destroy(eink_class); } module_init(eink_driver_init); module_exit(eink_driver_exit); MODULE_VERSION("1.0.0"); MODULE_DESCRIPTION("eInk display Allwinner TCON0 based bit-banging driver"); MODULE_AUTHOR("Ondrej Jirman "); MODULE_LICENSE("GPL v2");