# HG changeset patch # User Paul Boddie # Date 1590099445 -7200 # Node ID 3a398d4043cc2c6d5b12e84c51b91b0cac1efc69 # Parent 88f792e6a5037bcce3b8af32a4905730937e8856 Added some support for the JZ4780's HDMI peripheral and I2C/DDC querying. Fixed some details in the CPM support for the JZ4780's LCD peripheral. Added an example demonstrating DDC querying using the HDMI peripheral's I2C functionality instead of the Ingenic I2C peripherals. diff -r 88f792e6a503 -r 3a398d4043cc conf/landfall-examples/mips-ci20-hdmi-i2c.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/conf/landfall-examples/mips-ci20-hdmi-i2c.cfg Fri May 22 00:17:25 2020 +0200 @@ -0,0 +1,29 @@ +# this is a configuration to start 'ex_ci20_hdmi_i2c' + +local L4 = require("L4"); + +local l = L4.default_loader; + +local io_buses = + { + hdmi = l:new_channel(); + }; + +l:start({ + caps = { + hdmi = io_buses.hdmi:svr(), + icu = L4.Env.icu, + sigma0 = L4.cast(L4.Proto.Factory, L4.Env.sigma0):create(L4.Proto.Sigma0), + }, + log = { "IO", "y" }, + l4re_dbg = L4.Dbg.Warn, + }, + "rom/io -vvvv rom/hw_devices.io rom/mips-ci20-hdmi-i2c.io"); + +l:start({ + caps = { + icu = L4.Env.icu, + vbus = io_buses.hdmi, + }, + }, + "rom/ex_ci20_hdmi_i2c"); diff -r 88f792e6a503 -r 3a398d4043cc conf/landfall-examples/mips-ci20-hdmi-i2c.io --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/conf/landfall-examples/mips-ci20-hdmi-i2c.io Fri May 22 00:17:25 2020 +0200 @@ -0,0 +1,13 @@ +-- vi:ft=lua +-- configuration file for io + +local hw = Io.system_bus() + +local bus = Io.Vi.System_bus +{ + CPM = wrap(hw:match("jz4780-cpm")); + GPIO = wrap(hw:match("jz4780-gpio")); + HDMI = wrap(hw:match("jz4780-hdmi")); +} + +Io.add_vbus("hdmi", bus) diff -r 88f792e6a503 -r 3a398d4043cc conf/landfall-examples/mips-ci20-hdmi-i2c.list --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/conf/landfall-examples/mips-ci20-hdmi-i2c.list Fri May 22 00:17:25 2020 +0200 @@ -0,0 +1,14 @@ + +modaddr 0x1100000 + +entry mips-ci20-hdmi-i2c-example +bootstrap bootstrap -serial +kernel fiasco -serial_esc +roottask moe rom/mips-ci20-hdmi-i2c.cfg +module mips-ci20-hdmi-i2c.cfg +module mips-ci20-hdmi-i2c.io +module plat-mips-ci20/hw_devices.io +module l4re +module io +module ned +module ex_ci20_hdmi_i2c diff -r 88f792e6a503 -r 3a398d4043cc pkg/devices/lib/cpm/include/cpm-jz4780.h --- a/pkg/devices/lib/cpm/include/cpm-jz4780.h Sat Jan 25 16:07:18 2020 +0100 +++ b/pkg/devices/lib/cpm/include/cpm-jz4780.h Fri May 22 00:17:25 2020 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017, 2018 Paul Boddie + * Copyright (C) 2017, 2018, 2020 Paul Boddie * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -63,6 +63,7 @@ // Clock dividers. + void set_hdmi_divider(uint16_t division); void set_lcd_pixel_divider(uint16_t division); // Clock control. @@ -76,6 +77,7 @@ // Clock sources. void set_hclock2_source(uint8_t source); + void set_hdmi_source(uint8_t source); void set_lcd_source(uint8_t source); public: @@ -91,11 +93,15 @@ uint8_t get_hclock0_divider(); uint8_t get_hclock2_divider(); uint8_t get_pclock_divider(); + uint8_t get_hdmi_divider(); uint8_t get_lcd_pixel_divider(); uint8_t get_memory_divider(); // Clock control. + void start_hdmi(); + void stop_hdmi(); + void start_lcd(); void stop_lcd(); @@ -112,6 +118,7 @@ uint8_t get_cpu_source(); uint8_t get_hclock0_source(); uint8_t get_hclock2_source(); + uint8_t get_hdmi_source(); uint8_t get_lcd_source(); uint8_t get_memory_source(); uint8_t get_pclock_source(); @@ -119,6 +126,7 @@ uint32_t get_cpu_source_frequency(); uint32_t get_hclock0_source_frequency(); uint32_t get_hclock2_source_frequency(); + uint32_t get_hdmi_source_frequency(); uint32_t get_lcd_source_frequency(); uint32_t get_memory_source_frequency(); uint32_t get_pclock_source_frequency(); @@ -128,6 +136,7 @@ uint32_t get_cpu_frequency(); uint32_t get_hclock0_frequency(); uint32_t get_hclock2_frequency(); + uint32_t get_hdmi_frequency(); uint32_t get_lcd_pixel_frequency(); uint32_t get_memory_frequency(); uint32_t get_pclock_frequency(); @@ -137,6 +146,7 @@ uint32_t get_mpll_frequency(); uint32_t get_vpll_frequency(); + void set_hdmi_frequency(uint32_t pclk); void set_lcd_pixel_frequency(uint32_t pclk); void set_lcd_frequencies(uint32_t pclk, uint8_t multiplier); void set_pll_parameters(uint32_t pll_reg, uint16_t multiplier, uint8_t in_divider, uint8_t out_divider); @@ -157,24 +167,30 @@ int jz4780_cpm_have_clock(void *cpm); void jz4780_cpm_start_clock(void *cpm); +void jz4780_cpm_start_hdmi(void *cpm); +void jz4780_cpm_stop_hdmi(void *cpm); + void jz4780_cpm_start_lcd(void *cpm); void jz4780_cpm_stop_lcd(void *cpm); uint8_t jz4780_cpm_get_cpu_divider(void *cpm); uint8_t jz4780_cpm_get_hclock0_divider(void *cpm); uint8_t jz4780_cpm_get_hclock2_divider(void *cpm); +uint8_t jz4780_cpm_get_hdmi_divider(void *cpm); uint8_t jz4780_cpm_get_lcd_pixel_divider(void *cpm); uint8_t jz4780_cpm_get_memory_divider(void *cpm); uint8_t jz4780_cpm_get_pclock_divider(void *cpm); uint8_t jz4780_cpm_get_hclock0_source(void *cpm); uint8_t jz4780_cpm_get_hclock2_source(void *cpm); +uint8_t jz4780_cpm_get_hdmi_source(void *cpm); uint8_t jz4780_cpm_get_lcd_source(void *cpm); uint8_t jz4780_cpm_get_memory_source(void *cpm); uint8_t jz4780_cpm_get_pclock_source(void *cpm); uint32_t jz4780_cpm_get_hclock0_source_frequency(void *cpm); uint32_t jz4780_cpm_get_hclock2_source_frequency(void *cpm); +uint32_t jz4780_cpm_get_hdmi_source_frequency(void *cpm); uint32_t jz4780_cpm_get_lcd_source_frequency(void *cpm); uint32_t jz4780_cpm_get_memory_source_frequency(void *cpm); uint32_t jz4780_cpm_get_pclock_source_frequency(void *cpm); @@ -187,6 +203,7 @@ uint32_t jz4780_cpm_get_cpu_frequency(void *cpm); uint32_t jz4780_cpm_get_hclock0_frequency(void *cpm); uint32_t jz4780_cpm_get_hclock2_frequency(void *cpm); +uint32_t jz4780_cpm_get_hdmi_frequency(void *cpm); uint32_t jz4780_cpm_get_lcd_pixel_frequency(void *cpm); uint32_t jz4780_cpm_get_memory_frequency(void *cpm); uint32_t jz4780_cpm_get_pclock_frequency(void *cpm); @@ -196,6 +213,7 @@ uint32_t jz4780_cpm_get_mpll_frequency(void *cpm); uint32_t jz4780_cpm_get_vpll_frequency(void *cpm); +void jz4780_cpm_set_hdmi_frequency(void *cpm, uint32_t pclk); void jz4780_cpm_set_lcd_pixel_frequency(void *cpm, uint32_t pclk); void jz4780_cpm_set_mpll_parameters(void *cpm, uint16_t multiplier, uint8_t in_divider, uint8_t out_divider); diff -r 88f792e6a503 -r 3a398d4043cc pkg/devices/lib/cpm/src/jz4780.cc --- a/pkg/devices/lib/cpm/src/jz4780.cc Sat Jan 25 16:07:18 2020 +0100 +++ b/pkg/devices/lib/cpm/src/jz4780.cc Fri May 22 00:17:25 2020 +0200 @@ -3,7 +3,7 @@ * provided by the jz4780 and related SoCs. The power management * functionality could be exposed using a separate driver. * - * Copyright (C) 2017, 2018 Paul Boddie + * Copyright (C) 2017, 2018, 2020 Paul Boddie * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -48,6 +48,12 @@ Msc_divider2 = 0x0a8, // MSC2CDR Uhc_divider = 0x06c, // UHCCDR Ssi_divider = 0x074, // SSICDR + + // ... + + Hdmi_divider = 0x08c, // HDMICDR + + // ... }; enum Clock_bits : unsigned @@ -78,23 +84,39 @@ Clock_source_ddr = 30, // DCS Clock_source_i2s = 31, // I2CS Clock_source_lcd = 30, // LPCS + Clock_source_hdmi = 30, // HPCS }; enum Clock_sources : unsigned { + // Main clock sources. + Source_pll_A = 1, // APLL Source_external = 2, // EXCLK Source_realtime = 3, // RTCLK - Source_main_frequency = 1, // SCLK_A - Source_pll_M = 2, // MPLL - Source_pll_E = 3, // EPLL - Source_pll_V = 3, // VPLL + + // Stoppable clock sources. + + Source_mux_stopped = 0, + Source_mux_main = 1, // SCLK_A + Source_mux_pll_M = 2, // MPLL + Source_mux_pll_E = 3, // EPLL + Source_mux_realtime = 3, // RTCLK (TCK) + + // Unstoppable clock sources. + + Source_main = 0, // SCLK_A + Source_pll_M = 1, // MPLL + Source_pll_E = 2, // EPLL + Source_pll_V = 2, // VPLL + Source_otg_phy = 3, // OTG_PHY }; enum Clock_gate_bits : unsigned { Clock_gate_lcd = 28, // LCD (in CLKGR0) Clock_gate_tve = 27, // TVE (in CLKGR0) + Clock_gate_hdmi = 9, // HDMI (in CLKGR1) Clock_gate_smb4 = 12, // SMB4 (in CLKGR1) Clock_gate_smb3 = 0, // SMB3 (in CLKGR1) Clock_gate_smb2 = 25, // SMB2 (in CLKGR0) @@ -114,14 +136,23 @@ enum Divider_bits : unsigned { Ddr_divider_value = 0, // DDRCDR + Hdmi_divider_value = 0, // HDMICDR Lcd_divider_value = 0, // LPCDR }; -enum Lcd_clock_bits : unsigned +enum Lcd_clock_values : unsigned { - Lcd_change_enable = 0x1000, // CE_LCD - Lcd_change_busy = 0x0800, // LCD_BUSY - Lcd_clock_stop = 0x0400, // LCD_STOP + Lcd_change_enable = 0x10000000, // CE_LCD + Lcd_change_busy = 0x08000000, // LCD_BUSY + Lcd_clock_stop = 0x04000000, // LCD_STOP +}; + +enum Hdmi_divider_values : unsigned +{ + Hdmi_select_mask = 0xc0000000, // HPCS + Hdmi_change_enable = 0x20000000, // CE_HDMI + Hdmi_change_busy = 0x10000000, // HDMI_BUSY + Hdmi_clock_stop = 0x08000000, // HDMI_STOP }; @@ -313,6 +344,14 @@ return _get_divider(Clock_control, 0xf, Clock_pclock_divider); } +// HDMI clock divider. + +uint8_t +Cpm_jz4780_chip::get_hdmi_divider() +{ + return get_field(Hdmi_divider, 0xff, Hdmi_divider_value) + 1; +} + // LCD clock (LPCLK) divider for LCD0 pixel clock. uint8_t @@ -329,6 +368,28 @@ return _get_divider(Ddr_divider, 0xf, Ddr_divider_value); } +// HDMI clock divider. + +void +Cpm_jz4780_chip::set_hdmi_divider(uint16_t division) +{ + if ((division < 1) || (division > 256)) + return; + + // Enable change. + + _regs[Hdmi_divider] = _regs[Hdmi_divider] | Hdmi_change_enable; + + // Set the divider. + + set_field(Hdmi_divider, 0xff, Hdmi_divider_value, division - 1); + + // Restart clock and disable change. + + while (_regs[Hdmi_divider] & Hdmi_change_busy); + _regs[Hdmi_divider] = _regs[Hdmi_divider] & ~Hdmi_change_enable; +} + // LCD pixel clock divider. // NOTE: This only supports the first LCD peripheral. @@ -357,6 +418,18 @@ // Clock gating control. void +Cpm_jz4780_chip::start_hdmi() +{ + _regs[Clock_gate1] = _regs[Clock_gate1] & ~(1 << Clock_gate_hdmi); +} + +void +Cpm_jz4780_chip::stop_hdmi() +{ + _regs[Clock_gate1] = _regs[Clock_gate1] | (1 << Clock_gate_hdmi); +} + +void Cpm_jz4780_chip::start_lcd() { _regs[Clock_gate0] = _regs[Clock_gate0] & ~(1 << Clock_gate_lcd); @@ -415,9 +488,9 @@ { switch (get_memory_source()) { - case Source_main_frequency: + case Source_mux_main: return get_main_frequency(); - case Source_pll_M: + case Source_mux_pll_M: return get_pll_frequency(Pll_control_M); default: return 0; @@ -435,11 +508,11 @@ { switch (get_cpu_source()) { - case Source_main_frequency: + case Source_mux_main: return get_main_frequency(); - case Source_pll_M: + case Source_mux_pll_M: return get_pll_frequency(Pll_control_M); - case Source_pll_E: + case Source_mux_pll_E: return get_pll_frequency(Pll_control_E); default: return 0; @@ -457,11 +530,11 @@ { switch (get_hclock0_source()) { - case Source_main_frequency: + case Source_mux_main: return get_main_frequency(); - case Source_pll_M: + case Source_mux_pll_M: return get_pll_frequency(Pll_control_M); - case Source_pll_E: + case Source_mux_pll_E: return get_pll_frequency(Pll_control_E); default: return 0; @@ -479,11 +552,11 @@ { switch (get_hclock2_source()) { - case Source_main_frequency: + case Source_mux_main: return get_main_frequency(); - case Source_pll_M: + case Source_mux_pll_M: return get_pll_frequency(Pll_control_M); - case Source_realtime: + case Source_mux_realtime: return _rtclk_freq; // "TCK" in the manual, RTCLK in Linux driver code default: return 0; @@ -497,6 +570,45 @@ } uint8_t +Cpm_jz4780_chip::get_hdmi_source() +{ + return get_field(Hdmi_divider, 0x3, Clock_source_hdmi); +} + +uint32_t +Cpm_jz4780_chip::get_hdmi_source_frequency() +{ + switch (get_hdmi_source()) + { + case Source_main: + return get_main_frequency(); + case Source_pll_M: + return get_pll_frequency(Pll_control_M); + case Source_pll_V: + return get_pll_frequency(Pll_control_V); + default: + return 0; + } +} + +void +Cpm_jz4780_chip::set_hdmi_source(uint8_t source) +{ + // Stop clock and enable change. + + _regs[Hdmi_divider] = _regs[Hdmi_divider] | Hdmi_change_enable | Hdmi_clock_stop; + + // Set the source. + + set_field(Hdmi_divider, 0x03, Clock_source_hdmi, source); + + // Restart clock and disable change. + + while (_regs[Hdmi_divider] & Hdmi_change_busy); + _regs[Hdmi_divider] = _regs[Hdmi_divider] & ~(Hdmi_change_enable | Hdmi_clock_stop); +} + +uint8_t Cpm_jz4780_chip::get_lcd_source() { return get_field(Lcd_divider0, 0x3, Clock_source_lcd); @@ -507,7 +619,7 @@ { switch (get_lcd_source()) { - case Source_main_frequency: + case Source_main: return get_main_frequency(); case Source_pll_M: return get_pll_frequency(Pll_control_M); @@ -611,6 +723,14 @@ return get_pclock_source_frequency() / get_pclock_divider(); } +// Clock frequency for the HDMI peripheral. + +uint32_t +Cpm_jz4780_chip::get_hdmi_frequency() +{ + return get_hdmi_source_frequency() / get_hdmi_divider(); +} + // Clock frequency for the LCD0 controller. uint32_t @@ -653,6 +773,15 @@ +void +Cpm_jz4780_chip::set_hdmi_frequency(uint32_t pclk) +{ + // Switch to the video PLL and attempt to set the divider. + + set_hdmi_source(Source_pll_V); + set_hdmi_divider(get_hdmi_source_frequency() / pclk); +} + // Set the pixel frequency. // Unlike the jz4740, HCLK/AHB0 is used as the device frequency, with the pixel // frequency being based on the selected clock source (SCLK_A, MPLL or VPLL). @@ -710,6 +839,18 @@ void +jz4780_cpm_start_hdmi(void *cpm) +{ + static_cast(cpm)->start_hdmi(); +} + +void +jz4780_cpm_stop_hdmi(void *cpm) +{ + static_cast(cpm)->stop_hdmi(); +} + +void jz4780_cpm_start_lcd(void *cpm) { static_cast(cpm)->start_lcd(); @@ -742,6 +883,12 @@ } uint8_t +jz4780_cpm_get_hdmi_divider(void *cpm) +{ + return static_cast(cpm)->get_hdmi_divider(); +} + +uint8_t jz4780_cpm_get_lcd_pixel_divider(void *cpm) { return static_cast(cpm)->get_lcd_pixel_divider(); @@ -774,6 +921,12 @@ } uint8_t +jz4780_cpm_get_hdmi_source(void *cpm) +{ + return static_cast(cpm)->get_hdmi_source(); +} + +uint8_t jz4780_cpm_get_lcd_source(void *cpm) { return static_cast(cpm)->get_lcd_source(); @@ -812,6 +965,12 @@ } uint32_t +jz4780_cpm_get_hdmi_source_frequency(void *cpm) +{ + return static_cast(cpm)->get_hdmi_source_frequency(); +} + +uint32_t jz4780_cpm_get_lcd_source_frequency(void *cpm) { return static_cast(cpm)->get_lcd_source_frequency(); @@ -862,6 +1021,12 @@ } uint32_t +jz4780_cpm_get_hdmi_frequency(void *cpm) +{ + return static_cast(cpm)->get_hdmi_frequency(); +} + +uint32_t jz4780_cpm_get_lcd_pixel_frequency(void *cpm) { return static_cast(cpm)->get_lcd_pixel_frequency(); @@ -906,6 +1071,12 @@ void +jz4780_cpm_set_hdmi_frequency(void *cpm, uint32_t pclk) +{ + static_cast(cpm)->set_hdmi_frequency(pclk); +} + +void jz4780_cpm_set_lcd_pixel_frequency(void *cpm, uint32_t pclk) { static_cast(cpm)->set_lcd_pixel_frequency(pclk); diff -r 88f792e6a503 -r 3a398d4043cc pkg/devices/lib/hdmi/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/hdmi/Makefile Fri May 22 00:17:25 2020 +0200 @@ -0,0 +1,8 @@ +PKGDIR ?= ../.. +L4DIR ?= $(PKGDIR)/../.. + +TARGET := include src + +include $(L4DIR)/mk/subdir.mk + +src: include diff -r 88f792e6a503 -r 3a398d4043cc pkg/devices/lib/hdmi/include/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/hdmi/include/Makefile Fri May 22 00:17:25 2020 +0200 @@ -0,0 +1,4 @@ +PKGDIR = ../../.. +L4DIR ?= $(PKGDIR)/../.. + +include $(L4DIR)/mk/include.mk diff -r 88f792e6a503 -r 3a398d4043cc pkg/devices/lib/hdmi/include/hdmi-jz4780.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/hdmi/include/hdmi-jz4780.h Fri May 22 00:17:25 2020 +0200 @@ -0,0 +1,90 @@ +/* + * JZ4780 HDMI peripheral support. + * + * Copyright (C) 2020 Paul Boddie + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA + */ + +#pragma once + +#include +#include + + + +#ifdef __cplusplus + +#include + +// HDMI device control. + +class Hdmi_jz4780_chip +{ +private: + l4_addr_t _start, _end; + l4_cap_idx_t _irq; + Hw::Register_block<8> _regs; + + // Identification. + + uint16_t _version; + + // Transfer properties. + + bool _segment_read; + uint8_t _device_register; + +protected: + void get_identification(); + + void int_init(); + + void i2c_init(); + long i2c_wait(); + +public: + Hdmi_jz4780_chip(l4_addr_t start, l4_addr_t end, l4_cap_idx_t irq); + + void get_version(uint8_t *major, uint16_t *minor); + + int i2c_read(uint8_t *buf, unsigned int length); + void i2c_set_address(uint8_t address); + void i2c_set_segment(uint8_t segment); + void i2c_set_register(uint8_t device_register); +}; + +#endif /* __cplusplus */ + + + +/* C language interface. */ + +EXTERN_C_BEGIN + +void *jz4780_hdmi_init(l4_addr_t start, l4_addr_t end, l4_cap_idx_t irq); + +void jz4780_hdmi_get_version(void *hdmi, uint8_t *major, uint16_t *minor); + +int jz4780_hdmi_i2c_read(void *hdmi, uint8_t *buf, unsigned int length); + +void jz4780_hdmi_i2c_set_address(void *hdmi, uint8_t address); + +void jz4780_hdmi_i2c_set_segment(void *hdmi, uint8_t segment); + +void jz4780_hdmi_i2c_set_register(void *hdmi, uint8_t device_register); + +EXTERN_C_END diff -r 88f792e6a503 -r 3a398d4043cc pkg/devices/lib/hdmi/src/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/hdmi/src/Makefile Fri May 22 00:17:25 2020 +0200 @@ -0,0 +1,13 @@ +PKGDIR ?= ../../.. +L4DIR ?= $(PKGDIR)/../.. + +TARGET = libhdmi.o.a +PC_FILENAME := libdrivers-hdmi + +SRC_CC := jz4780.cc + +PRIVATE_INCDIR += $(PKGDIR)/lib/hdmi/include + +REQUIRES_LIBS := l4re_c l4re_c-util libdrivers-common + +include $(L4DIR)/mk/lib.mk diff -r 88f792e6a503 -r 3a398d4043cc pkg/devices/lib/hdmi/src/jz4780.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/hdmi/src/jz4780.cc Fri May 22 00:17:25 2020 +0200 @@ -0,0 +1,362 @@ +/* + * JZ4780 HDMI peripheral support. + * + * Copyright (C) 2020 Paul Boddie + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA + */ + +#include +#include + +#include +#include + +#include + +/* +I2C pins: + +HDMI: PF25/SMB4_SDA/DDCSDA, PF24/SMB4_SCK/DDCSCK + +See: http://mipscreator.imgtec.com/CI20/hardware/board/ci20_jz4780_v2.0.pdf +*/ + +enum Regs +{ + // Identification. + + Design_id = 0x000, // DESIGN_ID + Revision_id = 0x001, // REVISION_ID + Product_id0 = 0x002, // PRODUCT_ID0 + Product_id1 = 0x003, // PRODUCT_ID1 + Config_id0 = 0x004, // CONFIG_ID0 + Config_id1 = 0x005, // CONFIG_ID1 + Config_id2 = 0x006, // CONFIG_ID2 + Config_id3 = 0x007, // CONFIG_ID3 + + // Top-level interrupt control. + + Int_mask = 0x1ff, // MUTE + + // Interrupt status and mask for various functions. + + Fc_int_status0 = 0x100, // FC_STAT0 + Fc_int_status1 = 0x101, // FC_STAT1 + Fc_int_status2 = 0x102, // FC_STAT2 + As_int_status = 0x103, // AS_STAT0 + Phy_int_status = 0x104, // PHY_STAT0 + Cec_int_status = 0x106, // CEC_STAT0 + Vp_int_status = 0x107, // VP_STAT0 + Ahb_dma_audio_int_status = 0x109, // AHBDMAAUD_STAT0 + + Fc_int_mask0 = 0x180, // MUTE_FC_STAT0 + Fc_int_mask1 = 0x181, // MUTE_FC_STAT1 + Fc_int_mask2 = 0x182, // MUTE_FC_STAT2 + As_int_mask = 0x183, // MUTE_AS_STAT0 + Phy_int_mask = 0x184, // MUTE_PHY_STAT0 + Cec_int_mask = 0x186, // MUTE_CEC_STAT0 + Vp_int_mask = 0x187, // MUTE_VP_STAT0 + Ahb_dma_audio_int_mask = 0x189, // MUTE_AHBDMAAUD_STAT0 + + // I2C for E-DDC. + + I2c_int_status = 0x105, // I2CM_STAT0 + I2c_int_mask = 0x185, // MUTE_I2CM_STAT0 + + I2c_device_address = 0x7e00, // I2CM_SLAVE + I2c_register = 0x7e01, // I2CM_ADDRESS + I2c_data_out = 0x7e02, // I2CM_DATAO + I2c_data_in = 0x7e03, // I2CM_DATAI + I2c_operation = 0x7e04, // I2CM_OPERATION + I2c_int_config0 = 0x7e05, // I2CM_INT + I2c_int_config1 = 0x7e06, // I2CM_CTLINT + I2c_divider = 0x7e07, // I2CM_DIV + I2c_segment_address = 0x7e08, // I2CM_SEGADDR + I2c_software_reset = 0x7e09, // I2CM_SOFTRSTZ + I2c_segment_pointer = 0x7e0a, // I2CM_SEGPTR + + // I2C for PHY. + + I2c_phy_int_status = 0x108, // I2CMPHY_STAT0 + I2c_phy_int_mask = 0x188, // MUTE_I2CMPHY_STAT0 + + I2c_phy_int_config0 = 0x3027, // PHY_I2CM_INT_ADDR + I2c_phy_int_config1 = 0x3028, // PHY_I2CM_CTLINT_ADDR +}; + +// Identification values. + +enum Product_id_values : unsigned +{ + Product_id0_transmitter = 0xa0, // PRODUCT_ID0_HDMI_TX + + Product_id1_hdcp = 0xc0, // PRODUCT_ID1_HDCP + Product_id1_receiver = 0x02, // PRODUCT_ID1_HDMI_RX + Product_id1_transmitter = 0x01, // PRODUCT_ID1_HDMI_TX +}; + +// Configuration values. + +enum Config_id_values : unsigned +{ + Config_id0_i2s = 0x10, // CONFIG0_I2S + Config_id0_cec = 0x02, // CONFIG0_CEC + + Config_id1_ahb = 0x01, // CONFIG1_AHB + + Config2_dwc_hdmi_tx_phy = 0x00, // DWC_HDMI_TX_PHY + Config2_dwc_mhl_phy_heac = 0xb2, // DWC_MHL_PHY_HEAC + Config2_dwc_mhl_phy = 0xc2, // DWC_MHL_PHY + Config2_dwc_hdmi_3d_tx_phy_heac = 0xe2, // DWC_HDMI_3D_TX_PHY_HEAC + Config2_dwc_hdmi_3d_tx_phy = 0xf2, // DWC_HDMI_3D_TX_PHY + Config2_dwc_hdmi20_tx_phy = 0xf3, // DWC_HDMI20_TX_PHY + Config2_vendor_phy = 0xfe, // VENDOR_PHY + + Config_id3_ahb_audio_dma = 0x02, // CONFIG3_AHBAUDDMA + Config_id3_gp_audio = 0x01, // CONFIG3_GPAUD +}; + +// Status and mask bits. + +enum Int_mask_bits : unsigned +{ + Int_mask_wakeup = 0x2, + Int_mask_all = 0x1, +}; + +enum I2c_int_status_bits : unsigned +{ + I2c_int_status_done = 0x2, + I2c_int_status_error = 0x1, +}; + +// I2C operation bits. + +enum I2c_operation_bits : unsigned +{ + I2c_operation_write = 0x10, + I2c_operation_segment_read = 0x2, + I2c_operation_read = 0x1, +}; + +// Interrupt configuration bits. + +enum I2c_int_config0_bits : unsigned +{ + I2c_int_config_done_polarity = 0x8, + I2c_int_config_done_mask = 0x4, +}; + +enum I2c_int_config1_bits : unsigned +{ + I2c_int_config_nack_polarity = 0x80, + I2c_int_config_nack_mask = 0x40, + I2c_int_config_arb_polarity = 0x8, + I2c_int_config_arb_mask = 0x4, +}; + + + +// Initialise the HDMI peripheral. + +Hdmi_jz4780_chip::Hdmi_jz4780_chip(l4_addr_t start, l4_addr_t end, + l4_cap_idx_t irq) +: _start(start), _end(end), _irq(irq) +{ + // 8-bit registers with 2-bit address shifting. + + _regs = new Hw::Mmio_register_block<8>(start, 2); + + _segment_read = false; + _device_register = 0; + + get_identification(); + int_init(); + i2c_init(); +} + +void Hdmi_jz4780_chip::get_identification() +{ + _version = (_regs[Design_id] << 8) | _regs[Revision_id]; +} + +void Hdmi_jz4780_chip::get_version(uint8_t *major, uint16_t *minor) +{ + *major = _version >> 12; + *minor = _version & 0xfff; +} + +void Hdmi_jz4780_chip::int_init() +{ + // Disable interrupts. + + _regs[Int_mask] = _regs[Int_mask] | (Int_mask_wakeup | Int_mask_all); + + // Mask all interrupts. + + _regs[Fc_int_mask0] = 0xff; + _regs[Fc_int_mask1] = 0xff; + _regs[Fc_int_mask2] = 0xff; + _regs[As_int_mask] = 0xff; + _regs[Phy_int_mask] = 0xff; + _regs[I2c_int_mask] = 0xff; + _regs[Cec_int_mask] = 0xff; + _regs[Vp_int_mask] = 0xff; + _regs[I2c_phy_int_mask] = 0xff; + _regs[Ahb_dma_audio_int_mask] = 0xff; + + // Enable interrupts. + + _regs[Int_mask] = _regs[Int_mask] & ~(Int_mask_wakeup | Int_mask_all); +} + +void Hdmi_jz4780_chip::i2c_init() +{ + // Set PHY interrupt priorities. + + _regs[I2c_phy_int_config0] = I2c_int_config_done_polarity; + _regs[I2c_phy_int_config1] = I2c_int_config_nack_polarity | + I2c_int_config_arb_polarity; + + // Software reset. + + _regs[I2c_software_reset] = 0; + + // Standard mode (100kHz). + + _regs[I2c_divider] = 0; + + // Set interrupt polarities. + + _regs[I2c_int_config0] = I2c_int_config_done_polarity; + _regs[I2c_int_config1] = I2c_int_config_nack_polarity | + I2c_int_config_arb_polarity; + + // Clear and mask/mute interrupts. + + _regs[I2c_int_status] = I2c_int_status_done | I2c_int_status_error; + _regs[I2c_int_mask] = I2c_int_status_done | I2c_int_status_error; +} + +long Hdmi_jz4780_chip::i2c_wait() +{ + long err; + + // Wait for around 1s. + + l4_msgtag_t tag = l4_irq_receive(_irq, L4_IPC_NEVER); + + err = l4_ipc_error(tag, l4_utcb()); + if (err) + return err; + + // Test for an error condition. + + if (_regs[I2c_int_status] & I2c_int_status_error) + return -L4_EIO; + + _regs[I2c_int_status] = _regs[I2c_int_status] | I2c_int_status_done; + + return L4_EOK; +} + +int Hdmi_jz4780_chip::i2c_read(uint8_t *buf, unsigned int length) +{ + unsigned int i; + long err; + + // Clear interrupts. + + _regs[I2c_int_mask] = 0; + + for (i = 0; i < length; i++) + { + // Increment the device register. + + _regs[I2c_register] = _device_register++; + _regs[I2c_operation] = _segment_read ? I2c_operation_segment_read + : I2c_operation_read; + + // Wait and then read. + + err = i2c_wait(); + if (err) + break; + + buf[i] = _regs[I2c_data_in]; + } + + // Mask interrupts again. + + _regs[I2c_int_mask] = I2c_int_status_done | I2c_int_status_error; + + return i; +} + +void Hdmi_jz4780_chip::i2c_set_address(uint8_t address) +{ + _regs[I2c_device_address] = address; + _segment_read = false; + i2c_set_register(0); +} + +void Hdmi_jz4780_chip::i2c_set_segment(uint8_t segment) +{ + _regs[I2c_segment_address] = 0x30; + _regs[I2c_segment_pointer] = segment; + _segment_read = true; + i2c_set_register(0); +} + +void Hdmi_jz4780_chip::i2c_set_register(uint8_t device_register) +{ + _device_register = device_register; +} + + + +// C language interface functions. + +void *jz4780_hdmi_init(l4_addr_t start, l4_addr_t end, l4_cap_idx_t irq) +{ + return (void *) new Hdmi_jz4780_chip(start, end, irq); +} + +void jz4780_hdmi_get_version(void *hdmi, uint8_t *major, uint16_t *minor) +{ + static_cast(hdmi)->get_version(major, minor); +} + +int jz4780_hdmi_i2c_read(void *hdmi, uint8_t *buf, unsigned int length) +{ + return static_cast(hdmi)->i2c_read(buf, length); +} + +void jz4780_hdmi_i2c_set_address(void *hdmi, uint8_t address) +{ + static_cast(hdmi)->i2c_set_address(address); +} + +void jz4780_hdmi_i2c_set_segment(void *hdmi, uint8_t segment) +{ + static_cast(hdmi)->i2c_set_segment(segment); +} + +void jz4780_hdmi_i2c_set_register(void *hdmi, uint8_t device_register) +{ + static_cast(hdmi)->i2c_set_register(device_register); +} diff -r 88f792e6a503 -r 3a398d4043cc pkg/devices/lib/lcd/include/lcd-jz4740.h --- a/pkg/devices/lib/lcd/include/lcd-jz4740.h Sat Jan 25 16:07:18 2020 +0100 +++ b/pkg/devices/lib/lcd/include/lcd-jz4740.h Fri May 22 00:17:25 2020 +0200 @@ -1,7 +1,7 @@ /* * LCD peripheral support for the JZ4740 and related SoCs. * - * Copyright (C) 2018 Paul Boddie + * Copyright (C) 2018, 2020 Paul Boddie * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -38,6 +38,21 @@ uint32_t command; /* CMD: command */ }; +/* 8-word "new descriptor" variant for the JZ4780. */ + +struct Jz4780_lcd_descriptor +{ + struct Jz4780_lcd_descriptor *next; /* FDADR: frame descriptor address */ + uint32_t source; /* FSADR: frame source address */ + uint32_t identifier; /* FIDR: frame identifier */ + uint32_t command; /* CMD: command */ + uint32_t offset; /* OFFSIZE: offset in words between lines */ + uint32_t page_width; /* PW: number of words per line (16x16 block mode) */ + uint32_t command_position; /* CNUM: command number (smart LCD mode) or + CPOS: foreground position and properties */ + uint32_t fg_size; /* DESSIZE: foreground size and alpha properties */ +}; + /* C++ language interface. */ diff -r 88f792e6a503 -r 3a398d4043cc pkg/landfall-examples/ci20_hdmi_i2c/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/landfall-examples/ci20_hdmi_i2c/Makefile Fri May 22 00:17:25 2020 +0200 @@ -0,0 +1,8 @@ +PKGDIR ?= .. +L4DIR ?= $(PKGDIR)/../.. + +TARGET = ex_ci20_hdmi_i2c +SRC_C = ci20_hdmi_i2c.c +REQUIRES_LIBS = libio l4re_c-util libdevice-util libdrivers-cpm libdrivers-hdmi libdrivers-gpio libedid + +include $(L4DIR)/mk/prog.mk diff -r 88f792e6a503 -r 3a398d4043cc pkg/landfall-examples/ci20_hdmi_i2c/ci20_hdmi_i2c.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/landfall-examples/ci20_hdmi_i2c/ci20_hdmi_i2c.c Fri May 22 00:17:25 2020 +0200 @@ -0,0 +1,241 @@ +/* + * (c) 2008-2009 Adam Lackorzynski + * economic rights: Technische Universität Dresden (Germany) + * Copyright (C) 2017, 2018, 2019, 2020 Paul Boddie + * + * This file is part of TUD:OS and distributed under the terms of the + * GNU General Public License 2. + * Please see the COPYING-GPL-2 file for details. + */ +/* + * Access the HDMI I2C peripheral on the MIPS Creator CI20 board. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +enum { + DDCSCL = 24, /* via PORTF */ + DDCSDA = 25, /* via PORTF */ +}; + + + +/* Device and resource discovery. */ + +static long item_in_range(long start, long end, long index) +{ + if (start < end) + return start + index; + else + return start - index; +} + +static void show_timings(uint8_t *buf) +{ + unsigned int width, height; + + /* Attempt to decode EDID information. */ + + libedid_prefered_resolution(buf, &width, &height); + printf("Preferred resolution: %d x %d\n", width, height); + + libedid_dump_standard_timings(buf); +} + +int main(void) +{ + long err; + + /* Buffer for EDID data. */ + + uint8_t edid[128]; + unsigned int length = 0, i; + + /* Version details. */ + + uint8_t hdmi_major; + uint16_t hdmi_minor; + + /* Peripheral memory. */ + + l4_addr_t cpm_base = 0, cpm_base_end = 0; + l4_addr_t gpio_base = 0, gpio_base_end = 0; + l4_addr_t hdmi_base = 0, hdmi_base_end = 0; + l4_addr_t port_f, port_f_end; + + /* Peripheral abstractions. */ + + void *cpm; + void *gpio_port_f; + void *hdmi; + + /* Access to IRQs. */ + + l4_uint32_t hdmi_irq_start = 0, hdmi_irq_end = 0; + l4_cap_idx_t icucap, irqcap; + + irqcap = l4re_util_cap_alloc(); + icucap = l4re_env_get_cap("icu"); + + if (l4_is_invalid_cap(icucap)) + { + printf("No 'icu' capability available in the virtual bus.\n"); + return 1; + } + + if (l4_is_invalid_cap(irqcap)) + { + printf("Capabilities not available for interrupts.\n"); + return 1; + } + + /* Obtain resource details describing the interrupt for HDMI I2C. */ + + printf("Access IRQ...\n"); + + if (get_irq("jz4780-hdmi", &hdmi_irq_start, &hdmi_irq_end) < 0) + return 1; + + printf("IRQ range at %d...%d.\n", hdmi_irq_start, hdmi_irq_end); + + /* Create interrupt objects. */ + + err = l4_error(l4_factory_create_irq(l4re_global_env->factory, irqcap)); + + if (err) + { + printf("Could not create IRQ object: %lx\n", err); + return 1; + } + + /* Bind interrupt objects to IRQ numbers. Here, the first HDMI interrupt is + bound, this being the general HDMI interrupt. */ + + err = l4_error(l4_icu_bind(icucap, + item_in_range(hdmi_irq_start, hdmi_irq_end, 0), + irqcap)); + + if (err) + { + printf("Could not bind IRQ to the ICU: %ld\n", err); + return 1; + } + + /* Attach ourselves to the interrupt handler. */ + + err = l4_error(l4_rcv_ep_bind_thread(irqcap, l4re_env()->main_thread, 0)); + + if (err) + { + printf("Could not attach to IRQs: %ld\n", err); + return 1; + } + + /* Obtain resource details describing I/O memory. */ + + printf("Access CPM...\n"); + + if (get_memory("jz4780-cpm", &cpm_base, &cpm_base_end) < 0) + return 1; + + printf("CPM at 0x%lx...0x%lx.\n", cpm_base, cpm_base_end); + + printf("Access GPIO...\n"); + + if (get_memory("jz4780-gpio", &gpio_base, &gpio_base_end) < 0) + return 1; + + printf("GPIO at 0x%lx...0x%lx.\n", gpio_base, gpio_base_end); + + printf("Access HDMI...\n"); + + if (get_memory("jz4780-hdmi", &hdmi_base, &hdmi_base_end) < 0) + return 1; + + printf("HDMI at 0x%lx...0x%lx.\n", hdmi_base, hdmi_base_end); + + /* Obtain CPM object. */ + + cpm = jz4780_cpm_init(cpm_base); + + printf("VPLL frequency: %d\n", jz4780_cpm_get_vpll_frequency(cpm)); + printf("HDMI divider: %d\n", jz4780_cpm_get_hdmi_divider(cpm)); + printf("HDMI frequency: %d\n", jz4780_cpm_get_hdmi_frequency(cpm)); + + jz4780_cpm_stop_hdmi(cpm); + jz4780_cpm_set_hdmi_frequency(cpm, 27000000); + + printf("HDMI divider: %d\n", jz4780_cpm_get_hdmi_divider(cpm)); + printf("HDMI frequency: %d\n", jz4780_cpm_get_hdmi_frequency(cpm)); + + jz4780_cpm_start_hdmi(cpm); + + /* Configure pins. */ + + port_f = gpio_base + 0x500; + port_f_end = port_f + 0x100; + + printf("PORTF at 0x%lx...0x%lx.\n", port_f, port_f_end); + + gpio_port_f = jz4780_gpio_init(port_f, port_f_end, 32, 0x7fa7f00f, 0x00580ff0); + + printf("Set up GPIO pins...\n"); + + jz4780_gpio_config_pad(gpio_port_f, DDCSCL, Function_alt, 0); + jz4780_gpio_config_pad(gpio_port_f, DDCSDA, Function_alt, 0); + + /* Obtain HDMI reference. */ + + printf("Set up HDMI...\n"); + + hdmi = jz4780_hdmi_init(hdmi_base, hdmi_base_end, irqcap); + + printf("Read version...\n"); + + jz4780_hdmi_get_version(hdmi, &hdmi_major, &hdmi_minor); + + printf("HDMI version is %x.%03x\n", hdmi_major, hdmi_minor); + + printf("Read EDID...\n"); + + jz4780_hdmi_i2c_set_address(hdmi, 0x50); + length = jz4780_hdmi_i2c_read(hdmi, edid, 128); + + if (length) + { + for (i = 0; i < length; i++) + printf("%02x%c", edid[i], ((i % 16) != 15) ? ' ' : '\n'); + } + + show_timings(edid); + + /* Detach from the interrupt. */ + + err = l4_error(l4_irq_detach(irqcap)); + + if (err) + printf("Error detaching from IRQ: %ld\n", err); + + printf("Done.\n"); + + return 0; +} diff -r 88f792e6a503 -r 3a398d4043cc pkg/landfall-examples/ci20_i2c/ci20_i2c.c --- a/pkg/landfall-examples/ci20_i2c/ci20_i2c.c Sat Jan 25 16:07:18 2020 +0100 +++ b/pkg/landfall-examples/ci20_i2c/ci20_i2c.c Fri May 22 00:17:25 2020 +0200 @@ -29,6 +29,14 @@ #include #include +#define REG(x) *((volatile uint32_t *) (x)) + +#define RTC_RTCCR 0x00 +#define RTC_HCR 0x20 +#define RTC_HSPR 0x34 +#define RTC_WENR 0x3c +#define RTC_PWRONCR 0x3c + enum { @@ -233,6 +241,18 @@ return i2c_set_reg(i2c_channel, 0x02, rtcregs + 2, 7, irqcap); } +static void rtc_write_ready(l4_addr_t rtc_base) +{ + while (!(REG(rtc_base + RTC_RTCCR) & 0x80)); +} + +static void rtc_write_enable(l4_addr_t rtc_base) +{ + rtc_write_ready(rtc_base); + REG(rtc_base + RTC_WENR) = 0xa55a; + while (!(REG(rtc_base + RTC_WENR) & 0x80000000)); +} + int main(void) { long err; @@ -242,6 +262,7 @@ l4_addr_t gpio_base = 0, gpio_base_end = 0; l4_addr_t i2c_base = 0, i2c_base_end = 0; l4_addr_t cpm_base = 0, cpm_base_end = 0; + l4_addr_t rtc_base = 0, rtc_base_end = 0; l4_addr_t port_d, port_d_end; l4_addr_t port_e, port_e_end; @@ -311,6 +332,13 @@ printf("I2C at 0x%lx...0x%lx.\n", i2c_base, i2c_base_end); + printf("Access RTC...\n"); + + if (get_memory("jz4780-rtc", &rtc_base, &rtc_base_end) < 0) + return 1; + + printf("RTC at 0x%lx...0x%lx.\n", rtc_base, rtc_base_end); + /* Create interrupt objects. */ err = l4_error(l4_factory_create_irq(l4re_global_env->factory, irq0cap)) || @@ -406,13 +434,31 @@ printf("Updated %d registers.\n", rtc_update(i2c4, irq4cap, rtcregs)); - for (i = 0; i < 10; i++) + for (i = 0; i < 3; i++) { rtc_dump(i2c4, irq4cap, rtcregs); rtc_datetime(rtcregs); sleep(1); } + /* Investigate the internal RTC registers. */ + + printf("Control: %08x\n", REG(rtc_base + RTC_RTCCR)); + printf("Hibernate: %08x\n", REG(rtc_base + RTC_HCR)); + printf("Scratchpad: %08x\n", REG(rtc_base + RTC_HSPR)); + printf("Power on control: %08x\n", REG(rtc_base + RTC_PWRONCR)); + printf("Write enable: %08x\n", REG(rtc_base + RTC_WENR)); + + rtc_write_enable(rtc_base); + rtc_write_ready(rtc_base); + REG(rtc_base + RTC_HSPR) = 0x12345678; + + for (i = 0; i < 3; i++) + { + printf("Scratchpad: %08x\n", REG(rtc_base + RTC_HSPR)); + sleep(1); + } + jz4780_i2c_disable(i2c0); jz4780_i2c_disable(i2c4);