M7350/kernel/drivers/video/msm/tvout_msm.c
2024-09-09 08:52:07 +00:00

653 lines
17 KiB
C

/* Copyright (c) 2008-2011, 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/module.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include "msm_fb.h"
#include "tvenc.h"
#include "external_common.h"
#define TVOUT_HPD_DUTY_CYCLE 3000
#define TV_DIMENSION_MAX_WIDTH 720
#define TV_DIMENSION_MAX_HEIGHT 576
struct tvout_msm_state_type {
struct external_common_state_type common;
struct platform_device *pdev;
struct timer_list hpd_state_timer;
struct timer_list hpd_work_timer;
struct work_struct hpd_work;
uint32 hpd_int_status;
uint32 prev_hpd_int_status;
uint32 five_retry;
int irq;
uint16 y_res;
boolean hpd_initialized;
boolean disp_powered_up;
#ifdef CONFIG_SUSPEND
boolean pm_suspended;
#endif
};
static struct tvout_msm_state_type *tvout_msm_state;
static DEFINE_MUTEX(tvout_msm_state_mutex);
static int tvout_off(struct platform_device *pdev);
static int tvout_on(struct platform_device *pdev);
static void tvout_check_status(void);
static void tvout_msm_turn_on(boolean power_on)
{
uint32 reg_val = 0;
reg_val = TV_IN(TV_ENC_CTL);
if (power_on) {
DEV_DBG("%s: TV Encoder turned on\n", __func__);
reg_val |= TVENC_CTL_ENC_EN;
} else {
DEV_DBG("%s: TV Encoder turned off\n", __func__);
reg_val = 0;
}
/* Enable TV Encoder*/
TV_OUT(TV_ENC_CTL, reg_val);
}
static void tvout_check_status()
{
tvout_msm_state->hpd_int_status &= 0x05;
/* hpd_int_status could either be 0x05 or 0x04 for a cable
plug-out event when cable detect is driven by polling. */
if ((((tvout_msm_state->hpd_int_status == 0x05) ||
(tvout_msm_state->hpd_int_status == 0x04)) &&
(tvout_msm_state->prev_hpd_int_status == BIT(2))) ||
((tvout_msm_state->hpd_int_status == 0x01) &&
(tvout_msm_state->prev_hpd_int_status == BIT(0)))) {
DEV_DBG("%s: cable event sent already!", __func__);
return;
}
if (tvout_msm_state->hpd_int_status & BIT(2)) {
DEV_DBG("%s: cable plug-out\n", __func__);
mutex_lock(&external_common_state_hpd_mutex);
external_common_state->hpd_state = FALSE;
mutex_unlock(&external_common_state_hpd_mutex);
kobject_uevent(external_common_state->uevent_kobj,
KOBJ_OFFLINE);
tvout_msm_state->prev_hpd_int_status = BIT(2);
} else if (tvout_msm_state->hpd_int_status & BIT(0)) {
DEV_DBG("%s: cable plug-in\n", __func__);
mutex_lock(&external_common_state_hpd_mutex);
external_common_state->hpd_state = TRUE;
mutex_unlock(&external_common_state_hpd_mutex);
kobject_uevent(external_common_state->uevent_kobj,
KOBJ_ONLINE);
tvout_msm_state->prev_hpd_int_status = BIT(0);
}
}
/* ISR for TV out cable detect */
static irqreturn_t tvout_msm_isr(int irq, void *dev_id)
{
tvout_msm_state->hpd_int_status = TV_IN(TV_INTR_STATUS);
TV_OUT(TV_INTR_CLEAR, tvout_msm_state->hpd_int_status);
DEV_DBG("%s: ISR: 0x%02x\n", __func__,
tvout_msm_state->hpd_int_status & 0x05);
if (tvenc_pdata->poll)
if (!tvout_msm_state || !tvout_msm_state->disp_powered_up) {
DEV_DBG("%s: ISR ignored, display not yet powered on\n",
__func__);
return IRQ_HANDLED;
}
if (tvout_msm_state->hpd_int_status & BIT(0) ||
tvout_msm_state->hpd_int_status & BIT(2)) {
/* Use .75sec to debounce the interrupt */
mod_timer(&tvout_msm_state->hpd_state_timer, jiffies
+ msecs_to_jiffies(750));
}
return IRQ_HANDLED;
}
/* Interrupt debounce timer */
static void tvout_msm_hpd_state_timer(unsigned long data)
{
#ifdef CONFIG_SUSPEND
mutex_lock(&tvout_msm_state_mutex);
if (tvout_msm_state->pm_suspended) {
mutex_unlock(&tvout_msm_state_mutex);
DEV_WARN("%s: ignored, pm_suspended\n", __func__);
return;
}
mutex_unlock(&tvout_msm_state_mutex);
#endif
if (tvenc_pdata->poll)
if (!tvout_msm_state || !tvout_msm_state->disp_powered_up) {
DEV_DBG("%s: ignored, display powered off\n", __func__);
return;
}
/* TV_INTR_STATUS[0x204]
When a TV_ENC interrupt occurs, then reading this register will
indicate what caused the interrupt since that each bit indicates
the source of the interrupt that had happened. If multiple
interrupt sources had happened, then multiple bits of this
register will be set
Bit 0 : Load present on Video1
Bit 1 : Load present on Video2
Bit 2 : Load removed on Video1
Bit 3 : Load removed on Video2
*/
/* Locking interrupt status is not required because
last status read after debouncing is used */
if ((tvout_msm_state->hpd_int_status & 0x05) == 0x05) {
/* SW-workaround :If the status read after debouncing is
0x05(indicating both load present & load removed- which can't
happen in reality), force an update. If status remains 0x05
after retry, it's a cable unplug event */
if (++tvout_msm_state->five_retry < 2) {
uint32 reg;
DEV_DBG("tvout: Timer: 0x05\n");
TV_OUT(TV_INTR_CLEAR, 0xf);
reg = TV_IN(TV_DAC_INTF);
TV_OUT(TV_DAC_INTF, reg & ~TVENC_LOAD_DETECT_EN);
TV_OUT(TV_INTR_CLEAR, 0xf);
reg = TV_IN(TV_DAC_INTF);
TV_OUT(TV_DAC_INTF, reg | TVENC_LOAD_DETECT_EN);
return;
}
}
tvout_msm_state->five_retry = 0;
tvout_check_status();
}
static void tvout_msm_hpd_work(struct work_struct *work)
{
uint32 reg;
#ifdef CONFIG_SUSPEND
mutex_lock(&tvout_msm_state_mutex);
if (tvout_msm_state->pm_suspended) {
mutex_unlock(&tvout_msm_state_mutex);
DEV_WARN("%s: ignored, pm_suspended\n", __func__);
return;
}
mutex_unlock(&tvout_msm_state_mutex);
#endif
/* Enable power lines & clocks */
tvenc_pdata->pm_vid_en(1);
tvenc_set_clock(CLOCK_ON);
/* Enable encoder to get a stable interrupt */
reg = TV_IN(TV_ENC_CTL);
TV_OUT(TV_ENC_CTL, reg | TVENC_CTL_ENC_EN);
/* SW- workaround to update status register */
reg = TV_IN(TV_DAC_INTF);
TV_OUT(TV_DAC_INTF, reg & ~TVENC_LOAD_DETECT_EN);
TV_OUT(TV_INTR_CLEAR, 0xf);
reg = TV_IN(TV_DAC_INTF);
TV_OUT(TV_DAC_INTF, reg | TVENC_LOAD_DETECT_EN);
tvout_msm_state->hpd_int_status = TV_IN(TV_INTR_STATUS);
/* Disable TV encoder */
reg = TV_IN(TV_ENC_CTL);
TV_OUT(TV_ENC_CTL, reg & ~TVENC_CTL_ENC_EN);
/*Disable power lines & clocks */
tvenc_set_clock(CLOCK_OFF);
tvenc_pdata->pm_vid_en(0);
DEV_DBG("%s: ISR: 0x%02x\n", __func__,
tvout_msm_state->hpd_int_status & 0x05);
mod_timer(&tvout_msm_state->hpd_work_timer, jiffies
+ msecs_to_jiffies(TVOUT_HPD_DUTY_CYCLE));
tvout_check_status();
}
static void tvout_msm_hpd_work_timer(unsigned long data)
{
schedule_work(&tvout_msm_state->hpd_work);
}
static int tvout_on(struct platform_device *pdev)
{
uint32 reg = 0;
struct fb_var_screeninfo *var;
struct msm_fb_data_type *mfd = platform_get_drvdata(pdev);
if (!mfd)
return -ENODEV;
if (mfd->key != MFD_KEY)
return -EINVAL;
#ifdef CONFIG_SUSPEND
mutex_lock(&tvout_msm_state_mutex);
if (tvout_msm_state->pm_suspended) {
mutex_unlock(&tvout_msm_state_mutex);
DEV_WARN("%s: ignored, pm_suspended\n", __func__);
return -ENODEV;
}
mutex_unlock(&tvout_msm_state_mutex);
#endif
var = &mfd->fbi->var;
if (var->reserved[3] >= NTSC_M && var->reserved[3] <= PAL_N)
external_common_state->video_resolution = var->reserved[3];
tvout_msm_state->pdev = pdev;
if (del_timer(&tvout_msm_state->hpd_work_timer))
DEV_DBG("%s: work timer stopped\n", __func__);
TV_OUT(TV_ENC_CTL, 0); /* disable TV encoder */
switch (external_common_state->video_resolution) {
case NTSC_M:
case NTSC_J:
TV_OUT(TV_CGMS, 0x0);
/* NTSC Timing */
TV_OUT(TV_SYNC_1, 0x0020009e);
TV_OUT(TV_SYNC_2, 0x011306B4);
TV_OUT(TV_SYNC_3, 0x0006000C);
TV_OUT(TV_SYNC_4, 0x0028020D);
TV_OUT(TV_SYNC_5, 0x005E02FB);
TV_OUT(TV_SYNC_6, 0x0006000C);
TV_OUT(TV_SYNC_7, 0x00000012);
TV_OUT(TV_BURST_V1, 0x0013020D);
TV_OUT(TV_BURST_V2, 0x0014020C);
TV_OUT(TV_BURST_V3, 0x0013020D);
TV_OUT(TV_BURST_V4, 0x0014020C);
TV_OUT(TV_BURST_H, 0x00AE00F2);
TV_OUT(TV_SOL_REQ_ODD, 0x00280208);
TV_OUT(TV_SOL_REQ_EVEN, 0x00290209);
reg |= TVENC_CTL_TV_MODE_NTSC_M_PAL60;
if (external_common_state->video_resolution == NTSC_M) {
/* Cr gain 11, Cb gain C6, y_gain 97 */
TV_OUT(TV_GAIN, 0x0081B697);
} else {
/* Cr gain 11, Cb gain C6, y_gain 97 */
TV_OUT(TV_GAIN, 0x008bc4a3);
reg |= TVENC_CTL_NTSCJ_MODE;
}
var->yres = 480;
break;
case PAL_BDGHIN:
case PAL_N:
/* PAL Timing */
TV_OUT(TV_SYNC_1, 0x00180097);
TV_OUT(TV_SYNC_3, 0x0005000a);
TV_OUT(TV_SYNC_4, 0x00320271);
TV_OUT(TV_SYNC_5, 0x005602f9);
TV_OUT(TV_SYNC_6, 0x0005000a);
TV_OUT(TV_SYNC_7, 0x0000000f);
TV_OUT(TV_BURST_V1, 0x0012026e);
TV_OUT(TV_BURST_V2, 0x0011026d);
TV_OUT(TV_BURST_V3, 0x00100270);
TV_OUT(TV_BURST_V4, 0x0013026f);
TV_OUT(TV_SOL_REQ_ODD, 0x0030026e);
TV_OUT(TV_SOL_REQ_EVEN, 0x0031026f);
if (external_common_state->video_resolution == PAL_BDGHIN) {
/* Cr gain 11, Cb gain C6, y_gain 97 */
TV_OUT(TV_GAIN, 0x0088c1a0);
TV_OUT(TV_CGMS, 0x00012345);
TV_OUT(TV_SYNC_2, 0x011f06c0);
TV_OUT(TV_BURST_H, 0x00af00ea);
reg |= TVENC_CTL_TV_MODE_PAL_BDGHIN;
} else {
/* Cr gain 11, Cb gain C6, y_gain 97 */
TV_OUT(TV_GAIN, 0x0081b697);
TV_OUT(TV_CGMS, 0x000af317);
TV_OUT(TV_SYNC_2, 0x12006c0);
TV_OUT(TV_BURST_H, 0x00af00fa);
reg |= TVENC_CTL_TV_MODE_PAL_N;
}
var->yres = 576;
break;
case PAL_M:
/* Cr gain 11, Cb gain C6, y_gain 97 */
TV_OUT(TV_GAIN, 0x0081b697);
TV_OUT(TV_CGMS, 0x000af317);
TV_OUT(TV_TEST_MUX, 0x000001c3);
TV_OUT(TV_TEST_MODE, 0x00000002);
/* PAL Timing */
TV_OUT(TV_SYNC_1, 0x0020009e);
TV_OUT(TV_SYNC_2, 0x011306b4);
TV_OUT(TV_SYNC_3, 0x0006000c);
TV_OUT(TV_SYNC_4, 0x0028020D);
TV_OUT(TV_SYNC_5, 0x005e02fb);
TV_OUT(TV_SYNC_6, 0x0006000c);
TV_OUT(TV_SYNC_7, 0x00000012);
TV_OUT(TV_BURST_V1, 0x0012020b);
TV_OUT(TV_BURST_V2, 0x0016020c);
TV_OUT(TV_BURST_V3, 0x00150209);
TV_OUT(TV_BURST_V4, 0x0013020c);
TV_OUT(TV_BURST_H, 0x00bf010b);
TV_OUT(TV_SOL_REQ_ODD, 0x00280208);
TV_OUT(TV_SOL_REQ_EVEN, 0x00290209);
reg |= TVENC_CTL_TV_MODE_PAL_M;
var->yres = 480;
break;
default:
return -ENODEV;
}
reg |= TVENC_CTL_Y_FILTER_EN | TVENC_CTL_CR_FILTER_EN |
TVENC_CTL_CB_FILTER_EN | TVENC_CTL_SINX_FILTER_EN;
/* DC offset to 0. */
TV_OUT(TV_LEVEL, 0x00000000);
TV_OUT(TV_OFFSET, 0x008080f0);
#ifdef CONFIG_FB_MSM_TVOUT_SVIDEO
reg |= TVENC_CTL_S_VIDEO_EN;
#endif
#if defined(CONFIG_FB_MSM_MDP31)
TV_OUT(TV_DAC_INTF, 0x29);
#endif
TV_OUT(TV_ENC_CTL, reg);
if (!tvout_msm_state->hpd_initialized) {
tvout_msm_state->hpd_initialized = TRUE;
/* Load detect enable */
reg = TV_IN(TV_DAC_INTF);
reg |= TVENC_LOAD_DETECT_EN;
TV_OUT(TV_DAC_INTF, reg);
}
tvout_msm_state->disp_powered_up = TRUE;
tvout_msm_turn_on(TRUE);
if (tvenc_pdata->poll) {
/* Enable Load present & removal interrupts for Video1 */
TV_OUT(TV_INTR_ENABLE, 0x5);
/* Enable interrupts when display is on */
enable_irq(tvout_msm_state->irq);
}
return 0;
}
static int tvout_off(struct platform_device *pdev)
{
/* Disable TV encoder irqs when display is off */
if (tvenc_pdata->poll)
disable_irq(tvout_msm_state->irq);
tvout_msm_turn_on(FALSE);
tvout_msm_state->hpd_initialized = FALSE;
tvout_msm_state->disp_powered_up = FALSE;
if (tvenc_pdata->poll) {
mod_timer(&tvout_msm_state->hpd_work_timer, jiffies
+ msecs_to_jiffies(TVOUT_HPD_DUTY_CYCLE));
}
return 0;
}
static int __devinit tvout_probe(struct platform_device *pdev)
{
int rc = 0;
uint32 reg;
struct platform_device *fb_dev;
#ifdef CONFIG_FB_MSM_TVOUT_NTSC_M
external_common_state->video_resolution = NTSC_M;
#elif defined CONFIG_FB_MSM_TVOUT_NTSC_J
external_common_state->video_resolution = NTSC_J;
#elif defined CONFIG_FB_MSM_TVOUT_PAL_M
external_common_state->video_resolution = PAL_M;
#elif defined CONFIG_FB_MSM_TVOUT_PAL_N
external_common_state->video_resolution = PAL_N;
#elif defined CONFIG_FB_MSM_TVOUT_PAL_BDGHIN
external_common_state->video_resolution = PAL_BDGHIN;
#endif
external_common_state->dev = &pdev->dev;
if (pdev->id == 0) {
struct resource *res;
#define GET_RES(name, mode) do { \
res = platform_get_resource_byname(pdev, mode, name); \
if (!res) { \
DEV_DBG("'" name "' resource not found\n"); \
rc = -ENODEV; \
goto error; \
} \
} while (0)
#define GET_IRQ(var, name) do { \
GET_RES(name, IORESOURCE_IRQ); \
var = res->start; \
} while (0)
GET_IRQ(tvout_msm_state->irq, "tvout_device_irq");
#undef GET_IRQ
#undef GET_RES
return 0;
}
DEV_DBG("%s: tvout_msm_state->irq : %d",
__func__, tvout_msm_state->irq);
rc = request_irq(tvout_msm_state->irq, &tvout_msm_isr,
IRQF_TRIGGER_HIGH, "tvout_msm_isr", NULL);
if (rc) {
DEV_DBG("Init FAILED: IRQ request, rc=%d\n", rc);
goto error;
}
disable_irq(tvout_msm_state->irq);
init_timer(&tvout_msm_state->hpd_state_timer);
tvout_msm_state->hpd_state_timer.function =
tvout_msm_hpd_state_timer;
tvout_msm_state->hpd_state_timer.data = (uint32)NULL;
tvout_msm_state->hpd_state_timer.expires = jiffies
+ msecs_to_jiffies(1000);
if (tvenc_pdata->poll) {
init_timer(&tvout_msm_state->hpd_work_timer);
tvout_msm_state->hpd_work_timer.function =
tvout_msm_hpd_work_timer;
tvout_msm_state->hpd_work_timer.data = (uint32)NULL;
tvout_msm_state->hpd_work_timer.expires = jiffies
+ msecs_to_jiffies(1000);
}
fb_dev = msm_fb_add_device(pdev);
if (fb_dev) {
rc = external_common_state_create(fb_dev);
if (rc) {
DEV_ERR("Init FAILED: tvout_msm_state_create, rc=%d\n",
rc);
goto error;
}
if (tvenc_pdata->poll) {
/* Start polling timer to detect load */
mod_timer(&tvout_msm_state->hpd_work_timer, jiffies
+ msecs_to_jiffies(TVOUT_HPD_DUTY_CYCLE));
} else {
/* Enable interrupt to detect load */
tvenc_set_encoder_clock(CLOCK_ON);
reg = TV_IN(TV_DAC_INTF);
reg |= TVENC_LOAD_DETECT_EN;
TV_OUT(TV_DAC_INTF, reg);
TV_OUT(TV_INTR_ENABLE, 0x5);
enable_irq(tvout_msm_state->irq);
}
} else
DEV_ERR("Init FAILED: failed to add fb device\n");
error:
return 0;
}
static int __devexit tvout_remove(struct platform_device *pdev)
{
external_common_state_remove();
kfree(tvout_msm_state);
tvout_msm_state = NULL;
return 0;
}
#ifdef CONFIG_SUSPEND
static int tvout_device_pm_suspend(struct device *dev)
{
mutex_lock(&tvout_msm_state_mutex);
if (tvout_msm_state->pm_suspended) {
mutex_unlock(&tvout_msm_state_mutex);
return 0;
}
if (tvenc_pdata->poll) {
if (del_timer(&tvout_msm_state->hpd_work_timer))
DEV_DBG("%s: suspending cable detect timer\n",
__func__);
} else {
disable_irq(tvout_msm_state->irq);
tvenc_set_encoder_clock(CLOCK_OFF);
}
tvout_msm_state->pm_suspended = TRUE;
mutex_unlock(&tvout_msm_state_mutex);
return 0;
}
static int tvout_device_pm_resume(struct device *dev)
{
mutex_lock(&tvout_msm_state_mutex);
if (!tvout_msm_state->pm_suspended) {
mutex_unlock(&tvout_msm_state_mutex);
return 0;
}
if (tvenc_pdata->poll) {
tvout_msm_state->pm_suspended = FALSE;
mod_timer(&tvout_msm_state->hpd_work_timer, jiffies
+ msecs_to_jiffies(TVOUT_HPD_DUTY_CYCLE));
mutex_unlock(&tvout_msm_state_mutex);
DEV_DBG("%s: resuming cable detect timer\n", __func__);
} else {
tvenc_set_encoder_clock(CLOCK_ON);
tvout_msm_state->pm_suspended = FALSE;
mutex_unlock(&tvout_msm_state_mutex);
enable_irq(tvout_msm_state->irq);
DEV_DBG("%s: enable cable detect interrupt\n", __func__);
}
return 0;
}
#else
#define tvout_device_pm_suspend NULL
#define tvout_device_pm_resume NULL
#endif
static const struct dev_pm_ops tvout_device_pm_ops = {
.suspend = tvout_device_pm_suspend,
.resume = tvout_device_pm_resume,
};
static struct platform_driver this_driver = {
.probe = tvout_probe,
.remove = tvout_remove,
.driver = {
.name = "tvout_device",
.pm = &tvout_device_pm_ops,
},
};
static struct msm_fb_panel_data tvout_panel_data = {
.panel_info.xres = TV_DIMENSION_MAX_WIDTH,
.panel_info.yres = TV_DIMENSION_MAX_HEIGHT,
.panel_info.type = TV_PANEL,
.panel_info.pdest = DISPLAY_2,
.panel_info.wait_cycle = 0,
#ifdef CONFIG_FB_MSM_MDP40
.panel_info.bpp = 24,
#else
.panel_info.bpp = 16,
#endif
.panel_info.fb_num = 2,
.on = tvout_on,
.off = tvout_off,
};
static struct platform_device this_device = {
.name = "tvout_device",
.id = 1,
.dev = {
.platform_data = &tvout_panel_data,
}
};
static int __init tvout_init(void)
{
int ret;
if (msm_fb_detect_client("tvout_msm"))
return 0;
tvout_msm_state = kzalloc(sizeof(*tvout_msm_state), GFP_KERNEL);
if (!tvout_msm_state) {
DEV_ERR("tvout_msm_init FAILED: out of memory\n");
ret = -ENOMEM;
goto init_exit;
}
external_common_state = &tvout_msm_state->common;
ret = platform_driver_register(&this_driver);
if (ret) {
DEV_ERR("tvout_device_init FAILED: platform_driver_register\
rc=%d\n", ret);
goto init_exit;
}
ret = platform_device_register(&this_device);
if (ret) {
DEV_ERR("tvout_device_init FAILED: platform_driver_register\
rc=%d\n", ret);
platform_driver_unregister(&this_driver);
goto init_exit;
}
INIT_WORK(&tvout_msm_state->hpd_work, tvout_msm_hpd_work);
return 0;
init_exit:
kfree(tvout_msm_state);
tvout_msm_state = NULL;
return ret;
}
static void __exit tvout_exit(void)
{
platform_device_unregister(&this_device);
platform_driver_unregister(&this_driver);
}
module_init(tvout_init);
module_exit(tvout_exit);
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");
MODULE_AUTHOR("Qualcomm Innovation Center, Inc.");
MODULE_DESCRIPTION("TV out driver");