blob: 22524e6faf581e5f7752b23f7a5b6879f617050b [file] [log] [blame]
/*
* Copyright © 2015 Broadcom Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* A copy of the GPL is available at
* http://www.broadcom.com/licenses/GPLv2.php or from the Free Software
* Foundation at https://www.gnu.org/licenses/ .
*/
#include <asm/page.h>
#include <asm/setup.h> /* for meminfo */
#include <linux/device.h>
#include <linux/io.h>
#include <linux/libfdt.h>
#include <linux/memblock.h>
#include <linux/mm.h> /* for high_memory */
#include <linux/of_address.h>
#include <linux/of_fdt.h>
#include <linux/vme.h>
#include <linux/brcmstb/bmem.h>
#include <linux/brcmstb/cma_driver.h>
#include <linux/brcmstb/memory_api.h>
/* -------------------- Constants -------------------- */
#define DEFAULT_LOWMEM_PCT 20 /* used if only one membank */
/* Macros to help extract property data */
#define U8TOU32(b, offs) \
((((u32)b[0+offs] << 0) & 0x000000ff) | \
(((u32)b[1+offs] << 8) & 0x0000ff00) | \
(((u32)b[2+offs] << 16) & 0x00ff0000) | \
(((u32)b[3+offs] << 24) & 0xff000000))
#define DT_PROP_DATA_TO_U32(b, offs) (fdt32_to_cpu(U8TOU32(b, offs)))
/* Constants used when retrieving memc info */
#define NUM_BUS_RANGES 10
#define BUS_RANGE_ULIMIT_SHIFT 4
#define BUS_RANGE_LLIMIT_SHIFT 4
#define BUS_RANGE_PA_SHIFT 12
enum {
BUSNUM_MCP0 = 0x4,
BUSNUM_MCP1 = 0x5,
BUSNUM_MCP2 = 0x6,
};
/* -------------------- Shared and local vars -------------------- */
const enum brcmstb_reserve_type brcmstb_default_reserve = BRCMSTB_RESERVE_CMA;
bool brcmstb_memory_override_defaults = false;
static struct {
struct brcmstb_range range[MAX_BRCMSTB_RESERVED_RANGE];
int count;
} reserved_init;
/* -------------------- Functions -------------------- */
/*
* If the DT nodes are handy, determine which MEMC holds the specified
* physical address.
*/
int brcmstb_memory_phys_addr_to_memc(phys_addr_t pa)
{
int memc = -1;
int i;
struct device_node *np;
void __iomem *cpubiuctrl = NULL;
void __iomem *curr;
np = of_find_compatible_node(NULL, NULL, "brcm,brcmstb-cpu-biu-ctrl");
if (!np)
goto cleanup;
cpubiuctrl = of_iomap(np, 0);
if (!cpubiuctrl)
goto cleanup;
for (i = 0, curr = cpubiuctrl; i < NUM_BUS_RANGES; i++, curr += 8) {
const u64 ulimit_raw = readl(curr);
const u64 llimit_raw = readl(curr + 4);
const u64 ulimit =
((ulimit_raw >> BUS_RANGE_ULIMIT_SHIFT)
<< BUS_RANGE_PA_SHIFT) | 0xfff;
const u64 llimit = (llimit_raw >> BUS_RANGE_LLIMIT_SHIFT)
<< BUS_RANGE_PA_SHIFT;
const u32 busnum = (u32)(ulimit_raw & 0xf);
if (pa >= llimit && pa <= ulimit) {
if (busnum >= BUSNUM_MCP0 && busnum <= BUSNUM_MCP2) {
memc = busnum - BUSNUM_MCP0;
break;
}
}
}
cleanup:
if (cpubiuctrl)
iounmap(cpubiuctrl);
of_node_put(np);
return memc;
}
static int populate_memc(struct brcmstb_memory *mem, int addr_cells,
int size_cells)
{
const void *fdt = initial_boot_params;
const int mem_offset = fdt_path_offset(fdt, "/memory");
const struct fdt_property *prop;
int proplen, cellslen;
int i;
if (mem_offset < 0) {
pr_err("No memory node?\n");
return -EINVAL;
}
prop = fdt_get_property(fdt, mem_offset, "reg", &proplen);
cellslen = (int)sizeof(u32) * (addr_cells + size_cells);
if ((proplen % cellslen) != 0) {
pr_err("Invalid length of reg prop: %d\n", proplen);
return -EINVAL;
}
for (i = 0; i < proplen / cellslen; ++i) {
u64 addr = 0;
u64 size = 0;
int memc_idx;
int range_idx;
int j;
for (j = 0; j < addr_cells; ++j) {
int offset = (cellslen * i) + (sizeof(u32) * j);
addr |= (u64)DT_PROP_DATA_TO_U32(prop->data, offset) <<
((addr_cells - j - 1) * 32);
}
for (j = 0; j < size_cells; ++j) {
int offset = (cellslen * i) +
(sizeof(u32) * (j + addr_cells));
size |= (u64)DT_PROP_DATA_TO_U32(prop->data, offset) <<
((size_cells - j - 1) * 32);
}
if ((phys_addr_t)addr != addr) {
pr_err("phys_addr_t is smaller than provided address 0x%llx!\n",
addr);
return -EINVAL;
}
memc_idx = brcmstb_memory_phys_addr_to_memc((phys_addr_t)addr);
if (memc_idx == -1) {
pr_err("address 0x%llx does not appear to be in any memc\n",
addr);
return -EINVAL;
}
range_idx = mem->memc[memc_idx].count;
if (mem->memc[memc_idx].count >= MAX_BRCMSTB_RANGE)
pr_warn("%s: Exceeded max ranges for memc%d\n",
__func__, memc_idx);
else {
mem->memc[memc_idx].range[range_idx].addr = addr;
mem->memc[memc_idx].range[range_idx].size = size;
}
++mem->memc[memc_idx].count;
}
return 0;
}
static int populate_lowmem(struct brcmstb_memory *mem)
{
#ifdef CONFIG_ARM
mem->lowmem.range[0].addr = __pa(PAGE_OFFSET);
mem->lowmem.range[0].size = (unsigned long)high_memory - PAGE_OFFSET;
++mem->lowmem.count;
return 0;
#else
return -ENOSYS;
#endif
}
static int populate_bmem(struct brcmstb_memory *mem)
{
#ifdef CONFIG_BRCMSTB_BMEM
phys_addr_t addr, size;
int i;
for (i = 0; i < MAX_BRCMSTB_RANGE; ++i) {
if (bmem_region_info(i, &addr, &size))
break; /* no more regions */
mem->bmem.range[i].addr = addr;
mem->bmem.range[i].size = size;
++mem->bmem.count;
}
if (i >= MAX_BRCMSTB_RANGE) {
while (bmem_region_info(i, &addr, &size) == 0) {
pr_warn("%s: Exceeded max ranges\n", __func__);
++mem->bmem.count;
}
}
return 0;
#else
return -ENOSYS;
#endif
}
static int populate_cma(struct brcmstb_memory *mem)
{
#ifdef CONFIG_BRCMSTB_CMA
int i;
for (i = 0; i < CMA_NUM_RANGES; ++i) {
struct cma_dev *cdev = cma_dev_get_cma_dev(i);
if (cdev == NULL)
break;
if (i >= MAX_BRCMSTB_RANGE)
pr_warn("%s: Exceeded max ranges\n", __func__);
else {
mem->cma.range[i].addr = cdev->range.base;
mem->cma.range[i].size = cdev->range.size;
}
++mem->cma.count;
}
return 0;
#else
return -ENOSYS;
#endif
}
static int populate_reserved(struct brcmstb_memory *mem)
{
#ifdef CONFIG_HAVE_MEMBLOCK
memcpy(&mem->reserved, &reserved_init, sizeof(reserved_init));
return 0;
#else
return -ENOSYS;
#endif
}
/**
* brcmstb_memory_get_default_reserve() - find default reservation for given ID
* @bank_nr: bank index
* @pstart: pointer to the start address (output)
* @psize: pointer to the size address (output)
*
* NOTE: This interface will change in future kernels that do not have meminfo
*
* This takes in the bank number and determines the size and address of the
* default region reserved for refsw within the bank.
*/
int __init brcmstb_memory_get_default_reserve(int bank_nr,
phys_addr_t *pstart, phys_addr_t *psize)
{
/* min alignment for mm core */
const phys_addr_t alignment =
PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order);
struct membank *bank = &meminfo.bank[bank_nr];
phys_addr_t start = bank->start;
phys_addr_t size = 0;
phys_addr_t newstart, newsize;
int i;
if (!pstart || !psize)
return -EFAULT;
if (bank_nr == 0) {
if (meminfo.nr_banks == 1) {
u32 rem;
u64 tmp;
BUG_ON(bank->highmem);
if (bank->size < SZ_32M) {
pr_err("low memory too small for default bmem\n");
return -EINVAL;
}
/* kernel reserves X percent, bmem gets the rest */
tmp = ((u64)bank->size) * (100 - DEFAULT_LOWMEM_PCT);
rem = do_div(tmp, 100);
size = tmp + rem;
start = bank->start + bank->size - size;
} else {
/* If more than one bank, don't use first bank */
return -EINVAL;
}
} else if (bank->start >= VME_A32_MAX && bank->size > SZ_64M) {
/*
* Nexus doesn't use the address extension range yet, just
* reserve 64 MiB in these areas until we have a firmer
* specification
*/
size = SZ_64M;
} else {
size = bank->size;
}
/*
* To keep things simple, we only handle the case where reserved memory
* is at the start or end of a region.
*/
i = 0;
while (i < memblock.reserved.cnt) {
struct memblock_region *region = &memblock.reserved.regions[i];
newstart = start;
newsize = size;
if (start >= region->base &&
start < region->base + region->size) {
/* adjust for reserved region at beginning */
newstart = region->base + region->size;
newsize = size - (newstart - start);
} else if (start < region->base) {
if (start + size >
region->base + region->size) {
/* unhandled condition */
pr_err("%s: Split region %pa@%pa, reserve will fail\n",
__func__, &size, &start);
/* enable 'debug' param for dump output */
memblock_dump_all();
return -EINVAL;
}
/* adjust for reserved region at end */
newsize = min(region->base - start, size);
}
/* see if we had any modifications */
if (newsize != size || newstart != start) {
pr_debug("%s: moving default region from %pa@%pa to %pa@%pa\n",
__func__, &size, &start, &newsize,
&newstart);
size = newsize;
start = newstart;
i = 0; /* start over */
} else {
++i;
}
}
/* Fix up alignment */
newstart = ALIGN(start, alignment);
if (newstart != start) {
pr_debug("adjusting start from %pa to %pa\n",
&start, &newstart);
start = newstart;
}
newsize = round_down(size, alignment);
if (newsize != size) {
pr_debug("adjusting size from %pa to %pa\n",
&size, &newsize);
size = newsize;
}
if (size == 0) {
pr_debug("size available in bank was 0 - skipping\n");
return -EINVAL;
}
*pstart = start;
*psize = size;
return 0;
}
/**
* brcmstb_memory_reserve() - fill in static brcmstb_memory structure
*
* This is a boot-time initialization function used to copy the information
* stored in the memblock reserve function that is discarded after boot.
*/
void __init brcmstb_memory_reserve(void)
{
#ifdef CONFIG_HAVE_MEMBLOCK
struct memblock_type *type = &memblock.reserved;
int i;
for (i = 0; i < type->cnt; ++i) {
struct memblock_region *region = &type->regions[i];
if (i >= MAX_BRCMSTB_RESERVED_RANGE)
pr_warn_once("%s: Exceeded max ranges\n", __func__);
else {
reserved_init.range[i].addr = region->base;
reserved_init.range[i].size = region->size;
}
++reserved_init.count;
}
#else
pr_err("No memblock, cannot get reserved range\n");
#endif
}
/*
* brcmstb_memory_get() - fill in brcmstb_memory structure
* @mem: pointer to allocated struct brcmstb_memory to fill
*
* The brcmstb_memory struct is required by the brcmstb middleware to
* determine how to set up its memory heaps. This function expects that the
* passed pointer is valid. The struct does not need to have be zeroed
* before calling.
*/
int brcmstb_memory_get(struct brcmstb_memory *mem)
{
const void *fdt = initial_boot_params;
const struct fdt_property *prop;
int addr_cells = 1, size_cells = 1;
int proplen;
int ret;
if (!mem)
return -EFAULT;
if (!fdt) {
pr_err("No device tree?\n");
return -EINVAL;
}
/* Get root size and address cells if specified */
prop = fdt_get_property(fdt, 0, "#size-cells", &proplen);
if (prop)
size_cells = DT_PROP_DATA_TO_U32(prop->data, 0);
pr_debug("size_cells = %x\n", size_cells);
prop = fdt_get_property(fdt, 0, "#address-cells", &proplen);
if (prop)
addr_cells = DT_PROP_DATA_TO_U32(prop->data, 0);
pr_debug("address_cells = %x\n", addr_cells);
memset(mem, 0, sizeof(*mem));
ret = populate_memc(mem, addr_cells, size_cells);
if (ret)
return ret;
ret = populate_lowmem(mem);
if (ret)
return ret;
ret = populate_bmem(mem);
if (ret)
pr_debug("bmem is disabled\n");
ret = populate_cma(mem);
if (ret)
pr_debug("cma is disabled\n");
ret = populate_reserved(mem);
if (ret)
return ret;
return 0;
}
EXPORT_SYMBOL(brcmstb_memory_get);