head	1.1;
access;
symbols;
locks; strict;
comment	@ * @;


1.1
date	2026.06.27.13.28.34;	author rkujawa;	state Exp;
branches;
next	;
commitid	DeOWreJOXyKJfrLG;


desc
@@


1.1
log
@Add support for MPC5200B SoC, including:
- BestComm DMA controller
- Clock Distribution Module
- Fast Ethernet Controller, utilizing BestComm DMA
- AC97 controller, utilizing BestComm DMA
- ATA controller (PIO-only, hardware DMA is too broken)
- On-chip SRAM (needed for BestComm)
- PSC UART (for console)
- PCI along with bus mater DMA support

All of this is based on previous work by Robert Swindells and would not
be possible if it wasn't for his effort.
@
text
@/*	$NetBSD$	*/

/*-
 * Copyright (c) 2008, 2026 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Radoslaw Kujawa and Robert Swindells.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Driver for the MPC5200B Clock Distribution Module (CDM).
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD$");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/bus.h>

#include <dev/ofw/openfirm.h>

#include <machine/autoconf.h>

#include <powerpc/mpc5200/obiovar.h>
#include <powerpc/mpc5200/mpc5200reg.h>
#include <powerpc/mpc5200/cdmvar.h>

/*
 * External reference oscillator on the SYS_XTAL_IN pin, in Hz.
 */
#define CDM_SYS_XTAL_IN		33333333	/* XXX: should be configurable? */

/* CDM register offsets from the block base (MBAR + 0x0200). */
#define CDM_JTAG_ID		0x00	/* JTAG identification (ro)	*/
#define CDM_PORCFG		0x04	/* power-on reset config (ro)	*/
#define CDM_BREADCRUMB		0x08	/* firmware scratch (never reset) */
#define CDM_CFG			0x0c	/* clock configuration		*/
#define CDM_FRAC_DIV		0x10	/* 48MHz fractional divider	*/
#define CDM_CLK_ENABLE		0x14	/* per-module clock enables	*/

#define CDM_REG_SIZE		0x38	/* register window if OF omits a size */

/*
 * Register fields use the manual's big-endian bit numbering (MSB = bit 0);
 * CDM_BIT() converts a manual bit number to NetBSD __BIT() (LSB = bit 0).
 */
#define CDM_BIT(n)		__BIT(31 - (n))

/* CDM Power-On Reset Configuration Register (CDM_PORCFG). */
#define PORCFG_SYS_PLL_BYPASS	CDM_BIT(15)	/* PLL bypassed, xtal direct	*/
#define PORCFG_SYS_PLL_CFG_1	CDM_BIT(24)	/* doubles VCO only (no clk effect) */
#define PORCFG_SYS_PLL_CFG_0	CDM_BIT(25)	/* 0: fsys=16x, 1: fsys=12x xtal	*/
#define PORCFG_XLB_CLK_SEL	CDM_BIT(26)	/* 0: XLB=fsys/4, 1: XLB=fsys/8	*/
#define PORCFG_PPC_PLL_CFG	__BITS(0, 4)	/* e300 core APLL config [0:4]	*/

/* CDM Configuration Register (CDM_CFG). */
#define CFG_XLB_CLK_SEL		CDM_BIT(15)	/* read-only mirror of PORCFG	*/
#define CFG_IPB_CLK_SEL		CDM_BIT(23)	/* 0: IPB=XLB, 1: IPB=XLB/2	*/
#define CFG_PCI_CLK_SEL		__BITS(0, 1)	/* PCI clock select [1:0]	*/

#define PCI_CLK_SEL_IPB		0		/* PCI = IPB			*/
#define PCI_CLK_SEL_IPB_DIV2	1		/* PCI = IPB / 2		*/
/* values 2 and 3 both select PCI = XLB / 4 */

/*
 * Computed clock tree, shared with the on-demand accessors.
 */
static struct cdm_clocks {
	bool		cc_valid;
	uint32_t	cc_core;
	uint32_t	cc_xlb;
	uint32_t	cc_ipb;
	uint32_t	cc_pci;
} cdm_clocks;

static int	cdm_match(device_t, cfdata_t, void *);
static void	cdm_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(cdm, sizeof(struct cdm_softc),
    cdm_match, cdm_attach, NULL, NULL);

static const uint8_t cdm_core_ratio_x2[32] = {
	[0x03] =  2,	/* 1x  - xlb clocks core directly		*/
	[0x04] =  4,	/* 2x   */	[0x05] =  4,	/* 2x   */
	[0x06] =  5,	/* 2.5x */	[0x07] =  9,	/* 4.5x */
	[0x08] =  6,	/* 3x   */	[0x09] = 11,	/* 5.5x */
	[0x0a] =  8,	/* 4x   */	[0x0b] = 10,	/* 5x   */
	[0x0d] = 12,	/* 6x   */	[0x0e] =  7,	/* 3.5x */
	[0x10] =  6,	/* 3x   */	[0x11] =  5,	/* 2.5x */
	[0x12] = 13,	/* 6.5x */	[0x13] =  2,	/* 1x direct */
	[0x14] = 14,	/* 7x   */	[0x16] = 15,	/* 7.5x */
	[0x1c] = 16,	/* 8x   */
};

/*
 * Reconstruct the clock tree from the two strap registers and cache it.
 */
