/* Copyright (c) 2009-2013, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_MSM_BUS_SCALING #include #include #endif #include #define DRIVER_NAME "msm_rotator" #define MSM_ROTATOR_BASE (msm_rotator_dev->io_base) #define MSM_ROTATOR_INTR_ENABLE (MSM_ROTATOR_BASE+0x0020) #define MSM_ROTATOR_INTR_STATUS (MSM_ROTATOR_BASE+0x0024) #define MSM_ROTATOR_INTR_CLEAR (MSM_ROTATOR_BASE+0x0028) #define MSM_ROTATOR_START (MSM_ROTATOR_BASE+0x0030) #define MSM_ROTATOR_MAX_BURST_SIZE (MSM_ROTATOR_BASE+0x0050) #define MSM_ROTATOR_HW_VERSION (MSM_ROTATOR_BASE+0x0070) #define MSM_ROTATOR_SW_RESET (MSM_ROTATOR_BASE+0x0074) #define MSM_ROTATOR_SRC_SIZE (MSM_ROTATOR_BASE+0x1108) #define MSM_ROTATOR_SRCP0_ADDR (MSM_ROTATOR_BASE+0x110c) #define MSM_ROTATOR_SRCP1_ADDR (MSM_ROTATOR_BASE+0x1110) #define MSM_ROTATOR_SRCP2_ADDR (MSM_ROTATOR_BASE+0x1114) #define MSM_ROTATOR_SRC_YSTRIDE1 (MSM_ROTATOR_BASE+0x111c) #define MSM_ROTATOR_SRC_YSTRIDE2 (MSM_ROTATOR_BASE+0x1120) #define MSM_ROTATOR_SRC_FORMAT (MSM_ROTATOR_BASE+0x1124) #define MSM_ROTATOR_SRC_UNPACK_PATTERN1 (MSM_ROTATOR_BASE+0x1128) #define MSM_ROTATOR_SUB_BLOCK_CFG (MSM_ROTATOR_BASE+0x1138) #define MSM_ROTATOR_OUT_PACK_PATTERN1 (MSM_ROTATOR_BASE+0x1154) #define MSM_ROTATOR_OUTP0_ADDR (MSM_ROTATOR_BASE+0x1168) #define MSM_ROTATOR_OUTP1_ADDR (MSM_ROTATOR_BASE+0x116c) #define MSM_ROTATOR_OUTP2_ADDR (MSM_ROTATOR_BASE+0x1170) #define MSM_ROTATOR_OUT_YSTRIDE1 (MSM_ROTATOR_BASE+0x1178) #define MSM_ROTATOR_OUT_YSTRIDE2 (MSM_ROTATOR_BASE+0x117c) #define MSM_ROTATOR_SRC_XY (MSM_ROTATOR_BASE+0x1200) #define MSM_ROTATOR_SRC_IMAGE_SIZE (MSM_ROTATOR_BASE+0x1208) #define MSM_ROTATOR_MAX_ROT 0x07 #define MSM_ROTATOR_MAX_H 0x1fff #define MSM_ROTATOR_MAX_W 0x1fff /* from lsb to msb */ #define GET_PACK_PATTERN(a, x, y, z, bit) \ (((a)<<((bit)*3))|((x)<<((bit)*2))|((y)<<(bit))|(z)) #define CLR_G 0x0 #define CLR_B 0x1 #define CLR_R 0x2 #define CLR_ALPHA 0x3 #define CLR_Y CLR_G #define CLR_CB CLR_B #define CLR_CR CLR_R #define ROTATIONS_TO_BITMASK(r) ((((r) & MDP_ROT_90) ? 1 : 0) | \ (((r) & MDP_FLIP_LR) ? 2 : 0) | \ (((r) & MDP_FLIP_UD) ? 4 : 0)) #define IMEM_NO_OWNER -1; #define MAX_SESSIONS 16 #define INVALID_SESSION -1 #define VERSION_KEY_MASK 0xFFFFFF00 #define MAX_DOWNSCALE_RATIO 3 #define ROTATOR_REVISION_V0 0 #define ROTATOR_REVISION_V1 1 #define ROTATOR_REVISION_V2 2 #define ROTATOR_REVISION_NONE 0xffffffff uint32_t rotator_hw_revision; static char rot_iommu_split_domain; /* * rotator_hw_revision: * 0 == 7x30 * 1 == 8x60 * 2 == 8960 * */ struct tile_parm { unsigned int width; /* tile's width */ unsigned int height; /* tile's height */ unsigned int row_tile_w; /* tiles per row's width */ unsigned int row_tile_h; /* tiles per row's height */ }; struct msm_rotator_mem_planes { unsigned int num_planes; unsigned int plane_size[4]; unsigned int total_size; }; #define checkoffset(offset, size, max_size) \ ((size) > (max_size) || (offset) > ((max_size) - (size))) struct msm_rotator_fd_info { int pid; int ref_cnt; struct list_head list; }; struct msm_rotator_dev { void __iomem *io_base; int irq; struct msm_rotator_img_info *img_info[MAX_SESSIONS]; struct clk *core_clk; struct msm_rotator_fd_info *fd_info[MAX_SESSIONS]; struct list_head fd_list; struct clk *pclk; int rot_clk_state; struct regulator *regulator; struct delayed_work rot_clk_work; struct clk *imem_clk; int imem_clk_state; struct delayed_work imem_clk_work; struct platform_device *pdev; struct cdev cdev; struct device *device; struct class *class; dev_t dev_num; int processing; int last_session_idx; struct mutex rotator_lock; struct mutex imem_lock; int imem_owner; wait_queue_head_t wq; struct ion_client *client; #ifdef CONFIG_MSM_BUS_SCALING uint32_t bus_client_handle; #endif }; #define COMPONENT_5BITS 1 #define COMPONENT_6BITS 2 #define COMPONENT_8BITS 3 static struct msm_rotator_dev *msm_rotator_dev; enum { CLK_EN, CLK_DIS, CLK_SUSPEND, }; int msm_rotator_iommu_map_buf(int mem_id, int domain, unsigned long *start, unsigned long *len, struct ion_handle **pihdl, unsigned int secure) { if (!msm_rotator_dev->client) return -EINVAL; *pihdl = ion_import_dma_buf(msm_rotator_dev->client, mem_id); if (IS_ERR_OR_NULL(*pihdl)) { pr_err("ion_import_dma_buf() failed\n"); return PTR_ERR(*pihdl); } pr_debug("%s(): ion_hdl %p, ion_fd %d\n", __func__, *pihdl, mem_id); if (rot_iommu_split_domain) { if (secure) { if (ion_phys(msm_rotator_dev->client, *pihdl, start, (unsigned *)len)) { pr_err("%s:%d: ion_phys map failed\n", __func__, __LINE__); return -ENOMEM; } } else { if (ion_map_iommu(msm_rotator_dev->client, *pihdl, domain, GEN_POOL, SZ_4K, 0, start, len, 0, ION_IOMMU_UNMAP_DELAYED)) { pr_err("ion_map_iommu() failed\n"); return -EINVAL; } } } else { if (ion_map_iommu(msm_rotator_dev->client, *pihdl, ROTATOR_SRC_DOMAIN, GEN_POOL, SZ_4K, 0, start, len, 0, ION_IOMMU_UNMAP_DELAYED)) { pr_err("ion_map_iommu() failed\n"); return -EINVAL; } } pr_debug("%s(): mem_id %d, start 0x%lx, len 0x%lx\n", __func__, mem_id, *start, *len); return 0; } int msm_rotator_imem_allocate(int requestor) { int rc = 0; #ifdef CONFIG_MSM_ROTATOR_USE_IMEM switch (requestor) { case ROTATOR_REQUEST: if (mutex_trylock(&msm_rotator_dev->imem_lock)) { msm_rotator_dev->imem_owner = ROTATOR_REQUEST; rc = 1; } else rc = 0; break; case JPEG_REQUEST: mutex_lock(&msm_rotator_dev->imem_lock); msm_rotator_dev->imem_owner = JPEG_REQUEST; rc = 1; break; default: rc = 0; } #else if (requestor == JPEG_REQUEST) rc = 1; #endif if (rc == 1) { cancel_delayed_work(&msm_rotator_dev->imem_clk_work); if (msm_rotator_dev->imem_clk_state != CLK_EN && msm_rotator_dev->imem_clk) { clk_prepare_enable(msm_rotator_dev->imem_clk); msm_rotator_dev->imem_clk_state = CLK_EN; } } return rc; } EXPORT_SYMBOL(msm_rotator_imem_allocate); void msm_rotator_imem_free(int requestor) { #ifdef CONFIG_MSM_ROTATOR_USE_IMEM if (msm_rotator_dev->imem_owner == requestor) { schedule_delayed_work(&msm_rotator_dev->imem_clk_work, HZ); mutex_unlock(&msm_rotator_dev->imem_lock); } #else if (requestor == JPEG_REQUEST) schedule_delayed_work(&msm_rotator_dev->imem_clk_work, HZ); #endif } EXPORT_SYMBOL(msm_rotator_imem_free); static void msm_rotator_imem_clk_work_f(struct work_struct *work) { #ifdef CONFIG_MSM_ROTATOR_USE_IMEM if (mutex_trylock(&msm_rotator_dev->imem_lock)) { if (msm_rotator_dev->imem_clk_state == CLK_EN && msm_rotator_dev->imem_clk) { clk_disable_unprepare(msm_rotator_dev->imem_clk); msm_rotator_dev->imem_clk_state = CLK_DIS; } else if (msm_rotator_dev->imem_clk_state == CLK_SUSPEND) msm_rotator_dev->imem_clk_state = CLK_DIS; mutex_unlock(&msm_rotator_dev->imem_lock); } #endif } /* enable clocks needed by rotator block */ static void enable_rot_clks(void) { if (msm_rotator_dev->regulator) regulator_enable(msm_rotator_dev->regulator); if (msm_rotator_dev->core_clk != NULL) clk_prepare_enable(msm_rotator_dev->core_clk); if (msm_rotator_dev->pclk != NULL) clk_prepare_enable(msm_rotator_dev->pclk); } /* disable clocks needed by rotator block */ static void disable_rot_clks(void) { if (msm_rotator_dev->core_clk != NULL) clk_disable_unprepare(msm_rotator_dev->core_clk); if (msm_rotator_dev->pclk != NULL) clk_disable_unprepare(msm_rotator_dev->pclk); if (msm_rotator_dev->regulator) regulator_disable(msm_rotator_dev->regulator); } static void msm_rotator_rot_clk_work_f(struct work_struct *work) { if (mutex_trylock(&msm_rotator_dev->rotator_lock)) { if (msm_rotator_dev->rot_clk_state == CLK_EN) { disable_rot_clks(); msm_rotator_dev->rot_clk_state = CLK_DIS; } else if (msm_rotator_dev->rot_clk_state == CLK_SUSPEND) msm_rotator_dev->rot_clk_state = CLK_DIS; mutex_unlock(&msm_rotator_dev->rotator_lock); } } static irqreturn_t msm_rotator_isr(int irq, void *dev_id) { if (msm_rotator_dev->processing) { msm_rotator_dev->processing = 0; wake_up(&msm_rotator_dev->wq); } else printk(KERN_WARNING "%s: unexpected interrupt\n", DRIVER_NAME); return IRQ_HANDLED; } static unsigned int tile_size(unsigned int src_width, unsigned int src_height, const struct tile_parm *tp) { unsigned int tile_w, tile_h; unsigned int row_num_w, row_num_h; tile_w = tp->width * tp->row_tile_w; tile_h = tp->height * tp->row_tile_h; row_num_w = (src_width + tile_w - 1) / tile_w; row_num_h = (src_height + tile_h - 1) / tile_h; return ((row_num_w * row_num_h * tile_w * tile_h) + 8191) & ~8191; } static int get_bpp(int format) { switch (format) { case MDP_RGB_565: case MDP_BGR_565: return 2; case MDP_XRGB_8888: case MDP_ARGB_8888: case MDP_RGBA_8888: case MDP_BGRA_8888: case MDP_RGBX_8888: case MDP_BGRX_8888: return 4; case MDP_Y_CBCR_H2V2: case MDP_Y_CRCB_H2V2: case MDP_Y_CB_CR_H2V2: case MDP_Y_CR_CB_H2V2: case MDP_Y_CR_CB_GH2V2: case MDP_Y_CRCB_H2V2_TILE: case MDP_Y_CBCR_H2V2_TILE: return 1; case MDP_RGB_888: case MDP_YCBCR_H1V1: case MDP_YCRCB_H1V1: return 3; case MDP_YCRYCB_H2V1: return 2;/* YCrYCb interleave */ case MDP_Y_CRCB_H2V1: case MDP_Y_CBCR_H2V1: return 1; default: return -1; } } static int msm_rotator_get_plane_sizes(uint32_t format, uint32_t w, uint32_t h, struct msm_rotator_mem_planes *p) { /* * each row of samsung tile consists of two tiles in height * and two tiles in width which means width should align to * 64 x 2 bytes and height should align to 32 x 2 bytes. * video decoder generate two tiles in width and one tile * in height which ends up height align to 32 X 1 bytes. */ const struct tile_parm tile = {64, 32, 2, 1}; int i; if (p == NULL) return -EINVAL; if ((w > MSM_ROTATOR_MAX_W) || (h > MSM_ROTATOR_MAX_H)) return -ERANGE; memset(p, 0, sizeof(*p)); switch (format) { case MDP_XRGB_8888: case MDP_ARGB_8888: case MDP_RGBA_8888: case MDP_BGRA_8888: case MDP_RGBX_8888: case MDP_BGRX_8888: case MDP_RGB_888: case MDP_RGB_565: case MDP_BGR_565: case MDP_YCRYCB_H2V1: case MDP_YCBCR_H1V1: case MDP_YCRCB_H1V1: p->num_planes = 1; p->plane_size[0] = w * h * get_bpp(format); break; case MDP_Y_CRCB_H2V1: case MDP_Y_CBCR_H2V1: case MDP_Y_CRCB_H1V2: case MDP_Y_CBCR_H1V2: p->num_planes = 2; p->plane_size[0] = w * h; p->plane_size[1] = w * h; break; case MDP_Y_CBCR_H2V2: case MDP_Y_CRCB_H2V2: p->num_planes = 2; p->plane_size[0] = w * h; p->plane_size[1] = w * h / 2; break; case MDP_Y_CRCB_H2V2_TILE: case MDP_Y_CBCR_H2V2_TILE: p->num_planes = 2; p->plane_size[0] = tile_size(w, h, &tile); p->plane_size[1] = tile_size(w, h/2, &tile); break; case MDP_Y_CB_CR_H2V2: case MDP_Y_CR_CB_H2V2: p->num_planes = 3; p->plane_size[0] = w * h; p->plane_size[1] = (w / 2) * (h / 2); p->plane_size[2] = (w / 2) * (h / 2); break; case MDP_Y_CR_CB_GH2V2: p->num_planes = 3; p->plane_size[0] = ALIGN(w, 16) * h; p->plane_size[1] = ALIGN(w / 2, 16) * (h / 2); p->plane_size[2] = ALIGN(w / 2, 16) * (h / 2); break; default: return -EINVAL; } for (i = 0; i < p->num_planes; i++) p->total_size += p->plane_size[i]; return 0; } static int msm_rotator_ycxcx_h2v1(struct msm_rotator_img_info *info, unsigned int in_paddr, unsigned int out_paddr, unsigned int use_imem, int new_session, unsigned int in_chroma_paddr, unsigned int out_chroma_paddr) { int bpp; uint32_t dst_format; switch (info->src.format) { case MDP_Y_CRCB_H2V1: if (info->rotations & MDP_ROT_90) dst_format = MDP_Y_CRCB_H1V2; else dst_format = info->src.format; break; case MDP_Y_CBCR_H2V1: if (info->rotations & MDP_ROT_90) dst_format = MDP_Y_CBCR_H1V2; else dst_format = info->src.format; break; default: return -EINVAL; } if (info->dst.format != dst_format) return -EINVAL; bpp = get_bpp(info->src.format); if (bpp < 0) return -ENOTTY; iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR); iowrite32(in_chroma_paddr, MSM_ROTATOR_SRCP1_ADDR); iowrite32(out_paddr + ((info->dst_y * info->dst.width) + info->dst_x), MSM_ROTATOR_OUTP0_ADDR); iowrite32(out_chroma_paddr + ((info->dst_y * info->dst.width) + info->dst_x), MSM_ROTATOR_OUTP1_ADDR); if (new_session) { iowrite32(info->src.width | info->src.width << 16, MSM_ROTATOR_SRC_YSTRIDE1); if (info->rotations & MDP_ROT_90) iowrite32(info->dst.width | info->dst.width*2 << 16, MSM_ROTATOR_OUT_YSTRIDE1); else iowrite32(info->dst.width | info->dst.width << 16, MSM_ROTATOR_OUT_YSTRIDE1); if (info->src.format == MDP_Y_CBCR_H2V1) { iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8), MSM_ROTATOR_SRC_UNPACK_PATTERN1); iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8), MSM_ROTATOR_OUT_PACK_PATTERN1); } else { iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8), MSM_ROTATOR_SRC_UNPACK_PATTERN1); iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8), MSM_ROTATOR_OUT_PACK_PATTERN1); } iowrite32((1 << 18) | /* chroma sampling 1=H2V1 */ (ROTATIONS_TO_BITMASK(info->rotations) << 9) | 1 << 8 | /* ROT_EN */ info->downscale_ratio << 2 | /* downscale v ratio */ info->downscale_ratio, /* downscale h ratio */ MSM_ROTATOR_SUB_BLOCK_CFG); iowrite32(0 << 29 | /* frame format 0 = linear */ (use_imem ? 0 : 1) << 22 | /* tile size */ 2 << 19 | /* fetch planes 2 = pseudo */ 0 << 18 | /* unpack align */ 1 << 17 | /* unpack tight */ 1 << 13 | /* unpack count 0=1 component */ (bpp-1) << 9 | /* src Bpp 0=1 byte ... */ 0 << 8 | /* has alpha */ 0 << 6 | /* alpha bits 3=8bits */ 3 << 4 | /* R/Cr bits 1=5 2=6 3=8 */ 3 << 2 | /* B/Cb bits 1=5 2=6 3=8 */ 3 << 0, /* G/Y bits 1=5 2=6 3=8 */ MSM_ROTATOR_SRC_FORMAT); } return 0; } static int msm_rotator_ycxcx_h2v2(struct msm_rotator_img_info *info, unsigned int in_paddr, unsigned int out_paddr, unsigned int use_imem, int new_session, unsigned int in_chroma_paddr, unsigned int out_chroma_paddr, unsigned int in_chroma2_paddr) { uint32_t dst_format; int is_tile = 0; switch (info->src.format) { case MDP_Y_CRCB_H2V2_TILE: is_tile = 1; case MDP_Y_CR_CB_H2V2: case MDP_Y_CR_CB_GH2V2: case MDP_Y_CRCB_H2V2: dst_format = MDP_Y_CRCB_H2V2; break; case MDP_Y_CBCR_H2V2_TILE: is_tile = 1; case MDP_Y_CB_CR_H2V2: case MDP_Y_CBCR_H2V2: dst_format = MDP_Y_CBCR_H2V2; break; default: return -EINVAL; } if (info->dst.format != dst_format) return -EINVAL; /* rotator expects YCbCr for planar input format */ if ((info->src.format == MDP_Y_CR_CB_H2V2 || info->src.format == MDP_Y_CR_CB_GH2V2) && rotator_hw_revision < ROTATOR_REVISION_V2) swap(in_chroma_paddr, in_chroma2_paddr); iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR); iowrite32(in_chroma_paddr, MSM_ROTATOR_SRCP1_ADDR); iowrite32(in_chroma2_paddr, MSM_ROTATOR_SRCP2_ADDR); iowrite32(out_paddr + ((info->dst_y * info->dst.width) + info->dst_x), MSM_ROTATOR_OUTP0_ADDR); iowrite32(out_chroma_paddr + ((info->dst_y * info->dst.width)/2 + info->dst_x), MSM_ROTATOR_OUTP1_ADDR); if (new_session) { if (in_chroma2_paddr) { if (info->src.format == MDP_Y_CR_CB_GH2V2) { iowrite32(ALIGN(info->src.width, 16) | ALIGN((info->src.width / 2), 16) << 16, MSM_ROTATOR_SRC_YSTRIDE1); iowrite32(ALIGN((info->src.width / 2), 16), MSM_ROTATOR_SRC_YSTRIDE2); } else { iowrite32(info->src.width | (info->src.width / 2) << 16, MSM_ROTATOR_SRC_YSTRIDE1); iowrite32((info->src.width / 2), MSM_ROTATOR_SRC_YSTRIDE2); } } else { iowrite32(info->src.width | info->src.width << 16, MSM_ROTATOR_SRC_YSTRIDE1); } iowrite32(info->dst.width | info->dst.width << 16, MSM_ROTATOR_OUT_YSTRIDE1); if (dst_format == MDP_Y_CBCR_H2V2) { iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8), MSM_ROTATOR_SRC_UNPACK_PATTERN1); iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8), MSM_ROTATOR_OUT_PACK_PATTERN1); } else { iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8), MSM_ROTATOR_SRC_UNPACK_PATTERN1); iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8), MSM_ROTATOR_OUT_PACK_PATTERN1); } iowrite32((3 << 18) | /* chroma sampling 3=4:2:0 */ (ROTATIONS_TO_BITMASK(info->rotations) << 9) | 1 << 8 | /* ROT_EN */ info->downscale_ratio << 2 | /* downscale v ratio */ info->downscale_ratio, /* downscale h ratio */ MSM_ROTATOR_SUB_BLOCK_CFG); iowrite32((is_tile ? 2 : 0) << 29 | /* frame format */ (use_imem ? 0 : 1) << 22 | /* tile size */ (in_chroma2_paddr ? 1 : 2) << 19 | /* fetch planes */ 0 << 18 | /* unpack align */ 1 << 17 | /* unpack tight */ 1 << 13 | /* unpack count 0=1 component */ 0 << 9 | /* src Bpp 0=1 byte ... */ 0 << 8 | /* has alpha */ 0 << 6 | /* alpha bits 3=8bits */ 3 << 4 | /* R/Cr bits 1=5 2=6 3=8 */ 3 << 2 | /* B/Cb bits 1=5 2=6 3=8 */ 3 << 0, /* G/Y bits 1=5 2=6 3=8 */ MSM_ROTATOR_SRC_FORMAT); } return 0; } static int msm_rotator_ycrycb(struct msm_rotator_img_info *info, unsigned int in_paddr, unsigned int out_paddr, unsigned int use_imem, int new_session, unsigned int out_chroma_paddr) { int bpp; uint32_t dst_format; if (info->src.format == MDP_YCRYCB_H2V1) { if (info->rotations & MDP_ROT_90) dst_format = MDP_Y_CRCB_H1V2; else dst_format = MDP_Y_CRCB_H2V1; } else return -EINVAL; if (info->dst.format != dst_format) return -EINVAL; bpp = get_bpp(info->src.format); if (bpp < 0) return -ENOTTY; iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR); iowrite32(out_paddr + ((info->dst_y * info->dst.width) + info->dst_x), MSM_ROTATOR_OUTP0_ADDR); iowrite32(out_chroma_paddr + ((info->dst_y * info->dst.width)/2 + info->dst_x), MSM_ROTATOR_OUTP1_ADDR); if (new_session) { iowrite32(info->src.width * bpp, MSM_ROTATOR_SRC_YSTRIDE1); if (info->rotations & MDP_ROT_90) iowrite32(info->dst.width | (info->dst.width*2) << 16, MSM_ROTATOR_OUT_YSTRIDE1); else iowrite32(info->dst.width | (info->dst.width) << 16, MSM_ROTATOR_OUT_YSTRIDE1); iowrite32(GET_PACK_PATTERN(CLR_Y, CLR_CR, CLR_Y, CLR_CB, 8), MSM_ROTATOR_SRC_UNPACK_PATTERN1); iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8), MSM_ROTATOR_OUT_PACK_PATTERN1); iowrite32((1 << 18) | /* chroma sampling 1=H2V1 */ (ROTATIONS_TO_BITMASK(info->rotations) << 9) | 1 << 8 | /* ROT_EN */ info->downscale_ratio << 2 | /* downscale v ratio */ info->downscale_ratio, /* downscale h ratio */ MSM_ROTATOR_SUB_BLOCK_CFG); iowrite32(0 << 29 | /* frame format 0 = linear */ (use_imem ? 0 : 1) << 22 | /* tile size */ 0 << 19 | /* fetch planes 0=interleaved */ 0 << 18 | /* unpack align */ 1 << 17 | /* unpack tight */ 3 << 13 | /* unpack count 0=1 component */ (bpp-1) << 9 | /* src Bpp 0=1 byte ... */ 0 << 8 | /* has alpha */ 0 << 6 | /* alpha bits 3=8bits */ 3 << 4 | /* R/Cr bits 1=5 2=6 3=8 */ 3 << 2 | /* B/Cb bits 1=5 2=6 3=8 */ 3 << 0, /* G/Y bits 1=5 2=6 3=8 */ MSM_ROTATOR_SRC_FORMAT); } return 0; } static int msm_rotator_rgb_types(struct msm_rotator_img_info *info, unsigned int in_paddr, unsigned int out_paddr, unsigned int use_imem, int new_session) { int bpp, abits, rbits, gbits, bbits; if (info->src.format != info->dst.format) return -EINVAL; bpp = get_bpp(info->src.format); if (bpp < 0) return -ENOTTY; iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR); iowrite32(out_paddr + ((info->dst_y * info->dst.width) + info->dst_x) * bpp, MSM_ROTATOR_OUTP0_ADDR); if (new_session) { iowrite32(info->src.width * bpp, MSM_ROTATOR_SRC_YSTRIDE1); iowrite32(info->dst.width * bpp, MSM_ROTATOR_OUT_YSTRIDE1); iowrite32((0 << 18) | /* chroma sampling 0=rgb */ (ROTATIONS_TO_BITMASK(info->rotations) << 9) | 1 << 8 | /* ROT_EN */ info->downscale_ratio << 2 | /* downscale v ratio */ info->downscale_ratio, /* downscale h ratio */ MSM_ROTATOR_SUB_BLOCK_CFG); switch (info->src.format) { case MDP_RGB_565: iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8), MSM_ROTATOR_SRC_UNPACK_PATTERN1); iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8), MSM_ROTATOR_OUT_PACK_PATTERN1); abits = 0; rbits = COMPONENT_5BITS; gbits = COMPONENT_6BITS; bbits = COMPONENT_5BITS; break; case MDP_BGR_565: iowrite32(GET_PACK_PATTERN(0, CLR_B, CLR_G, CLR_R, 8), MSM_ROTATOR_SRC_UNPACK_PATTERN1); iowrite32(GET_PACK_PATTERN(0, CLR_B, CLR_G, CLR_R, 8), MSM_ROTATOR_OUT_PACK_PATTERN1); abits = 0; rbits = COMPONENT_5BITS; gbits = COMPONENT_6BITS; bbits = COMPONENT_5BITS; break; case MDP_RGB_888: case MDP_YCBCR_H1V1: case MDP_YCRCB_H1V1: iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8), MSM_ROTATOR_SRC_UNPACK_PATTERN1); iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8), MSM_ROTATOR_OUT_PACK_PATTERN1); abits = 0; rbits = COMPONENT_8BITS; gbits = COMPONENT_8BITS; bbits = COMPONENT_8BITS; break; case MDP_ARGB_8888: case MDP_RGBA_8888: case MDP_XRGB_8888: case MDP_RGBX_8888: iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, 8), MSM_ROTATOR_SRC_UNPACK_PATTERN1); iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, 8), MSM_ROTATOR_OUT_PACK_PATTERN1); abits = COMPONENT_8BITS; rbits = COMPONENT_8BITS; gbits = COMPONENT_8BITS; bbits = COMPONENT_8BITS; break; case MDP_BGRA_8888: case MDP_BGRX_8888: iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_B, CLR_G, CLR_R, 8), MSM_ROTATOR_SRC_UNPACK_PATTERN1); iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_B, CLR_G, CLR_R, 8), MSM_ROTATOR_OUT_PACK_PATTERN1); abits = COMPONENT_8BITS; rbits = COMPONENT_8BITS; gbits = COMPONENT_8BITS; bbits = COMPONENT_8BITS; break; default: return -EINVAL; } iowrite32(0 << 29 | /* frame format 0 = linear */ (use_imem ? 0 : 1) << 22 | /* tile size */ 0 << 19 | /* fetch planes 0=interleaved */ 0 << 18 | /* unpack align */ 1 << 17 | /* unpack tight */ (abits ? 3 : 2) << 13 | /* unpack count 0=1 comp */ (bpp-1) << 9 | /* src Bpp 0=1 byte ... */ (abits ? 1 : 0) << 8 | /* has alpha */ abits << 6 | /* alpha bits 3=8bits */ rbits << 4 | /* R/Cr bits 1=5 2=6 3=8 */ bbits << 2 | /* B/Cb bits 1=5 2=6 3=8 */ gbits << 0, /* G/Y bits 1=5 2=6 3=8 */ MSM_ROTATOR_SRC_FORMAT); } return 0; } static int get_img(struct msmfb_data *fbd, int domain, unsigned long *start, unsigned long *len, struct file **p_file, int *p_need, struct ion_handle **p_ihdl, unsigned int secure) { int ret = 0; #ifdef CONFIG_FB struct file *file = NULL; int put_needed, fb_num; #endif *p_need = 0; #ifdef CONFIG_FB if (fbd->flags & MDP_MEMORY_ID_TYPE_FB) { file = fget_light(fbd->memory_id, &put_needed); if (file == NULL) { pr_err("fget_light returned NULL\n"); return -EINVAL; } if (MAJOR(file->f_dentry->d_inode->i_rdev) == FB_MAJOR) { fb_num = MINOR(file->f_dentry->d_inode->i_rdev); if (get_fb_phys_info(start, len, fb_num, ROTATOR_SUBSYSTEM_ID)) { pr_err("get_fb_phys_info() failed\n"); ret = -1; } else { *p_file = file; *p_need = put_needed; } } else { pr_err("invalid FB_MAJOR failed\n"); ret = -1; } if (ret) fput_light(file, put_needed); return ret; } #endif return msm_rotator_iommu_map_buf(fbd->memory_id, domain, start, len, p_ihdl, secure); } static void put_img(struct file *p_file, struct ion_handle *p_ihdl, int domain, unsigned int secure) { #ifdef CONFIG_MSM_MULTIMEDIA_USE_ION if (!IS_ERR_OR_NULL(p_ihdl)) { pr_debug("%s(): p_ihdl %p\n", __func__, p_ihdl); if (rot_iommu_split_domain) { if (!secure) ion_unmap_iommu(msm_rotator_dev->client, p_ihdl, domain, GEN_POOL); } else { ion_unmap_iommu(msm_rotator_dev->client, p_ihdl, ROTATOR_SRC_DOMAIN, GEN_POOL); } ion_free(msm_rotator_dev->client, p_ihdl); } #endif } static int msm_rotator_do_rotate(unsigned long arg) { unsigned int status, format; struct msm_rotator_data_info info; unsigned int in_paddr, out_paddr; unsigned long src_len, dst_len; int use_imem = 0, rc = 0, s; struct file *srcp0_file = NULL, *dstp0_file = NULL; struct file *srcp1_file = NULL, *dstp1_file = NULL; struct ion_handle *srcp0_ihdl = NULL, *dstp0_ihdl = NULL; struct ion_handle *srcp1_ihdl = NULL, *dstp1_ihdl = NULL; int ps0_need, p_need; unsigned int in_chroma_paddr = 0, out_chroma_paddr = 0; unsigned int in_chroma2_paddr = 0; struct msm_rotator_img_info *img_info; struct msm_rotator_mem_planes src_planes, dst_planes; if (copy_from_user(&info, (void __user *)arg, sizeof(info))) return -EFAULT; mutex_lock(&msm_rotator_dev->rotator_lock); for (s = 0; s < MAX_SESSIONS; s++) if ((msm_rotator_dev->img_info[s] != NULL) && (info.session_id == (unsigned int)msm_rotator_dev->img_info[s] )) break; if (s == MAX_SESSIONS) { pr_err("%s() : Attempt to use invalid session_id %d\n", __func__, s); rc = -EINVAL; goto do_rotate_unlock_mutex; } if (msm_rotator_dev->img_info[s]->enable == 0) { dev_dbg(msm_rotator_dev->device, "%s() : Session_id %d not enabled \n", __func__, s); rc = -EINVAL; goto do_rotate_unlock_mutex; } img_info = msm_rotator_dev->img_info[s]; if (msm_rotator_get_plane_sizes(img_info->src.format, img_info->src.width, img_info->src.height, &src_planes)) { pr_err("%s: invalid src format\n", __func__); rc = -EINVAL; goto do_rotate_unlock_mutex; } if (msm_rotator_get_plane_sizes(img_info->dst.format, img_info->dst.width, img_info->dst.height, &dst_planes)) { pr_err("%s: invalid dst format\n", __func__); rc = -EINVAL; goto do_rotate_unlock_mutex; } rc = get_img(&info.src, ROTATOR_SRC_DOMAIN, (unsigned long *)&in_paddr, (unsigned long *)&src_len, &srcp0_file, &ps0_need, &srcp0_ihdl, 0); if (rc) { pr_err("%s: in get_img() failed id=0x%08x\n", DRIVER_NAME, info.src.memory_id); goto do_rotate_unlock_mutex; } rc = get_img(&info.dst, ROTATOR_DST_DOMAIN, (unsigned long *)&out_paddr, (unsigned long *)&dst_len, &dstp0_file, &p_need, &dstp0_ihdl, img_info->secure); if (rc) { pr_err("%s: out get_img() failed id=0x%08x\n", DRIVER_NAME, info.dst.memory_id); goto do_rotate_unlock_mutex; } format = msm_rotator_dev->img_info[s]->src.format; if (((info.version_key & VERSION_KEY_MASK) == 0xA5B4C300) && ((info.version_key & ~VERSION_KEY_MASK) > 0) && (src_planes.num_planes == 2)) { if (checkoffset(info.src.offset, src_planes.plane_size[0], src_len)) { pr_err("%s: invalid src buffer (len=%lu offset=%x)\n", __func__, src_len, info.src.offset); rc = -ERANGE; goto do_rotate_unlock_mutex; } if (checkoffset(info.dst.offset, dst_planes.plane_size[0], dst_len)) { pr_err("%s: invalid dst buffer (len=%lu offset=%x)\n", __func__, dst_len, info.dst.offset); rc = -ERANGE; goto do_rotate_unlock_mutex; } rc = get_img(&info.src_chroma, ROTATOR_SRC_DOMAIN, (unsigned long *)&in_chroma_paddr, (unsigned long *)&src_len, &srcp1_file, &p_need, &srcp1_ihdl, 0); if (rc) { pr_err("%s: in chroma get_img() failed id=0x%08x\n", DRIVER_NAME, info.src_chroma.memory_id); goto do_rotate_unlock_mutex; } rc = get_img(&info.dst_chroma, ROTATOR_DST_DOMAIN, (unsigned long *)&out_chroma_paddr, (unsigned long *)&dst_len, &dstp1_file, &p_need, &dstp1_ihdl, img_info->secure); if (rc) { pr_err("%s: out chroma get_img() failed id=0x%08x\n", DRIVER_NAME, info.dst_chroma.memory_id); goto do_rotate_unlock_mutex; } if (checkoffset(info.src_chroma.offset, src_planes.plane_size[1], src_len)) { pr_err("%s: invalid chr src buf len=%lu offset=%x\n", __func__, src_len, info.src_chroma.offset); rc = -ERANGE; goto do_rotate_unlock_mutex; } if (checkoffset(info.dst_chroma.offset, src_planes.plane_size[1], dst_len)) { pr_err("%s: invalid chr dst buf len=%lu offset=%x\n", __func__, dst_len, info.dst_chroma.offset); rc = -ERANGE; goto do_rotate_unlock_mutex; } in_chroma_paddr += info.src_chroma.offset; out_chroma_paddr += info.dst_chroma.offset; } else { if (checkoffset(info.src.offset, src_planes.total_size, src_len)) { pr_err("%s: invalid src buffer (len=%lu offset=%x)\n", __func__, src_len, info.src.offset); rc = -ERANGE; goto do_rotate_unlock_mutex; } if (checkoffset(info.dst.offset, dst_planes.total_size, dst_len)) { pr_err("%s: invalid dst buffer (len=%lu offset=%x)\n", __func__, dst_len, info.dst.offset); rc = -ERANGE; goto do_rotate_unlock_mutex; } } in_paddr += info.src.offset; out_paddr += info.dst.offset; if (!in_chroma_paddr && src_planes.num_planes >= 2) in_chroma_paddr = in_paddr + src_planes.plane_size[0]; if (!out_chroma_paddr && dst_planes.num_planes >= 2) out_chroma_paddr = out_paddr + dst_planes.plane_size[0]; if (src_planes.num_planes >= 3) in_chroma2_paddr = in_chroma_paddr + src_planes.plane_size[1]; cancel_delayed_work(&msm_rotator_dev->rot_clk_work); if (msm_rotator_dev->rot_clk_state != CLK_EN) { enable_rot_clks(); msm_rotator_dev->rot_clk_state = CLK_EN; } enable_irq(msm_rotator_dev->irq); #ifdef CONFIG_MSM_ROTATOR_USE_IMEM use_imem = msm_rotator_imem_allocate(ROTATOR_REQUEST); #else use_imem = 0; #endif /* * workaround for a hardware bug. rotator hardware hangs when we * use write burst beat size 16 on 128X128 tile fetch mode. As a * temporary fix use 0x42 for BURST_SIZE when imem used. */ if (use_imem) iowrite32(0x42, MSM_ROTATOR_MAX_BURST_SIZE); iowrite32(((msm_rotator_dev->img_info[s]->src_rect.h & 0x1fff) << 16) | (msm_rotator_dev->img_info[s]->src_rect.w & 0x1fff), MSM_ROTATOR_SRC_SIZE); iowrite32(((msm_rotator_dev->img_info[s]->src_rect.y & 0x1fff) << 16) | (msm_rotator_dev->img_info[s]->src_rect.x & 0x1fff), MSM_ROTATOR_SRC_XY); iowrite32(((msm_rotator_dev->img_info[s]->src.height & 0x1fff) << 16) | (msm_rotator_dev->img_info[s]->src.width & 0x1fff), MSM_ROTATOR_SRC_IMAGE_SIZE); switch (format) { case MDP_RGB_565: case MDP_BGR_565: case MDP_RGB_888: case MDP_ARGB_8888: case MDP_RGBA_8888: case MDP_XRGB_8888: case MDP_BGRA_8888: case MDP_RGBX_8888: case MDP_BGRX_8888: case MDP_YCBCR_H1V1: case MDP_YCRCB_H1V1: rc = msm_rotator_rgb_types(msm_rotator_dev->img_info[s], in_paddr, out_paddr, use_imem, msm_rotator_dev->last_session_idx != s); break; case MDP_Y_CBCR_H2V2: case MDP_Y_CRCB_H2V2: case MDP_Y_CB_CR_H2V2: case MDP_Y_CR_CB_H2V2: case MDP_Y_CR_CB_GH2V2: case MDP_Y_CRCB_H2V2_TILE: case MDP_Y_CBCR_H2V2_TILE: rc = msm_rotator_ycxcx_h2v2(msm_rotator_dev->img_info[s], in_paddr, out_paddr, use_imem, msm_rotator_dev->last_session_idx != s, in_chroma_paddr, out_chroma_paddr, in_chroma2_paddr); break; case MDP_Y_CBCR_H2V1: case MDP_Y_CRCB_H2V1: rc = msm_rotator_ycxcx_h2v1(msm_rotator_dev->img_info[s], in_paddr, out_paddr, use_imem, msm_rotator_dev->last_session_idx != s, in_chroma_paddr, out_chroma_paddr); break; case MDP_YCRYCB_H2V1: rc = msm_rotator_ycrycb(msm_rotator_dev->img_info[s], in_paddr, out_paddr, use_imem, msm_rotator_dev->last_session_idx != s, out_chroma_paddr); break; default: rc = -EINVAL; pr_err("%s(): Unsupported format %u\n", __func__, format); goto do_rotate_exit; } if (rc != 0) { msm_rotator_dev->last_session_idx = INVALID_SESSION; pr_err("%s(): Invalid session error\n", __func__); goto do_rotate_exit; } iowrite32(3, MSM_ROTATOR_INTR_ENABLE); msm_rotator_dev->processing = 1; iowrite32(0x1, MSM_ROTATOR_START); wait_event(msm_rotator_dev->wq, (msm_rotator_dev->processing == 0)); status = (unsigned char)ioread32(MSM_ROTATOR_INTR_STATUS); if ((status & 0x03) != 0x01) { pr_err("%s(): AXI Bus Error, issuing SW_RESET\n", __func__); iowrite32(0x1, MSM_ROTATOR_SW_RESET); rc = -EFAULT; } iowrite32(0, MSM_ROTATOR_INTR_ENABLE); iowrite32(3, MSM_ROTATOR_INTR_CLEAR); do_rotate_exit: disable_irq(msm_rotator_dev->irq); #ifdef CONFIG_MSM_ROTATOR_USE_IMEM msm_rotator_imem_free(ROTATOR_REQUEST); #endif schedule_delayed_work(&msm_rotator_dev->rot_clk_work, HZ); do_rotate_unlock_mutex: put_img(dstp1_file, dstp1_ihdl, ROTATOR_DST_DOMAIN, msm_rotator_dev->img_info[s]->secure); put_img(srcp1_file, srcp1_ihdl, ROTATOR_SRC_DOMAIN, 0); put_img(dstp0_file, dstp0_ihdl, ROTATOR_DST_DOMAIN, msm_rotator_dev->img_info[s]->secure); /* only source may use frame buffer */ if (info.src.flags & MDP_MEMORY_ID_TYPE_FB) fput_light(srcp0_file, ps0_need); else put_img(srcp0_file, srcp0_ihdl, ROTATOR_SRC_DOMAIN, 0); mutex_unlock(&msm_rotator_dev->rotator_lock); dev_dbg(msm_rotator_dev->device, "%s() returning rc = %d\n", __func__, rc); return rc; } static void msm_rotator_set_perf_level(u32 wh, u32 is_rgb) { u32 perf_level; if (is_rgb) perf_level = 1; else if (wh <= (640 * 480)) perf_level = 2; else if (wh <= (736 * 1280)) perf_level = 3; else perf_level = 4; #ifdef CONFIG_MSM_BUS_SCALING msm_bus_scale_client_update_request(msm_rotator_dev->bus_client_handle, perf_level); #endif } static int msm_rotator_start(unsigned long arg, struct msm_rotator_fd_info *fd_info) { struct msm_rotator_img_info info; int rc = 0; int s, is_rgb = 0; int first_free_index = INVALID_SESSION; unsigned int dst_w, dst_h; if (copy_from_user(&info, (void __user *)arg, sizeof(info))) return -EFAULT; if ((info.rotations > MSM_ROTATOR_MAX_ROT) || (info.src.height > MSM_ROTATOR_MAX_H) || (info.src.width > MSM_ROTATOR_MAX_W) || (info.dst.height > MSM_ROTATOR_MAX_H) || (info.dst.width > MSM_ROTATOR_MAX_W) || (info.downscale_ratio > MAX_DOWNSCALE_RATIO)) { pr_err("%s: Invalid parameters\n", __func__); return -EINVAL; } if (info.rotations & MDP_ROT_90) { dst_w = info.src_rect.h >> info.downscale_ratio; dst_h = info.src_rect.w >> info.downscale_ratio; } else { dst_w = info.src_rect.w >> info.downscale_ratio; dst_h = info.src_rect.h >> info.downscale_ratio; } if (checkoffset(info.src_rect.x, info.src_rect.w, info.src.width) || checkoffset(info.src_rect.y, info.src_rect.h, info.src.height) || checkoffset(info.dst_x, dst_w, info.dst.width) || checkoffset(info.dst_y, dst_h, info.dst.height)) { pr_err("%s: Invalid src or dst rect\n", __func__); return -ERANGE; } switch (info.src.format) { case MDP_RGB_565: case MDP_BGR_565: case MDP_RGB_888: case MDP_ARGB_8888: case MDP_RGBA_8888: case MDP_XRGB_8888: case MDP_RGBX_8888: case MDP_BGRA_8888: case MDP_BGRX_8888: is_rgb = 1; info.dst.format = info.src.format; break; case MDP_Y_CBCR_H2V1: if (info.rotations & MDP_ROT_90) { info.dst.format = MDP_Y_CBCR_H1V2; break; } case MDP_Y_CRCB_H2V1: if (info.rotations & MDP_ROT_90) { info.dst.format = MDP_Y_CRCB_H1V2; break; } case MDP_Y_CBCR_H2V2: case MDP_Y_CRCB_H2V2: case MDP_YCBCR_H1V1: case MDP_YCRCB_H1V1: info.dst.format = info.src.format; break; case MDP_YCRYCB_H2V1: if (info.rotations & MDP_ROT_90) info.dst.format = MDP_Y_CRCB_H1V2; else info.dst.format = MDP_Y_CRCB_H2V1; break; case MDP_Y_CB_CR_H2V2: case MDP_Y_CBCR_H2V2_TILE: info.dst.format = MDP_Y_CBCR_H2V2; break; case MDP_Y_CR_CB_H2V2: case MDP_Y_CR_CB_GH2V2: case MDP_Y_CRCB_H2V2_TILE: info.dst.format = MDP_Y_CRCB_H2V2; break; default: return -EINVAL; } mutex_lock(&msm_rotator_dev->rotator_lock); msm_rotator_set_perf_level((info.src.width*info.src.height), is_rgb); for (s = 0; s < MAX_SESSIONS; s++) { if ((msm_rotator_dev->img_info[s] != NULL) && (info.session_id == (unsigned int)msm_rotator_dev->img_info[s] )) { *(msm_rotator_dev->img_info[s]) = info; msm_rotator_dev->fd_info[s] = fd_info; if (msm_rotator_dev->last_session_idx == s) msm_rotator_dev->last_session_idx = INVALID_SESSION; break; } if ((msm_rotator_dev->img_info[s] == NULL) && (first_free_index == INVALID_SESSION)) first_free_index = s; } if ((s == MAX_SESSIONS) && (first_free_index != INVALID_SESSION)) { /* allocate a session id */ msm_rotator_dev->img_info[first_free_index] = kzalloc(sizeof(struct msm_rotator_img_info), GFP_KERNEL); if (!msm_rotator_dev->img_info[first_free_index]) { printk(KERN_ERR "%s : unable to alloc mem\n", __func__); rc = -ENOMEM; goto rotator_start_exit; } info.session_id = (unsigned int) msm_rotator_dev->img_info[first_free_index]; *(msm_rotator_dev->img_info[first_free_index]) = info; msm_rotator_dev->fd_info[first_free_index] = fd_info; } else if (s == MAX_SESSIONS) { dev_dbg(msm_rotator_dev->device, "%s: all sessions in use\n", __func__); rc = -EBUSY; } if (rc == 0 && copy_to_user((void __user *)arg, &info, sizeof(info))) rc = -EFAULT; rotator_start_exit: mutex_unlock(&msm_rotator_dev->rotator_lock); return rc; } static int msm_rotator_finish(unsigned long arg) { int rc = 0; int s; unsigned int session_id; if (copy_from_user(&session_id, (void __user *)arg, sizeof(s))) return -EFAULT; mutex_lock(&msm_rotator_dev->rotator_lock); for (s = 0; s < MAX_SESSIONS; s++) { if ((msm_rotator_dev->img_info[s] != NULL) && (session_id == (unsigned int)msm_rotator_dev->img_info[s])) { if (msm_rotator_dev->last_session_idx == s) msm_rotator_dev->last_session_idx = INVALID_SESSION; kfree(msm_rotator_dev->img_info[s]); msm_rotator_dev->img_info[s] = NULL; msm_rotator_dev->fd_info[s] = NULL; break; } } if (s == MAX_SESSIONS) rc = -EINVAL; #ifdef CONFIG_MSM_BUS_SCALING msm_bus_scale_client_update_request(msm_rotator_dev->bus_client_handle, 0); #endif mutex_unlock(&msm_rotator_dev->rotator_lock); return rc; } static int msm_rotator_open(struct inode *inode, struct file *filp) { struct msm_rotator_fd_info *tmp, *fd_info = NULL; int i; if (filp->private_data) return -EBUSY; mutex_lock(&msm_rotator_dev->rotator_lock); for (i = 0; i < MAX_SESSIONS; i++) { if (msm_rotator_dev->fd_info[i] == NULL) break; } if (i == MAX_SESSIONS) { mutex_unlock(&msm_rotator_dev->rotator_lock); return -EBUSY; } list_for_each_entry(tmp, &msm_rotator_dev->fd_list, list) { if (tmp->pid == current->pid) { fd_info = tmp; break; } } if (!fd_info) { fd_info = kzalloc(sizeof(*fd_info), GFP_KERNEL); if (!fd_info) { mutex_unlock(&msm_rotator_dev->rotator_lock); pr_err("%s: insufficient memory to alloc resources\n", __func__); return -ENOMEM; } list_add(&fd_info->list, &msm_rotator_dev->fd_list); fd_info->pid = current->pid; } fd_info->ref_cnt++; mutex_unlock(&msm_rotator_dev->rotator_lock); filp->private_data = fd_info; return 0; } static int msm_rotator_close(struct inode *inode, struct file *filp) { struct msm_rotator_fd_info *fd_info; int s; fd_info = (struct msm_rotator_fd_info *)filp->private_data; mutex_lock(&msm_rotator_dev->rotator_lock); if (--fd_info->ref_cnt > 0) { mutex_unlock(&msm_rotator_dev->rotator_lock); return 0; } for (s = 0; s < MAX_SESSIONS; s++) { if (msm_rotator_dev->img_info[s] != NULL && msm_rotator_dev->fd_info[s] == fd_info) { pr_debug("%s: freeing rotator session %p (pid %d)\n", __func__, msm_rotator_dev->img_info[s], fd_info->pid); kfree(msm_rotator_dev->img_info[s]); msm_rotator_dev->img_info[s] = NULL; msm_rotator_dev->fd_info[s] = NULL; if (msm_rotator_dev->last_session_idx == s) msm_rotator_dev->last_session_idx = INVALID_SESSION; } } list_del(&fd_info->list); kfree(fd_info); mutex_unlock(&msm_rotator_dev->rotator_lock); return 0; } static long msm_rotator_ioctl(struct file *file, unsigned cmd, unsigned long arg) { struct msm_rotator_fd_info *fd_info; if (_IOC_TYPE(cmd) != MSM_ROTATOR_IOCTL_MAGIC) return -ENOTTY; fd_info = (struct msm_rotator_fd_info *)file->private_data; switch (cmd) { case MSM_ROTATOR_IOCTL_START: return msm_rotator_start(arg, fd_info); case MSM_ROTATOR_IOCTL_ROTATE: return msm_rotator_do_rotate(arg); case MSM_ROTATOR_IOCTL_FINISH: return msm_rotator_finish(arg); default: dev_dbg(msm_rotator_dev->device, "unexpected IOCTL %d\n", cmd); return -ENOTTY; } } static const struct file_operations msm_rotator_fops = { .owner = THIS_MODULE, .open = msm_rotator_open, .release = msm_rotator_close, .unlocked_ioctl = msm_rotator_ioctl, }; static int __devinit msm_rotator_probe(struct platform_device *pdev) { int rc = 0; struct resource *res; struct msm_rotator_platform_data *pdata = NULL; int i, number_of_clks; uint32_t ver; msm_rotator_dev = kzalloc(sizeof(struct msm_rotator_dev), GFP_KERNEL); if (!msm_rotator_dev) { printk(KERN_ERR "%s Unable to allocate memory for struct\n", __func__); return -ENOMEM; } for (i = 0; i < MAX_SESSIONS; i++) msm_rotator_dev->img_info[i] = NULL; msm_rotator_dev->last_session_idx = INVALID_SESSION; pdata = pdev->dev.platform_data; number_of_clks = pdata->number_of_clocks; rot_iommu_split_domain = pdata->rot_iommu_split_domain; msm_rotator_dev->imem_owner = IMEM_NO_OWNER; mutex_init(&msm_rotator_dev->imem_lock); INIT_LIST_HEAD(&msm_rotator_dev->fd_list); msm_rotator_dev->imem_clk_state = CLK_DIS; INIT_DELAYED_WORK(&msm_rotator_dev->imem_clk_work, msm_rotator_imem_clk_work_f); msm_rotator_dev->imem_clk = NULL; msm_rotator_dev->pdev = pdev; msm_rotator_dev->core_clk = NULL; msm_rotator_dev->pclk = NULL; #ifdef CONFIG_MSM_BUS_SCALING if (!msm_rotator_dev->bus_client_handle && pdata && pdata->bus_scale_table) { msm_rotator_dev->bus_client_handle = msm_bus_scale_register_client( pdata->bus_scale_table); if (!msm_rotator_dev->bus_client_handle) { pr_err("%s not able to get bus scale handle\n", __func__); } } #endif for (i = 0; i < number_of_clks; i++) { if (pdata->rotator_clks[i].clk_type == ROTATOR_IMEM_CLK) { msm_rotator_dev->imem_clk = clk_get(&msm_rotator_dev->pdev->dev, pdata->rotator_clks[i].clk_name); if (IS_ERR(msm_rotator_dev->imem_clk)) { rc = PTR_ERR(msm_rotator_dev->imem_clk); msm_rotator_dev->imem_clk = NULL; printk(KERN_ERR "%s: cannot get imem_clk " "rc=%d\n", DRIVER_NAME, rc); goto error_imem_clk; } if (pdata->rotator_clks[i].clk_rate) clk_set_rate(msm_rotator_dev->imem_clk, pdata->rotator_clks[i].clk_rate); } if (pdata->rotator_clks[i].clk_type == ROTATOR_PCLK) { msm_rotator_dev->pclk = clk_get(&msm_rotator_dev->pdev->dev, pdata->rotator_clks[i].clk_name); if (IS_ERR(msm_rotator_dev->pclk)) { rc = PTR_ERR(msm_rotator_dev->pclk); msm_rotator_dev->pclk = NULL; printk(KERN_ERR "%s: cannot get pclk rc=%d\n", DRIVER_NAME, rc); goto error_pclk; } if (pdata->rotator_clks[i].clk_rate) clk_set_rate(msm_rotator_dev->pclk, pdata->rotator_clks[i].clk_rate); } if (pdata->rotator_clks[i].clk_type == ROTATOR_CORE_CLK) { msm_rotator_dev->core_clk = clk_get(&msm_rotator_dev->pdev->dev, pdata->rotator_clks[i].clk_name); if (IS_ERR(msm_rotator_dev->core_clk)) { rc = PTR_ERR(msm_rotator_dev->core_clk); msm_rotator_dev->core_clk = NULL; printk(KERN_ERR "%s: cannot get core clk " "rc=%d\n", DRIVER_NAME, rc); goto error_core_clk; } if (pdata->rotator_clks[i].clk_rate) clk_set_rate(msm_rotator_dev->core_clk, pdata->rotator_clks[i].clk_rate); } } msm_rotator_dev->regulator = regulator_get(&msm_rotator_dev->pdev->dev, "vdd"); if (IS_ERR(msm_rotator_dev->regulator)) msm_rotator_dev->regulator = NULL; msm_rotator_dev->rot_clk_state = CLK_DIS; INIT_DELAYED_WORK(&msm_rotator_dev->rot_clk_work, msm_rotator_rot_clk_work_f); mutex_init(&msm_rotator_dev->rotator_lock); #ifdef CONFIG_MSM_MULTIMEDIA_USE_ION msm_rotator_dev->client = msm_ion_client_create(-1, pdev->name); #endif platform_set_drvdata(pdev, msm_rotator_dev); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { printk(KERN_ALERT "%s: could not get IORESOURCE_MEM\n", DRIVER_NAME); rc = -ENODEV; goto error_get_resource; } msm_rotator_dev->io_base = ioremap(res->start, resource_size(res)); #ifdef CONFIG_MSM_ROTATOR_USE_IMEM if (msm_rotator_dev->imem_clk) clk_prepare_enable(msm_rotator_dev->imem_clk); #endif enable_rot_clks(); ver = ioread32(MSM_ROTATOR_HW_VERSION); disable_rot_clks(); #ifdef CONFIG_MSM_ROTATOR_USE_IMEM if (msm_rotator_dev->imem_clk) clk_disable_unprepare(msm_rotator_dev->imem_clk); #endif if (ver != pdata->hardware_version_number) pr_debug("%s: invalid HW version ver 0x%x\n", DRIVER_NAME, ver); rotator_hw_revision = ver; rotator_hw_revision >>= 16; /* bit 31:16 */ rotator_hw_revision &= 0xff; pr_info("%s: rotator_hw_revision=%x\n", __func__, rotator_hw_revision); msm_rotator_dev->irq = platform_get_irq(pdev, 0); if (msm_rotator_dev->irq < 0) { printk(KERN_ALERT "%s: could not get IORESOURCE_IRQ\n", DRIVER_NAME); rc = -ENODEV; goto error_get_irq; } rc = request_irq(msm_rotator_dev->irq, msm_rotator_isr, IRQF_TRIGGER_RISING, DRIVER_NAME, NULL); if (rc) { printk(KERN_ERR "%s: request_irq() failed\n", DRIVER_NAME); goto error_get_irq; } /* we enable the IRQ when we need it in the ioctl */ disable_irq(msm_rotator_dev->irq); rc = alloc_chrdev_region(&msm_rotator_dev->dev_num, 0, 1, DRIVER_NAME); if (rc < 0) { printk(KERN_ERR "%s: alloc_chrdev_region Failed rc = %d\n", __func__, rc); goto error_get_irq; } msm_rotator_dev->class = class_create(THIS_MODULE, DRIVER_NAME); if (IS_ERR(msm_rotator_dev->class)) { rc = PTR_ERR(msm_rotator_dev->class); printk(KERN_ERR "%s: couldn't create class rc = %d\n", DRIVER_NAME, rc); goto error_class_create; } msm_rotator_dev->device = device_create(msm_rotator_dev->class, NULL, msm_rotator_dev->dev_num, NULL, DRIVER_NAME); if (IS_ERR(msm_rotator_dev->device)) { rc = PTR_ERR(msm_rotator_dev->device); printk(KERN_ERR "%s: device_create failed %d\n", DRIVER_NAME, rc); goto error_class_device_create; } cdev_init(&msm_rotator_dev->cdev, &msm_rotator_fops); rc = cdev_add(&msm_rotator_dev->cdev, MKDEV(MAJOR(msm_rotator_dev->dev_num), 0), 1); if (rc < 0) { printk(KERN_ERR "%s: cdev_add failed %d\n", __func__, rc); goto error_cdev_add; } init_waitqueue_head(&msm_rotator_dev->wq); dev_dbg(msm_rotator_dev->device, "probe successful\n"); return rc; error_cdev_add: device_destroy(msm_rotator_dev->class, msm_rotator_dev->dev_num); error_class_device_create: class_destroy(msm_rotator_dev->class); error_class_create: unregister_chrdev_region(msm_rotator_dev->dev_num, 1); error_get_irq: iounmap(msm_rotator_dev->io_base); error_get_resource: mutex_destroy(&msm_rotator_dev->rotator_lock); if (msm_rotator_dev->regulator) regulator_put(msm_rotator_dev->regulator); clk_put(msm_rotator_dev->core_clk); error_core_clk: clk_put(msm_rotator_dev->pclk); error_pclk: if (msm_rotator_dev->imem_clk) clk_put(msm_rotator_dev->imem_clk); error_imem_clk: mutex_destroy(&msm_rotator_dev->imem_lock); kfree(msm_rotator_dev); return rc; } static int __devexit msm_rotator_remove(struct platform_device *plat_dev) { int i; #ifdef CONFIG_MSM_BUS_SCALING msm_bus_scale_unregister_client(msm_rotator_dev->bus_client_handle); #endif free_irq(msm_rotator_dev->irq, NULL); mutex_destroy(&msm_rotator_dev->rotator_lock); cdev_del(&msm_rotator_dev->cdev); device_destroy(msm_rotator_dev->class, msm_rotator_dev->dev_num); class_destroy(msm_rotator_dev->class); unregister_chrdev_region(msm_rotator_dev->dev_num, 1); iounmap(msm_rotator_dev->io_base); if (msm_rotator_dev->imem_clk) { if (msm_rotator_dev->imem_clk_state == CLK_EN) clk_disable_unprepare(msm_rotator_dev->imem_clk); clk_put(msm_rotator_dev->imem_clk); msm_rotator_dev->imem_clk = NULL; } if (msm_rotator_dev->rot_clk_state == CLK_EN) disable_rot_clks(); clk_put(msm_rotator_dev->core_clk); clk_put(msm_rotator_dev->pclk); if (msm_rotator_dev->regulator) regulator_put(msm_rotator_dev->regulator); msm_rotator_dev->core_clk = NULL; msm_rotator_dev->pclk = NULL; mutex_destroy(&msm_rotator_dev->imem_lock); for (i = 0; i < MAX_SESSIONS; i++) if (msm_rotator_dev->img_info[i] != NULL) kfree(msm_rotator_dev->img_info[i]); kfree(msm_rotator_dev); return 0; } #ifdef CONFIG_PM static int msm_rotator_suspend(struct platform_device *dev, pm_message_t state) { mutex_lock(&msm_rotator_dev->imem_lock); if (msm_rotator_dev->imem_clk_state == CLK_EN && msm_rotator_dev->imem_clk) { clk_disable_unprepare(msm_rotator_dev->imem_clk); msm_rotator_dev->imem_clk_state = CLK_SUSPEND; } mutex_unlock(&msm_rotator_dev->imem_lock); mutex_lock(&msm_rotator_dev->rotator_lock); if (msm_rotator_dev->rot_clk_state == CLK_EN) { disable_rot_clks(); msm_rotator_dev->rot_clk_state = CLK_SUSPEND; } mutex_unlock(&msm_rotator_dev->rotator_lock); return 0; } static int msm_rotator_resume(struct platform_device *dev) { mutex_lock(&msm_rotator_dev->imem_lock); if (msm_rotator_dev->imem_clk_state == CLK_SUSPEND && msm_rotator_dev->imem_clk) { clk_prepare_enable(msm_rotator_dev->imem_clk); msm_rotator_dev->imem_clk_state = CLK_EN; } mutex_unlock(&msm_rotator_dev->imem_lock); mutex_lock(&msm_rotator_dev->rotator_lock); if (msm_rotator_dev->rot_clk_state == CLK_SUSPEND) { enable_rot_clks(); msm_rotator_dev->rot_clk_state = CLK_EN; } mutex_unlock(&msm_rotator_dev->rotator_lock); return 0; } #endif static struct platform_driver msm_rotator_platform_driver = { .probe = msm_rotator_probe, .remove = __devexit_p(msm_rotator_remove), #ifdef CONFIG_PM .suspend = msm_rotator_suspend, .resume = msm_rotator_resume, #endif .driver = { .owner = THIS_MODULE, .name = DRIVER_NAME } }; static int __init msm_rotator_init(void) { return platform_driver_register(&msm_rotator_platform_driver); } static void __exit msm_rotator_exit(void) { return platform_driver_unregister(&msm_rotator_platform_driver); } module_init(msm_rotator_init); module_exit(msm_rotator_exit); MODULE_DESCRIPTION("MSM Offline Image Rotator driver"); MODULE_VERSION("1.0"); MODULE_LICENSE("GPL v2");