avenc.c revision 1.1
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, &reg, 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, &reg, 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, &reg, 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, &reg, 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