static void
cdm_compute(uint32_t porcfg, uint32_t cfg)
{
	uint32_t fsystem, xlb, ipb, pci, core;
	uint32_t ppc_cfg, ratio_x2;

	if (porcfg & PORCFG_SYS_PLL_BYPASS)
		fsystem = CDM_SYS_XTAL_IN;	/* PLL bypassed, no multiply */
	else
		fsystem = CDM_SYS_XTAL_IN *
		    ((porcfg & PORCFG_SYS_PLL_CFG_0) ? 12 : 16);

	xlb = fsystem / ((porcfg & PORCFG_XLB_CLK_SEL) ? 8 : 4);
	ipb = xlb / ((cfg & CFG_IPB_CLK_SEL) ? 2 : 1);

	switch (__SHIFTOUT(cfg, CFG_PCI_CLK_SEL)) {
	case PCI_CLK_SEL_IPB:
		pci = ipb;
		break;
	case PCI_CLK_SEL_IPB_DIV2:
		pci = ipb / 2;
		break;
	default:			/* 2, 3: PCI = XLB / 4 */
		pci = xlb / 4;
		break;
	}

	ppc_cfg = __SHIFTOUT(porcfg, PORCFG_PPC_PLL_CFG);
	ratio_x2 = cdm_core_ratio_x2[ppc_cfg];
	if (ratio_x2 != 0)
		core = (uint32_t)(((uint64_t)xlb * ratio_x2) / 2);
	else
		core = 0;		/* reserved/unclocked strap */

	cdm_clocks.cc_core = core;
	cdm_clocks.cc_xlb = xlb;
	cdm_clocks.cc_ipb = ipb;
	cdm_clocks.cc_pci = pci;
	cdm_clocks.cc_valid = true;
}

/*
 * Ensure the clock tree is populated.
 */
static void
cdm_ensure_valid(void)
{
	volatile uint32_t *cdm;
	uint32_t porcfg, cfg;

	if (cdm_clocks.cc_valid)
		return;

	cdm = (volatile uint32_t *)(uintptr_t)
	    (MPC5200_MBAR_DEFAULT + MPC5200_REG_CDM);
	porcfg = cdm[CDM_PORCFG / sizeof(*cdm)];
	cfg = cdm[CDM_CFG / sizeof(*cdm)];

	cdm_compute(porcfg, cfg);

	/*
	 * Guard against an implausible read
	 */
	if (cdm_clocks.cc_ipb < 16000000 || cdm_clocks.cc_ipb > 200000000) {
		cdm_clocks.cc_core = MPC5200_CORE_FREQ_DEFAULT;
		cdm_clocks.cc_xlb = MPC5200_XLB_FREQ_DEFAULT;
		cdm_clocks.cc_ipb = MPC5200_IPB_FREQ_DEFAULT;
		cdm_clocks.cc_pci = MPC5200_PCI_FREQ_DEFAULT;
	}
}

uint32_t
mpc5200_cdm_get_core_freq(void)
{
	cdm_ensure_valid();
	return cdm_clocks.cc_core != 0 ?
	    cdm_clocks.cc_core : MPC5200_CORE_FREQ_DEFAULT;
}

uint32_t
mpc5200_cdm_get_xlb_freq(void)
{
	cdm_ensure_valid();
	return cdm_clocks.cc_xlb;
}

uint32_t
mpc5200_cdm_get_ipb_freq(void)
{
	cdm_ensure_valid();
	return cdm_clocks.cc_ipb;
}

uint32_t
mpc5200_cdm_get_pci_freq(void)
{
	cdm_ensure_valid();
	return cdm_clocks.cc_pci;
}

static int
cdm_match(device_t parent, cfdata_t cf, void *aux)
{
	struct obio_attach_args *oba = aux;
	char compat[32];
	int len;

	if (strcmp(oba->obio_name, "cdm") == 0)
		return 1;

	len = OF_getprop(oba->obio_node, "compatible", compat, sizeof(compat));
	if (len > 0 &&
	    (strcmp(compat, "mpc5200-cdm") == 0 ||
	     strcmp(compat, "mpc5200b-cdm") == 0))
		return 1;

	return 0;
}

static void
cdm_attach(device_t parent, device_t self, void *aux)
{
	struct cdm_softc *sc = device_private(self);
	struct obio_attach_args *oba = aux;
	bus_size_t size;
	uint32_t porcfg, cfg;

	sc->sc_dev = self;
	sc->sc_iot = oba->obio_bst;

	size = oba->obio_size != 0 ? oba->obio_size : CDM_REG_SIZE;
	if (bus_space_map(sc->sc_iot, oba->obio_addr, size, 0,
	    &sc->sc_ioh) != 0) {
		aprint_error(": can't map registers\n");
		return;
	}

	porcfg = bus_space_read_4(sc->sc_iot, sc->sc_ioh, CDM_PORCFG);
	cfg = bus_space_read_4(sc->sc_iot, sc->sc_ioh, CDM_CFG);
	cdm_compute(porcfg, cfg);

	aprint_normal(": core %u.%u MHz, XLB %u.%u MHz, IPB %u.%u MHz, "
	    "PCI %u.%u MHz\n",
	    cdm_clocks.cc_core / 1000000, (cdm_clocks.cc_core / 100000) % 10,
	    cdm_clocks.cc_xlb / 1000000, (cdm_clocks.cc_xlb / 100000) % 10,
	    cdm_clocks.cc_ipb / 1000000, (cdm_clocks.cc_ipb / 100000) % 10,
	    cdm_clocks.cc_pci / 1000000, (cdm_clocks.cc_pci / 100000) % 10);
}
@
