398 lines
9.1 KiB
C
398 lines
9.1 KiB
C
/*
|
|
* Copyright (C) 2013 Freescale Semiconductor, Inc. All Rights Reserved.
|
|
*/
|
|
|
|
/*
|
|
* The code contained herein is licensed under the GNU General Public
|
|
* License. You may obtain a copy of the GNU General Public License
|
|
* Version 2 or later at the following locations:
|
|
*
|
|
* http://www.opensource.org/licenses/gpl-license.html
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*/
|
|
#include <linux/delay.h>
|
|
#include <linux/ipu.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/mxcfb.h>
|
|
|
|
#include "mxc_dispdrv.h"
|
|
#include "crtc.h"
|
|
|
|
#define DISPDRV_ADV739X "adv739x"
|
|
|
|
#define ADV739X_MODE_NTSC 0
|
|
#define ADV739X_MODE_PAL 1
|
|
|
|
|
|
struct adv739x_data {
|
|
struct platform_device *pdev;
|
|
struct i2c_client *client;
|
|
struct mxc_dispdrv_handle *disp_adv739x;
|
|
struct fb_info *fbi;
|
|
|
|
int ipu_id;
|
|
int disp_id;
|
|
int default_ifmt;
|
|
|
|
int cur_mode;
|
|
int enabled;
|
|
struct notifier_block nb;
|
|
};
|
|
|
|
/*
|
|
* left_margin: used for field0 vStart width in lines
|
|
*
|
|
* right_margin: used for field0 vEnd width in lines
|
|
*
|
|
* up_margin: used for field1 vStart width in lines
|
|
*
|
|
* down_margin: used for field1 vEnd width in lines
|
|
*
|
|
* hsync_len: EAV Code + Blanking Video + SAV Code (in pixel clock count)
|
|
* For BT656 NTSC, it is 4 + 67*4 + 4 = 276.
|
|
* For BT1120 NTSC, it is 4 + 67*2 + 4 = 142.
|
|
* For BT656 PAL, it is 4 + 70*4 + 4 = 288.
|
|
* For BT1120 PAL, it is 4 + 70*2 + 4 = 148.
|
|
*
|
|
* vsync_len: not used, set to 1
|
|
*/
|
|
static struct fb_videomode adv739x_modedb[] = {
|
|
{
|
|
/* NTSC Interlaced output */
|
|
"BT656-NTSC", 60, 720, 480, 37037,
|
|
19, 3,
|
|
20, 3,
|
|
276, 1,
|
|
FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
|
|
FB_VMODE_INTERLACED,
|
|
FB_MODE_IS_DETAILED,},
|
|
{
|
|
/* PAL Interlaced output */
|
|
"BT656-PAL", 50, 720, 576, 37037,
|
|
22, 2,
|
|
23, 2,
|
|
288, 1,
|
|
FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
|
|
FB_VMODE_INTERLACED,
|
|
FB_MODE_IS_DETAILED,},
|
|
};
|
|
static int adv739x_modedb_sz = ARRAY_SIZE(adv739x_modedb);
|
|
|
|
static int adv739x_write(struct i2c_client *client, u8 reg, u8 data)
|
|
{
|
|
int ret = 0;
|
|
ret = i2c_smbus_write_byte_data(client, reg, data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
static int adv739x_read(struct i2c_client *client, u8 reg)
|
|
{
|
|
int data = 0;
|
|
data = i2c_smbus_read_byte_data(client, reg);
|
|
|
|
return data;
|
|
}
|
|
*/
|
|
|
|
static void adv739x_setmode(struct adv739x_data *adv739x, int mode)
|
|
{
|
|
struct i2c_client *client = adv739x->client;
|
|
|
|
if(adv739x->enabled == 0)
|
|
return;
|
|
|
|
dev_dbg(&adv739x->client->dev, "adv739x_setmode: mode = %d.\n", mode);
|
|
switch (mode) {
|
|
case ADV739X_MODE_NTSC:
|
|
// Reg 0x17: reset
|
|
adv739x_write(client, 0x17, 0x02);
|
|
|
|
mdelay(20);
|
|
|
|
// Reg 0x00: DAC1~3 power on
|
|
adv739x_write(client, 0x00, 0x1C);
|
|
|
|
// Reg 0x01: SD input
|
|
adv739x_write(client, 0x01, 0x00);
|
|
|
|
//NTSC
|
|
// Reg 0x80: SD, NTSC
|
|
adv739x_write(client, 0x80, 0x10);
|
|
|
|
// Reg 0x82: SD, CVBS
|
|
adv739x_write(client, 0x82, 0xCB);
|
|
break;
|
|
|
|
case ADV739X_MODE_PAL:
|
|
// Reg 0x17: reset
|
|
adv739x_write(client, 0x17, 0x02);
|
|
|
|
mdelay(20);
|
|
|
|
// Reg 0x00: DAC1~3 power on
|
|
adv739x_write(client, 0x00, 0x1C);
|
|
|
|
// Reg 0x01: SD input
|
|
adv739x_write(client, 0x01, 0x00);
|
|
|
|
// Reg 0x80: SD, PAL
|
|
adv739x_write(client, 0x80, 0x11);
|
|
|
|
// Reg 0x82: SD, CVBS
|
|
adv739x_write(client, 0x82, 0xC3);
|
|
adv739x_write(client, 0x8C, 0xCB);
|
|
adv739x_write(client, 0x8D, 0x8A);
|
|
adv739x_write(client, 0x8E, 0x09);
|
|
adv739x_write(client, 0x8F, 0x2A);
|
|
break;
|
|
|
|
default:
|
|
dev_err(&adv739x->client->dev, "unsupported mode.\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void adv739x_poweroff(struct adv739x_data *adv739x)
|
|
{
|
|
if (adv739x->enabled != 0) {
|
|
dev_dbg(&adv739x->client->dev, "adv739x_poweroff.\n");
|
|
|
|
/* power off the adv739x */
|
|
adv739x_write(adv739x->client, 0x00, 0x1F);
|
|
|
|
adv739x->enabled = 0;
|
|
}
|
|
}
|
|
|
|
static void adv739x_poweron(struct adv739x_data *adv739x)
|
|
{
|
|
if (adv739x->enabled == 0) {
|
|
dev_dbg(&adv739x->client->dev, "adv739x_poweron.\n");
|
|
|
|
adv739x->enabled = 1;
|
|
adv739x_setmode(adv739x, adv739x->cur_mode);
|
|
}
|
|
}
|
|
|
|
int adv739x_fb_event(struct notifier_block *nb, unsigned long val, void *v)
|
|
{
|
|
struct fb_event *event = v;
|
|
struct fb_info *fbi = event->info;
|
|
struct adv739x_data *adv739x = container_of(nb, struct adv739x_data, nb);
|
|
|
|
if (strcmp(event->info->fix.id, adv739x->fbi->fix.id))
|
|
return 0;
|
|
|
|
dev_dbg(&adv739x->client->dev, "%s\n", __func__);
|
|
|
|
fbi->mode = (struct fb_videomode *)fb_match_mode(&fbi->var,
|
|
&fbi->modelist);
|
|
if (!fbi->mode) {
|
|
dev_warn(&adv739x->pdev->dev,
|
|
"adv739x: can not find mode for xres=%d, yres=%d\n",
|
|
fbi->var.xres, fbi->var.yres);
|
|
return 0;
|
|
}
|
|
|
|
switch (val) {
|
|
case FB_EVENT_MODE_CHANGE:
|
|
if(strcmp(fbi->mode->name, "BT656-NTSC") == 0)
|
|
adv739x->cur_mode = ADV739X_MODE_NTSC;
|
|
else if(strcmp(fbi->mode->name, "BT656-PAL") == 0)
|
|
adv739x->cur_mode = ADV739X_MODE_PAL;
|
|
adv739x_setmode(adv739x, adv739x->cur_mode);
|
|
break;
|
|
case FB_EVENT_BLANK:
|
|
if (*((int *)event->data) == FB_BLANK_UNBLANK)
|
|
adv739x_poweron(adv739x);
|
|
else
|
|
adv739x_poweroff(adv739x);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int adv739x_disp_init(struct mxc_dispdrv_handle *disp,
|
|
struct mxc_dispdrv_setting *setting)
|
|
{
|
|
int ret = 0, i;
|
|
struct adv739x_data *adv739x = mxc_dispdrv_getdata(disp);
|
|
struct fb_videomode *modedb = adv739x_modedb;
|
|
int modedb_sz = adv739x_modedb_sz;
|
|
static bool inited = false;
|
|
|
|
dev_dbg(&adv739x->client->dev, "%s\n", __func__);
|
|
if (inited)
|
|
return -EBUSY;
|
|
|
|
inited = true;
|
|
|
|
ret = ipu_di_to_crtc(&adv739x->pdev->dev, adv739x->ipu_id,
|
|
adv739x->disp_id, &setting->crtc);
|
|
if (ret < 0)
|
|
return ret;
|
|
/*
|
|
setting->dev_id = adv739x->ipu_id;
|
|
setting->disp_id = adv739x->disp_id;
|
|
*/
|
|
|
|
ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str,
|
|
modedb, modedb_sz, NULL, setting->default_bpp);
|
|
if (!ret) {
|
|
fb_videomode_to_var(&setting->fbi->var, &modedb[0]);
|
|
setting->if_fmt = adv739x->default_ifmt;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&setting->fbi->modelist);
|
|
for (i = 0; i < modedb_sz; i++) {
|
|
fb_add_videomode(&modedb[i],
|
|
&setting->fbi->modelist);
|
|
}
|
|
|
|
adv739x->fbi = setting->fbi;
|
|
adv739x->enabled = 0;
|
|
adv739x->cur_mode = ADV739X_MODE_NTSC; //default mode
|
|
|
|
adv739x->pdev = platform_device_register_simple("mxc_adv739x", 0, NULL, 0);
|
|
if (IS_ERR(adv739x->pdev)) {
|
|
dev_err(&adv739x->client->dev,
|
|
"Unable to register adv739x as a platform device\n");
|
|
ret = PTR_ERR(adv739x->pdev);
|
|
goto register_pltdev_failed;
|
|
}
|
|
|
|
adv739x->nb.notifier_call = adv739x_fb_event;
|
|
ret = fb_register_client(&adv739x->nb);
|
|
if (ret < 0)
|
|
goto reg_fbclient_failed;
|
|
|
|
return ret;
|
|
|
|
reg_fbclient_failed:
|
|
platform_device_unregister(adv739x->pdev);
|
|
register_pltdev_failed:
|
|
return ret;
|
|
}
|
|
|
|
static void adv739x_disp_deinit(struct mxc_dispdrv_handle *disp)
|
|
{
|
|
struct adv739x_data *adv739x = mxc_dispdrv_getdata(disp);
|
|
|
|
dev_dbg(&adv739x->client->dev, "%s\n", __func__);
|
|
if (adv739x->client->irq)
|
|
free_irq(adv739x->client->irq, adv739x);
|
|
|
|
fb_unregister_client(&adv739x->nb);
|
|
|
|
adv739x_poweroff(adv739x);
|
|
|
|
platform_device_unregister(adv739x->pdev);
|
|
}
|
|
|
|
static struct mxc_dispdrv_driver adv739x_drv = {
|
|
.name = DISPDRV_ADV739X,
|
|
.init = adv739x_disp_init,
|
|
.deinit = adv739x_disp_deinit,
|
|
};
|
|
|
|
static int adv739x_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct adv739x_data *adv739x;
|
|
struct device_node *np = client->dev.of_node;
|
|
int ret = 0;
|
|
|
|
if (!i2c_check_functionality(client->adapter,
|
|
I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA))
|
|
return -ENODEV;
|
|
|
|
adv739x = kzalloc(sizeof(struct adv739x_data), GFP_KERNEL);
|
|
if (!adv739x) {
|
|
ret = -ENOMEM;
|
|
goto alloc_failed;
|
|
}
|
|
|
|
adv739x->client = client;
|
|
|
|
ret = of_property_read_u32(np, "ipu_id", &adv739x->ipu_id);
|
|
if (ret) {
|
|
dev_err(&client->dev, "get of property ipu_id fail\n");
|
|
goto prop_failed;
|
|
}
|
|
ret = of_property_read_u32(np, "disp_id", &adv739x->disp_id);
|
|
if (ret) {
|
|
dev_err(&client->dev, "get of property disp_id fail\n");
|
|
goto prop_failed;
|
|
}
|
|
adv739x->default_ifmt = IPU_PIX_FMT_BT656;
|
|
|
|
adv739x->disp_adv739x = mxc_dispdrv_register(&adv739x_drv);
|
|
mxc_dispdrv_setdata(adv739x->disp_adv739x, adv739x);
|
|
|
|
i2c_set_clientdata(client, adv739x);
|
|
|
|
return 0;
|
|
|
|
prop_failed:
|
|
kfree(adv739x);
|
|
alloc_failed:
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int adv739x_remove(struct i2c_client *client)
|
|
{
|
|
struct adv739x_data *adv739x = i2c_get_clientdata(client);
|
|
|
|
mxc_dispdrv_puthandle(adv739x->disp_adv739x);
|
|
mxc_dispdrv_unregister(adv739x->disp_adv739x);
|
|
kfree(adv739x);
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id adv739x_id[] = {
|
|
{ "mxc_adv739x", 0 },
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, adv739x_id);
|
|
|
|
static struct of_device_id adv739x_dt_ids[] = {
|
|
{ .compatible = "adi,adv7393", },
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
static struct i2c_driver adv739x_i2c_driver = {
|
|
.driver = {
|
|
.name = "mxc_adv739x",
|
|
.of_match_table = adv739x_dt_ids,
|
|
},
|
|
.probe = adv739x_probe,
|
|
.remove = adv739x_remove,
|
|
.id_table = adv739x_id,
|
|
};
|
|
|
|
static int __init adv739x_i2c_init(void)
|
|
{
|
|
return i2c_add_driver(&adv739x_i2c_driver);
|
|
}
|
|
|
|
static void __exit adv739x_i2c_exit(void)
|
|
{
|
|
i2c_del_driver(&adv739x_i2c_driver);
|
|
}
|
|
|
|
module_init(adv739x_i2c_init);
|
|
module_exit(adv739x_i2c_exit);
|
|
|
|
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
|
MODULE_DESCRIPTION("ADV739x TV encoder driver");
|
|
MODULE_LICENSE("GPL");
|
|
|