1/* $NetBSD: tegra_hdaudio.c,v 1.15 2021/01/27 03:10:19 thorpej Exp $ */
2
3/*-
4 * Copyright (c) 2015 Jared D. 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 AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: tegra_hdaudio.c,v 1.15 2021/01/27 03:10:19 thorpej Exp $");
31
32#include <sys/param.h>
33#include <sys/bus.h>
34#include <sys/device.h>
35#include <sys/intr.h>
36#include <sys/systm.h>
37#include <sys/kernel.h>
38
39#include <dev/hdaudio/hdaudioreg.h>
40#include <dev/hdaudio/hdaudiovar.h>
41
42#include <arm/nvidia/tegra_var.h>
43#include <arm/nvidia/tegra_pmcreg.h>
44#include <arm/nvidia/tegra_hdaudioreg.h>
45
46#include <dev/fdt/fdtvar.h>
47
48#define TEGRA_HDAUDIO_OFFSET	0x8000
49
50#define TEGRA_HDA_IFPS_BAR0_REG		0x0080
51#define TEGRA_HDA_IFPS_CONFIG_REG	0x0180
52#define TEGRA_HDA_IFPS_INTR_REG		0x0188
53#define TEGRA_HDA_CFG_CMD_REG		0x1004
54#define TEGRA_HDA_CFG_BAR0_REG		0x1010
55
56static int	tegra_hdaudio_match(device_t, cfdata_t, void *);
57static void	tegra_hdaudio_attach(device_t, device_t, void *);
58static int	tegra_hdaudio_detach(device_t, int);
59static int	tegra_hdaudio_rescan(device_t, const char *, const int *);
60static void	tegra_hdaudio_childdet(device_t, device_t);
61
62static int	tegra_hdaudio_intr(void *);
63
64struct tegra_hdaudio_softc {
65	struct hdaudio_softc	sc;
66	bus_space_tag_t		sc_bst;
67	bus_space_handle_t	sc_bsh;
68	void			*sc_ih;
69	int			sc_phandle;
70	struct clk		*sc_clk_hda;
71	struct clk		*sc_clk_hda2hdmi;
72	struct clk		*sc_clk_hda2codec_2x;
73	struct fdtbus_reset	*sc_rst_hda;
74	struct fdtbus_reset	*sc_rst_hda2hdmi;
75	struct fdtbus_reset	*sc_rst_hda2codec_2x;
76};
77
78static int	tegra_hdaudio_init_clocks(struct tegra_hdaudio_softc *);
79static void	tegra_hdaudio_init(struct tegra_hdaudio_softc *);
80
81CFATTACH_DECL2_NEW(tegra_hdaudio, sizeof(struct tegra_hdaudio_softc),
82	tegra_hdaudio_match, tegra_hdaudio_attach, tegra_hdaudio_detach, NULL,
83	tegra_hdaudio_rescan, tegra_hdaudio_childdet);
84
85static const struct device_compatible_entry compat_data[] = {
86	{ .compat = "nvidia,tegra210-hda" },
87	{ .compat = "nvidia,tegra124-hda" },
88	DEVICE_COMPAT_EOL
89};
90
91static int
92tegra_hdaudio_match(device_t parent, cfdata_t cf, void *aux)
93{
94	struct fdt_attach_args * const faa = aux;
95
96	return of_compatible_match(faa->faa_phandle, compat_data);
97}
98
99static void
100tegra_hdaudio_attach(device_t parent, device_t self, void *aux)
101{
102	struct tegra_hdaudio_softc * const sc = device_private(self);
103	struct fdt_attach_args * const faa = aux;
104	const int phandle = faa->faa_phandle;
105	char intrstr[128];
106	bus_addr_t addr;
107	bus_size_t size;
108	int error;
109
110	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
111		aprint_error(": couldn't get registers\n");
112		return;
113	}
114	sc->sc_clk_hda = fdtbus_clock_get(phandle, "hda");
115	if (sc->sc_clk_hda == NULL) {
116		aprint_error(": couldn't get clock hda\n");
117		return;
118	}
119	sc->sc_clk_hda2hdmi = fdtbus_clock_get(phandle, "hda2hdmi");
120	if (sc->sc_clk_hda2hdmi == NULL) {
121		aprint_error(": couldn't get clock hda2hdmi\n");
122		return;
123	}
124	sc->sc_clk_hda2codec_2x = fdtbus_clock_get(phandle, "hda2codec_2x");
125	if (sc->sc_clk_hda2codec_2x == NULL) {
126		aprint_error(": couldn't get clock hda2codec_2x\n");
127		return;
128	}
129	sc->sc_rst_hda = fdtbus_reset_get(phandle, "hda");
130	if (sc->sc_rst_hda == NULL) {
131		aprint_error(": couldn't get reset hda\n");
132		return;
133	}
134	sc->sc_rst_hda2hdmi = fdtbus_reset_get(phandle, "hda2hdmi");
135	if (sc->sc_rst_hda2hdmi == NULL) {
136		aprint_error(": couldn't get reset hda2hdmi\n");
137		return;
138	}
139	sc->sc_rst_hda2codec_2x = fdtbus_reset_get(phandle, "hda2codec_2x");
140	if (sc->sc_rst_hda2codec_2x == NULL) {
141		aprint_error(": couldn't get reset hda2codec_2x\n");
142		return;
143	}
144
145	sc->sc_phandle = phandle;
146	sc->sc_bst = faa->faa_bst;
147	error = bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh);
148	if (error) {
149		aprint_error(": couldn't map %#" PRIxBUSADDR ": %d", addr, error);
150		return;
151	}
152
153	sc->sc.sc_dev = self;
154	sc->sc.sc_memt = faa->faa_bst;
155	bus_space_subregion(sc->sc.sc_memt, sc->sc_bsh, TEGRA_HDAUDIO_OFFSET,
156	    size - TEGRA_HDAUDIO_OFFSET, &sc->sc.sc_memh);
157	sc->sc.sc_memvalid = true;
158	sc->sc.sc_dmat = faa->faa_dmat;
159	sc->sc.sc_flags = HDAUDIO_FLAG_32BIT;
160
161	aprint_naive("\n");
162	aprint_normal(": HDA\n");
163
164	if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
165		aprint_error_dev(self, "failed to decode interrupt\n");
166		return;
167	}
168
169	sc->sc_ih = fdtbus_intr_establish_xname(phandle, 0, IPL_AUDIO, 0,
170	    tegra_hdaudio_intr, sc, device_xname(self));
171	if (sc->sc_ih == NULL) {
172		aprint_error_dev(self, "couldn't establish interrupt on %s\n",
173		    intrstr);
174		return;
175	}
176	aprint_normal_dev(self, "interrupting on %s\n", intrstr);
177
178	tegra_pmc_power(PMC_PARTID_DISB, true);
179
180	if (tegra_hdaudio_init_clocks(sc) != 0)
181		return;
182
183	tegra_hdaudio_init(sc);
184
185	hdaudio_attach(self, &sc->sc);
186}
187
188static int
189tegra_hdaudio_init_clocks(struct tegra_hdaudio_softc *sc)
190{
191	device_t self = sc->sc.sc_dev;
192	int error;
193
194	/* Assert resets */
195	fdtbus_reset_assert(sc->sc_rst_hda);
196	fdtbus_reset_assert(sc->sc_rst_hda2hdmi);
197	fdtbus_reset_assert(sc->sc_rst_hda2codec_2x);
198
199	/* Set hda to 48MHz and enable it */
200	error = clk_set_rate(sc->sc_clk_hda, 48000000);
201	if (error) {
202		aprint_error_dev(self, "couldn't set hda frequency: %d\n",
203		    error);
204		return error;
205	}
206	error = clk_enable(sc->sc_clk_hda);
207	if (error) {
208		aprint_error_dev(self, "couldn't enable clock hda: %d\n",
209		    error);
210		return error;
211	}
212
213	/* Enable hda2hdmi clock */
214	error = clk_enable(sc->sc_clk_hda2hdmi);
215	if (error) {
216		aprint_error_dev(self, "couldn't enable clock hda2hdmi: %d\n",
217		    error);
218		return error;
219	}
220
221	/* Set hda2codec_2x to 48MHz and enable it */
222	error = clk_set_rate(sc->sc_clk_hda2codec_2x, 48000000);
223	if (error) {
224		aprint_error_dev(self,
225		    "couldn't set clock hda2codec_2x frequency: %d\n", error);
226		return error;
227	}
228	error = clk_enable(sc->sc_clk_hda2codec_2x);
229	if (error) {
230		aprint_error_dev(self,
231		    "couldn't enable clock hda2codec_2x: %d\n", error);
232		return error;
233	}
234
235	/* De-assert resets */
236	fdtbus_reset_deassert(sc->sc_rst_hda);
237	fdtbus_reset_deassert(sc->sc_rst_hda2hdmi);
238	fdtbus_reset_deassert(sc->sc_rst_hda2codec_2x);
239
240	return 0;
241}
242
243static void
244tegra_hdaudio_init(struct tegra_hdaudio_softc *sc)
245{
246	tegra_reg_set_clear(sc->sc_bst, sc->sc_bsh, TEGRA_HDA_IFPS_CONFIG_REG,
247	    TEGRA_HDA_IFPS_CONFIG_FPCI_EN, 0);
248	tegra_reg_set_clear(sc->sc_bst, sc->sc_bsh, TEGRA_HDA_CFG_CMD_REG,
249	    TEGRA_HDA_CFG_CMD_ENABLE_SERR |
250	    TEGRA_HDA_CFG_CMD_BUS_MASTER |
251	    TEGRA_HDA_CFG_CMD_MEM_SPACE |
252	    TEGRA_HDA_CFG_CMD_IO_SPACE,
253	    TEGRA_HDA_CFG_CMD_DISABLE_INTR);
254	bus_space_write_4(sc->sc_bst, sc->sc_bsh, TEGRA_HDA_CFG_BAR0_REG,
255	    0xffffffff);
256	bus_space_write_4(sc->sc_bst, sc->sc_bsh, TEGRA_HDA_CFG_BAR0_REG,
257	    0x00004000);
258	bus_space_write_4(sc->sc_bst, sc->sc_bsh, TEGRA_HDA_IFPS_BAR0_REG,
259	    TEGRA_HDA_CFG_BAR0_START);
260	tegra_reg_set_clear(sc->sc_bst, sc->sc_bsh, TEGRA_HDA_IFPS_INTR_REG,
261	    TEGRA_HDA_IFPS_INTR_EN, 0);
262}
263
264static int
265tegra_hdaudio_detach(device_t self, int flags)
266{
267	struct tegra_hdaudio_softc * const sc = device_private(self);
268
269	hdaudio_detach(&sc->sc, flags);
270
271	if (sc->sc_ih) {
272		fdtbus_intr_disestablish(sc->sc_phandle, sc->sc_ih);
273		sc->sc_ih = NULL;
274	}
275
276	sc->sc.sc_memvalid = false;
277
278	return 0;
279}
280
281static int
282tegra_hdaudio_rescan(device_t self, const char *ifattr, const int *locs)
283{
284	struct tegra_hdaudio_softc * const sc = device_private(self);
285
286	return hdaudio_rescan(&sc->sc, ifattr, locs);
287}
288
289static void
290tegra_hdaudio_childdet(device_t self, device_t child)
291{
292	struct tegra_hdaudio_softc * const sc = device_private(self);
293
294	hdaudio_childdet(&sc->sc, child);
295}
296
297static int
298tegra_hdaudio_intr(void *priv)
299{
300	struct tegra_hdaudio_softc * const sc = priv;
301
302	return hdaudio_intr(&sc->sc);
303}
304