blob: 2f87803ecea93d8ec648eb75b30971c61d2b9d35 [file] [log] [blame]
/* Copyright (c) 2009-2012, 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 <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/clk.h>
#include <linux/android_pmem.h>
#include <linux/msm_rotator.h>
#include <linux/io.h>
#include <mach/msm_rotator_imem.h>
#include <linux/ktime.h>
#include <linux/workqueue.h>
#include <linux/file.h>
#include <linux/major.h>
#include <linux/regulator/consumer.h>
#include <linux/msm_ion.h>
#ifdef CONFIG_MSM_BUS_SCALING
#include <mach/msm_bus.h>
#include <mach/msm_bus_board.h>
#endif
#include <mach/msm_subsystem_map.h>
#include <mach/iommu_domains.h>
#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,
ion_share_dma_buf(msm_rotator_dev->client, *pihdl));
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:
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_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:
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
#ifdef CONFIG_ANDROID_PMEM
unsigned long vstart;
#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
#ifdef CONFIG_MSM_MULTIMEDIA_USE_ION
return msm_rotator_iommu_map_buf(fbd->memory_id, domain, start,
len, p_ihdl, secure);
#endif
#ifdef CONFIG_ANDROID_PMEM
if (!get_pmem_file(fbd->memory_id, start, &vstart, len, p_file))
return 0;
else
return -ENOMEM;
#endif
}
static void put_img(struct file *p_file, struct ion_handle *p_ihdl,
int domain, unsigned int secure)
{
#ifdef CONFIG_ANDROID_PMEM
if (p_file != NULL)
put_pmem_file(p_file);
#endif
#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_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:
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");