| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/platform-defs.h> |
| #include <ddk/protocol/platform/device.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <hw/reg.h> |
| #include <ddk/protocol/i2c.h> |
| #include <ddk/protocol/i2c-lib.h> |
| #include <ddk/protocol/gpio.h> |
| #include "hi-display.h" |
| #include "adv7533.h" |
| #include "edid.h" |
| |
| #define TRACE zxlogf(INFO, "%s %d\n", __FUNCTION__, __LINE__); |
| |
| static const uint8_t adv7533_fixed_registers[] = { |
| 0x16, 0x20, |
| 0x9a, 0xe0, |
| 0xba, 0x70, |
| 0xde, 0x82, |
| 0xe4, 0x40, |
| 0xe5, 0x80 |
| }; |
| |
| static const uint8_t adv7533_cec_fixed_registers[] = { |
| 0x15, 0xd0 , |
| 0x17, 0xd0 , |
| 0x24, 0x20 , |
| 0x57, 0x11 , |
| 0x05, 0xc8 |
| }; |
| |
| uint8_t edid_buf[256]; |
| bool edid_complete = false; |
| |
| uint8_t* adv7533_get_edid_buffer(void) { |
| if (edid_complete) { |
| return edid_buf; |
| } |
| return 0; |
| } |
| |
| /* Helper functions for reading/writing to single registers */ |
| static void adv7533_mainchn_write(display_t* display, uint8_t d1, uint8_t d2) { |
| display->write_buf[0] = d1; |
| display->write_buf[1] = d2; |
| i2c_write_read_sync(&display->i2c_dev.i2c_main, display->write_buf, 2, NULL, 0); |
| } |
| |
| static void adv7533_mainchn_read(display_t* display, uint8_t d1, uint8_t len) { |
| display->write_buf[0] = d1; |
| i2c_write_read_sync(&display->i2c_dev.i2c_main, display->write_buf, 1, display->write_buf, len); |
| } |
| |
| static void adv7533_cecchn_write(display_t* display, uint8_t d1, uint8_t d2) { |
| display->write_buf[0] = d1; |
| display->write_buf[1] = d2; |
| i2c_write_read_sync(&display->i2c_dev.i2c_cec, display->write_buf, 2, NULL, 0); |
| } |
| |
| static void adv7533_edidchn_read(display_t* display, uint8_t d1, uint8_t len) { |
| display->write_buf[0] = d1; |
| i2c_write_read_sync(&display->i2c_dev.i2c_edid, display->write_buf, 1, display->write_buf, len); |
| } |
| |
| zx_status_t adv7533_init(display_t* display) { |
| gpio_protocol_t* gpios = display->hdmi_gpio.gpios; |
| |
| adv7533_mainchn_read(display, ADV7533_REG_CHIP_REVISION, 1); |
| zxlogf(INFO, "%s: HDMI Ver 0x%x\n", __FUNCTION__, display->write_buf[0]); |
| |
| /* Write ADV7533 fixed register values */ |
| for (size_t i = 0; i < sizeof(adv7533_fixed_registers); i += 2) { |
| adv7533_mainchn_write(display, adv7533_fixed_registers[i], adv7533_fixed_registers[i + 1]); |
| } |
| |
| /* Write EDID I2C Slave Address */ |
| adv7533_mainchn_write(display, ADV7533_REG_EDID_I2C_ADDR, EDID_I2C_ADDR); |
| |
| /* Write packet i2c address */ |
| adv7533_mainchn_write(display, ADV7533_REG_PACKET_I2C_ADDR, PACKET_I2C_ADDR); |
| |
| /* Write cec i2c address */ |
| adv7533_mainchn_write(display, ADV7533_REG_CEC_I2C_ADDR, CEC_I2C_ADDR); |
| |
| /* Disable packet_enable1 */ |
| adv7533_mainchn_write(display, ADV7533_REG_PACKET_ENABLE1, PACKET_ENABLE_DISABLE); |
| |
| /* Write ADV7533 CEC fixed register values */ |
| for (size_t i = 0; i < sizeof(adv7533_cec_fixed_registers); i += 2) { |
| adv7533_mainchn_write(display, adv7533_cec_fixed_registers[i], |
| adv7533_cec_fixed_registers[i + 1]); |
| } |
| |
| /* ADV7533_REG_CEC_CTRL write 1 */ |
| adv7533_mainchn_write(display, ADV7533_REG_CEC_CTRL, 0x1); |
| |
| /* Power off display */ |
| /* Spec doc missing for these magic registers */ |
| adv7533_cecchn_write(display, 0x3, 0xb); |
| adv7533_cecchn_write(display, 0x27, 0xb); |
| |
| /* Detect display */ |
| /* TODO: Once GPIO IRQ support is added, we can properly implement hotplug detection */ |
| adv7533_mainchn_read(display, ADV7533_REG_STATUS, 1); |
| if ( (display->write_buf[0] & REG_STATUS_HPD_DET) == 0) { |
| zxlogf(INFO, "%s: No external display detected\n", __FUNCTION__); |
| return ZX_ERR_IO_NOT_PRESENT; |
| } |
| |
| /* Clear HPD INTR */ |
| adv7533_mainchn_write(display, ADV7533_REG_INT0, REG_INT0_HPD); |
| |
| /* Power up interface */ |
| adv7533_mainchn_write(display, ADV7533_REG_POWER, REG_POWER_PWR_UP); |
| |
| /* Enable HPD and EDID RDY Interrupt */ |
| adv7533_mainchn_write(display, ADV7533_REG_INT0_ENABLE, |
| (REG_INT0_ENABLE_HPD | REG_INT0_ENABLE_EDID_RDY)); |
| |
| /* Enable DDC Errors */ |
| adv7533_mainchn_write(display, ADV7533_REG_INT1_ENABLE, REG_INT1_ENABLE_DDC_ERR); |
| |
| /* Assume HPD is always HIGH (ignore HPD line) */ |
| adv7533_mainchn_write(display, ADV7533_REG_POWER2, REG_POWER2_HPD_ALWAYS_HIGH); |
| |
| /* Set EDID I2C Slave Address */ |
| adv7533_mainchn_write(display, ADV7533_REG_EDID_I2C_ADDR, EDID_I2C_ADDR); |
| |
| /* Wait EDID once ready */ |
| //TODO: Use GPIO IRQ once it is implemented |
| uint8_t g = 0; |
| do { |
| gpio_read(&gpios[GPIO_INT], &g); |
| } while(g); |
| |
| /* Interrupt fired. Let's see if EDID is ready to be read */ |
| adv7533_mainchn_read(display, ADV7533_REG_DDC_STATUS, 1); |
| if (display->write_buf[0] != REG_DDC_STATUS_EDID_READY) { |
| zxlogf(ERROR, "%s: EDID not ready!!!!\n", __FUNCTION__); |
| adv7533_mainchn_read(display, ADV7533_REG_INT0, 2); |
| zxlogf(ERROR, "%s: INTR REGS: ADV7533_REG_INT0 = 0x%x, ADV7533_REG_INT1 = 0x%x\n", |
| __FUNCTION__, display->write_buf[0], display->write_buf[1]); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // Save EDID value. Read first 128 first |
| for (int h = 0; h < 128; h += 32) { |
| adv7533_edidchn_read(display, h, 32); |
| memcpy(&edid_buf[h], &display->write_buf[0], 32); |
| } |
| |
| if (edid_has_extension(edid_buf)) { |
| zxlogf(INFO, "EDID has extension. Continue Reading\n"); |
| for (int h = 128; h < 256; h += 32) { |
| adv7533_edidchn_read(display, h, 32); |
| memcpy(&edid_buf[h], &display->write_buf[0], 32); |
| } |
| } |
| edid_complete = true; |
| |
| /* Power down interface for now */ |
| adv7533_mainchn_write(display, ADV7533_REG_POWER, REG_POWER_PWR_DWN); |
| |
| /* Enable HDMI Mode */ |
| adv7533_mainchn_write(display, ADV7533_REG_HDCP_HDMI_CFG, REG_HDCP_HDMI_CFG_ENB_HDMI); |
| |
| return ZX_OK; |
| } |
| |
| void hdmi_init(display_t* display) { |
| |
| /* Power up the interface */ |
| adv7533_mainchn_write(display, ADV7533_REG_POWER, REG_POWER_PWR_UP); |
| |
| /* Enable HPD and EDID RDY Interrupt */ |
| adv7533_mainchn_write(display, ADV7533_REG_INT0_ENABLE, |
| (REG_INT0_ENABLE_HPD | REG_INT0_ENABLE_EDID_RDY)); |
| |
| /* Enable DDC Errors */ |
| adv7533_mainchn_write(display, ADV7533_REG_INT1_ENABLE, REG_INT1_ENABLE_DDC_ERR); |
| |
| /* Write ADV7533 fixed register values */ |
| for (size_t i = 0; i < sizeof(adv7533_fixed_registers); i += 2) { |
| adv7533_mainchn_write(display, adv7533_fixed_registers[i], adv7533_fixed_registers[i + 1]); |
| } |
| |
| /* Write EDID I2C Slave Address */ |
| adv7533_mainchn_write(display, ADV7533_REG_EDID_I2C_ADDR, EDID_I2C_ADDR); |
| |
| /* Write packet i2c address */ |
| adv7533_mainchn_write(display, ADV7533_REG_PACKET_I2C_ADDR, PACKET_I2C_ADDR); |
| |
| /* Write cec i2c address */ |
| adv7533_mainchn_write(display, ADV7533_REG_CEC_I2C_ADDR, CEC_I2C_ADDR); |
| |
| /* Enable HDMI Mode */ |
| adv7533_mainchn_write(display, ADV7533_REG_HDCP_HDMI_CFG, REG_HDCP_HDMI_CFG_ENB_HDMI); |
| |
| /* Assume HPD is always HIGH (ignore HPD line) */ |
| adv7533_mainchn_write(display, ADV7533_REG_POWER2, REG_POWER2_HPD_ALWAYS_HIGH); |
| |
| /* set number of display lanes */ |
| adv7533_cecchn_write(display, 0x1c, 0x40); |
| |
| /* disable internal timing generator */ |
| adv7533_cecchn_write(display, 0x27, 0xb); |
| /* enable hdmi */ |
| adv7533_cecchn_write(display, 0x3, 0x89); |
| |
| /* disable test mode */ |
| adv7533_cecchn_write(display, 0x55, 0x0); |
| |
| /* Write ADV7533 CEC fixed register values */ |
| for (size_t i = 0; i < sizeof(adv7533_cec_fixed_registers); i += 2) { |
| adv7533_mainchn_write(display, adv7533_cec_fixed_registers[i], |
| adv7533_cec_fixed_registers[i + 1]); |
| } |
| } |