/* $OpenBSD: arml2cc.c,v 1.4 2015/05/20 00:39:16 jsg Exp $ */
/*
 * Copyright (c) 2013 Patrick Wildt <patrick@blueri.se>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/queue.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/evcount.h>
#include <sys/socket.h>
#include <sys/timeout.h>
#include <machine/intr.h>
#include <machine/bus.h>
#include <arm/cpufunc.h>
#include <arm/cortex/cortex.h>
#include <arm/cortex/smc.h>

#define PL310_ERRATA_727915

/* offset from periphbase */
#define L2C_ADDR			0x2000
#define L2C_SIZE			0x1000

/* registers */
#define L2C_CACHE_ID			0x000
#define L2C_CACHE_TYPE			0x004
#define L2C_CTL				0x100
#define L2C_AUXCTL			0x104
#define L2C_TAG_RAM_CTL			0x108
#define L2C_DATA_RAM_CTL		0x10c
#define L2C_EVC_CTR_CTL			0x200
#define L2C_EVC_CTR0_CTL		0x204
#define L2C_EVC_CTR1_CTL		0x208
#define L2C_EVC_CTR0_VAL		0x20c
#define L2C_EVC_CTR1_VAL		0x210
#define L2C_INT_MASK			0x214
#define L2C_INT_MASK_STS		0x218
#define L2C_INT_RAW_STS			0x21c
#define L2C_INT_CLR			0x220
#define L2C_CACHE_SYNC			0x730
#define L2C_INV_PA			0x770
#define L2C_INV_WAY			0x77c
#define L2C_CLEAN_PA			0x7b0
#define L2C_CLEAN_INDEX			0x7b8
#define L2C_CLEAN_WAY			0x7bc
#define L2C_CLEAN_INV_PA		0x7f0
#define L2C_CLEAN_INV_INDEX		0x7f8
#define L2C_CLEAN_INV_WAY		0x7fc
#define L2C_D_LOCKDOWN0			0x900
#define L2C_I_LOCKDOWN0			0x904
#define L2C_D_LOCKDOWN1			0x908
#define L2C_I_LOCKDOWN1			0x90c
#define L2C_D_LOCKDOWN2			0x910
#define L2C_I_LOCKDOWN2			0x914
#define L2C_D_LOCKDOWN3			0x918
#define L2C_I_LOCKDOWN3			0x91c
#define L2C_D_LOCKDOWN4			0x920
#define L2C_I_LOCKDOWN4			0x924
#define L2C_D_LOCKDOWN5			0x928
#define L2C_I_LOCKDOWN5			0x92c
#define L2C_D_LOCKDOWN6			0x930
#define L2C_I_LOCKDOWN6			0x934
#define L2C_D_LOCKDOWN7			0x938
#define L2C_I_LOCKDOWN7			0x93c
#define L2C_LOCKDOWN_LINE_EN		0x950
#define L2C_UNLOCK_WAY			0x954
#define L2C_ADDR_FILTER_START		0xc00
#define L2C_ADDR_FILTER_END		0xc04
#define L2C_DEBUG_CTL			0xf40
#define L2C_PREFETCH_CTL		0xf60
#define L2C_POWER_CTL			0xf80

#define L2C_CACHE_ID_RELEASE_MASK	0x3f
#define L2C_CACHE_TYPE_LINESIZE		0x3
#define L2C_AUXCTL_ASSOC_SHIFT		16
#define L2C_AUXCTL_ASSOC_MASK		0x1

#define roundup2(size, unit) (((size) + (unit) - 1) & ~((unit) - 1))

struct arml2cc_softc {
	struct device		sc_dev;
	bus_space_tag_t		sc_iot;
	bus_space_handle_t	sc_ioh;
	uint32_t		sc_enabled;
	uint32_t		sc_waymask;
	uint32_t		sc_dcache_line_size;
};

struct arml2cc_softc *arml2cc_sc;

