/* * DRM driver for Riverdi RVT3.5B320240xxxxxx * * Copyright 2017 Matt Gattis * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FTSETUP_SPI_SPEED 10000000 /* 10MHz */ #define FTMAX_SPI_SPEED 30000000 /* 30MHz */ #define CMD_DLSTART 0xFFFFFF00UL #define CMD_SWAP 0xFFFFFF01UL #define CMD_MEMZERO 0xFFFFFF1CUL #define FT_BORDER 0 #define FT_BILINEAR 1 #define FT_BITMAPS 1 #define FT_DLSWAP_FRAME 2 #define FT80X_CHIPID 0x7c #define FT_ACTIVE 0x00 #define FT_CORERST 0x68 #define FT_CLK48M 0x62 #define FT_CLKEXT 0x44 #define FT_RGB565 7 #define FT_RAM_G 0x000000UL #define FT_RAM_DL 0x100000UL #define REG_PCLK 0x10246CUL #define REG_DLSWAP 0x102450UL #define REG_GPIO 0x102490UL #define REG_GPIO_DIR 0x10248CUL #define REG_CSPREAD 0x102464UL #define REG_PCLK_POL 0x102468UL #define REG_HSIZE 0x102430UL #define REG_VSIZE 0x102444UL #define REG_VSYNC0 0x102448UL #define REG_VSYNC1 0x10244CUL #define REG_VOFFSET 0x102440UL #define REG_VCYCLE 0x10243CUL #define REG_HSYNC1 0x102438UL #define REG_HSYNC0 0x102434UL #define REG_HOFFSET 0x10242CUL #define REG_HCYCLE 0x102428UL #define REG_ID 0x102400UL #define REG_PWM_HZ 0x1024C0UL #define REG_PWM_DUTY 0x1024C4UL #define REG_CMD_WRITE 0x1024E8UL #define REG_CMD_READ 0x1024E4UL #define FT_RAM_CMD 0x108000UL #define REG_SWIZZLE 0x102460UL #define REG_DITHER 0x10245CUL #define REG_ROTATE 0x102454UL #define FT_INT_CONVCOMPLETE 0x80 #define REG_CTOUCH_MODE 0x1024F0UL #define REG_CTOUCH_EXTENDED 0x1024F4UL #define REG_CTOUCH_REG 0x1024F8UL #define REG_CTOUCH_RAW_XY 0x102508UL #define REG_CTOUCH_TOUCH1_XY 0x102508UL #define REG_CTOUCH_TOUCH4_Y 0x10250CUL #define REG_CTOUCH_TOUCH0_XY 0x102510UL #define REG_CTOUCH_TRANSFORM_A 0x10251CUL #define REG_CTOUCH_TRANSFORM_B 0x102520UL #define REG_CTOUCH_TRANSFORM_C 0x102524UL #define REG_CTOUCH_TRANSFORM_D 0x102528UL #define REG_CTOUCH_TRANSFORM_E 0x10252CUL #define REG_CTOUCH_TRANSFORM_F 0x102530UL #define REG_CTOUCH_TOUCH4_X 0x102538UL #define REG_CTOUCH_DIRECT_XY 0x102574UL #define REG_CTOUCH_TOUCH2_XY 0x102574UL #define REG_CTOUCH_DIRECT_Z1Z2 0x102578UL #define REG_CTOUCH_TOUCH3_XY 0x102578UL #define REG_INT_FLAGS 0x102498UL #define REG_INT_EN 0x10249CUL #define REG_INT_MASK 0x1024A0UL #define CLEAR_COLOR_A(alpha) ((15UL<<24)|(((alpha)&255UL)<<0)) #define BEGIN(prim) ((31UL<<24)|(((prim)&15UL)<<0)) #define CLEAR(c,s,t) ((38UL<<24)|(((c)&1UL)<<2)|(((s)&1UL)<<1)|(((t)&1UL)<<0)) #define CLEAR_COLOR_RGB(red,green,blue) ((2UL<<24)|(((red)&255UL)<<16)|(((green)&255UL)<<8)|(((blue)&255UL)<<0)) #define COLOR_A(alpha) ((16UL<<24)|(((alpha)&255UL)<<0)) #define COLOR_RGB(red,green,blue) ((4UL<<24)|(((red)&255UL)<<16)|(((green)&255UL)<<8)|(((blue)&255UL)<<0)) #define BITMAP_SIZE(filter,wrapx,wrapy,width,height) ((8UL<<24)|(((filter)&1UL)<<20)|(((wrapx)&1UL)<<19)|(((wrapy)&1UL)<<18)|(((width)&511UL)<<9)|(((height)&511UL)<<0)) #define BITMAP_LAYOUT(format,linestride,height) ((7UL<<24)|(((format)&31UL)<<19)|(((linestride)&1023UL)<<9)|(((height)&511UL)<<0)) #define BITMAP_HANDLE(handle) ((5UL<<24)|(((handle)&31UL)<<0)) #define BITMAP_SOURCE(addr) ((1UL<<24)|(((addr)&1048575UL)<<0)) #define VERTEX2II(x,y,handle,cell) ((2UL<<30)|(((x)&511UL)<<21)|(((y)&511UL)<<12)|(((handle)&31UL)<<7)|(((cell)&127UL)<<0)) #define DISPLAY() ((0UL<<24)) #define END() ((33UL<<24)) #define FTPIXBUF_SIZE 65536 struct ftdevice { struct tinydrm_device tinydrm; struct touchscreen_properties prop; struct spi_device *spi; struct input_dev *input; struct gpio_desc *interrupt_gpio; struct gpio_desc *pd; //struct delayed_work work; struct mutex cmdlock; u8 pix_buf[FTPIXBUF_SIZE]; bool enabled; unsigned int rotation; }; int ftmem_read(struct ftdevice *ft801, u8 *buf, size_t len) { int ret; struct spi_transfer xfer = { .tx_buf = buf, .rx_buf = buf, .len = len, .bits_per_word = 8, }; struct spi_message msg; spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); ret = spi_sync(ft801->spi, &msg); if (ret) dev_err(&ft801->spi->dev, "failed to read ft801 mem\n"); return ret; } int ftmem_write(struct ftdevice *ft801, u8 *buf, size_t len) { return spi_write(ft801->spi, buf, len); } void ftset_mem_addr(u8 *buf, u32 addr, bool is_read) { u8 *parts = (u8 *)(&addr); buf[0] = parts[2] | (is_read ? 0 : 0x80); buf[1] = parts[1]; buf[2] = parts[0]; } u8 ft8bit_reg_read(struct ftdevice *ft801, u32 reg_addr) { u8 tx_buf[5]; ftset_mem_addr(tx_buf, reg_addr, true); tx_buf[3] = 0; tx_buf[4] = 0; ftmem_read(ft801, tx_buf, 5); return tx_buf[4]; } u16 ft16bit_reg_read(struct ftdevice *ft801, u32 reg_addr) { u8 tx_buf[6]; ftset_mem_addr(tx_buf, reg_addr, true); tx_buf[3] = 0; tx_buf[4] = 0; tx_buf[5] = 0; ftmem_read(ft801, tx_buf, 6); return *((u16*) (tx_buf+4)); } u32 ft32bit_reg_read(struct ftdevice *ft801, u32 reg_addr) { u8 tx_buf[8]; ftset_mem_addr(tx_buf, reg_addr, true); tx_buf[3] = 0; tx_buf[4] = 0; tx_buf[5] = 0; tx_buf[6] = 0; tx_buf[7] = 0; ftmem_read(ft801, tx_buf, 8); return *((u32*) (tx_buf+4)); } int ft8bit_reg_write(struct ftdevice *ft801, u32 reg_addr, u8 val) { int ret; u8 tx_buf[4]; ftset_mem_addr(tx_buf, reg_addr, false); tx_buf[3] = val; ret = ftmem_write(ft801, tx_buf, 4); return ret; } int ft16bit_reg_write(struct ftdevice *ft801, u32 reg_addr, u16 val) { int ret; u8 tx_buf[5]; u8 *parts = (u8 *)(&val); ftset_mem_addr(tx_buf, reg_addr, false); tx_buf[3] = parts[0]; tx_buf[4] = parts[1]; ret = ftmem_write(ft801, tx_buf, 5); return ret; } int ft32bit_reg_write(struct ftdevice *ft801, u32 reg_addr, u32 val) { int ret; u8 tx_buf[7]; u8 *parts = (u8 *)(&val); ftset_mem_addr(tx_buf, reg_addr, false); tx_buf[3] = parts[0]; tx_buf[4] = parts[1]; tx_buf[5] = parts[2]; tx_buf[6] = parts[3]; ret = ftmem_write(ft801, tx_buf, 7); return ret; } int fthost_cmd(struct ftdevice *ft801, u8 command) { int ret; u8 tx_buf[3]; tx_buf[0] = command; tx_buf[1] = 0; tx_buf[2] = 0; ret = ftmem_write(ft801, tx_buf, 3); return ret; } // TODO: why 32bit? maybe 4k boundary void ftcmd(struct ftdevice *ft801, u32 cmd) { uint16_t cmdr,cmdw; while (true) { cmdr = ft32bit_reg_read(ft801, REG_CMD_READ); cmdw = ft32bit_reg_read(ft801, REG_CMD_WRITE); if (4096-(cmdw-cmdr) > 4) { ft32bit_reg_write(ft801, FT_RAM_CMD+cmdw, cmd); ft32bit_reg_write(ft801, REG_CMD_WRITE, cmdw+4); break; } usleep_range(1000,2000); } } void ftcmd_flush(struct ftdevice *ft801) { uint16_t cmdr,cmdw; cmdr = ft32bit_reg_read(ft801, REG_CMD_READ); cmdw = ft32bit_reg_read(ft801, REG_CMD_WRITE); while (cmdr != cmdw) { usleep_range(1000, 2000); cmdr = ft32bit_reg_read(ft801, REG_CMD_READ); cmdw = ft32bit_reg_read(ft801, REG_CMD_WRITE); } } static int ftfb_dirty(struct drm_framebuffer *fb, struct drm_file *file_priv, unsigned int flags, unsigned int color, struct drm_clip_rect *clips, unsigned int num_clips) { struct tinydrm_device *tdev = fb->dev->dev_private; struct ftdevice *ft801 = container_of(tdev, struct ftdevice, tinydrm); struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0); struct dma_buf_attachment *import_attach = cma_obj->base.import_attach; struct drm_format_name_buf format_name; struct drm_clip_rect clip; int ret = 0, fb_size; u32 start_pix,end_pix,fb_start_byte,fb_end_byte,ft_start_addr,txlen; mutex_lock(&tdev->dirty_lock); if (!ft801->enabled) goto out_unlock; if (tdev->pipe.plane.fb != fb) goto out_unlock; if (fb->format->format != DRM_FORMAT_RGB565) { dev_err_once(fb->dev->dev, "Format is not supported: %s\n", drm_get_format_name(fb->format->format,&format_name)); ret = -EINVAL; goto out_unlock; } tinydrm_merge_clips(&clip, clips, num_clips, flags, fb->width, fb->height); start_pix = clip.y1*320+clip.x1; end_pix = clip.y2*320+clip.x2; fb_start_byte = start_pix * 2; fb_end_byte = end_pix * 2; ft_start_addr = FT_RAM_G + fb_start_byte; fb_size = fb_end_byte - fb_start_byte; if (import_attach) { ret = dma_buf_begin_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); if (ret) goto out_unlock; } while (fb_size > 0) { txlen = min(fb_size + 3, FTPIXBUF_SIZE); ftset_mem_addr(ft801->pix_buf, ft_start_addr, false); memcpy(ft801->pix_buf + 3, cma_obj->vaddr + fb_start_byte, txlen-3); ftmem_write(ft801, ft801->pix_buf, txlen); fb_size -= txlen-3; ft_start_addr += txlen-3; fb_start_byte += txlen-3; } if (import_attach) ret = dma_buf_end_cpu_access(import_attach->dmabuf, DMA_FROM_DEVICE); out_unlock: mutex_unlock(&tdev->dirty_lock); if (ret) dev_err_once(fb->dev->dev, "Failed to update display rect (%d, %d) to (%d, %d): %d\n", clip.x1, clip.y1, clip.x2, clip.y2, ret); return ret; } static const struct drm_framebuffer_funcs ftfb_funcs = { .destroy = drm_fb_cma_destroy, .create_handle = drm_fb_cma_create_handle, .dirty = ftfb_dirty, }; void ftpipe_enable(struct drm_simple_display_pipe *pipe, struct drm_crtc_state *crtc_state) { struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); struct ftdevice *ft801 = container_of(tdev, struct ftdevice, tinydrm); mutex_lock(&ft801->cmdlock); ftcmd(ft801, CMD_MEMZERO); ftcmd(ft801, FT_RAM_G); ftcmd(ft801, 320 * 240 * 2); ftcmd_flush(ft801); mutex_unlock(&ft801->cmdlock); ft801->enabled = true; ft8bit_reg_write(ft801, REG_PWM_DUTY, 128); } void ftpipe_disable(struct drm_simple_display_pipe *pipe) { struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); struct ftdevice *ft801 = container_of(tdev, struct ftdevice, tinydrm); DRM_DEBUG_KMS("\n"); ft801->enabled = false; ft8bit_reg_write(ft801, REG_PWM_DUTY, 0); } static const struct drm_simple_display_pipe_funcs pipe_funcs = { .enable = ftpipe_enable, .disable = ftpipe_disable, .update = tinydrm_display_pipe_update, .prepare_fb = tinydrm_display_pipe_prepare_fb, }; static const uint32_t ftformats[] = { DRM_FORMAT_RGB565, }; DEFINE_DRM_GEM_CMA_FOPS(rvt320240_fops); static struct drm_driver rvt320240_driver = { .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC, .fops = &rvt320240_fops, TINYDRM_GEM_DRIVER_OPS, .lastclose = tinydrm_lastclose, .name = "rvt320240", .desc = "Riverdi rvt320240x", .date = "20170829", .major = 1, .minor = 0, }; static const struct drm_display_mode rvt320240_mode = { TINYDRM_MODE(320, 240, 70, 52), }; int display_init(struct ftdevice *ft801) { int ret; struct spi_device *spi = ft801->spi; struct device *dev = &spi->dev; struct tinydrm_device *tdev = &ft801->tinydrm; const struct dma_map_ops *ops = get_dma_ops(dev); gfp_t flag = GFP_KERNEL; if (!dev->coherent_dma_mask) { if (dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32))) { dev_warn(dev, "Failed to set dma mask, returns -ENOMEM\n"); } } mutex_init(&ft801->cmdlock); ret = devm_tinydrm_init(dev, tdev, &ftfb_funcs, &rvt320240_driver); if (ret) return ret; device_property_read_u32(dev, "rotation", &(ft801->rotation)); if (ft801->rotation != 0 || ft801->rotation != 1) ft801->rotation = 0; ret = tinydrm_display_pipe_init(tdev, &pipe_funcs, DRM_MODE_CONNECTOR_VIRTUAL, ftformats, ARRAY_SIZE(ftformats), &rvt320240_mode, ft801->rotation); dev_info(dev,"tinydrm_display_pipe_init returned %d\n", ret); if (ret) return ret; tdev->drm->mode_config.preferred_depth = 16; drm_mode_config_reset(tdev->drm); DRM_DEBUG_KMS("preferred_depth=%u, rotation = %u\n", tdev->drm->mode_config.preferred_depth, ft801->rotation); return 0; } bool ftdisplay_is_on(struct ftdevice *ft801) { return (ft8bit_reg_read(ft801, REG_ID) == FT80X_CHIPID); } static irqreturn_t touch_isr(int irq, void *dev_id) { struct ftdevice *ft801 = dev_id; u8 flags; s16 x,y; u32 xy; flags = ft8bit_reg_read(ft801, REG_INT_FLAGS); if (flags == FT_INT_CONVCOMPLETE) { xy = ft32bit_reg_read(ft801,REG_CTOUCH_TOUCH0_XY); x = xy >> 16; y = xy; input_mt_slot(ft801->input, 0); if (x > 0 && x < 320 && y > 0 && y < 240) { input_mt_report_slot_state(ft801->input, MT_TOOL_FINGER, 1); touchscreen_report_pos(ft801->input, &ft801->prop, x, y, true); } else input_mt_report_slot_state(ft801->input, MT_TOOL_FINGER, 0); input_mt_report_pointer_emulation(ft801->input, true); input_sync(ft801->input); } return IRQ_HANDLED; } int touch_init(struct spi_device *spi, struct ftdevice *ft801) { struct input_dev *input; int error, irq; input = devm_input_allocate_device(&spi->dev); if (!input) { dev_err(&spi->dev, "failed to allocate input device.\n"); return -ENOMEM; } ft801->spi = spi; ft801->input = input; dev_dbg(&spi->dev,"ft801 touch screen initialized\n"); input->name = "FT801 Capacitive Touch Screen"; input->phys = "input/ts"; input->id.bustype = BUS_SPI; input->dev.parent = &spi->dev; input_set_abs_params(input, ABS_MT_POSITION_X, 0, 320, 0, 0); input_set_abs_params(input, ABS_MT_POSITION_Y, 0, 240, 0, 0); touchscreen_parse_properties(input, true, &ft801->prop); input_mt_init_slots(input, 5, INPUT_MT_DIRECT); irq = gpiod_to_irq(ft801->interrupt_gpio); dev_warn(&spi->dev, "using irq %d from irq-gpio, current value=%d, isrptr %p\n", irq, gpiod_get_value(ft801->interrupt_gpio), ft801); error = request_threaded_irq(irq, NULL, touch_isr, IRQF_TRIGGER_LOW | IRQF_ONESHOT, "rvt320240", ft801); if (error) { dev_err(&spi->dev, "Unable to request touchscreen IRQ.\n"); return error; } error = input_register_device(input); if (error) { dev_err(&spi->dev, "Unable to register touchscreen input\n"); return error; } return 0; } void fthw_reset(struct ftdevice *ft801) { dev_info(&ft801->spi->dev, "resetting rvt320240"); if (!ft801->pd) return; gpiod_set_value_cansleep(ft801->pd, 0); msleep(20); gpiod_set_value_cansleep(ft801->pd, 1); msleep(120); } static int chip_powerup(struct ftdevice *ft801) { int tries; dev_info(&ft801->spi->dev, "initializing rvt320240"); ft801->spi->max_speed_hz = FTSETUP_SPI_SPEED; ft8bit_reg_write(ft801, REG_PWM_DUTY, 0); fthost_cmd(ft801,FT_CORERST); ft8bit_reg_write(ft801, REG_PWM_DUTY, 0); msleep(20); ft8bit_reg_write(ft801, REG_PWM_DUTY, 0); fthost_cmd(ft801,FT_ACTIVE); msleep(20); ft8bit_reg_write(ft801, REG_PWM_DUTY, 0); fthost_cmd(ft801,FT_CLKEXT); fthost_cmd(ft801,FT_CLK48M); tries = 0; while (!ftdisplay_is_on(ft801) && tries < 50) { // wait 1s msleep(20); tries++; } if (tries >= 50) { dev_err(&ft801->spi->dev, "couldnt find display\n"); fthw_reset(ft801); return -517; } ft16bit_reg_write(ft801, REG_HCYCLE, 408); ft16bit_reg_write(ft801, REG_HOFFSET, 70); ft16bit_reg_write(ft801, REG_HSYNC0, 0); ft16bit_reg_write(ft801, REG_HSYNC1, 10); ft16bit_reg_write(ft801, REG_VCYCLE, 263); ft16bit_reg_write(ft801, REG_VOFFSET, 13); ft16bit_reg_write(ft801, REG_VSYNC0, 0); ft16bit_reg_write(ft801, REG_VSYNC1, 2); ft8bit_reg_write(ft801, REG_SWIZZLE, 2); ft8bit_reg_write(ft801, REG_PCLK_POL, 1); ft16bit_reg_write(ft801, REG_HSIZE, 320); ft16bit_reg_write(ft801, REG_VSIZE, 240); ft8bit_reg_write(ft801, REG_CSPREAD, 0); ft8bit_reg_write(ft801, REG_DITHER, 1); ft8bit_reg_write(ft801, REG_GPIO_DIR, 0xff); ft8bit_reg_write(ft801, REG_GPIO, 0xff); msleep(20); ft32bit_reg_write(ft801, FT_RAM_DL+0,CLEAR_COLOR_RGB(0,0,0)); ft32bit_reg_write(ft801, FT_RAM_DL+0,CLEAR_COLOR_A(255)); ft32bit_reg_write(ft801, FT_RAM_DL+4,CLEAR(1,1,1)); ft32bit_reg_write(ft801, FT_RAM_DL+8,DISPLAY()); ft8bit_reg_write(ft801, REG_DLSWAP,FT_DLSWAP_FRAME); ft8bit_reg_write(ft801, REG_PCLK,6); if (ft801->rotation == 1) { ft8bit_reg_write(ft801, REG_ROTATE, 1); } msleep(20); ft801->spi->max_speed_hz = FTMAX_SPI_SPEED; ft16bit_reg_write(ft801, REG_PWM_HZ, 4096); mutex_lock(&ft801->cmdlock); ftcmd(ft801, CMD_DLSTART); ftcmd(ft801, BEGIN(FT_BITMAPS)); ftcmd(ft801, COLOR_RGB(255,255,255)); ftcmd(ft801, COLOR_A(255)); ftcmd(ft801, BITMAP_SOURCE(FT_RAM_G)); ftcmd(ft801, BITMAP_LAYOUT(FT_RGB565, 640, 240)); ftcmd(ft801, BITMAP_SIZE(FT_BILINEAR, FT_BORDER, FT_BORDER, 320, 240)); ftcmd(ft801, VERTEX2II(0,0,0,0)); ftcmd(ft801, END()); ftcmd(ft801, DISPLAY()); ftcmd(ft801, CMD_SWAP); ftcmd_flush(ft801); mutex_unlock(&ft801->cmdlock); ft32bit_reg_write(ft801, REG_CTOUCH_TRANSFORM_A, 0x00005d2b); ft32bit_reg_write(ft801, REG_CTOUCH_TRANSFORM_B, 0xfffffe28); ft32bit_reg_write(ft801, REG_CTOUCH_TRANSFORM_C, 0xfff388da); ft32bit_reg_write(ft801, REG_CTOUCH_TRANSFORM_D, 0x0000007e); ft32bit_reg_write(ft801, REG_CTOUCH_TRANSFORM_E, 0x0000686b); ft32bit_reg_write(ft801, REG_CTOUCH_TRANSFORM_F, 0xfff23bed); ft8bit_reg_write(ft801, REG_INT_MASK, FT_INT_CONVCOMPLETE); ft8bit_reg_read(ft801, REG_INT_FLAGS); // dummy read to clear the flag ft8bit_reg_write(ft801, REG_INT_EN, 1); ft8bit_reg_read(ft801, REG_INT_FLAGS); // dummy read to clear the flag return 0; } static const struct of_device_id rvt320240_of_match[] = { { .compatible = "riverdi,rvt320240" }, {}, }; MODULE_DEVICE_TABLE(of, rvt320240_of_match); static const struct spi_device_id rvt320240_id[] = { { "rvt320240", 0 }, { }, }; MODULE_DEVICE_TABLE(spi, rvt320240_id); static int rvt320240_probe(struct spi_device *spi) { struct device *dev = &spi->dev; struct ftdevice *ft801; int ret; if (!dev->coherent_dma_mask) { if (dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32))) dev_warn(dev, "Failed to set dma mask %d\n", ret); } ft801 = devm_kzalloc(dev, sizeof(*ft801), GFP_KERNEL); ft801->pd = devm_gpiod_get_optional(dev, "pd", GPIOD_OUT_HIGH); if (IS_ERR(ft801->pd)) { dev_err(dev, "Failed to get gpio 'pd'\n"); return PTR_ERR(ft801->pd); } ft801->interrupt_gpio = devm_gpiod_get(dev, "irq", GPIOD_IN); if (IS_ERR(ft801->interrupt_gpio)) { dev_err(dev, "Failed to get gpio 'irq' %ld\n", PTR_ERR(ft801->interrupt_gpio)); return PTR_ERR(ft801->interrupt_gpio); } ft801->spi = spi; ret = display_init(ft801); if (ret) return ret; dev_info(dev,"initialized display\n"); spi->bits_per_word = 8; spi->mode = SPI_MODE_0; spi->max_speed_hz = FTSETUP_SPI_SPEED; ret = spi_setup(spi); if (ret) { dev_err(dev, "bad spi_setup ret %d\n", ret); return ret; } ret = chip_powerup(ft801); if (ret) return ret; dev_info(dev,"setup spi & powered up chip\n"); ret = devm_tinydrm_register(&ft801->tinydrm); if (ret) return ret; dev_info(dev,"registered tinydrm\n"); ret = touch_init(spi, ft801); if (ret) return ret; spi_set_drvdata(spi, ft801); dev_warn(dev, "exiting probe after initialized rvt320240 on minor %d\n", ft801->tinydrm.drm->primary->index); return 0; } static void rvt320240_shutdown(struct spi_device *spi) { struct ftdevice *ft801 = spi_get_drvdata(spi); input_unregister_device(ft801->input); tinydrm_shutdown(&ft801->tinydrm); } static struct spi_driver rvt320240_spi_driver = { .driver = { .name = "rvt320240", .owner = THIS_MODULE, .of_match_table = rvt320240_of_match, }, .id_table = rvt320240_id, .probe = rvt320240_probe, .shutdown = rvt320240_shutdown, }; module_spi_driver(rvt320240_spi_driver); MODULE_DESCRIPTION("Riverdi rvt320240 DRM driver"); MODULE_AUTHOR("Matt Gattis"); MODULE_LICENSE("GPL");