653 lines
17 KiB
C
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");
|