int arml2cc_match(struct device *, void *, void *);
void arml2cc_attach(struct device *parent, struct device *self, void *args);
void arml2cc_enable(struct arml2cc_softc *);
void arml2cc_disable(struct arml2cc_softc *);
void arml2cc_sdcache_wbinv_all(void);
void arml2cc_sdcache_wbinv_range(vaddr_t, paddr_t, psize_t);
void arml2cc_sdcache_inv_range(vaddr_t, paddr_t, psize_t);
void arml2cc_sdcache_wb_range(vaddr_t, paddr_t, psize_t);
void arml2cc_cache_range_op(paddr_t, psize_t, bus_size_t);
void arml2cc_cache_way_op(struct arml2cc_softc *, bus_size_t, uint32_t);
void arml2cc_cache_op(struct arml2cc_softc *, bus_size_t, uint32_t);
void arml2cc_cache_sync(struct arml2cc_softc *);

struct cfattach armliicc_ca = {
	sizeof (struct arml2cc_softc), arml2cc_match, arml2cc_attach
};

struct cfdriver armliicc_cd = {
	NULL, "armliicc", DV_DULL
};

int
arml2cc_match(struct device *parent, void *cfdata, void *aux)
{
	if ((cpufunc_id() & CPU_ID_CORTEX_A9_MASK) == CPU_ID_CORTEX_A9)
		return (1);

	return (0);
}

void
arml2cc_attach(struct device *parent, struct device *self, void *args)
{
	struct cortex_attach_args *ia = args;
	struct arml2cc_softc *sc = (struct arml2cc_softc *) self;

	sc->sc_iot = ia->ca_iot;
	if (bus_space_map(sc->sc_iot, ia->ca_periphbase + L2C_ADDR,
	    L2C_SIZE, 0, &sc->sc_ioh))
		panic("arml2cc_attach: bus_space_map failed!");

	printf(": rtl %d", bus_space_read_4(sc->sc_iot, sc->sc_ioh,
	    L2C_CACHE_ID) & 0x3f);

	arml2cc_sc = sc;

	if (bus_space_read_4(sc->sc_iot, sc->sc_ioh, L2C_CTL))
		panic("L2 Cache controller was already enabled\n");

	sc->sc_dcache_line_size = 32 << (bus_space_read_4(sc->sc_iot, sc->sc_ioh, L2C_CACHE_TYPE) & L2C_CACHE_TYPE_LINESIZE);
	sc->sc_waymask = (8 << ((bus_space_read_4(sc->sc_iot, sc->sc_ioh, L2C_AUXCTL)
			    >> L2C_AUXCTL_ASSOC_SHIFT) & L2C_AUXCTL_ASSOC_MASK)) - 1;
	printf(" waymask: 0x%08x\n", sc->sc_waymask);

	arml2cc_enable(sc);
	sc->sc_enabled = 1;

	arml2cc_sdcache_wbinv_all();

	cpufuncs.cf_sdcache_wbinv_all = arml2cc_sdcache_wbinv_all;
	cpufuncs.cf_sdcache_wbinv_range = arml2cc_sdcache_wbinv_range;
	cpufuncs.cf_sdcache_inv_range = arml2cc_sdcache_inv_range;
	cpufuncs.cf_sdcache_wb_range = arml2cc_sdcache_wb_range;
}

void
arml2cc_enable(struct arml2cc_softc *sc)
{
	int s;
	s = splhigh();

	platform_smc_write(sc->sc_iot, sc->sc_ioh, L2C_CTL, SMC_L2_CTL,
	    1);

	arml2cc_cache_way_op(sc, L2C_INV_WAY, sc->sc_waymask);
	arml2cc_cache_sync(sc);

	splx(s);
}

void
arml2cc_disable(struct arml2cc_softc *sc)
{
	int s;
	s = splhigh();

	arml2cc_cache_way_op(sc, L2C_CLEAN_INV_WAY, sc->sc_waymask);
	arml2cc_cache_sync(sc);

	platform_smc_write(sc->sc_iot, sc->sc_ioh, L2C_CTL, SMC_L2_CTL, 0);

	splx(s);
}

