| /* |
| * drivers/media/video/omap/sil9022.c |
| * |
| * Copyright (C) 2009 Texas Instruments |
| * |
| * This file is licensed under the terms of the GNU General Public License |
| * version 2. This program is licensed "as is" without any warranty of any |
| * kind, whether express or implied. |
| * |
| * SIL9022 |
| * Owner: kiran Chitriki |
| * |
| */ |
| |
| /***********************************/ |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/string.h> |
| #include <linux/types.h> |
| #include <linux/io.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/i2c.h> |
| #include <linux/device.h> |
| #include <linux/delay.h> |
| #include <linux/platform_device.h> |
| #include <linux/sil9022.h> |
| |
| #include <mach/display.h> |
| #include <mach/io.h> |
| |
| static struct i2c_client *sil9022_client; |
| |
| static struct omap_video_timings omap_dss_hdmi_timings = { |
| .x_res = HDMI_XRES, |
| .y_res = HDMI_YRES, |
| .pixel_clock = HDMI_PIXCLOCK_MAX, |
| .hfp = 63, |
| .hsw = 255, |
| .hbp = 49, |
| .vfp = 5, |
| .vsw = 20, |
| .vbp = 4, |
| }; |
| |
| static struct hdmi_reg_data hdmi_tpi_audio_config_data[] = { |
| /* Transmitter is brought to Full operation when value of power |
| * state register is 0x0 */ |
| { HDMI_TPI_POWER_STATE_CTRL_REG, TPI_AVI_POWER_STATE_D0 }, |
| /* TMDS output lines active. bit 3 1:TMDS inactive, 0: TMDS active */ |
| { HDMI_SYS_CTRL_DATA_REG, 0x01 }, |
| /*HDCP Enable - Disable */ |
| { HDMI_TPI_HDCP_CONTROLDATA_REG, 0 }, |
| /* I2S mode , Mute Enabled , PCM */ |
| { HDMI_TPI_AUDIO_CONFIG_BYTE2_REG, TPI_AUDIO_INTERFACE_I2S | |
| TPI_AUDIO_MUTE_ENABLE | |
| TPI_AUDIO_CODING_PCM }, |
| /* I2S Input configuration register */ |
| { HDMI_TPI_I2S_INPUT_CONFIG_REG, TPI_I2S_SCK_EDGE_RISING | |
| TPI_I2S_MCLK_MULTIPLIER_256 | |
| TPI_I2S_WS_POLARITY_HIGH | |
| TPI_I2S_SD_JUSTIFY_LEFT | |
| TPI_I2S_SD_DIRECTION_MSB_FIRST | |
| TPI_I2S_FIRST_BIT_SHIFT_YES }, |
| /* I2S Enable ad Mapping Register */ |
| { HDMI_TPI_I2S_ENABLE_MAPPING_REG, TPI_I2S_SD_CHANNEL_ENABLE | |
| TPI_I2S_SD_FIFO_0 | |
| TPI_I2S_DOWNSAMPLE_DISABLE | |
| TPI_I2S_LF_RT_SWAP_NO | |
| TPI_I2S_SD_CONFIG_SELECT_SD0 }, |
| { HDMI_TPI_I2S_ENABLE_MAPPING_REG, TPI_I2S_SD_CHANNEL_DISABLE | |
| TPI_I2S_SD_FIFO_1 | |
| TPI_I2S_DOWNSAMPLE_DISABLE | |
| TPI_I2S_LF_RT_SWAP_NO | |
| TPI_I2S_SD_CONFIG_SELECT_SD1 }, |
| { HDMI_TPI_I2S_ENABLE_MAPPING_REG, TPI_I2S_SD_CHANNEL_DISABLE | |
| TPI_I2S_SD_FIFO_2 | |
| TPI_I2S_DOWNSAMPLE_DISABLE | |
| TPI_I2S_LF_RT_SWAP_NO | |
| TPI_I2S_SD_CONFIG_SELECT_SD2 }, |
| { HDMI_TPI_I2S_ENABLE_MAPPING_REG, TPI_I2S_SD_CHANNEL_DISABLE | |
| TPI_I2S_SD_FIFO_3 | |
| TPI_I2S_DOWNSAMPLE_DISABLE | |
| TPI_I2S_LF_RT_SWAP_NO | |
| TPI_I2S_SD_CONFIG_SELECT_SD3 }, |
| { HDMI_TPI_AUDIO_CONFIG_BYTE3_REG, TPI_AUDIO_SAMPLE_SIZE_16 | |
| TPI_AUDIO_FREQ_44KHZ | |
| TPI_AUDIO_2_CHANNEL }, |
| /* Speaker Configuration refer CEA Specification*/ |
| { HDMI_TPI_AUDIO_CONFIG_BYTE4_REG, (0x0 << 0)}, |
| /* Stream Header Settings */ |
| { HDMI_TPI_I2S_STRM_HDR_0_REG, I2S_CHAN_STATUS_MODE }, |
| { HDMI_TPI_I2S_STRM_HDR_1_REG, I2S_CHAN_STATUS_CAT_CODE }, |
| { HDMI_TPI_I2S_STRM_HDR_2_REG, I2S_CHAN_SOURCE_CHANNEL_NUM }, |
| { HDMI_TPI_I2S_STRM_HDR_3_REG, I2S_CHAN_ACCURACY_N_44_SAMPLING_FS }, |
| { HDMI_TPI_I2S_STRM_HDR_4_REG, I2S_CHAN_ORIGIN_FS_N_SAMP_LENGTH }, |
| /* Infoframe data Select */ |
| { HDMI_CPI_MISC_IF_SELECT_REG, HDMI_INFOFRAME_TX_ENABLE | |
| HDMI_INFOFRAME_TX_REPEAT | |
| HDMI_AUDIO_INFOFRAME }, |
| }; |
| |
| static u8 misc_audio_info_frame_data[] = { |
| MISC_INFOFRAME_TYPE | MISC_INFOFRAME_ALWAYS_SET, |
| MISC_INFOFRAME_VERSION, |
| MISC_INFOFRAME_LENGTH, |
| 0, /* Checksum byte*/ |
| HDMI_SH_PCM | HDMI_SH_TWO_CHANNELS, |
| HDMI_SH_44KHz | HDMI_SH_16BIT, /* 44.1 KHz*/ |
| 0x0, /* Default 0*/ |
| HDMI_SH_SPKR_FLFR, |
| HDMI_SH_0dB_ATUN | 0x1, /* 0 dB Attenuation*/ |
| 0x0, |
| 0x0, |
| 0x0, |
| 0x0, |
| 0x0 |
| }; |
| |
| static u8 avi_info_frame_data[] = { |
| 0x00, |
| 0x00, |
| 0xA8, |
| 0x00, |
| 0x04, |
| 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| 0x00, |
| 0x00 |
| }; |
| |
| static int |
| sil9022_blockwrite_reg(struct i2c_client *client, |
| u8 reg, u16 alength, u8 *val, u16 *out_len) |
| { |
| int err = 0, i; |
| struct i2c_msg msg[1]; |
| u8 data[2]; |
| |
| if (!client->adapter) { |
| dev_err(&client->dev, "<%s> ERROR: No HDMI Device\n", __func__); |
| return -ENODEV; |
| } |
| |
| msg->addr = client->addr; |
| msg->flags = I2C_M_WR; |
| msg->len = 2; |
| msg->buf = data; |
| |
| /* high byte goes out first */ |
| data[0] = reg >> 8; |
| |
| for (i = 0; i < alength - 1; i++) { |
| data[1] = val[i]; |
| err = i2c_transfer(client->adapter, msg, 1); |
| udelay(50); |
| dev_dbg(&client->dev, "<%s> i2c Block write at 0x%x, " |
| "*val=%d flags=%d byte[%d] err=%d\n", |
| __func__, data[0], data[1], msg->flags, i, err); |
| if (err < 0) |
| break; |
| } |
| /* set the number of bytes written*/ |
| *out_len = i; |
| |
| if (err < 0) { |
| dev_err(&client->dev, "<%s> ERROR: i2c Block Write at 0x%x, " |
| "*val=%d flags=%d bytes written=%d " |
| "err=%d\n", |
| __func__, data[0], data[1], msg->flags, i, err); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int |
| sil9022_blockread_reg(struct i2c_client *client, |
| u16 data_length, u16 alength, |
| u8 reg, u8 *val, u16 *out_len) |
| { |
| int err = 0, i; |
| struct i2c_msg msg[1]; |
| u8 data[2]; |
| |
| if (!client->adapter) { |
| dev_err(&client->dev, "<%s> ERROR: No HDMI Device\n", __func__); |
| return -ENODEV; |
| } |
| |
| msg->addr = client->addr; |
| msg->flags = I2C_M_WR; |
| msg->len = 1; |
| msg->buf = data; |
| |
| /* High byte goes out first */ |
| data[0] = reg; |
| |
| for (i = 0; i < alength; i++) { |
| err = i2c_transfer(client->adapter, msg, 1); |
| dev_dbg(&client->dev, "<%s> i2c Block Read1 at 0x%x, " |
| "*val=%d flags=%d err=%d\n", |
| __func__, data[0], data[1], msg->flags, err); |
| if (err >= 0) { |
| mdelay(3); |
| msg->flags = I2C_M_RD; |
| msg->len = data_length; |
| err = i2c_transfer(client->adapter, msg, 1); |
| } else |
| break; |
| if (err >= 0) { |
| val[i] = 0; |
| /* High byte comes first */ |
| if (data_length == 1) |
| val[i] = data[0]; |
| else if (data_length == 2) |
| val[i] = data[1] + (data[0] << 8); |
| dev_dbg(&client->dev, "<%s> i2c Block Read2 at 0x%x, " |
| "*val=%d flags=%d byte=%d " |
| "err=%d\n", |
| __func__, reg, val[i], msg->flags, i, err); |
| } else |
| break; |
| } |
| *out_len = i; |
| dev_info(&client->dev, "<%s> i2c Block Read at 0x%x, bytes read = %d\n", |
| __func__, reg, *out_len); |
| |
| if (err < 0) { |
| dev_err(&client->dev, "<%s> ERROR: i2c Read at 0x%x, " |
| "*val=%d flags=%d bytes read=%d err=%d\n", |
| __func__, reg, *val, msg->flags, i, err); |
| return err; |
| } |
| return 0; |
| } |
| |
| |
| /* Write a value to a register in sil9022 device. |
| * @client: i2c driver client structure. |
| * @reg: Address of the register to read value from. |
| * @val: Value to be written to a specific register. |
| * Returns zero if successful, or non-zero otherwise. |
| */ |
| static int |
| sil9022_write_reg(struct i2c_client *client, u8 reg, u8 val) |
| { |
| int err = 0; |
| struct i2c_msg msg[1]; |
| u8 data[2]; |
| int retries = 0; |
| |
| if (!client->adapter) { |
| dev_err(&client->dev, "<%s> ERROR: No HDMI Device\n", __func__); |
| return -ENODEV; |
| } |
| |
| retry: |
| msg->addr = client->addr; |
| msg->flags = I2C_M_WR; |
| msg->len = 2; |
| msg->buf = data; |
| |
| data[0] = reg; |
| data[1] = val; |
| |
| err = i2c_transfer(client->adapter, msg, 1); |
| dev_dbg(&client->dev, "<%s> i2c write at=%x " |
| "val=%x flags=%d err=%d\n", |
| __func__, data[0], data[1], msg->flags, err); |
| udelay(50); |
| |
| if (err >= 0) |
| return 0; |
| |
| dev_err(&client->dev, "<%s> ERROR: i2c write at=%x " |
| "val=%x flags=%d err=%d\n", |
| __func__, data[0], data[1], msg->flags, err); |
| if (retries <= 5) { |
| dev_info(&client->dev, "Retrying I2C... %d\n", retries); |
| retries++; |
| set_current_state(TASK_UNINTERRUPTIBLE); |
| schedule_timeout(msecs_to_jiffies(20)); |
| goto retry; |
| } |
| return err; |
| } |
| |
| /* |
| * Read a value from a register in sil9022 device. |
| * The value is returned in 'val'. |
| * Returns zero if successful, or non-zero otherwise. |
| */ |
| static int |
| sil9022_read_reg(struct i2c_client *client, u16 data_length, u8 reg, u8 *val) |
| { |
| int err = 0; |
| struct i2c_msg msg[1]; |
| u8 data[2]; |
| |
| if (!client->adapter) { |
| dev_err(&client->dev, "<%s> ERROR: No HDMI Device\n", __func__); |
| return -ENODEV; |
| } |
| |
| msg->addr = client->addr; |
| msg->flags = I2C_M_WR; |
| msg->len = 1; |
| msg->buf = data; |
| |
| data[0] = reg; |
| err = i2c_transfer(client->adapter, msg, 1); |
| dev_dbg(&client->dev, "<%s> i2c Read1 reg=%x val=%d " |
| "flags=%d err=%d\n", |
| __func__, reg, data[1], msg->flags, err); |
| |
| if (err >= 0) { |
| mdelay(3); |
| msg->flags = I2C_M_RD; |
| msg->len = data_length; |
| err = i2c_transfer(client->adapter, msg, 1); |
| } |
| |
| if (err >= 0) { |
| *val = 0; |
| if (data_length == 1) |
| *val = data[0]; |
| else if (data_length == 2) |
| *val = data[1] + (data[0] << 8); |
| dev_dbg(&client->dev, "<%s> i2c Read2 at 0x%x, *val=%d " |
| "flags=%d err=%d\n", |
| __func__, reg, *val, msg->flags, err); |
| return 0; |
| } |
| |
| dev_err(&client->dev, "<%s> ERROR: i2c Read at 0x%x, " |
| "*val=%d flags=%d err=%d\n", |
| __func__, reg, *val, msg->flags, err); |
| return err; |
| } |
| |
| static int |
| hdmi_read_edid(struct i2c_client *client, u16 len, |
| char *p_buffer, u16 *out_len) |
| { |
| int err = 0; |
| u8 val = 0; |
| int retries = 0; |
| |
| len = (len < HDMI_EDID_MAX_LENGTH) ? len : HDMI_EDID_MAX_LENGTH; |
| |
| /* Request DDC bus access to read EDID info from HDTV */ |
| dev_info(&client->dev, "<%s> Reading HDMI EDID\n", __func__); |
| |
| err = sil9022_read_reg(client, 1, HDMI_SYS_CTRL_DATA_REG, &val); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: Reading DDC BUS REQUEST\n", __func__); |
| return err; |
| } |
| |
| val |= TPI_SYS_CTRL_DDC_BUS_REQUEST; |
| err = sil9022_write_reg(client, HDMI_SYS_CTRL_DATA_REG, val); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: Writing DDC BUS REQUEST\n", __func__); |
| return err; |
| } |
| |
| /* Poll for bus access granted */ |
| dev_info(&client->dev, "<%s> Poll for DDC bus access\n", __func__); |
| val = 0; |
| do { |
| err = sil9022_read_reg(client, 1, HDMI_SYS_CTRL_DATA_REG, &val); |
| if (retries++ > 100) |
| return err; |
| |
| } while ((val & TPI_SYS_CTRL_DDC_BUS_GRANTED) == 0); |
| |
| /* Close the switch to the DDC */ |
| val |= TPI_SYS_CTRL_DDC_BUS_REQUEST | TPI_SYS_CTRL_DDC_BUS_GRANTED; |
| err = sil9022_write_reg(client, HDMI_SYS_CTRL_DATA_REG, val); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: Close switch to DDC BUS REQUEST\n", |
| __func__); |
| return err; |
| } |
| |
| /* Read the EDID structure from the monitor I2C address */ |
| memset(p_buffer, 0, len); |
| /* I2C SetSlaveAddress to HDMI_I2C_MONITOR_ADDRESS */ |
| |
| client->addr = HDMI_I2C_MONITOR_ADDRESS; |
| err = sil9022_blockread_reg(client, 1, len, |
| 0x00, p_buffer, out_len); |
| if (err < 0 || *out_len <= 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: Reading EDID from " |
| "HDMI_I2C_MONITOR_ADDRESS\n", __func__); |
| return err; |
| } |
| |
| /* Release DDC bus access */ |
| client->addr = SI9022_I2CSLAVEADDRESS; |
| val &= ~(TPI_SYS_CTRL_DDC_BUS_REQUEST | TPI_SYS_CTRL_DDC_BUS_GRANTED); |
| err = sil9022_write_reg(client, HDMI_SYS_CTRL_DATA_REG, val); |
| |
| if (err < 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: Releasing DDC Bus Access\n", |
| __func__); |
| return err; |
| } |
| |
| /* Success */ |
| return 0; |
| } |
| |
| static int |
| hdmi_enable_audio(struct i2c_client *client) |
| { |
| int err = 0; |
| u8 val = 0; |
| u8 crc = 0; |
| u32 count = 0; |
| int index = 0; |
| |
| for (index = 0; |
| index < sizeof(hdmi_tpi_audio_config_data)/sizeof(hdmi_reg_data); |
| index++) { |
| err = sil9022_write_reg( |
| client, |
| hdmi_tpi_audio_config_data[index].reg_offset, |
| hdmi_tpi_audio_config_data[index].value); |
| if (err != 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: Writing " |
| "tpi_audio_config_data[%d]={ %d, %d }\n", |
| __func__, index, |
| hdmi_tpi_audio_config_data[index].reg_offset, |
| hdmi_tpi_audio_config_data[index].value); |
| return err; |
| } |
| } |
| |
| /* Fill the checksum byte for Infoframe data*/ |
| count = 0; |
| while (count < MISC_INFOFRAME_SIZE_MEMORY) { |
| crc += misc_audio_info_frame_data[count]; |
| count++; |
| } |
| crc = 0x100 - crc; |
| |
| /* Fill CRC Byte*/ |
| misc_audio_info_frame_data[0x3] = crc; |
| |
| for (count = 0; count < MISC_INFOFRAME_SIZE_MEMORY; count++) { |
| err = sil9022_write_reg(client, |
| (HDMI_CPI_MISC_IF_OFFSET + count), |
| misc_audio_info_frame_data[count]); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: writing audio info frame" |
| " CRC data: %d\n", __func__, count); |
| return err; |
| } |
| } |
| |
| /* Decode Level 0 Packets */ |
| val = 0x2; |
| sil9022_write_reg(client, 0xBC, val); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: writing level 0 packets to 0xBC\n", |
| __func__); |
| return err; |
| } |
| |
| val = 0x24; |
| err = sil9022_write_reg(client, 0xBD, val); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: writing level 0 packets to 0xBD\n", |
| __func__); |
| return err; |
| } |
| |
| val = 0x2; |
| err = sil9022_write_reg(client, 0xBE, val); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: writing level 0 packets to 0xBE\n", |
| __func__); |
| return err; |
| } |
| |
| /* Disable Mute */ |
| val = TPI_AUDIO_INTERFACE_I2S | |
| TPI_AUDIO_MUTE_DISABLE | |
| TPI_AUDIO_CODING_PCM; |
| err = sil9022_write_reg(client, HDMI_TPI_AUDIO_CONFIG_BYTE2_REG, val); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: Disabling mute\n", |
| __func__); |
| return err; |
| } |
| |
| dev_info(&client->dev, "<%s> hdmi audio enabled\n", |
| __func__); |
| return 0; |
| |
| } |
| |
| static int |
| hdmi_disable_audio(struct i2c_client *client) |
| { |
| u8 val = 0; |
| int err = 0; |
| /* Disable Audio */ |
| val = TPI_AUDIO_INTERFACE_DISABLE; |
| err = sil9022_write_reg(client, HDMI_TPI_AUDIO_CONFIG_BYTE2_REG, val); |
| if (err < 0) |
| dev_err(&client->dev, |
| "<%s> ERROR: Disisable audio interface", __func__); |
| |
| dev_info(&client->dev, "<%s> hdmi audio disabled\n", __func__); |
| return err; |
| } |
| |
| static int |
| hdmi_enable(void) |
| { |
| int err; |
| u8 val, vals[14]; |
| int i; |
| u16 out_len = 0; |
| u8 edid[HDMI_EDID_MAX_LENGTH]; |
| HDMI_EDID *p_edid = (HDMI_EDID *)edid; |
| HDMI_EDID_DTD *p_video_spec = NULL; |
| HDMI_EDID_DTD *p_monitor_limits = NULL; |
| |
| memset(edid, 0, HDMI_EDID_MAX_LENGTH); |
| memset(vals, 0, 14); |
| |
| err = hdmi_read_edid(sil9022_client, HDMI_EDID_MAX_LENGTH, |
| edid, &out_len); |
| if (err < 0 || out_len == 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> Unable to read EDID for monitor\n", __func__); |
| return err; |
| } |
| |
| /* Determine DTD info for the attached monitor */ |
| for (i = 0; i < HDMI_EDID_MAX_DTDS; i++) { |
| if (p_edid->DTD[i].monitor_limits.block_type == |
| HDMI_EDID_DTD_TAG_MONITOR_LIMITS) |
| p_monitor_limits = &p_edid->DTD[i]; |
| |
| |
| if (p_edid->DTD[i].video.pixel_clock[0] != 0 && |
| p_edid->DTD[i].video.pixel_clock[1] != 0) |
| p_video_spec = &p_edid->DTD[i]; |
| } |
| |
| /* Fill the TPI Video Mode Data structure */ |
| vals[0] = p_video_spec->video.pixel_clock[0]; /* Pixel clock */ |
| vals[1] = p_video_spec->video.pixel_clock[1]; |
| vals[2] = 60; /* Vertical freq */ |
| vals[3] = 0; |
| vals[4] = p_video_spec->video.horiz_active; /* Horizontal pixels*/ |
| vals[5] = (p_video_spec->video.horiz_high & 0xF0) >> 4; |
| vals[6] = p_video_spec->video.vert_active; /* Vertical pixels */ |
| vals[7] = (p_video_spec->video.vert_high & 0xF0) >> 4; |
| |
| |
| /* Write out the TPI Video Mode Data */ |
| out_len = 0; |
| err = sil9022_blockwrite_reg(sil9022_client, |
| HDMI_TPI_VIDEO_DATA_BASE_REG, |
| 8, vals, &out_len); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: writing TPI video mode data\n", __func__); |
| return err; |
| } |
| |
| /* Write out the TPI Pixel Repetition Data (24 bit wide bus, |
| falling edge, no pixel replication) */ |
| val = TPI_AVI_PIXEL_REP_BUS_24BIT | |
| TPI_AVI_PIXEL_REP_FALLING_EDGE | |
| TPI_AVI_PIXEL_REP_NONE; |
| err = sil9022_write_reg(sil9022_client, |
| HDMI_TPI_PIXEL_REPETITION_REG, |
| val); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: writing TPI pixel repetition data\n", |
| __func__); |
| return err; |
| } |
| |
| /* Write out the TPI AVI Input Format */ |
| val = TPI_AVI_INPUT_BITMODE_8BIT | |
| TPI_AVI_INPUT_RANGE_AUTO | |
| TPI_AVI_INPUT_COLORSPACE_RGB; |
| err = sil9022_write_reg(sil9022_client, |
| HDMI_TPI_AVI_IN_FORMAT_REG, |
| val); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: writing TPI AVI Input format\n", __func__); |
| return err; |
| } |
| |
| /* Write out the TPI AVI Output Format */ |
| val = TPI_AVI_OUTPUT_CONV_BT709 | |
| TPI_AVI_OUTPUT_RANGE_AUTO | |
| TPI_AVI_OUTPUT_COLORSPACE_RGBHDMI; |
| err = sil9022_write_reg(sil9022_client, |
| HDMI_TPI_AVI_OUT_FORMAT_REG, val); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: writing TPI AVI output format\n", |
| __func__); |
| return err; |
| } |
| |
| /* Write out the TPI System Control Data to power down */ |
| val = TPI_SYS_CTRL_POWER_DOWN; |
| err = sil9022_write_reg(sil9022_client, HDMI_SYS_CTRL_DATA_REG, val); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: writing TPI power down control data\n", |
| __func__); |
| return err; |
| } |
| |
| /* Write out the TPI AVI InfoFrame Data (all defaults) */ |
| /* Compute CRC*/ |
| val = 0x82 + 0x02 + 13; |
| |
| for (i = 0; i < sizeof(avi_info_frame_data); i++) |
| val += avi_info_frame_data[i]; |
| |
| avi_info_frame_data[0] = 0x100 - val; |
| |
| out_len = 0; |
| err = sil9022_blockwrite_reg(sil9022_client, |
| HDMI_TPI_AVI_DBYTE_BASE_REG, |
| sizeof(avi_info_frame_data), |
| avi_info_frame_data, &out_len); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: writing TPI AVI infoframe data\n", |
| __func__); |
| return err; |
| } |
| |
| /* Audio Configuration */ |
| err = hdmi_enable_audio(sil9022_client); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: Unable set audio configuration\n", |
| __func__); |
| return err; |
| } |
| |
| /* Write out the TPI Device Power State (D0) */ |
| val = TPI_AVI_POWER_STATE_D0; |
| err = sil9022_write_reg(sil9022_client, |
| HDMI_TPI_POWER_STATE_CTRL_REG, val); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: Setting device power state to D0\n", |
| __func__); |
| return err; |
| } |
| |
| /* Write out the TPI System Control Data to power up and |
| * select output mode |
| */ |
| val = TPI_SYS_CTRL_POWER_ACTIVE | TPI_SYS_CTRL_OUTPUT_MODE_HDMI; |
| err = sil9022_write_reg(sil9022_client, HDMI_SYS_CTRL_DATA_REG, val); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: Writing system control data\n", __func__); |
| return err; |
| } |
| |
| /* Read back TPI System Control Data to latch settings */ |
| msleep(10); |
| err = sil9022_read_reg(sil9022_client, 1, HDMI_SYS_CTRL_DATA_REG, &val); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: Writing system control data\n", |
| __func__); |
| return err; |
| } |
| |
| /* HDCP Enable - Disable */ |
| val = 0; |
| err = sil9022_write_reg(sil9022_client, |
| HDMI_TPI_HDCP_CONTROLDATA_REG, val); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: Enable (1) / Disable (0) => HDCP: %d\n", |
| __func__, val); |
| return err; |
| } |
| |
| dev_info(&sil9022_client->dev, "<%s> hdmi enabled\n", __func__); |
| |
| return 0; |
| |
| } |
| |
| static int |
| hdmi_disable(void) |
| { |
| u8 val = 0; |
| int err = 0; |
| |
| err = hdmi_disable_audio(sil9022_client); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: failed to disable audio\n", __func__); |
| return err; |
| } |
| |
| /* Write out the TPI System Control Data to power down */ |
| val = TPI_SYS_CTRL_POWER_DOWN; |
| err = sil9022_write_reg(sil9022_client, HDMI_SYS_CTRL_DATA_REG, val); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: writing control data - power down\n", |
| __func__); |
| return err; |
| } |
| |
| /* Write out the TPI Device Power State (D2) */ |
| val = TPI_AVI_POWER_STATE_D2; |
| err = sil9022_write_reg(sil9022_client, |
| HDMI_TPI_DEVICE_POWER_STATE_DATA, val); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: Setting device power state to D2\n", |
| __func__); |
| return err; |
| } |
| |
| /* Read back TPI System Control Data to latch settings */ |
| mdelay(10); |
| err = sil9022_read_reg(sil9022_client, 1, HDMI_SYS_CTRL_DATA_REG, &val); |
| if (err < 0) { |
| dev_err(&sil9022_client->dev, |
| "<%s> ERROR: Reading System control data " |
| "- latch settings\n", __func__); |
| return err; |
| } |
| |
| dev_info(&sil9022_client->dev, "<%s> hdmi disabled\n", __func__); |
| |
| return 0; |
| |
| } |
| |
| static int sil9022_set_cm_clkout_ctrl(struct i2c_client *client) |
| { |
| int err = 0; |
| u8 ver; |
| u32 clkout_ctrl = 0; |
| /* from TRM*/ |
| /* intitialize the CM_CLKOUT_CTRL register*/ |
| clkout_ctrl = CLKOUT2_EN | /* sys_clkout2 is enabled*/ |
| CLKOUT2_DIV | /* sys_clkout2 / 16*/ |
| CLKOUT2SOURCE; /* CM_96M_FCLK */ |
| |
| omap_writel(clkout_ctrl, CM_CLKOUT_CTRL); /*CM_CLKOUT_CTRL*/ |
| |
| /* probe for sil9022 chip version*/ |
| err = sil9022_write_reg(client, SI9022_REG_TPI_RQB, 0x00); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: Writing HDMI configuration to " |
| "reg - SI9022_REG_TPI_RQB\n", __func__); |
| err = -ENODEV; |
| goto ERROR1; |
| } |
| |
| err = sil9022_read_reg(client, 1, SI9022_REG_CHIPID0, &ver); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "<%s> ERROR: Reading HDMI version Id\n", __func__); |
| err = -ENODEV; |
| goto ERROR1; |
| } else if (ver != SI9022_CHIPID_902x) { |
| dev_err(&client->dev, |
| "<%s> Not a valid verId: 0x%x \n", __func__, ver); |
| err = -ENODEV; |
| goto ERROR1; |
| } else |
| dev_info(&client->dev, |
| "<%s> sil9022 HDMI Chip version = %x \n", |
| __func__, ver); |
| |
| return 0; |
| ERROR1: |
| return err; |
| } |
| |
| /* omap_dss_hdmi_driver */ |
| static int hdmi_panel_probe(struct omap_dss_device *dssdev) |
| { |
| |
| dssdev->panel.config = OMAP_DSS_LCD_TFT | OMAP_DSS_LCD_IVS | |
| OMAP_DSS_LCD_IHS; |
| |
| dssdev->panel.timings = omap_dss_hdmi_timings; |
| |
| return 0; |
| } |
| |
| static void hdmi_panel_remove(struct omap_dss_device *dssdev) |
| { |
| omap_dss_unregister_driver(dssdev->driver); |
| } |
| |
| static int hdmi_panel_enable(struct omap_dss_device *dssdev) |
| { |
| int r = 0; |
| |
| if (dssdev->platform_enable) |
| r = dssdev->platform_enable(dssdev); |
| |
| r = sil9022_set_cm_clkout_ctrl(sil9022_client); |
| if (r) |
| goto ERROR0; |
| |
| r = hdmi_enable(); |
| if (r) |
| goto ERROR0; |
| /* wait couple of vsyncs until enabling the LCD */ |
| msleep(50); |
| |
| return 0; |
| ERROR0: |
| return r; |
| } |
| |
| static void hdmi_panel_disable(struct omap_dss_device *dssdev) |
| { |
| hdmi_disable(); |
| |
| /* wait couple of vsyncs until enabling the hdmi */ |
| msleep(50); |
| |
| if (dssdev->platform_disable) |
| dssdev->platform_disable(dssdev); |
| } |
| |
| static int hdmi_panel_suspend(struct omap_dss_device *dssdev) |
| { |
| hdmi_panel_disable(dssdev); |
| return 0; |
| } |
| |
| static int hdmi_panel_resume(struct omap_dss_device *dssdev) |
| { |
| return hdmi_panel_enable(dssdev); |
| } |
| |
| static struct omap_dss_driver hdmi_driver = { |
| .probe = hdmi_panel_probe, |
| .remove = hdmi_panel_remove, |
| |
| .enable = hdmi_panel_enable, |
| .disable = hdmi_panel_disable, |
| .suspend = hdmi_panel_suspend, |
| .resume = hdmi_panel_resume, |
| |
| .driver = { |
| .name = "hdmi_panel", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| /* hdmi omap_dss_driver end */ |
| |
| |
| static int __devinit |
| sil9022_probe(struct i2c_client *client, const struct i2c_device_id *id) |
| { |
| int err = 0; |
| |
| sil9022_client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); |
| if (!sil9022_client) { |
| err = -ENOMEM; |
| goto ERROR0; |
| } |
| memset(sil9022_client, 0, sizeof(struct i2c_client)); |
| |
| strncpy(sil9022_client->name, SIL9022_DRV_NAME, I2C_NAME_SIZE); |
| sil9022_client->addr = SI9022_I2CSLAVEADDRESS; |
| sil9022_client->adapter = client->adapter; |
| |
| i2c_set_clientdata(client, sil9022_client); |
| |
| err = sil9022_set_cm_clkout_ctrl(client); |
| if (err) |
| goto ERROR1; |
| |
| omap_dss_register_driver(&hdmi_driver); |
| |
| return 0; |
| ERROR1: |
| kfree(client); |
| ERROR0: |
| return err; |
| } |
| |
| |
| static int |
| sil9022_remove(struct i2c_client *client) |
| |
| { |
| int err = 0; |
| |
| if (!client->adapter) { |
| dev_err(&sil9022_client->dev, "<%s> No HDMI Device \n", |
| __func__); |
| return -ENODEV; |
| } |
| kfree(sil9022_client); |
| |
| return err; |
| } |
| |
| static const struct i2c_device_id sil9022_id[] = { |
| { SIL9022_DRV_NAME, 0 }, |
| { }, |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, sil9022_id); |
| |
| static struct i2c_driver sil9022_driver = { |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = SIL9022_DRV_NAME, |
| }, |
| .probe = sil9022_probe, |
| .remove = sil9022_remove, |
| .id_table = sil9022_id, |
| }; |
| |
| static int __init |
| sil9022_init(void) |
| { |
| int err = 0; |
| |
| err = i2c_add_driver(&sil9022_driver); |
| if (err < 0) { |
| printk(KERN_ERR "<%s> Driver registration failed\n", __func__); |
| err = -ENODEV; |
| goto ERROR0; |
| } |
| |
| if (sil9022_client == NULL) { |
| printk(KERN_ERR "<%s> sil9022_client not allocated, \n" |
| "<%s> No HDMI Device \n", __func__, __func__); |
| err = -ENODEV; |
| goto ERROR0; |
| } |
| |
| return 0; |
| ERROR0: |
| return err; |
| } |
| |
| static void __exit |
| sil9022_exit(void) |
| { |
| i2c_del_driver(&sil9022_driver); |
| } |
| |
| late_initcall(sil9022_init); |
| module_exit(sil9022_exit); |
| |
| MODULE_AUTHOR("Texas Instruments"); |
| MODULE_DESCRIPTION("SIL9022 HDMI Driver"); |
| MODULE_LICENSE("GPL"); |
| |
| |
| |