diff options
Diffstat (limited to 'drivers/gpu/drm/i2c/sii9022_drv.c')
-rw-r--r-- | drivers/gpu/drm/i2c/sii9022_drv.c | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/drivers/gpu/drm/i2c/sii9022_drv.c b/drivers/gpu/drm/i2c/sii9022_drv.c new file mode 100644 index 000000000000..5ba82d0182f5 --- /dev/null +++ b/drivers/gpu/drm/i2c/sii9022_drv.c @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2013 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * Silicon Image SiI9022 driver for DRM I2C encoder slave + */ + +#include <linux/module.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> + +#define SII9022_INFORMAT_REG 0x09 +#define SII9022_OUTFORMAT_REG 0x0a +#define SII9022_SYS_CONTROL_REG 0x1a +# define SII9022_DDC_OUT_MODE (1 << 0) +# define SII9022_DDC_BUS_STAT (1 << 1) +# define SII9022_DDC_BUS_GRANT (1 << 2) +# define SII9022_AV_MUTE (1 << 3) +# define SII9022_OUTPUT_EN (1 << 4) +#define SII9022_ID_REG 0x1b +#define SII9022_POWER_REG 0x1e +#define SII9022_SEC_CTRL_REG 0x2a +#define SII9022_SEC_STATUS_REG 0x29 +#define SII9022_SEC_VERSION_REG 0x30 +#define SII9022_INTR_REG 0x3c +#define SII9022_INTR_STATUS 0x3d +# define SII9022_HOTPLUG_EVENT (1 << 0) +# define SII9022_RECEIVER_EVENT (1 << 1) +# define SII9022_HOTPLUG_STATE (1 << 2) +# define SII9022_RECEIVER_STATE (1 << 3) +#define SII9022_VENDOR_ID 0xb0 +#define SII9022_INTERNAL_PAGE 0xbc +#define SII9022_INTERNAL_INDEX 0xbd +#define SII9022_INTERNAL_REG 0xbe +#define SII9022_CTRL_REG 0xc7 + + +struct sii9022_video_regs { + uint16_t pixel_clock; + uint16_t vrefresh; + uint16_t cols; + uint16_t lines; + uint8_t pixel_data; +}; + +static void sii9022_write(struct i2c_client *client, uint8_t addr, uint8_t val) +{ + uint8_t buf[] = { addr, val }; + int ret; + + ret = i2c_master_send(client, buf, ARRAY_SIZE(buf)); + if (ret < 0) + dev_err(&client->dev, "Error writing to subaddress 0x%x: %d\n", addr, ret); +} + +static uint8_t sii9022_read(struct i2c_client *client, uint8_t addr) +{ + uint8_t val; + int ret; + + ret = i2c_master_send(client, &addr, sizeof(addr)); + if (ret < 0) + goto fail; + + ret = i2c_master_recv(client, &val, sizeof(val)); + if (ret < 0) + goto fail; + + return val; + +fail: + dev_err(&client->dev, "Error reading from subaddress 0x%x: %d\n", addr, ret); + return 0; +} + +static void sii9022_encoder_set_config(struct drm_encoder *encoder, void *params) +{ +} + +static void sii9022_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + uint8_t val; + + switch (mode) { + case DRM_MODE_DPMS_OFF: + val = sii9022_read(client, SII9022_SYS_CONTROL_REG); + val |= SII9022_OUTPUT_EN; + sii9022_write(client, SII9022_SYS_CONTROL_REG, val); + /* wait for AVI InfoFrames to flush */ + mdelay(128); + /* use D2 for OFF as we cannot control the reset pin */ + sii9022_write(client, SII9022_POWER_REG, 0x2); + break; + case DRM_MODE_DPMS_ON: + val = sii9022_read(client, SII9022_SYS_CONTROL_REG); + val &= ~SII9022_OUTPUT_EN; + sii9022_write(client, SII9022_SYS_CONTROL_REG, val); + /* fall through */ + default: + sii9022_write(client, SII9022_POWER_REG, mode); + break; + } +} + +static enum drm_connector_status +sii9022_encoder_detect(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + enum drm_connector_status con_status = connector_status_unknown; + uint8_t status = sii9022_read(client, SII9022_INTR_STATUS); + + if (status & SII9022_HOTPLUG_STATE) + con_status = connector_status_connected; + else + con_status = connector_status_disconnected; + + /* clear the event status bits */ + sii9022_write(client, SII9022_INTR_STATUS, + 0xff /*SII9022_HOTPLUG_EVENT | SII9022_RECEIVER_EVENT */); + status = sii9022_read(client, SII9022_INTR_STATUS); + + return con_status; +} + +static int sii9022_encoder_get_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + struct edid *edid = NULL; + uint8_t status; + int ret = 0, timeout = 10; + + /* Disable HDCP link security */ + do { + sii9022_write(client, SII9022_SEC_CTRL_REG, 0); + status = sii9022_read(client, SII9022_SEC_CTRL_REG); + } while (status); + status = sii9022_read(client, SII9022_SEC_STATUS_REG); + + /* first, request the pass-through mode in order to read the edid */ + status = sii9022_read(client, SII9022_SYS_CONTROL_REG); + status |= SII9022_DDC_BUS_GRANT; + sii9022_write(client, SII9022_SYS_CONTROL_REG, status); + do { + /* wait for state change */ + status = sii9022_read(client, SII9022_SYS_CONTROL_REG); + --timeout; + } while (((status & SII9022_DDC_BUS_STAT) != SII9022_DDC_BUS_STAT) && timeout); + + if (!timeout) { + dev_warn(&client->dev, "timeout waiting for DDC bus grant\n"); + goto release_ddc; + } + + /* write back the value read in order to close the i2c switch */ + sii9022_write(client, SII9022_SYS_CONTROL_REG, status); + + edid = drm_get_edid(connector, client->adapter); + if (!edid) { + dev_err(&client->dev, "failed to get EDID data\n"); + ret = -1; + } + +release_ddc: + timeout = 10; + do { + status &= ~(SII9022_DDC_BUS_STAT | SII9022_DDC_BUS_GRANT); + sii9022_write(client, SII9022_SYS_CONTROL_REG, status); + status = sii9022_read(client, SII9022_SYS_CONTROL_REG); + --timeout; + } while ((status & (SII9022_DDC_BUS_STAT | SII9022_DDC_BUS_GRANT)) && timeout); + + if (edid) { + drm_mode_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + if (drm_detect_hdmi_monitor(edid)) + sii9022_write(client, SII9022_SYS_CONTROL_REG, status | 1); + else + sii9022_write(client, SII9022_SYS_CONTROL_REG, status & 0xfe); + kfree(edid); + } + + return ret; +} + +static bool sii9022_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int sii9022_encoder_mode_valid(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static void sii9022_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct i2c_client *client = drm_i2c_encoder_get_client(encoder); + /* SiI9022 clock is pixclock / 10000 Hz */ + int clk = adjusted_mode->clock / 10; + int i, vrefresh = adjusted_mode->vrefresh * 100; + uint8_t buf[15]; /* start address reg + 14 bytes max */ + + /* Set Video Mode (8 registers block) */ + buf[0] = 0; /* start register */ + buf[1] = clk & 0xff; + buf[2] = (clk & 0xff00) >> 8; + buf[3] = vrefresh & 0xff; + buf[4] = (vrefresh & 0xff00) >> 8; + buf[5] = adjusted_mode->crtc_hdisplay & 0xff; + buf[6] = (adjusted_mode->crtc_hdisplay & 0xff00) >> 8; + buf[7] = adjusted_mode->crtc_vdisplay & 0xff; + buf[8] = (adjusted_mode->crtc_vdisplay & 0xff00) >> 8; + + if (i2c_master_send(client, buf, 9) < 0) { + dev_err(&client->dev, "Could not write video mode data\n"); + return; + } + + /* input is full range RGB */ + sii9022_write(client, SII9022_INFORMAT_REG, 0x04); + /* output is full range digital RGB */ + sii9022_write(client, SII9022_OUTFORMAT_REG, 0x17); + + /* set the AVI InfoFrame (14 registers block */ + buf[0] = 0x0c; /* start register */ + buf[1] = 0x0e; /* AVI_DBYTE0 = checksum */ + buf[2] = 0x10; /* AVI_DBYTE1 */ + buf[3] = 0x50; /* AVI_DBYTE2 (colorimetry) */ + buf[4] = 0; /* AVI_DBYTE3 (scaling) */ + buf[5] = drm_match_cea_mode(adjusted_mode); + buf[6] = 0; /* AVI_DBYTE5 (pixel repetition factor) */ + buf[7] = 0; + buf[8] = 0; + buf[9] = 0; + buf[10] = 0; + buf[11] = 0; + buf[12] = 0; + buf[13] = 0; + buf[14] = 0; + + /* calculate checksum */ + buf[1] = 0x82 + 0x02 + 13; /* Identifier code for AVI InfoFrame, length */ + for (i = 2; i < 15; i++) + buf[1] += buf[i]; + buf[1] = 0x100 - buf[1]; + + if (i2c_master_send(client, buf, ARRAY_SIZE(buf)) < 0) { + dev_err(&client->dev, "Could not write video mode data\n"); + return; + } +} + +static struct drm_encoder_slave_funcs sii9022_encoder_funcs = { + .set_config = sii9022_encoder_set_config, + .dpms = sii9022_encoder_dpms, + .detect = sii9022_encoder_detect, + .get_modes = sii9022_encoder_get_modes, + .mode_fixup = sii9022_encoder_mode_fixup, + .mode_valid = sii9022_encoder_mode_valid, + .mode_set = sii9022_encoder_mode_set, +}; + +static int sii9022_encoder_init(struct i2c_client *client, + struct drm_device *dev, + struct drm_encoder_slave *encoder) +{ + encoder->slave_funcs = &sii9022_encoder_funcs; + + return 0; +} + +static struct i2c_device_id sii9022_ids[] = { + { "sii9022-tpi", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, sii9022_ids); + +static int sii9022_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int dev_id, dev_rev; + + /* first step is to enable the TPI mode */ + sii9022_write(client, SII9022_CTRL_REG, 0x00); + dev_id = sii9022_read(client, SII9022_ID_REG); + dev_rev = sii9022_read(client, SII9022_ID_REG+1); + if (dev_id != SII9022_VENDOR_ID) { + printk(KERN_INFO "sii9022 not found\n"); + return -ENODEV; + } + dev_id = sii9022_read(client, SII9022_SEC_VERSION_REG); + dev_info(&client->dev, "found %s chip (rev %01u.%01u)\n", + dev_id ? "SiI9024" : "SiI9022", + (dev_rev >> 4) & 0xf, dev_rev & 0xf); + + /* disable interrupts */ + sii9022_write(client, SII9022_INTR_REG, 0x00); + + return 0; +} + +static int sii9022_remove(struct i2c_client *client) +{ + return 0; +} + +static struct drm_i2c_encoder_driver sii9022_driver = { + .i2c_driver = { + .probe = sii9022_probe, + .remove = sii9022_remove, + .driver = { + .name = "sii9022-tpi", + }, + .id_table = sii9022_ids, + .class = I2C_CLASS_DDC, + }, + .encoder_init = sii9022_encoder_init, +}; + +static int __init sii9022_init(void) +{ + return drm_i2c_encoder_register(THIS_MODULE, &sii9022_driver); +} + +static void __exit sii9022_exit(void) +{ + drm_i2c_encoder_unregister(&sii9022_driver); +} + +module_init(sii9022_init); +module_exit(sii9022_exit); + +MODULE_ALIAS(I2C_MODULE_PREFIX "sii9022-tpi"); +MODULE_AUTHOR("Liviu Dudau <Liviu.Dudau@arm.com>"); +MODULE_DESCRIPTION("Silicon Image SiI9022 HDMI transmitter driver"); +MODULE_LICENSE("GPL v2"); |