// SPDX-License-Identifier: GPL-2.0-or-later
/*
* cx18 firmware functions
*
* Copyright ( C ) 2007 Hans Verkuil < hverkuil @ xs4all . nl >
* Copyright ( C ) 2008 Andy Walls < awalls @ md . metrocast . net >
*/
#include "cx18-driver.h"
#include "cx18-io.h"
#include "cx18-scb.h"
#include "cx18-irq.h"
#include "cx18-firmware.h"
#include "cx18-cards.h"
#include <linux/firmware.h>
#define CX18_PROC_SOFT_RESET 0 xc70010
#define CX18_DDR_SOFT_RESET 0 xc70014
#define CX18_CLOCK_SELECT1 0 xc71000
#define CX18_CLOCK_SELECT2 0 xc71004
#define CX18_HALF_CLOCK_SELECT1 0 xc71008
#define CX18_HALF_CLOCK_SELECT2 0 xc7100C
#define CX18_CLOCK_POLARITY1 0 xc71010
#define CX18_CLOCK_POLARITY2 0 xc71014
#define CX18_ADD_DELAY_ENABLE1 0 xc71018
#define CX18_ADD_DELAY_ENABLE2 0 xc7101C
#define CX18_CLOCK_ENABLE1 0 xc71020
#define CX18_CLOCK_ENABLE2 0 xc71024
#define CX18_REG_BUS_TIMEOUT_EN 0 xc72024
#define CX18_FAST_CLOCK_PLL_INT 0 xc78000
#define CX18_FAST_CLOCK_PLL_FRAC 0 xc78004
#define CX18_FAST_CLOCK_PLL_POST 0 xc78008
#define CX18_FAST_CLOCK_PLL_PRESCALE 0 xc7800C
#define CX18_FAST_CLOCK_PLL_ADJUST_BANDWIDTH 0 xc78010
#define CX18_SLOW_CLOCK_PLL_INT 0 xc78014
#define CX18_SLOW_CLOCK_PLL_FRAC 0 xc78018
#define CX18_SLOW_CLOCK_PLL_POST 0 xc7801C
#define CX18_MPEG_CLOCK_PLL_INT 0 xc78040
#define CX18_MPEG_CLOCK_PLL_FRAC 0 xc78044
#define CX18_MPEG_CLOCK_PLL_POST 0 xc78048
#define CX18_PLL_POWER_DOWN 0 xc78088
#define CX18_SW1_INT_STATUS 0 xc73104
#define CX18_SW1_INT_ENABLE_PCI 0 xc7311C
#define CX18_SW2_INT_SET 0 xc73140
#define CX18_SW2_INT_STATUS 0 xc73144
#define CX18_ADEC_CONTROL 0 xc78120
#define CX18_DDR_REQUEST_ENABLE 0 xc80000
#define CX18_DDR_CHIP_CONFIG 0 xc80004
#define CX18_DDR_REFRESH 0 xc80008
#define CX18_DDR_TIMING1 0 xc8000C
#define CX18_DDR_TIMING2 0 xc80010
#define CX18_DDR_POWER_REG 0 xc8001C
#define CX18_DDR_TUNE_LANE 0 xc80048
#define CX18_DDR_INITIAL_EMRS 0 xc80054
#define CX18_DDR_MB_PER_ROW_7 0 xc8009C
#define CX18_DDR_BASE_63_ADDR 0 xc804FC
#define CX18_WMB_CLIENT02 0 xc90108
#define CX18_WMB_CLIENT05 0 xc90114
#define CX18_WMB_CLIENT06 0 xc90118
#define CX18_WMB_CLIENT07 0 xc9011C
#define CX18_WMB_CLIENT08 0 xc90120
#define CX18_WMB_CLIENT09 0 xc90124
#define CX18_WMB_CLIENT10 0 xc90128
#define CX18_WMB_CLIENT11 0 xc9012C
#define CX18_WMB_CLIENT12 0 xc90130
#define CX18_WMB_CLIENT13 0 xc90134
#define CX18_WMB_CLIENT14 0 xc90138
#define CX18_DSP0_INTERRUPT_MASK 0 xd0004C
#define APU_ROM_SYNC1 0 x6D676553 /* "mgeS" */
#define APU_ROM_SYNC2 0 x72646548 /* "rdeH" */
struct cx18_apu_rom_seghdr {
u32 sync1;
u32 sync2;
u32 addr;
u32 size;
};
static int load_cpu_fw_direct(const char *fn, u8 __iomem *mem, struct cx18 *cx)
{
const struct firmware *fw = NULL;
int i, j;
unsigned size;
u32 __iomem *dst = (u32 __iomem *)mem;
const u32 *src;
if (request_firmware(&fw, fn, &cx->pci_dev->dev)) {
CX18_ERR("Unable to open firmware %s\n" , fn);
CX18_ERR("Did you put the firmware in the hotplug firmware directory?\n" );
return -ENOMEM;
}
src = (const u32 *)fw->data;
for (i = 0 ; i < fw->size; i += 4096 ) {
cx18_setup_page(cx, i);
for (j = i; j < fw->size && j < i + 4096 ; j += 4 ) {
/* no need for endianness conversion on the ppc */
cx18_raw_writel(cx, *src, dst);
if (cx18_raw_readl(cx, dst) != *src) {
CX18_ERR("Mismatch at offset %x\n" , i);
release_firmware(fw);
cx18_setup_page(cx, 0 );
return -EIO;
}
dst++;
src++;
}
}
if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags))
CX18_INFO("loaded %s firmware (%zu bytes)\n" , fn, fw->size);
size = fw->size;
release_firmware(fw);
cx18_setup_page(cx, SCB_OFFSET);
return size;
}
static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx,
u32 *entry_addr)
{
const struct firmware *fw = NULL;
int i, j;
unsigned size;
const u32 *src;
struct cx18_apu_rom_seghdr seghdr;
const u8 *vers;
u32 offset = 0 ;
u32 apu_version = 0 ;
int sz;
if (request_firmware(&fw, fn, &cx->pci_dev->dev)) {
CX18_ERR("unable to open firmware %s\n" , fn);
CX18_ERR("did you put the firmware in the hotplug firmware directory?\n" );
cx18_setup_page(cx, 0 );
return -ENOMEM;
}
*entry_addr = 0 ;
src = (const u32 *)fw->data;
vers = fw->data + sizeof (seghdr);
sz = fw->size;
apu_version = (vers[0 ] << 24 ) | (vers[4 ] << 16 ) | vers[32 ];
while (offset + sizeof (seghdr) < fw->size) {
const __le32 *shptr = (__force __le32 *)src + offset / 4 ;
seghdr.sync1 = le32_to_cpu(shptr[0 ]);
seghdr.sync2 = le32_to_cpu(shptr[1 ]);
seghdr.addr = le32_to_cpu(shptr[2 ]);
seghdr.size = le32_to_cpu(shptr[3 ]);
offset += sizeof (seghdr);
if (seghdr.sync1 != APU_ROM_SYNC1 ||
seghdr.sync2 != APU_ROM_SYNC2) {
offset += seghdr.size;
continue ;
}
CX18_DEBUG_INFO("load segment %x-%x\n" , seghdr.addr,
seghdr.addr + seghdr.size - 1 );
if (*entry_addr == 0 )
*entry_addr = seghdr.addr;
if (offset + seghdr.size > sz)
break ;
for (i = 0 ; i < seghdr.size; i += 4096 ) {
cx18_setup_page(cx, seghdr.addr + i);
for (j = i; j < seghdr.size && j < i + 4096 ; j += 4 ) {
/* no need for endianness conversion on the ppc */
cx18_raw_writel(cx, src[(offset + j) / 4 ],
dst + seghdr.addr + j);
if (cx18_raw_readl(cx, dst + seghdr.addr + j)
!= src[(offset + j) / 4 ]) {
CX18_ERR("Mismatch at offset %x\n" ,
offset + j);
release_firmware(fw);
cx18_setup_page(cx, 0 );
return -EIO;
}
}
}
offset += seghdr.size;
}
if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags))
CX18_INFO("loaded %s firmware V%08x (%zu bytes)\n" ,
fn, apu_version, fw->size);
size = fw->size;
release_firmware(fw);
cx18_setup_page(cx, 0 );
return size;
}
void cx18_halt_firmware(struct cx18 *cx)
{
CX18_DEBUG_INFO("Preparing for firmware halt.\n" );
cx18_write_reg_expect(cx, 0 x000F000F, CX18_PROC_SOFT_RESET,
0 x0000000F, 0 x000F000F);
cx18_write_reg_expect(cx, 0 x00020002, CX18_ADEC_CONTROL,
0 x00000002, 0 x00020002);
}
void cx18_init_power(struct cx18 *cx, int lowpwr)
{
/* power-down Spare and AOM PLLs */
/* power-up fast, slow and mpeg PLLs */
cx18_write_reg(cx, 0 x00000008, CX18_PLL_POWER_DOWN);
/* ADEC out of sleep */
cx18_write_reg_expect(cx, 0 x00020000, CX18_ADEC_CONTROL,
0 x00000000, 0 x00020002);
/*
* The PLL parameters are based on the external crystal frequency that
* would ideally be :
*
* NTSC Color subcarrier freq * 8 =
* 4 . 5 MHz / 286 * 455 / 2 * 8 = 28 . 63636363 . . . MHz
*
* The accidents of history and rationale that explain from where this
* combination of magic numbers originate can be found in :
*
* [ 1 ] Abrahams , I . C . , " Choice of Chrominance Subcarrier Frequency in
* the NTSC Standards " , Proceedings of the I - R - E , January 1954 , pp 79 - 80
*
* [ 2 ] Abrahams , I . C . , " The ' Frequency Interleaving ' Principle in the
* NTSC Standards " , Proceedings of the I - R - E , January 1954 , pp 81 - 83
*
* As Mike Bradley has rightly pointed out , it ' s not the exact crystal
* frequency that matters , only that all parts of the driver and
* firmware are using the same value ( close to the ideal value ) .
*
* Since I have a strong suspicion that , if the firmware ever assumes a
* crystal value at all , it will assume 28 . 636360 MHz , the crystal
* freq used in calculations in this driver will be :
*
* xtal_freq = 28 . 636360 MHz
*
* an error of less than 0 . 13 ppm which is way , way better than any off
* the shelf crystal will have for accuracy anyway .
*
* Below I aim to run the PLLs ' VCOs near 400 MHz to minimize errors .
*
* Many thanks to Jeff Campbell and Mike Bradley for their extensive
* investigation , experimentation , testing , and suggested solutions of
* audio / video sync problems with SVideo and CVBS captures .
*/
/* the fast clock is at 200/245 MHz */
/* 1 * xtal_freq * 0x0d.f7df9b8 / 2 = 200 MHz: 400 MHz pre post-divide*/
/* 1 * xtal_freq * 0x11.1c71eb8 / 2 = 245 MHz: 490 MHz pre post-divide*/
cx18_write_reg(cx, lowpwr ? 0 xD : 0 x11, CX18_FAST_CLOCK_PLL_INT);
cx18_write_reg(cx, lowpwr ? 0 x1EFBF37 : 0 x038E3D7,
CX18_FAST_CLOCK_PLL_FRAC);
cx18_write_reg(cx, 2 , CX18_FAST_CLOCK_PLL_POST);
cx18_write_reg(cx, 1 , CX18_FAST_CLOCK_PLL_PRESCALE);
cx18_write_reg(cx, 4 , CX18_FAST_CLOCK_PLL_ADJUST_BANDWIDTH);
/* set slow clock to 125/120 MHz */
/* xtal_freq * 0x0d.1861a20 / 3 = 125 MHz: 375 MHz before post-divide */
/* xtal_freq * 0x0c.92493f8 / 3 = 120 MHz: 360 MHz before post-divide */
cx18_write_reg(cx, lowpwr ? 0 xD : 0 xC, CX18_SLOW_CLOCK_PLL_INT);
cx18_write_reg(cx, lowpwr ? 0 x30C344 : 0 x124927F,
CX18_SLOW_CLOCK_PLL_FRAC);
cx18_write_reg(cx, 3 , CX18_SLOW_CLOCK_PLL_POST);
/* mpeg clock pll 54MHz */
/* xtal_freq * 0xf.15f17f0 / 8 = 54 MHz: 432 MHz before post-divide */
cx18_write_reg(cx, 0 xF, CX18_MPEG_CLOCK_PLL_INT);
cx18_write_reg(cx, 0 x2BE2FE, CX18_MPEG_CLOCK_PLL_FRAC);
cx18_write_reg(cx, 8 , CX18_MPEG_CLOCK_PLL_POST);
/* Defaults */
/* APU = SC or SC/2 = 125/62.5 */
/* EPU = SC = 125 */
/* DDR = FC = 180 */
/* ENC = SC = 125 */
/* AI1 = SC = 125 */
/* VIM2 = disabled */
/* PCI = FC/2 = 90 */
/* AI2 = disabled */
/* DEMUX = disabled */
/* AO = SC/2 = 62.5 */
/* SER = 54MHz */
/* VFC = disabled */
/* USB = disabled */
if (lowpwr) {
cx18_write_reg_expect(cx, 0 xFFFF0020, CX18_CLOCK_SELECT1,
0 x00000020, 0 xFFFFFFFF);
cx18_write_reg_expect(cx, 0 xFFFF0004, CX18_CLOCK_SELECT2,
0 x00000004, 0 xFFFFFFFF);
} else {
/* This doesn't explicitly set every clock select */
cx18_write_reg_expect(cx, 0 x00060004, CX18_CLOCK_SELECT1,
0 x00000004, 0 x00060006);
cx18_write_reg_expect(cx, 0 x00060006, CX18_CLOCK_SELECT2,
0 x00000006, 0 x00060006);
}
cx18_write_reg_expect(cx, 0 xFFFF0002, CX18_HALF_CLOCK_SELECT1,
0 x00000002, 0 xFFFFFFFF);
cx18_write_reg_expect(cx, 0 xFFFF0104, CX18_HALF_CLOCK_SELECT2,
0 x00000104, 0 xFFFFFFFF);
cx18_write_reg_expect(cx, 0 xFFFF9026, CX18_CLOCK_ENABLE1,
0 x00009026, 0 xFFFFFFFF);
cx18_write_reg_expect(cx, 0 xFFFF3105, CX18_CLOCK_ENABLE2,
0 x00003105, 0 xFFFFFFFF);
}
void cx18_init_memory(struct cx18 *cx)
{
cx18_msleep_timeout(10 , 0 );
cx18_write_reg_expect(cx, 0 x00010000, CX18_DDR_SOFT_RESET,
0 x00000000, 0 x00010001);
cx18_msleep_timeout(10 , 0 );
cx18_write_reg(cx, cx->card->ddr.chip_config, CX18_DDR_CHIP_CONFIG);
cx18_msleep_timeout(10 , 0 );
cx18_write_reg(cx, cx->card->ddr.refresh, CX18_DDR_REFRESH);
cx18_write_reg(cx, cx->card->ddr.timing1, CX18_DDR_TIMING1);
cx18_write_reg(cx, cx->card->ddr.timing2, CX18_DDR_TIMING2);
cx18_msleep_timeout(10 , 0 );
/* Initialize DQS pad time */
cx18_write_reg(cx, cx->card->ddr.tune_lane, CX18_DDR_TUNE_LANE);
cx18_write_reg(cx, cx->card->ddr.initial_emrs, CX18_DDR_INITIAL_EMRS);
cx18_msleep_timeout(10 , 0 );
cx18_write_reg_expect(cx, 0 x00020000, CX18_DDR_SOFT_RESET,
0 x00000000, 0 x00020002);
cx18_msleep_timeout(10 , 0 );
/* use power-down mode when idle */
cx18_write_reg(cx, 0 x00000010, CX18_DDR_POWER_REG);
cx18_write_reg_expect(cx, 0 x00010001, CX18_REG_BUS_TIMEOUT_EN,
0 x00000001, 0 x00010001);
cx18_write_reg(cx, 0 x48, CX18_DDR_MB_PER_ROW_7);
cx18_write_reg(cx, 0 xE0000, CX18_DDR_BASE_63_ADDR);
cx18_write_reg(cx, 0 x00000101, CX18_WMB_CLIENT02); /* AO */
cx18_write_reg(cx, 0 x00000101, CX18_WMB_CLIENT09); /* AI2 */
cx18_write_reg(cx, 0 x00000101, CX18_WMB_CLIENT05); /* VIM1 */
cx18_write_reg(cx, 0 x00000101, CX18_WMB_CLIENT06); /* AI1 */
cx18_write_reg(cx, 0 x00000101, CX18_WMB_CLIENT07); /* 3D comb */
cx18_write_reg(cx, 0 x00000101, CX18_WMB_CLIENT10); /* ME */
cx18_write_reg(cx, 0 x00000101, CX18_WMB_CLIENT12); /* ENC */
cx18_write_reg(cx, 0 x00000101, CX18_WMB_CLIENT13); /* PK */
cx18_write_reg(cx, 0 x00000101, CX18_WMB_CLIENT11); /* RC */
cx18_write_reg(cx, 0 x00000101, CX18_WMB_CLIENT14); /* AVO */
}
#define CX18_CPU_FIRMWARE "v4l-cx23418-cpu.fw"
#define CX18_APU_FIRMWARE "v4l-cx23418-apu.fw"
int cx18_firmware_init(struct cx18 *cx)
{
u32 fw_entry_addr;
int sz, retries;
u32 api_args[MAX_MB_ARGUMENTS];
/* Allow chip to control CLKRUN */
cx18_write_reg(cx, 0 x5, CX18_DSP0_INTERRUPT_MASK);
/* Stop the firmware */
cx18_write_reg_expect(cx, 0 x000F000F, CX18_PROC_SOFT_RESET,
0 x0000000F, 0 x000F000F);
cx18_msleep_timeout(1 , 0 );
/* If the CPU is still running */
if ((cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 8 ) == 0 ) {
CX18_ERR("%s: couldn't stop CPU to load firmware\n" , __func__);
return -EIO;
}
cx18_sw1_irq_enable(cx, IRQ_CPU_TO_EPU | IRQ_APU_TO_EPU);
cx18_sw2_irq_enable(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK);
sz = load_cpu_fw_direct(CX18_CPU_FIRMWARE, cx->enc_mem, cx);
if (sz <= 0 )
return sz;
/* The SCB & IPC area *must* be correct before starting the firmwares */
cx18_init_scb(cx);
fw_entry_addr = 0 ;
sz = load_apu_fw_direct(CX18_APU_FIRMWARE, cx->enc_mem, cx,
&fw_entry_addr);
if (sz <= 0 )
return sz;
/* Start the CPU. The CPU will take care of the APU for us. */
cx18_write_reg_expect(cx, 0 x00080000, CX18_PROC_SOFT_RESET,
0 x00000000, 0 x00080008);
/* Wait up to 500 ms for the APU to come out of reset */
for (retries = 0 ;
retries < 50 && (cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 1 ) == 1 ;
retries++)
cx18_msleep_timeout(10 , 0 );
cx18_msleep_timeout(200 , 0 );
if (retries == 50 &&
(cx18_read_reg(cx, CX18_PROC_SOFT_RESET) & 1 ) == 1 ) {
CX18_ERR("Could not start the CPU\n" );
return -EIO;
}
/*
* The CPU had once before set up to receive an interrupt for it ' s
* outgoing IRQ_CPU_TO_EPU_ACK to us . If it ever does this , we get an
* interrupt when it sends us an ack , but by the time we process it ,
* that flag in the SW2 status register has been cleared by the CPU
* firmware . We ' ll prevent that not so useful condition from happening
* by clearing the CPU ' s interrupt enables for Ack IRQ ' s we want to
* process .
*/
cx18_sw2_irq_disable_cpu(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK);
/* Try a benign command to see if the CPU is alive and well */
sz = cx18_vapi_result(cx, api_args, CX18_CPU_DEBUG_PEEK32, 1 , 0 );
if (sz < 0 )
return sz;
/* initialize GPIO */
cx18_write_reg_expect(cx, 0 x14001400, 0 xc78110, 0 x00001400, 0 x14001400);
return 0 ;
}
MODULE_FIRMWARE(CX18_CPU_FIRMWARE);
MODULE_FIRMWARE(CX18_APU_FIRMWARE);
Messung V0.5 in Prozent C=93 H=91 G=91
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-10-12)
¤
*© Formatika GbR, Deutschland