Add an OMAPDSS encoder driver for Silicon Image SiI9022 HDMI encoder. The driver supports only video at the moment, audio support will be added separately. Signed-off-by: Tomi Valkeinen <tomi.valkeinen@xxxxxx> --- drivers/video/fbdev/omap2/displays-new/Kconfig | 8 + drivers/video/fbdev/omap2/displays-new/Makefile | 1 + .../fbdev/omap2/displays-new/encoder-sii9022.c | 966 +++++++++++++++++++++ .../fbdev/omap2/displays-new/encoder-sii9022.h | 58 ++ 4 files changed, 1033 insertions(+) create mode 100644 drivers/video/fbdev/omap2/displays-new/encoder-sii9022.c create mode 100644 drivers/video/fbdev/omap2/displays-new/encoder-sii9022.h diff --git a/drivers/video/fbdev/omap2/displays-new/Kconfig b/drivers/video/fbdev/omap2/displays-new/Kconfig index e6cfc38160d3..f0ca306edd25 100644 --- a/drivers/video/fbdev/omap2/displays-new/Kconfig +++ b/drivers/video/fbdev/omap2/displays-new/Kconfig @@ -12,6 +12,14 @@ config DISPLAY_ENCODER_TPD12S015 Driver for TPD12S015, which offers HDMI ESD protection and level shifting. +config DISPLAY_ENCODER_SII9022 + tristate "SiI9022 HDMI Encoder" + depends on I2C + help + Driver for Silicon Image SiI9022 HDMI encoder. + A brief about SiI9022 can be found here: + http://www.semiconductorstore.com/pdf/newsite/siliconimage/SiI9022a_pb.pdf + config DISPLAY_CONNECTOR_DVI tristate "DVI Connector" depends on I2C diff --git a/drivers/video/fbdev/omap2/displays-new/Makefile b/drivers/video/fbdev/omap2/displays-new/Makefile index 0323a8a1c682..f7f034b1c2b7 100644 --- a/drivers/video/fbdev/omap2/displays-new/Makefile +++ b/drivers/video/fbdev/omap2/displays-new/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_DISPLAY_ENCODER_TFP410) += encoder-tfp410.o obj-$(CONFIG_DISPLAY_ENCODER_TPD12S015) += encoder-tpd12s015.o +obj-$(CONFIG_DISPLAY_ENCODER_SII9022) += encoder-sii9022.o obj-$(CONFIG_DISPLAY_CONNECTOR_DVI) += connector-dvi.o obj-$(CONFIG_DISPLAY_CONNECTOR_HDMI) += connector-hdmi.o obj-$(CONFIG_DISPLAY_CONNECTOR_ANALOG_TV) += connector-analog-tv.o diff --git a/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.c b/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.c new file mode 100644 index 000000000000..955beae57e71 --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.c @@ -0,0 +1,966 @@ +/* + * Silicon Image SiI9022 Encoder Driver + * + * Copyright (C) 2014 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@xxxxxx> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/slab.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/gpio.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/of_gpio.h> +#include <linux/workqueue.h> +#include <linux/of_irq.h> +#include <linux/hdmi.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> + +#include <drm/drm_edid.h> + +#include "encoder-sii9022.h" + +static const struct regmap_config sii9022_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +struct panel_drv_data { + struct omap_dss_device dssdev; + struct omap_dss_device *in; + struct i2c_client *i2c_client; + struct gpio_desc *reset_gpio; + struct regmap *regmap; + struct omap_video_timings timings; + struct delayed_work work; + struct mutex lock; + + int irq; + bool use_polling; + + bool htplg_state; + bool rxsense_state; + + bool hdmi_mode; + struct hdmi_avi_infoframe frame; +}; + +#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev) + +static int sii9022_set_power_state(struct panel_drv_data *ddata, + enum sii9022_power_state state) +{ + unsigned pwr; + unsigned cold; + int r; + + switch (state) { + case SII9022_POWER_STATE_D0: + pwr = 0; + cold = 0; + break; + case SII9022_POWER_STATE_D2: + pwr = 2; + cold = 0; + break; + case SII9022_POWER_STATE_D3_HOT: + pwr = 3; + cold = 0; + break; + case SII9022_POWER_STATE_D3_COLD: + pwr = 3; + cold = 1; + break; + default: + return -EINVAL; + } + + r = regmap_update_bits(ddata->regmap, SII9022_POWER_STATE_CTRL_REG, + 1 << 2, cold << 2); + if (r) { + dev_err(&ddata->i2c_client->dev, "failed to set hot/cold bit\n"); + return r; + } + + r = regmap_update_bits(ddata->regmap, SII9022_POWER_STATE_CTRL_REG, + 0x3, pwr); + if (r) { + dev_err(&ddata->i2c_client->dev, + "failed to set power state to %d\n", pwr); + return r; + } + + return 0; +} + +static int sii9022_ddc_read(struct i2c_adapter *adapter, + unsigned char *buf, u16 count, u8 offset) +{ + int r, retries; + + for (retries = 3; retries > 0; retries--) { + struct i2c_msg msgs[] = { + { + .addr = HDMI_I2C_MONITOR_ADDRESS, + .flags = 0, + .len = 1, + .buf = &offset, + }, { + .addr = HDMI_I2C_MONITOR_ADDRESS, + .flags = I2C_M_RD, + .len = count, + .buf = buf, + } + }; + + r = i2c_transfer(adapter, msgs, 2); + if (r == 2) + return 0; + + if (r != -EAGAIN) + break; + } + + return r < 0 ? r : -EIO; +} + +static int sii9022_request_ddc_access(struct panel_drv_data *ddata, + unsigned *ctrl_reg) +{ + struct device *dev = &ddata->i2c_client->dev; + unsigned int val; + int r; + unsigned retries; + + *ctrl_reg = 0; + + /* Read TPI system control register*/ + r = regmap_read(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, &val); + if (r) { + dev_err(dev, "error reading DDC BUS REQUEST\n"); + return r; + } + + /* set SII9022_SYS_CTRL_DDC_BUS_REQUEST to request the DDC bus */ + val |= SII9022_SYS_CTRL_DDC_BUS_REQUEST; + + r = regmap_write(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, val); + if (r) { + dev_err(dev, "error writing DDC BUS REQUEST\n"); + return r; + } + + /* Poll for bus DDC Bus control to be granted */ + retries = 0; + do { + r = regmap_read(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, &val); + if (retries++ > 100) + return r; + + } while ((val & SII9022_SYS_CTRL_DDC_BUS_GRANTED) == 0); + + /* Close the switch to the DDC */ + val |= SII9022_SYS_CTRL_DDC_BUS_REQUEST | + SII9022_SYS_CTRL_DDC_BUS_GRANTED; + r = regmap_write(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, val); + if (r) { + dev_err(dev, "error closing switch to DDC BUS REQUEST\n"); + return r; + } + + *ctrl_reg = val; + + return 0; +} + +static int sii9022_release_ddc_access(struct panel_drv_data *ddata, + unsigned ctrl_reg) +{ + struct device *dev = &ddata->i2c_client->dev; + unsigned int val; + int r; + unsigned retries; + + val = ctrl_reg; + val &= ~(SII9022_SYS_CTRL_DDC_BUS_REQUEST | + SII9022_SYS_CTRL_DDC_BUS_GRANTED); + + /* retry write until we can read the register, and the bits are 0 */ + for (retries = 5; retries > 0; --retries) { + unsigned v; + + /* ignore error, as the chip won't ACK this. */ + regmap_write(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, val); + + r = regmap_read(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, &v); + if (r) + continue; + + if (v == val) + break; + } + + if (retries == 0) { + dev_err(dev, "error releasing DDC Bus Access\n"); + return r; + } + + return 0; +} + +static int sii9022_write_avi_infoframe(struct panel_drv_data *ddata) +{ + struct regmap *regmap = ddata->regmap; + u8 data[HDMI_INFOFRAME_SIZE(AVI)]; + int r; + + r = hdmi_avi_infoframe_pack(&ddata->frame, data, sizeof(data)); + if (r < 0) + return r; + + print_hex_dump_debug("AVI: ", DUMP_PREFIX_NONE, 16, 1, data, + HDMI_INFOFRAME_SIZE(AVI), false); + + /* SiI9022 wants the checksum + the avi infoframe */ + r = regmap_bulk_write(regmap, SII9022_AVI_INFOFRAME_BASE_REG, + &data[3], 1 + HDMI_AVI_INFOFRAME_SIZE); + + return r; +} + +static int sii9022_clear_avi_infoframe(struct panel_drv_data *ddata) +{ + struct regmap *regmap = ddata->regmap; + u8 data[1 + HDMI_AVI_INFOFRAME_SIZE] = { 0 }; + int r; + + r = regmap_bulk_write(regmap, SII9022_AVI_INFOFRAME_BASE_REG, + data, 1 + HDMI_AVI_INFOFRAME_SIZE); + + return r; +} + +static int sii9022_probe_chip_version(struct panel_drv_data *ddata) +{ + struct device *dev = &ddata->i2c_client->dev; + int r = 0; + unsigned id, rev, tpi_id; + + r = regmap_read(ddata->regmap, SII9022_DEVICE_ID_REG, &id); + if (r) { + dev_err(dev, "failed to read device ID\n"); + return r; + } + + if (id != SII9022_ID_902xA) { + dev_err(dev, "unsupported device ID: 0x%x\n", id); + return -ENODEV; + } + + r = regmap_read(ddata->regmap, SII9022_DEVICE_REV_ID_REG, &rev); + if (r) { + dev_err(dev, "failed to read device revision\n"); + return r; + } + + r = regmap_read(ddata->regmap, SII9022_DEVICE_TPI_ID_REG, &tpi_id); + if (r) { + dev_err(dev, "failed to read TPI ID\n"); + return r; + } + + dev_info(dev, "SiI902xA HDMI device %x, rev %x, tpi %x\n", + id, rev, tpi_id); + + return r; +} + +static int sii9022_enable_tpi(struct panel_drv_data *ddata) +{ + struct device *dev = &ddata->i2c_client->dev; + int r; + + r = regmap_write(ddata->regmap, SII9022_TPI_RQB_REG, 0); + if (r) { + dev_err(dev, "failed to enable TPI commands\n"); + return r; + } + + return 0; +} + +static int sii9022_enable_tmds(struct panel_drv_data *ddata, bool enable) +{ + struct regmap *regmap = ddata->regmap; + struct device *dev = &ddata->i2c_client->dev; + int r; + + r = regmap_update_bits(regmap, SII9022_SYS_CTRL_DATA_REG, + 1 << 4, (enable ? 0 : 1) << 4); + if (r) { + dev_err(dev, "failed to %s TMDS output\n", + enable ? "enable" : "disable"); + return r; + } + + return 0; +} + +static int sii9022_setup_video(struct panel_drv_data *ddata, + struct omap_video_timings *timings) +{ + struct regmap *regmap = ddata->regmap; + struct device *dev = &ddata->i2c_client->dev; + int r; + unsigned pck = timings->pixelclock / 10000; + unsigned xres = timings->x_res; + unsigned yres = timings->y_res; + unsigned vfreq = 60; + + u8 vals[] = { + pck & 0xff, + (pck & 0xff00) >> 8, + vfreq & 0xff, + (vfreq & 0xff00) >> 8, + (xres & 0xff), + (xres & 0xff00) >> 8, + (yres & 0xff), + (yres & 0xff00) >> 8, + }; + + r = regmap_bulk_write(regmap, SII9022_VIDEO_DATA_BASE_REG, + &vals, ARRAY_SIZE(vals)); + if (r) { + dev_err(dev, "failed to write video mode config\n"); + return r; + } + + return 0; +} + +static int sii9022_hw_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct regmap *regmap = ddata->regmap; + struct device *dev = &ddata->i2c_client->dev; + int r; + + /* make sure we're in D2 */ + r = sii9022_set_power_state(ddata, SII9022_POWER_STATE_D2); + if (r) + return r; + + r = sii9022_setup_video(ddata, &ddata->timings); + if (r) + return r; + + /* configure input video format */ + r = regmap_write(regmap, SII9022_AVI_IN_FORMAT_REG, 0); + if (r) { + dev_err(dev, "failed to set input format\n"); + return r; + } + + /* configure output video format */ + r = regmap_write(regmap, SII9022_AVI_OUT_FORMAT_REG, + (1 << 4)); /* CONV_BT709 */ + if (r) { + dev_err(dev, "failed to set output format\n"); + return r; + } + + if (ddata->hdmi_mode) + r = sii9022_write_avi_infoframe(ddata); + else + r = sii9022_clear_avi_infoframe(ddata); + + if (r) { + dev_err(dev, "failed to write AVI infoframe\n"); + return r; + } + + /* select DVI / HDMI */ + /* note: must be done before D0 */ + r = regmap_update_bits(regmap, SII9022_SYS_CTRL_DATA_REG, + 1 << 0, ddata->hdmi_mode ? 1 : 0); /* 0 = DVI, 1 = HDMI */ + if (r) { + dev_err(dev, "failed to set DVI/HDMI mode\n"); + return r; + } + + /* power up transmitter */ + r = sii9022_set_power_state(ddata, SII9022_POWER_STATE_D0); + if (r) + return r; + + /* enable TMDS */ + r = sii9022_enable_tmds(ddata, true); + if (r) + return r; + + /* configure input bus and pixel repetition */ + /* Note: must be done after enabling TMDS */ + r = regmap_write(regmap, SII9022_PIXEL_REPETITION_REG, + (1 << 5) | /* 24BIT */ + (1 << 6) /* CLK_RATIO_1X */ + ); + if (r) { + dev_err(dev, "failed to write pixel repetition reg\n"); + return r; + } + + return 0; +} + +static int sii9022_hw_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + int r; + + sii9022_enable_tmds(ddata, false); + + r = sii9022_set_power_state(ddata, SII9022_POWER_STATE_D2); + if (r) + return r; + + return 0; +} + +static void sii9022_handle_hpd(struct panel_drv_data *ddata) +{ + struct device *dev = &ddata->i2c_client->dev; + unsigned int stat; + int r; + bool htplg, rxsense; + bool htplg_ev, rxsense_ev; + + htplg_ev = rxsense_ev = false; + + r = regmap_read(ddata->regmap, SII9022_IRQ_STATUS_REG, &stat); + + if (stat & 0x3) { + if (stat & 1) + htplg_ev = true; + if (stat & 2) + rxsense_ev = true; + + regmap_write(ddata->regmap, SII9022_IRQ_STATUS_REG, 0x3); + } + + htplg = stat & (1 << 2); + rxsense = stat & (1 << 3); + + if (ddata->htplg_state != htplg || htplg_ev) { + dev_dbg(dev, "hotplug %sconnect\n", htplg ? "" : "dis"); + ddata->htplg_state = htplg; + } + + if (ddata->rxsense_state != rxsense || rxsense_ev) { + dev_dbg(dev, "rxsense %sconnect\n", rxsense ? "" : "dis"); + ddata->rxsense_state = rxsense; + } +} + +static irqreturn_t dispc_irq_handler(int irq, void *arg) +{ + struct panel_drv_data *ddata = arg; + + mutex_lock(&ddata->lock); + + sii9022_handle_hpd(ddata); + + mutex_unlock(&ddata->lock); + + return IRQ_HANDLED; +} + +static void sii9022_poll(struct work_struct *work) +{ + struct panel_drv_data *ddata; + + ddata = container_of(work, struct panel_drv_data, work.work); + + mutex_lock(&ddata->lock); + + sii9022_handle_hpd(ddata); + + mutex_unlock(&ddata->lock); + + schedule_delayed_work(&ddata->work, msecs_to_jiffies(250)); +} + +static int sii9022_connect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct device *dev = &ddata->i2c_client->dev; + struct omap_dss_device *in = ddata->in; + int r; + + if (omapdss_device_is_connected(dssdev)) + return -EBUSY; + + r = in->ops.dpi->connect(in, dssdev); + if (r) + return r; + + mutex_lock(&ddata->lock); + + r = sii9022_set_power_state(ddata, SII9022_POWER_STATE_D2); + if (r) + goto err_pwr; + + ddata->htplg_state = ddata->rxsense_state = false; + + sii9022_handle_hpd(ddata); + + regmap_write(ddata->regmap, SII9022_IRQ_ENABLE_REG, 0x3); + + if (ddata->use_polling) { + INIT_DELAYED_WORK(&ddata->work, sii9022_poll); + schedule_delayed_work(&ddata->work, msecs_to_jiffies(250)); + } else { + r = devm_request_threaded_irq(dev, ddata->irq, + NULL, dispc_irq_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "sii9022 int", ddata); + if (r) { + dev_err(dev, "failed to request irq\n"); + goto err_irq; + } + } + + dst->src = dssdev; + dssdev->dst = dst; + + mutex_unlock(&ddata->lock); + + return 0; + +err_irq: +err_pwr: + mutex_unlock(&ddata->lock); + in->ops.dpi->disconnect(in, dssdev); + return r; +} + +static void sii9022_disconnect(struct omap_dss_device *dssdev, + struct omap_dss_device *dst) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct device *dev = &ddata->i2c_client->dev; + struct omap_dss_device *in = ddata->in; + + WARN_ON(!omapdss_device_is_connected(dssdev)); + if (!omapdss_device_is_connected(dssdev)) + return; + + WARN_ON(dst != dssdev->dst); + if (dst != dssdev->dst) + return; + + if (ddata->use_polling) + cancel_delayed_work_sync(&ddata->work); + else + devm_free_irq(dev, ddata->irq, ddata); + + dst->src = NULL; + dssdev->dst = NULL; + + in->ops.dpi->disconnect(in, dssdev); +} + +static int sii9022_enable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + int r; + + if (!omapdss_device_is_connected(dssdev)) + return -ENODEV; + + if (omapdss_device_is_enabled(dssdev)) + return 0; + + in->ops.dpi->set_timings(in, &ddata->timings); + + r = in->ops.dpi->enable(in); + if (r) + return r; + + if (ddata->reset_gpio) + gpiod_set_value_cansleep(ddata->reset_gpio, 0); + + mutex_lock(&ddata->lock); + + r = sii9022_hw_enable(dssdev); + if (r) + goto err_hw_enable; + + mutex_unlock(&ddata->lock); + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + return 0; + +err_hw_enable: + mutex_unlock(&ddata->lock); + + if (ddata->reset_gpio) + gpiod_set_value_cansleep(ddata->reset_gpio, 1); + + in->ops.dpi->disable(in); + + return r; +} + +static void sii9022_disable(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + if (!omapdss_device_is_enabled(dssdev)) + return; + + mutex_lock(&ddata->lock); + + sii9022_hw_disable(dssdev); + + mutex_unlock(&ddata->lock); + + if (ddata->reset_gpio) + gpiod_set_value_cansleep(ddata->reset_gpio, 1); + + in->ops.dpi->disable(in); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; +} + +static void sii9022_set_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + struct omap_video_timings t = *timings; + + /* update DPI specific timing info */ + t.data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + t.de_level = OMAPDSS_SIG_ACTIVE_HIGH; + t.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; + + ddata->timings = t; + dssdev->panel.timings = t; + + in->ops.dpi->set_timings(in, &t); +} + +static void sii9022_get_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + *timings = ddata->timings; +} + +static int sii9022_check_timings(struct omap_dss_device *dssdev, + struct omap_video_timings *timings) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct omap_dss_device *in = ddata->in; + + /* update DPI specific timing info */ + timings->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + timings->de_level = OMAPDSS_SIG_ACTIVE_HIGH; + timings->sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES; + + return in->ops.dpi->check_timings(in, timings); +} + +static int sii9022_read_edid(struct omap_dss_device *dssdev, + u8 *edid, int len) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + struct i2c_client *client = ddata->i2c_client; + unsigned ctrl_reg; + int r, l, bytes_read; + + mutex_lock(&ddata->lock); + + if (ddata->use_polling) + sii9022_handle_hpd(ddata); + + if (ddata->htplg_state == false) { + r = -ENODEV; + goto err_hpd; + } + + r = sii9022_request_ddc_access(ddata, &ctrl_reg); + if (r) + goto err_ddc_request; + + l = min(len, EDID_LENGTH); + + r = sii9022_ddc_read(client->adapter, edid, l, 0); + if (r) + goto err_ddc_read; + + bytes_read = l; + + /* if there are extensions, read second block */ + if (len > EDID_LENGTH && edid[0x7e] > 0) { + l = min(EDID_LENGTH, len - EDID_LENGTH); + + r = sii9022_ddc_read(client->adapter, edid + EDID_LENGTH, + l, EDID_LENGTH); + if (r) + goto err_ddc_read; + + bytes_read += l; + } + + r = sii9022_release_ddc_access(ddata, ctrl_reg); + if (r) + goto err_ddc_read; + + print_hex_dump_debug("EDID: ", DUMP_PREFIX_NONE, 16, 1, edid, + bytes_read, false); + + mutex_unlock(&ddata->lock); + + return bytes_read; + +err_ddc_read: + sii9022_release_ddc_access(ddata, ctrl_reg); +err_ddc_request: +err_hpd: + mutex_unlock(&ddata->lock); + + return r; +} + +static bool sii9022_detect(struct omap_dss_device *dssdev) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + bool hpd; + + mutex_lock(&ddata->lock); + + if (ddata->use_polling) + sii9022_handle_hpd(ddata); + + hpd = ddata->htplg_state; + + mutex_unlock(&ddata->lock); + + return hpd; +} + +static int sii9022_set_infoframe(struct omap_dss_device *dssdev, + const struct hdmi_avi_infoframe *infoframe) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + ddata->frame = *infoframe; + + return 0; +} + + +static int sii9022_set_hdmi_mode(struct omap_dss_device *dssdev, bool hdmi_mode) +{ + struct panel_drv_data *ddata = to_panel_data(dssdev); + + ddata->hdmi_mode = hdmi_mode; + + return 0; +} + +static bool sii9022_audio_supported(struct omap_dss_device *dssdev) +{ + return false; +} + +static const struct omapdss_hdmi_ops sii9022_hdmi_ops = { + .connect = sii9022_connect, + .disconnect = sii9022_disconnect, + + .enable = sii9022_enable, + .disable = sii9022_disable, + + .check_timings = sii9022_check_timings, + .set_timings = sii9022_set_timings, + .get_timings = sii9022_get_timings, + + .read_edid = sii9022_read_edid, + .detect = sii9022_detect, + .set_hdmi_mode = sii9022_set_hdmi_mode, + .set_infoframe = sii9022_set_infoframe, + + .audio_supported = sii9022_audio_supported, +}; + +static int sii9022_probe_of(struct i2c_client *client) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&client->dev); + struct device_node *node = client->dev.of_node; + struct omap_dss_device *in; + struct gpio_desc *gpio; + + gpio = devm_gpiod_get(&client->dev, "reset"); + + if (IS_ERR(gpio)) { + if (PTR_ERR(gpio) != -ENOENT) + return PTR_ERR(gpio); + else + gpio = NULL; + } else { + gpiod_direction_output(gpio, 0); + } + + ddata->reset_gpio = gpio; + + ddata->irq = irq_of_parse_and_map(node, 0); + if (ddata->irq > 0) + ddata->use_polling = false; + else + ddata->use_polling = true; + + in = omapdss_of_find_source_for_first_ep(node); + if (IS_ERR(in)) { + dev_err(&client->dev, "failed to find video source\n"); + return PTR_ERR(in); + } + + ddata->in = in; + + return 0; +} + +static int sii9022_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct panel_drv_data *ddata; + struct omap_dss_device *dssdev; + struct regmap *regmap; + int r = 0; + + regmap = devm_regmap_init_i2c(client, &sii9022_regmap_config); + if (IS_ERR(regmap)) { + r = PTR_ERR(regmap); + dev_err(&client->dev, "Failed to init regmap: %d\n", r); + return r; + } + + ddata = devm_kzalloc(&client->dev, sizeof(*ddata), GFP_KERNEL); + if (ddata == NULL) + return -ENOMEM; + + dev_set_drvdata(&client->dev, ddata); + + mutex_init(&ddata->lock); + + if (client->dev.of_node) { + r = sii9022_probe_of(client); + if (r) + return r; + } else { + return -ENODEV; + } + + ddata->regmap = regmap; + ddata->i2c_client = client; + + dssdev = &ddata->dssdev; + dssdev->dev = &client->dev; + dssdev->ops.hdmi = &sii9022_hdmi_ops; + dssdev->type = OMAP_DISPLAY_TYPE_DPI; + dssdev->output_type = OMAP_DISPLAY_TYPE_HDMI; + dssdev->owner = THIS_MODULE; + + r = sii9022_enable_tpi(ddata); + if (r) + goto err_tpi; + + r = sii9022_probe_chip_version(ddata); + if (r) + goto err_i2c; + + r = omapdss_register_output(dssdev); + if (r) { + dev_err(&client->dev, "Failed to register output\n"); + goto err_reg; + } + + return 0; + +err_reg: +err_i2c: +err_tpi: + omap_dss_put_device(ddata->in); + return r; +} + +static int sii9022_remove(struct i2c_client *client) +{ + struct panel_drv_data *ddata = dev_get_drvdata(&client->dev); + struct omap_dss_device *dssdev = &ddata->dssdev; + + omapdss_unregister_output(dssdev); + + WARN_ON(omapdss_device_is_enabled(dssdev)); + if (omapdss_device_is_enabled(dssdev)) + sii9022_disable(dssdev); + + WARN_ON(omapdss_device_is_connected(dssdev)); + if (omapdss_device_is_connected(dssdev)) + sii9022_disconnect(dssdev, dssdev->dst); + + omap_dss_put_device(ddata->in); + + return 0; +} + +static const struct i2c_device_id sii9022_id[] = { + { "sii9022", 0 }, + { }, +}; + +static const struct of_device_id sii9022_of_match[] = { + { .compatible = "omapdss,sil,sii9022", }, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, sii9022_id); + +static struct i2c_driver sii9022_driver = { + .driver = { + .name = "sii9022", + .owner = THIS_MODULE, + .of_match_table = sii9022_of_match, + }, + .probe = sii9022_probe, + .remove = sii9022_remove, + .id_table = sii9022_id, +}; + +module_i2c_driver(sii9022_driver); + +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@xxxxxx>"); +MODULE_DESCRIPTION("SiI9022 HDMI Encoder Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.h b/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.h new file mode 100644 index 000000000000..f9a340437b08 --- /dev/null +++ b/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014 Texas Instruments + * Author : Tomi Valkeinen <tomi.valkeinen@xxxxxx> + * + * 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. + * + */ + +#ifndef __ENCODER_SII9022_H_ +#define __ENCODER_SII9022_H_ + +#define SII9022_ID_902xA 0xb0 + +#define HDMI_I2C_MONITOR_ADDRESS 0x50 + +#define SII9022_VIDEO_DATA_BASE_REG 0x00 +#define SII9022_PIXEL_CLK_LSB_REG (SII9022_VIDEO_DATA_BASE_REG + 0x00) +#define SII9022_PIXEL_CLK_MSB_REG (SII9022_VIDEO_DATA_BASE_REG + 0x01) +#define SII9022_VFREQ_LSB_REG (SII9022_VIDEO_DATA_BASE_REG + 0x02) +#define SII9022_VFREQ_MSB_REG (SII9022_VIDEO_DATA_BASE_REG + 0x03) +#define SII9022_PIXELS_LSB_REG (SII9022_VIDEO_DATA_BASE_REG + 0x04) +#define SII9022_PIXELS_MSB_REG (SII9022_VIDEO_DATA_BASE_REG + 0x05) +#define SII9022_LINES_LSB_REG (SII9022_VIDEO_DATA_BASE_REG + 0x06) +#define SII9022_LINES_MSB_REG (SII9022_VIDEO_DATA_BASE_REG + 0x07) + +#define SII9022_PIXEL_REPETITION_REG 0x08 + +#define SII9022_AVI_IN_FORMAT_REG 0x09 +#define SII9022_AVI_OUT_FORMAT_REG 0x0a +#define SII9022_AVI_INFOFRAME_BASE_REG 0x0c + +#define SII9022_SYS_CTRL_DATA_REG 0x1a +#define SII9022_DEVICE_ID_REG 0x1b +#define SII9022_DEVICE_REV_ID_REG 0x1c +#define SII9022_DEVICE_TPI_ID_REG 0x1d + +#define SII9022_POWER_STATE_CTRL_REG 0x1e + +#define SII9022_IRQ_ENABLE_REG 0x3c +#define SII9022_IRQ_STATUS_REG 0x3d + +#define SII9022_TPI_RQB_REG 0xc7 + +/* SII9022_SYS_CTRL_DATA_REG */ +#define SII9022_SYS_CTRL_DDC_BUS_GRANTED BIT(1) +#define SII9022_SYS_CTRL_DDC_BUS_REQUEST BIT(2) + + +enum sii9022_power_state { + SII9022_POWER_STATE_D0, + SII9022_POWER_STATE_D2, + SII9022_POWER_STATE_D3_HOT, + SII9022_POWER_STATE_D3_COLD, +}; + +#endif -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html