void
arml2cc_cache_op(struct arml2cc_softc *sc, bus_size_t off, uint32_t val)
{
	bus_space_write_4(sc->sc_iot, sc->sc_ioh, off, val);
	while (bus_space_read_4(sc->sc_iot, sc->sc_ioh, off) & 1) {
		/* spin */
	}
}

void
arml2cc_cache_way_op(struct arml2cc_softc *sc, bus_size_t off, uint32_t way_mask)
{
	bus_space_write_4(sc->sc_iot, sc->sc_ioh, off, way_mask);
	while (bus_space_read_4(sc->sc_iot, sc->sc_ioh, off) & way_mask) {
		/* spin */
	}
}

void
arml2cc_cache_sync(struct arml2cc_softc *sc)
{
	/* ARM Errata 753970 */
	bus_space_write_4(sc->sc_iot, sc->sc_ioh, 0x740, 0xffffffff);
}

void
arml2cc_cache_range_op(paddr_t pa, psize_t len, bus_size_t cache_op)
{
	struct arml2cc_softc * const sc = arml2cc_sc;
	size_t line_size = sc->sc_dcache_line_size;
	size_t line_mask = line_size - 1;
	paddr_t endpa;

	endpa = pa + len;
	pa = pa & ~line_mask;

	// printf("l2inv op %x %08x %08x incr %d %d\n", cache_op, pa, endpa, line_size, len);
	while (endpa > pa) {
		arml2cc_cache_op(sc, cache_op, pa);
		pa += line_size;
	}
}

void
arml2cc_sdcache_wbinv_all(void)
{
	struct arml2cc_softc *sc = arml2cc_sc;
	if (sc == NULL || !sc->sc_enabled)
		return;

#ifdef PL310_ERRATA_727915
	platform_smc_write(sc->sc_iot, sc->sc_ioh, L2C_DEBUG_CTL, SMC_L2_DBG, 3);
#endif

	bus_space_write_4(sc->sc_iot, sc->sc_ioh, L2C_CLEAN_INV_WAY, sc->sc_waymask);
	while(bus_space_read_4(sc->sc_iot, sc->sc_ioh, L2C_CLEAN_INV_WAY) & sc->sc_waymask);

#ifdef PL310_ERRATA_727915
	platform_smc_write(sc->sc_iot, sc->sc_ioh, L2C_DEBUG_CTL, SMC_L2_DBG, 0);
#endif

	arml2cc_cache_sync(sc);
}
void
arml2cc_sdcache_wbinv_range(vaddr_t va, paddr_t pa, psize_t len)
{
	struct arml2cc_softc *sc = arml2cc_sc;
	if (sc == NULL || !sc->sc_enabled)
		return;

#ifdef PL310_ERRATA_727915
	platform_smc_write(sc->sc_iot, sc->sc_ioh, L2C_DEBUG_CTL, SMC_L2_DBG, 3);
#endif

	arml2cc_cache_range_op(pa, len, L2C_CLEAN_INV_PA);
	arml2cc_cache_sync(sc);

#ifdef PL310_ERRATA_727915
	platform_smc_write(sc->sc_iot, sc->sc_ioh, L2C_DEBUG_CTL, SMC_L2_DBG, 0);
#endif
}

void
arml2cc_sdcache_inv_range(vaddr_t va, paddr_t pa, psize_t len)
{
	struct arml2cc_softc *sc = arml2cc_sc;
	if (sc == NULL || !sc->sc_enabled)
		return;
	arml2cc_cache_range_op(pa, len, L2C_INV_PA);
	arml2cc_cache_sync(sc);
}

void
arml2cc_sdcache_wb_range(vaddr_t va, paddr_t pa, psize_t len)
{
	struct arml2cc_softc *sc = arml2cc_sc;
	if (sc == NULL || !sc->sc_enabled)
		return;
	arml2cc_cache_range_op(pa, len, L2C_CLEAN_PA);
	arml2cc_cache_sync(sc);
}
