1/* $NetBSD: avenc.c,v 1.1 2026/01/09 22:54:29 jmcneill Exp $ */ 2 3/*- 4 * Copyright (c) 2024 Jared McNeill <jmcneill@invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#include <sys/cdefs.h> 30__KERNEL_RCSID(0, "$NetBSD: avenc.c,v 1.1 2026/01/09 22:54:29 jmcneill Exp $"); 31 32#include <sys/param.h> 33#include <sys/systm.h> 34#include <sys/kernel.h> 35#include <sys/device.h> 36#include <sys/conf.h> 37#include <sys/bus.h> 38#include <sys/kmem.h> 39#include <machine/wii.h> 40#include <machine/wiiu.h> 41 42#include <lib/libkern/libkern.h> /* hexdump */ 43 44#include <dev/i2c/i2cvar.h> 45 46#include "avenc.h" 47#include "vireg.h" 48 49#define AVENC_DEBUG 0 50 51#define AVENC_ADDR 0x70 52 53#define AVENC_VOLUME 0x71 54#define AVENC_VOLUME_RIGHT __BITS(15,8) 55#define AVENC_VOLUME_LEFT __BITS(7,0) 56 57#define RD1 avenc_read_1 58#define RD2 avenc_read_2 59#define WR1 avenc_write_1 60#define WR2 avenc_write_2 61#define WR4 avenc_write_4 62#define WRBUF avenc_write_buf 63 64static i2c_tag_t avenc_tag; 65static i2c_addr_t avenc_addr; 66 67static uint8_t 68avenc_read_1(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg) 69{ 70 uint8_t val; 71 72 if (iic_smbus_read_byte(tag, addr, reg, &val, 0) != 0) { 73 val = 0xff; 74 } 75 76 return val; 77} 78 79static uint16_t 80avenc_read_2(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg) 81{ 82 uint8_t vbuf[2]; 83 84 if (iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, ®, 1, 85 vbuf, sizeof(vbuf), 0) != 0) { 86 return 0xffff; 87 } else { 88 return ((uint16_t)vbuf[0] << 8) | vbuf[1]; 89 } 90} 91 92static void 93avenc_write_1(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg, uint8_t val) 94{ 95 iic_smbus_write_byte(tag, addr, reg, val, 0); 96} 97 98static void 99avenc_write_2(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg, uint16_t val) 100{ 101 uint8_t vbuf[2]; 102 103 vbuf[0] = (val >> 8) & 0xff; 104 vbuf[1] = val & 0xff; 105 106 iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, ®, 1, 107 vbuf, sizeof(vbuf), 0); 108} 109 110static void 111avenc_write_4(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg, uint32_t val) 112{ 113 uint8_t vbuf[4]; 114 115 vbuf[0] = (val >> 24) & 0xff; 116 vbuf[1] = (val >> 16) & 0xff; 117 vbuf[2] = (val >> 8) & 0xff; 118 vbuf[3] = val & 0xff; 119 120 iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, ®, 1, 121 vbuf, sizeof(vbuf), 0); 122} 123 124static void 125avenc_write_buf(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg, uint8_t *buf, 126 size_t buflen) 127{ 128 iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, ®, 1, 129 buf, buflen, 0); 130} 131 132void 133avenc_get_volume(uint8_t *left, uint8_t *right) 134{ 135 uint16_t val; 136 137 if (avenc_addr == 0) { 138 *left = *right = 0; 139 return; 140 } 141 142 iic_acquire_bus(avenc_tag, 0); 143 val = RD2(avenc_tag, avenc_addr, AVENC_VOLUME); 144 iic_release_bus(avenc_tag, 0); 145 146 *left = __SHIFTOUT(val, AVENC_VOLUME_LEFT); 147 *right = __SHIFTOUT(val, AVENC_VOLUME_RIGHT); 148} 149 150void 151avenc_set_volume(uint8_t left, uint8_t right) 152{ 153 uint16_t val; 154 155 if (avenc_addr == 0) { 156 return; 157 } 158 159 val = __SHIFTIN(left, AVENC_VOLUME_LEFT) | 160 __SHIFTIN(right, AVENC_VOLUME_RIGHT); 161 162 iic_acquire_bus(avenc_tag, 0); 163 WR2(avenc_tag, avenc_addr, AVENC_VOLUME, val); 164 iic_release_bus(avenc_tag, 0); 165} 166 167static void 168avenc_dump_regs(const char *pfx, i2c_tag_t tag, i2c_addr_t addr) 169{ 170 uint8_t regdump[0x100]; 171 unsigned int reg; 172 173 iic_acquire_bus(tag, 0); 174 175 for (reg = 0; reg < sizeof(regdump); reg++) { 176 regdump[reg] = RD1(tag, addr, reg); 177 } 178 179 iic_release_bus(tag, 0); 180 181 hexdump(printf, pfx, regdump, sizeof(regdump)); 182} 183 184static void 185avenc_init(i2c_tag_t tag, i2c_addr_t addr) 186{ 187 uint8_t mvinit[26] = {}; 188 uint8_t ginit[] = { 189 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 190 0x10, 0x00, 0x10, 0x00, 0x10, 0x20, 0x40, 0x60, 191 0x80, 0xa0, 0xeb, 0x10, 0x00, 0x20, 0x00, 0x40, 192 0x00, 0x60, 0x00, 0x80, 0x00, 0xa0, 0x00, 0xeb, 193 0x00 194 }; 195 uint8_t video_fmt; 196 197 video_fmt = 0; 198 if (__SHIFTOUT(in16(VI_BASE + VI_DCR), VI_DCR_FMT) == VI_DCR_FMT_PAL) { 199 video_fmt |= 0x02; 200 } 201 if ((in16(VI_BASE + VI_VISEL) & VI_VISEL_COMPONENT_CABLE) != 0) { 202 video_fmt |= 0x20; 203 } 204 205 iic_acquire_bus(tag, 0); 206 WR1(tag, addr, 0x6a, 1); 207 WR1(tag, addr, 0x65, 3); 208 WR1(tag, addr, 0x01, video_fmt); 209 WR1(tag, addr, 0x00, 0); 210 WR1(tag, addr, 0x02, 7); 211 WR2(tag, addr, 0x05, 0); 212 WR2(tag, addr, 0x08, 0); 213 WR4(tag, addr, 0x7a, 0); 214 WRBUF(tag, addr, 0x40, mvinit, sizeof(mvinit)); 215 WR1(tag, addr, 0x0a, 0); 216 WR1(tag, addr, 0x03, 1); 217 WRBUF(tag, addr, 0x10, ginit, sizeof(ginit)); 218 WR1(tag, addr, 0x04, 1); 219 WR4(tag, addr, 0x7a, 0); 220 WR2(tag, addr, 0x08, 0); 221 WR1(tag, addr, 0x03, 1); 222 WR1(tag, addr, 0x6e, 0); 223 iic_release_bus(tag, 0); 224 225 avenc_set_volume(0x8e, 0x8e); 226} 227 228 229static int 230avenc_match(device_t parent, cfdata_t match, void *aux) 231{ 232 struct i2c_attach_args *ia = aux; 233 234 return !wiiu_plat && ia->ia_addr == AVENC_ADDR; 235} 236 237static void 238avenc_attach(device_t parent, device_t self, void *aux) 239{ 240 struct i2c_attach_args *ia = aux; 241 i2c_tag_t tag = ia->ia_tag; 242 i2c_addr_t addr = ia->ia_addr; 243 244 KASSERT(device_unit(self) == 0); 245 246 aprint_naive("\n"); 247 aprint_normal(": A/V Encoder\n"); 248 249 if (AVENC_DEBUG) { 250 avenc_dump_regs("avenc pre ", tag, addr); 251 } 252 253 avenc_tag = tag; 254 avenc_addr = addr; 255 256 avenc_init(tag, addr); 257 258 if (AVENC_DEBUG) { 259 avenc_dump_regs("avenc post", tag, addr); 260 } 261} 262 263CFATTACH_DECL_NEW(avenc, 0, avenc_match, avenc_attach, NULL, NULL); 264