android_kernel_xiaomi_sm8350/arch/mips/powertv/asic/asic_devices.c
David VomLehn 59dfa2fcae MIPS: PowerTV: Streamline access to platform device registers
Pre-compute addresses for the basic ASIC registers. This speeds up access
and allows memory for unused configurations to be freed. In addition,
uninitialized register addresses will be returned as NULL to catch bad
usage quickly.

Signed-off-by: David VomLehn <dvomlehn@cisco.com>
To: linux-mips@linux-mips.org
Patchwork: http://patchwork.linux-mips.org/patch/806/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
2010-01-28 00:03:31 +01:00

780 lines
18 KiB
C

/*
* ASIC Device List Intialization
*
* Description: Defines the platform resources for the SA settop.
*
* Copyright (C) 2005-2009 Scientific-Atlanta, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Author: Ken Eppinett
* David Schleef <ds@schleef.org>
*
* Description: Defines the platform resources for the SA settop.
*
* NOTE: The bootloader allocates persistent memory at an address which is
* 16 MiB below the end of the highest address in KSEG0. All fixed
* address memory reservations must avoid this region.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/resource.h>
#include <linux/serial_reg.h>
#include <linux/io.h>
#include <linux/bootmem.h>
#include <linux/mm.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <asm/page.h>
#include <linux/swap.h>
#include <linux/highmem.h>
#include <linux/dma-mapping.h>
#include <asm/mach-powertv/asic.h>
#include <asm/mach-powertv/asic_regs.h>
#include <asm/mach-powertv/interrupts.h>
#ifdef CONFIG_BOOTLOADER_DRIVER
#include <asm/mach-powertv/kbldr.h>
#endif
#include <asm/bootinfo.h>
#define BOOTLDRFAMILY(byte1, byte0) (((byte1) << 8) | (byte0))
/*
* Forward Prototypes
*/
static void pmem_setup_resource(void);
/*
* Global Variables
*/
enum asic_type asic;
unsigned int platform_features;
unsigned int platform_family;
struct register_map _asic_register_map;
EXPORT_SYMBOL(_asic_register_map); /* Exported for testing */
unsigned long asic_phy_base;
unsigned long asic_base;
EXPORT_SYMBOL(asic_base); /* Exported for testing */
struct resource *gp_resources;
static bool usb_configured;
/*
* Don't recommend to use it directly, it is usually used by kernel internally.
* Portable code should be using interfaces such as ioremp, dma_map_single, etc.
*/
unsigned long phys_to_bus_offset;
EXPORT_SYMBOL(phys_to_bus_offset);
/*
*
* IO Resource Definition
*
*/
struct resource asic_resource = {
.name = "ASIC Resource",
.start = 0,
.end = ASIC_IO_SIZE,
.flags = IORESOURCE_MEM,
};
/*
*
* USB Host Resource Definition
*
*/
static struct resource ehci_resources[] = {
{
.parent = &asic_resource,
.start = 0,
.end = 0xff,
.flags = IORESOURCE_MEM,
},
{
.start = irq_usbehci,
.end = irq_usbehci,
.flags = IORESOURCE_IRQ,
},
};
static u64 ehci_dmamask = DMA_BIT_MASK(32);
static struct platform_device ehci_device = {
.name = "powertv-ehci",
.id = 0,
.num_resources = 2,
.resource = ehci_resources,
.dev = {
.dma_mask = &ehci_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
},
};
static struct resource ohci_resources[] = {
{
.parent = &asic_resource,
.start = 0,
.end = 0xff,
.flags = IORESOURCE_MEM,
},
{
.start = irq_usbohci,
.end = irq_usbohci,
.flags = IORESOURCE_IRQ,
},
};
static u64 ohci_dmamask = DMA_BIT_MASK(32);
static struct platform_device ohci_device = {
.name = "powertv-ohci",
.id = 0,
.num_resources = 2,
.resource = ohci_resources,
.dev = {
.dma_mask = &ohci_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
},
};
static struct platform_device *platform_devices[] = {
&ehci_device,
&ohci_device,
};
/*
*
* Platform Configuration and Device Initialization
*
*/
static void __init fs_update(int pe, int md, int sdiv, int disable_div_by_3)
{
int en_prg, byp, pwr, nsb, val;
int sout;
sout = 1;
en_prg = 1;
byp = 0;
nsb = 1;
pwr = 1;
val = ((sdiv << 29) | (md << 24) | (pe<<8) | (sout<<3) | (byp<<2) |
(nsb<<1) | (disable_div_by_3<<5));
asic_write(val, usb_fs);
asic_write(val | (en_prg<<4), usb_fs);
asic_write(val | (en_prg<<4) | pwr, usb_fs);
}
/*
* Allow override of bootloader-specified model
*/
static char __initdata cmdline[COMMAND_LINE_SIZE];
#define FORCEFAMILY_PARAM "forcefamily"
static __init int check_forcefamily(unsigned char forced_family[2])
{
const char *p;
forced_family[0] = '\0';
forced_family[1] = '\0';
/* Check the command line for a forcefamily directive */
strncpy(cmdline, arcs_cmdline, COMMAND_LINE_SIZE - 1);
p = strstr(cmdline, FORCEFAMILY_PARAM);
if (p && (p != cmdline) && (*(p - 1) != ' '))
p = strstr(p, " " FORCEFAMILY_PARAM "=");
if (p) {
p += strlen(FORCEFAMILY_PARAM "=");
if (*p == '\0' || *(p + 1) == '\0' ||
(*(p + 2) != '\0' && *(p + 2) != ' '))
pr_err(FORCEFAMILY_PARAM " must be exactly two "
"characters long, ignoring value\n");
else {
forced_family[0] = *p;
forced_family[1] = *(p + 1);
}
}
return 0;
}
/*
* platform_set_family - determine major platform family type.
*
* Returns family type; -1 if none
* Returns the family type; -1 if none
*
*/
static __init noinline void platform_set_family(void)
{
#define BOOTLDRFAMILY(byte1, byte0) (((byte1) << 8) | (byte0))
unsigned char forced_family[2];
unsigned short bootldr_family;
check_forcefamily(forced_family);
if (forced_family[0] != '\0' && forced_family[1] != '\0')
bootldr_family = BOOTLDRFAMILY(forced_family[0],
forced_family[1]);
else {
#ifdef CONFIG_BOOTLOADER_DRIVER
bootldr_family = (unsigned short) kbldr_GetSWFamily();
#else
#if defined(CONFIG_BOOTLOADER_FAMILY)
bootldr_family = (unsigned short) BOOTLDRFAMILY(
CONFIG_BOOTLOADER_FAMILY[0],
CONFIG_BOOTLOADER_FAMILY[1]);
#else
#error "Unknown Bootloader Family"
#endif
#endif
}
pr_info("Bootloader Family = 0x%04X\n", bootldr_family);
switch (bootldr_family) {
case BOOTLDRFAMILY('R', '1'):
platform_family = FAMILY_1500;
break;
case BOOTLDRFAMILY('4', '4'):
platform_family = FAMILY_4500;
break;
case BOOTLDRFAMILY('4', '6'):
platform_family = FAMILY_4600;
break;
case BOOTLDRFAMILY('A', '1'):
platform_family = FAMILY_4600VZA;
break;
case BOOTLDRFAMILY('8', '5'):
platform_family = FAMILY_8500;
break;
case BOOTLDRFAMILY('R', '2'):
platform_family = FAMILY_8500RNG;
break;
case BOOTLDRFAMILY('8', '6'):
platform_family = FAMILY_8600;
break;
case BOOTLDRFAMILY('B', '1'):
platform_family = FAMILY_8600VZB;
break;
case BOOTLDRFAMILY('E', '1'):
platform_family = FAMILY_1500VZE;
break;
case BOOTLDRFAMILY('F', '1'):
platform_family = FAMILY_1500VZF;
break;
default:
platform_family = -1;
}
}
unsigned int platform_get_family(void)
{
return platform_family;
}
EXPORT_SYMBOL(platform_get_family);
/*
* \brief usb_eye_configure() for optimizing the USB eye on Calliope.
*
* \param unsigned int value saved to the register.
*
* \return none
*
*/
static void __init usb_eye_configure(unsigned int value)
{
asic_write(asic_read(crt_spare) | value, crt_spare);
}
/*
* platform_get_asic - determine the ASIC type.
*
* \param none
*
* \return ASIC type; ASIC_UNKNOWN if none
*
*/
enum asic_type platform_get_asic(void)
{
return asic;
}
EXPORT_SYMBOL(platform_get_asic);
/*
* platform_configure_usb - usb configuration based on platform type.
* @bcm1_usb2_ctl: value for the BCM1_USB2_CTL register, which is
* quirky
*/
static void __init platform_configure_usb(void)
{
u32 bcm1_usb2_ctl;
if (usb_configured)
return;
switch (asic) {
case ASIC_ZEUS:
fs_update(0x0000, 0x11, 0x02, 0);
bcm1_usb2_ctl = 0x803;
break;
case ASIC_CRONUS:
case ASIC_CRONUSLITE:
fs_update(0x0000, 0x11, 0x02, 0);
bcm1_usb2_ctl = 0x803;
break;
case ASIC_CALLIOPE:
fs_update(0x0000, 0x11, 0x02, 1);
switch (platform_family) {
case FAMILY_1500VZE:
break;
case FAMILY_1500VZF:
usb_eye_configure(0x003c0000);
break;
default:
usb_eye_configure(0x00300000);
break;
}
bcm1_usb2_ctl = 0x803;
break;
default:
pr_err("Unknown ASIC type: %d\n", asic);
break;
}
/* turn on USB power */
asic_write(0, usb2_strap);
/* Enable all OHCI interrupts */
asic_write(bcm1_usb2_ctl, usb2_control);
/* USB2_STBUS_OBC store32/load32 */
asic_write(3, usb2_stbus_obc);
/* USB2_STBUS_MESS_SIZE 2 packets */
asic_write(1, usb2_stbus_mess_size);
/* USB2_STBUS_CHUNK_SIZE 2 packets */
asic_write(1, usb2_stbus_chunk_size);
usb_configured = true;
}
/*
* Set up the USB EHCI interface
*/
void platform_configure_usb_ehci()
{
platform_configure_usb();
}
/*
* Set up the USB OHCI interface
*/
void platform_configure_usb_ohci()
{
platform_configure_usb();
}
/*
* Shut the USB EHCI interface down--currently a NOP
*/
void platform_unconfigure_usb_ehci()
{
}
/*
* Shut the USB OHCI interface down--currently a NOP
*/
void platform_unconfigure_usb_ohci()
{
}
static void __init set_register_map(unsigned long phys_base,
const struct register_map *map)
{
asic_phy_base = phys_base;
_asic_register_map = *map;
register_map_virtualize(&_asic_register_map);
asic_base = (unsigned long)ioremap_nocache(phys_base, ASIC_IO_SIZE);
}
/**
* configure_platform - configuration based on platform type.
*/
void __init configure_platform(void)
{
platform_set_family();
switch (platform_family) {
case FAMILY_1500:
case FAMILY_1500VZE:
case FAMILY_1500VZF:
platform_features = FFS_CAPABLE;
asic = ASIC_CALLIOPE;
set_register_map(CALLIOPE_IO_BASE, &calliope_register_map);
if (platform_family == FAMILY_1500VZE) {
gp_resources = non_dvr_vze_calliope_resources;
pr_info("Platform: 1500/Vz Class E - "
"CALLIOPE, NON_DVR_CAPABLE\n");
} else if (platform_family == FAMILY_1500VZF) {
gp_resources = non_dvr_vzf_calliope_resources;
pr_info("Platform: 1500/Vz Class F - "
"CALLIOPE, NON_DVR_CAPABLE\n");
} else {
gp_resources = non_dvr_calliope_resources;
pr_info("Platform: 1500/RNG100 - CALLIOPE, "
"NON_DVR_CAPABLE\n");
}
break;
case FAMILY_4500:
platform_features = FFS_CAPABLE | PCIE_CAPABLE |
DISPLAY_CAPABLE;
asic = ASIC_ZEUS;
set_register_map(ZEUS_IO_BASE, &zeus_register_map);
gp_resources = non_dvr_zeus_resources;
pr_info("Platform: 4500 - ZEUS, NON_DVR_CAPABLE\n");
break;
case FAMILY_4600:
{
unsigned int chipversion = 0;
/* The settop has PCIE but it isn't used, so don't advertise
* it*/
platform_features = FFS_CAPABLE | DISPLAY_CAPABLE;
/* ASIC version will determine if this is a real CronusLite or
* Castrati(Cronus) */
chipversion = asic_read(chipver3) << 24;
chipversion |= asic_read(chipver2) << 16;
chipversion |= asic_read(chipver1) << 8;
chipversion |= asic_read(chipver0);
if ((chipversion == CRONUS_10) || (chipversion == CRONUS_11))
asic = ASIC_CRONUS;
else
asic = ASIC_CRONUSLITE;
/* Cronus and Cronus Lite have the same register map */
set_register_map(CRONUS_IO_BASE, &cronus_register_map);
gp_resources = non_dvr_cronuslite_resources;
pr_info("Platform: 4600 - %s, NON_DVR_CAPABLE, "
"chipversion=0x%08X\n",
(asic == ASIC_CRONUS) ? "CRONUS" : "CRONUS LITE",
chipversion);
break;
}
case FAMILY_4600VZA:
platform_features = FFS_CAPABLE | DISPLAY_CAPABLE;
asic = ASIC_CRONUS;
set_register_map(CRONUS_IO_BASE, &cronus_register_map);
gp_resources = non_dvr_cronus_resources;
pr_info("Platform: Vz Class A - CRONUS, NON_DVR_CAPABLE\n");
break;
case FAMILY_8500:
case FAMILY_8500RNG:
platform_features = DVR_CAPABLE | PCIE_CAPABLE |
DISPLAY_CAPABLE;
asic = ASIC_ZEUS;
set_register_map(ZEUS_IO_BASE, &zeus_register_map);
gp_resources = dvr_zeus_resources;
pr_info("Platform: 8500/RNG200 - ZEUS, DVR_CAPABLE\n");
break;
case FAMILY_8600:
case FAMILY_8600VZB:
platform_features = DVR_CAPABLE | PCIE_CAPABLE |
DISPLAY_CAPABLE;
asic = ASIC_CRONUS;
set_register_map(CRONUS_IO_BASE, &cronus_register_map);
gp_resources = dvr_cronus_resources;
pr_info("Platform: 8600/Vz Class B - CRONUS, "
"DVR_CAPABLE\n");
break;
default:
pr_crit("Platform: UNKNOWN PLATFORM\n");
break;
}
switch (asic) {
case ASIC_ZEUS:
phys_to_bus_offset = 0x30000000;
break;
case ASIC_CALLIOPE:
phys_to_bus_offset = 0x10000000;
break;
case ASIC_CRONUSLITE:
/* Fall through */
case ASIC_CRONUS:
/*
* TODO: We suppose 0x10000000 aliases into 0x20000000-
* 0x2XXXXXXX. If 0x10000000 aliases into 0x60000000-
* 0x6XXXXXXX, the offset should be 0x50000000, not 0x10000000.
*/
phys_to_bus_offset = 0x10000000;
break;
default:
phys_to_bus_offset = 0x00000000;
break;
}
}
/**
* platform_devices_init - sets up USB device resourse.
*/
static int __init platform_devices_init(void)
{
pr_notice("%s: ----- Initializing USB resources -----\n", __func__);
asic_resource.start = asic_phy_base;
asic_resource.end += asic_resource.start;
ehci_resources[0].start = asic_reg_phys_addr(ehci_hcapbase);
ehci_resources[0].end += ehci_resources[0].start;
ohci_resources[0].start = asic_reg_phys_addr(ohci_hc_revision);
ohci_resources[0].end += ohci_resources[0].start;
set_io_port_base(0);
platform_add_devices(platform_devices, ARRAY_SIZE(platform_devices));
return 0;
}
arch_initcall(platform_devices_init);
/*
*
* BOOTMEM ALLOCATION
*
*/
/*
* Allocates/reserves the Platform memory resources early in the boot process.
* This ignores any resources that are designated IORESOURCE_IO
*/
void __init platform_alloc_bootmem(void)
{
int i;
int total = 0;
/* Get persistent memory data from command line before allocating
* resources. This need to happen before normal command line parsing
* has been done */
pmem_setup_resource();
/* Loop through looking for resources that want a particular address */
for (i = 0; gp_resources[i].flags != 0; i++) {
int size = gp_resources[i].end - gp_resources[i].start + 1;
if ((gp_resources[i].start != 0) &&
((gp_resources[i].flags & IORESOURCE_MEM) != 0)) {
reserve_bootmem(bus_to_phys(gp_resources[i].start),
size, 0);
total += gp_resources[i].end -
gp_resources[i].start + 1;
pr_info("reserve resource %s at %08x (%u bytes)\n",
gp_resources[i].name, gp_resources[i].start,
gp_resources[i].end -
gp_resources[i].start + 1);
}
}
/* Loop through assigning addresses for those that are left */
for (i = 0; gp_resources[i].flags != 0; i++) {
int size = gp_resources[i].end - gp_resources[i].start + 1;
if ((gp_resources[i].start == 0) &&
((gp_resources[i].flags & IORESOURCE_MEM) != 0)) {
void *mem = alloc_bootmem_pages(size);
if (mem == NULL)
pr_err("Unable to allocate bootmem pages "
"for %s\n", gp_resources[i].name);
else {
gp_resources[i].start =
phys_to_bus(virt_to_phys(mem));
gp_resources[i].end =
gp_resources[i].start + size - 1;
total += size;
pr_info("allocate resource %s at %08x "
"(%u bytes)\n",
gp_resources[i].name,
gp_resources[i].start, size);
}
}
}
pr_info("Total Platform driver memory allocation: 0x%08x\n", total);
/* indicate resources that are platform I/O related */
for (i = 0; gp_resources[i].flags != 0; i++) {
if ((gp_resources[i].start != 0) &&
((gp_resources[i].flags & IORESOURCE_IO) != 0)) {
pr_info("reserved platform resource %s at %08x\n",
gp_resources[i].name, gp_resources[i].start);
}
}
}
/*
*
* PERSISTENT MEMORY (PMEM) CONFIGURATION
*
*/
static unsigned long pmemaddr __initdata;
static int __init early_param_pmemaddr(char *p)
{
pmemaddr = (unsigned long)simple_strtoul(p, NULL, 0);
return 0;
}
early_param("pmemaddr", early_param_pmemaddr);
static long pmemlen __initdata;
static int __init early_param_pmemlen(char *p)
{
/* TODO: we can use this code when and if the bootloader ever changes this */
#if 0
pmemlen = (unsigned long)simple_strtoul(p, NULL, 0);
#else
pmemlen = 0x20000;
#endif
return 0;
}
early_param("pmemlen", early_param_pmemlen);
/*
* Set up persistent memory. If we were given values, we patch the array of
* resources. Otherwise, persistent memory may be allocated anywhere at all.
*/
static void __init pmem_setup_resource(void)
{
struct resource *resource;
resource = asic_resource_get("DiagPersistentMemory");
if (resource && pmemaddr && pmemlen) {
/* The address provided by bootloader is in kseg0. Convert to
* a bus address. */
resource->start = phys_to_bus(pmemaddr - 0x80000000);
resource->end = resource->start + pmemlen - 1;
pr_info("persistent memory: start=0x%x end=0x%x\n",
resource->start, resource->end);
}
}
/*
*
* RESOURCE ACCESS FUNCTIONS
*
*/
/**
* asic_resource_get - retrieves parameters for a platform resource.
* @name: string to match resource
*
* Returns a pointer to a struct resource corresponding to the given name.
*
* CANNOT BE NAMED platform_resource_get, which would be the obvious choice,
* as this function name is already declared
*/
struct resource *asic_resource_get(const char *name)
{
int i;
for (i = 0; gp_resources[i].flags != 0; i++) {
if (strcmp(gp_resources[i].name, name) == 0)
return &gp_resources[i];
}
return NULL;
}
EXPORT_SYMBOL(asic_resource_get);
/**
* platform_release_memory - release pre-allocated memory
* @ptr: pointer to memory to release
* @size: size of resource
*
* This must only be called for memory allocated or reserved via the boot
* memory allocator.
*/
void platform_release_memory(void *ptr, int size)
{
unsigned long addr;
unsigned long end;
addr = ((unsigned long)ptr + (PAGE_SIZE - 1)) & PAGE_MASK;
end = ((unsigned long)ptr + size) & PAGE_MASK;
for (; addr < end; addr += PAGE_SIZE) {
ClearPageReserved(virt_to_page(__va(addr)));
init_page_count(virt_to_page(__va(addr)));
free_page((unsigned long)__va(addr));
}
}
EXPORT_SYMBOL(platform_release_memory);
/*
*
* FEATURE AVAILABILITY FUNCTIONS
*
*/
int platform_supports_dvr(void)
{
return (platform_features & DVR_CAPABLE) != 0;
}
int platform_supports_ffs(void)
{
return (platform_features & FFS_CAPABLE) != 0;
}
int platform_supports_pcie(void)
{
return (platform_features & PCIE_CAPABLE) != 0;
}
int platform_supports_display(void)
{
return (platform_features & DISPLAY_CAPABLE) != 0;
}