M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions
@@ -0,0 +1,40 @@
config PPC_PASEMI
depends on PPC64 && PPC_BOOK3S
bool "PA Semi SoC-based platforms"
default n
select MPIC
select PCI
select PPC_UDBG_16550
select PPC_NATIVE
select MPIC_BROKEN_REGREAD
help
This option enables support for PA Semi's PWRficient line
of SoC processors, including PA6T-1682M
menu "PA Semi PWRficient options"
depends on PPC_PASEMI
config PPC_PASEMI_IOMMU
bool "PA Semi IOMMU support"
depends on PPC_PASEMI
help
IOMMU support for PA Semi PWRficient
config PPC_PASEMI_IOMMU_DMA_FORCE
bool "Force DMA engine to use IOMMU"
depends on PPC_PASEMI_IOMMU
help
This option forces the use of the IOMMU also for the
DMA engine. Otherwise the kernel will use it only when
running under a hypervisor.
If in doubt, say "N".
config PPC_PASEMI_MDIO
depends on PHYLIB
tristate "MDIO support via GPIO"
default y
help
Driver for MDIO via GPIO on PWRficient platforms
endmenu
@@ -0,0 +1,3 @@
obj-y += setup.o pci.o time.o idle.o powersave.o iommu.o dma_lib.o misc.o
obj-$(CONFIG_PPC_PASEMI_MDIO) += gpio_mdio.o
obj-$(CONFIG_PPC_PASEMI_CPUFREQ) += cpufreq.o
@@ -0,0 +1,324 @@
/*
* Copyright (C) 2007 PA Semi, Inc
*
* Authors: Egor Martovetsky <egor@pasemi.com>
* Olof Johansson <olof@lixom.net>
*
* Maintained by: Olof Johansson <olof@lixom.net>
*
* Based on arch/powerpc/platforms/cell/cbe_cpufreq.c:
* (C) Copyright IBM Deutschland Entwicklung GmbH 2005
*
* 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <linux/cpufreq.h>
#include <linux/timer.h>
#include <linux/module.h>
#include <asm/hw_irq.h>
#include <asm/io.h>
#include <asm/prom.h>
#include <asm/time.h>
#include <asm/smp.h>
#define SDCASR_REG 0x0100
#define SDCASR_REG_STRIDE 0x1000
#define SDCPWR_CFGA0_REG 0x0100
#define SDCPWR_PWST0_REG 0x0000
#define SDCPWR_GIZTIME_REG 0x0440
/* SDCPWR_GIZTIME_REG fields */
#define SDCPWR_GIZTIME_GR 0x80000000
#define SDCPWR_GIZTIME_LONGLOCK 0x000000ff
/* Offset of ASR registers from SDC base */
#define SDCASR_OFFSET 0x120000
static void __iomem *sdcpwr_mapbase;
static void __iomem *sdcasr_mapbase;
static DEFINE_MUTEX(pas_switch_mutex);
/* Current astate, is used when waking up from power savings on
* one core, in case the other core has switched states during
* the idle time.
*/
static int current_astate;
/* We support 5(A0-A4) power states excluding turbo(A5-A6) modes */
static struct cpufreq_frequency_table pas_freqs[] = {
{0, 0},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{0, CPUFREQ_TABLE_END},
};
static struct freq_attr *pas_cpu_freqs_attr[] = {
&cpufreq_freq_attr_scaling_available_freqs,
NULL,
};
/*
* hardware specific functions
*/
static int get_astate_freq(int astate)
{
u32 ret;
ret = in_le32(sdcpwr_mapbase + SDCPWR_CFGA0_REG + (astate * 0x10));
return ret & 0x3f;
}
static int get_cur_astate(int cpu)
{
u32 ret;
ret = in_le32(sdcpwr_mapbase + SDCPWR_PWST0_REG);
ret = (ret >> (cpu * 4)) & 0x7;
return ret;
}
static int get_gizmo_latency(void)
{
u32 giztime, ret;
giztime = in_le32(sdcpwr_mapbase + SDCPWR_GIZTIME_REG);
/* just provide the upper bound */
if (giztime & SDCPWR_GIZTIME_GR)
ret = (giztime & SDCPWR_GIZTIME_LONGLOCK) * 128000;
else
ret = (giztime & SDCPWR_GIZTIME_LONGLOCK) * 1000;
return ret;
}
static void set_astate(int cpu, unsigned int astate)
{
unsigned long flags;
/* Return if called before init has run */
if (unlikely(!sdcasr_mapbase))
return;
local_irq_save(flags);
out_le32(sdcasr_mapbase + SDCASR_REG + SDCASR_REG_STRIDE*cpu, astate);
local_irq_restore(flags);
}
int check_astate(void)
{
return get_cur_astate(hard_smp_processor_id());
}
void restore_astate(int cpu)
{
set_astate(cpu, current_astate);
}
/*
* cpufreq functions
*/
static int pas_cpufreq_cpu_init(struct cpufreq_policy *policy)
{
const u32 *max_freqp;
u32 max_freq;
int i, cur_astate;
struct resource res;
struct device_node *cpu, *dn;
int err = -ENODEV;
cpu = of_get_cpu_node(policy->cpu, NULL);
if (!cpu)
goto out;
dn = of_find_compatible_node(NULL, NULL, "1682m-sdc");
if (!dn)
dn = of_find_compatible_node(NULL, NULL,
"pasemi,pwrficient-sdc");
if (!dn)
goto out;
err = of_address_to_resource(dn, 0, &res);
of_node_put(dn);
if (err)
goto out;
sdcasr_mapbase = ioremap(res.start + SDCASR_OFFSET, 0x2000);
if (!sdcasr_mapbase) {
err = -EINVAL;
goto out;
}
dn = of_find_compatible_node(NULL, NULL, "1682m-gizmo");
if (!dn)
dn = of_find_compatible_node(NULL, NULL,
"pasemi,pwrficient-gizmo");
if (!dn) {
err = -ENODEV;
goto out_unmap_sdcasr;
}
err = of_address_to_resource(dn, 0, &res);
of_node_put(dn);
if (err)
goto out_unmap_sdcasr;
sdcpwr_mapbase = ioremap(res.start, 0x1000);
if (!sdcpwr_mapbase) {
err = -EINVAL;
goto out_unmap_sdcasr;
}
pr_debug("init cpufreq on CPU %d\n", policy->cpu);
max_freqp = of_get_property(cpu, "clock-frequency", NULL);
if (!max_freqp) {
err = -EINVAL;
goto out_unmap_sdcpwr;
}
/* we need the freq in kHz */
max_freq = *max_freqp / 1000;
pr_debug("max clock-frequency is at %u kHz\n", max_freq);
pr_debug("initializing frequency table\n");
/* initialize frequency table */
for (i=0; pas_freqs[i].frequency!=CPUFREQ_TABLE_END; i++) {
pas_freqs[i].frequency = get_astate_freq(pas_freqs[i].index) * 100000;
pr_debug("%d: %d\n", i, pas_freqs[i].frequency);
}
policy->cpuinfo.transition_latency = get_gizmo_latency();
cur_astate = get_cur_astate(policy->cpu);
pr_debug("current astate is at %d\n",cur_astate);
policy->cur = pas_freqs[cur_astate].frequency;
cpumask_copy(policy->cpus, cpu_online_mask);
ppc_proc_freq = policy->cur * 1000ul;
cpufreq_frequency_table_get_attr(pas_freqs, policy->cpu);
/* this ensures that policy->cpuinfo_min and policy->cpuinfo_max
* are set correctly
*/
return cpufreq_frequency_table_cpuinfo(policy, pas_freqs);
out_unmap_sdcpwr:
iounmap(sdcpwr_mapbase);
out_unmap_sdcasr:
iounmap(sdcasr_mapbase);
out:
return err;
}
static int pas_cpufreq_cpu_exit(struct cpufreq_policy *policy)
{
if (sdcasr_mapbase)
iounmap(sdcasr_mapbase);
if (sdcpwr_mapbase)
iounmap(sdcpwr_mapbase);
cpufreq_frequency_table_put_attr(policy->cpu);
return 0;
}
static int pas_cpufreq_verify(struct cpufreq_policy *policy)
{
return cpufreq_frequency_table_verify(policy, pas_freqs);
}
static int pas_cpufreq_target(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
struct cpufreq_freqs freqs;
int pas_astate_new;
int i;
cpufreq_frequency_table_target(policy,
pas_freqs,
target_freq,
relation,
&pas_astate_new);
freqs.old = policy->cur;
freqs.new = pas_freqs[pas_astate_new].frequency;
freqs.cpu = policy->cpu;
mutex_lock(&pas_switch_mutex);
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
pr_debug("setting frequency for cpu %d to %d kHz, 1/%d of max frequency\n",
policy->cpu,
pas_freqs[pas_astate_new].frequency,
pas_freqs[pas_astate_new].index);
current_astate = pas_astate_new;
for_each_online_cpu(i)
set_astate(i, pas_astate_new);
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
mutex_unlock(&pas_switch_mutex);
ppc_proc_freq = freqs.new * 1000ul;
return 0;
}
static struct cpufreq_driver pas_cpufreq_driver = {
.name = "pas-cpufreq",
.owner = THIS_MODULE,
.flags = CPUFREQ_CONST_LOOPS,
.init = pas_cpufreq_cpu_init,
.exit = pas_cpufreq_cpu_exit,
.verify = pas_cpufreq_verify,
.target = pas_cpufreq_target,
.attr = pas_cpu_freqs_attr,
};
/*
* module init and destoy
*/
static int __init pas_cpufreq_init(void)
{
if (!of_machine_is_compatible("PA6T-1682M") &&
!of_machine_is_compatible("pasemi,pwrficient"))
return -ENODEV;
return cpufreq_register_driver(&pas_cpufreq_driver);
}
static void __exit pas_cpufreq_exit(void)
{
cpufreq_unregister_driver(&pas_cpufreq_driver);
}
module_init(pas_cpufreq_init);
module_exit(pas_cpufreq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>, Olof Johansson <olof@lixom.net>");
@@ -0,0 +1,634 @@
/*
* Copyright (C) 2006-2007 PA Semi, Inc
*
* Common functions for DMA access on PA Semi PWRficient
*
* 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/export.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/sched.h>
#include <asm/pasemi_dma.h>
#define MAX_TXCH 64
#define MAX_RXCH 64
#define MAX_FLAGS 64
#define MAX_FUN 8
static struct pasdma_status *dma_status;
static void __iomem *iob_regs;
static void __iomem *mac_regs[6];
static void __iomem *dma_regs;
static int base_hw_irq;
static int num_txch, num_rxch;
static struct pci_dev *dma_pdev;
/* Bitmaps to handle allocation of channels */
static DECLARE_BITMAP(txch_free, MAX_TXCH);
static DECLARE_BITMAP(rxch_free, MAX_RXCH);
static DECLARE_BITMAP(flags_free, MAX_FLAGS);
static DECLARE_BITMAP(fun_free, MAX_FUN);
/* pasemi_read_iob_reg - read IOB register
* @reg: Register to read (offset into PCI CFG space)
*/
unsigned int pasemi_read_iob_reg(unsigned int reg)
{
return in_le32(iob_regs+reg);
}
EXPORT_SYMBOL(pasemi_read_iob_reg);
/* pasemi_write_iob_reg - write IOB register
* @reg: Register to write to (offset into PCI CFG space)
* @val: Value to write
*/
void pasemi_write_iob_reg(unsigned int reg, unsigned int val)
{
out_le32(iob_regs+reg, val);
}
EXPORT_SYMBOL(pasemi_write_iob_reg);
/* pasemi_read_mac_reg - read MAC register
* @intf: MAC interface
* @reg: Register to read (offset into PCI CFG space)
*/
unsigned int pasemi_read_mac_reg(int intf, unsigned int reg)
{
return in_le32(mac_regs[intf]+reg);
}
EXPORT_SYMBOL(pasemi_read_mac_reg);
/* pasemi_write_mac_reg - write MAC register
* @intf: MAC interface
* @reg: Register to write to (offset into PCI CFG space)
* @val: Value to write
*/
void pasemi_write_mac_reg(int intf, unsigned int reg, unsigned int val)
{
out_le32(mac_regs[intf]+reg, val);
}
EXPORT_SYMBOL(pasemi_write_mac_reg);
/* pasemi_read_dma_reg - read DMA register
* @reg: Register to read (offset into PCI CFG space)
*/
unsigned int pasemi_read_dma_reg(unsigned int reg)
{
return in_le32(dma_regs+reg);
}
EXPORT_SYMBOL(pasemi_read_dma_reg);
/* pasemi_write_dma_reg - write DMA register
* @reg: Register to write to (offset into PCI CFG space)
* @val: Value to write
*/
void pasemi_write_dma_reg(unsigned int reg, unsigned int val)
{
out_le32(dma_regs+reg, val);
}
EXPORT_SYMBOL(pasemi_write_dma_reg);
static int pasemi_alloc_tx_chan(enum pasemi_dmachan_type type)
{
int bit;
int start, limit;
switch (type & (TXCHAN_EVT0|TXCHAN_EVT1)) {
case TXCHAN_EVT0:
start = 0;
limit = 10;
break;
case TXCHAN_EVT1:
start = 10;
limit = MAX_TXCH;
break;
default:
start = 0;
limit = MAX_TXCH;
break;
}
retry:
bit = find_next_bit(txch_free, MAX_TXCH, start);
if (bit >= limit)
return -ENOSPC;
if (!test_and_clear_bit(bit, txch_free))
goto retry;
return bit;
}
static void pasemi_free_tx_chan(int chan)
{
BUG_ON(test_bit(chan, txch_free));
set_bit(chan, txch_free);
}
static int pasemi_alloc_rx_chan(void)
{
int bit;
retry:
bit = find_first_bit(rxch_free, MAX_RXCH);
if (bit >= MAX_TXCH)
return -ENOSPC;
if (!test_and_clear_bit(bit, rxch_free))
goto retry;
return bit;
}
static void pasemi_free_rx_chan(int chan)
{
BUG_ON(test_bit(chan, rxch_free));
set_bit(chan, rxch_free);
}
/* pasemi_dma_alloc_chan - Allocate a DMA channel
* @type: Type of channel to allocate
* @total_size: Total size of structure to allocate (to allow for more
* room behind the structure to be used by the client)
* @offset: Offset in bytes from start of the total structure to the beginning
* of struct pasemi_dmachan. Needed when struct pasemi_dmachan is
* not the first member of the client structure.
*
* pasemi_dma_alloc_chan allocates a DMA channel for use by a client. The
* type argument specifies whether it's a RX or TX channel, and in the case
* of TX channels which group it needs to belong to (if any).
*
* Returns a pointer to the total structure allocated on success, NULL
* on failure.
*/
void *pasemi_dma_alloc_chan(enum pasemi_dmachan_type type,
int total_size, int offset)
{
void *buf;
struct pasemi_dmachan *chan;
int chno;
BUG_ON(total_size < sizeof(struct pasemi_dmachan));
buf = kzalloc(total_size, GFP_KERNEL);
if (!buf)
return NULL;
chan = buf + offset;
chan->priv = buf;
switch (type & (TXCHAN|RXCHAN)) {
case RXCHAN:
chno = pasemi_alloc_rx_chan();
chan->chno = chno;
chan->irq = irq_create_mapping(NULL,
base_hw_irq + num_txch + chno);
chan->status = &dma_status->rx_sta[chno];
break;
case TXCHAN:
chno = pasemi_alloc_tx_chan(type);
chan->chno = chno;
chan->irq = irq_create_mapping(NULL, base_hw_irq + chno);
chan->status = &dma_status->tx_sta[chno];
break;
}
chan->chan_type = type;
return chan;
}
EXPORT_SYMBOL(pasemi_dma_alloc_chan);
/* pasemi_dma_free_chan - Free a previously allocated channel
* @chan: Channel to free
*
* Frees a previously allocated channel. It will also deallocate any
* descriptor ring associated with the channel, if allocated.
*/
void pasemi_dma_free_chan(struct pasemi_dmachan *chan)
{
if (chan->ring_virt)
pasemi_dma_free_ring(chan);
switch (chan->chan_type & (RXCHAN|TXCHAN)) {
case RXCHAN:
pasemi_free_rx_chan(chan->chno);
break;
case TXCHAN:
pasemi_free_tx_chan(chan->chno);
break;
}
kfree(chan->priv);
}
EXPORT_SYMBOL(pasemi_dma_free_chan);
/* pasemi_dma_alloc_ring - Allocate descriptor ring for a channel
* @chan: Channel for which to allocate
* @ring_size: Ring size in 64-bit (8-byte) words
*
* Allocate a descriptor ring for a channel. Returns 0 on success, errno
* on failure. The passed in struct pasemi_dmachan is updated with the
* virtual and DMA addresses of the ring.
*/
int pasemi_dma_alloc_ring(struct pasemi_dmachan *chan, int ring_size)
{
BUG_ON(chan->ring_virt);
chan->ring_size = ring_size;
chan->ring_virt = dma_alloc_coherent(&dma_pdev->dev,
ring_size * sizeof(u64),
&chan->ring_dma, GFP_KERNEL);
if (!chan->ring_virt)
return -ENOMEM;
memset(chan->ring_virt, 0, ring_size * sizeof(u64));
return 0;
}
EXPORT_SYMBOL(pasemi_dma_alloc_ring);
/* pasemi_dma_free_ring - Free an allocated descriptor ring for a channel
* @chan: Channel for which to free the descriptor ring
*
* Frees a previously allocated descriptor ring for a channel.
*/
void pasemi_dma_free_ring(struct pasemi_dmachan *chan)
{
BUG_ON(!chan->ring_virt);
dma_free_coherent(&dma_pdev->dev, chan->ring_size * sizeof(u64),
chan->ring_virt, chan->ring_dma);
chan->ring_virt = NULL;
chan->ring_size = 0;
chan->ring_dma = 0;
}
EXPORT_SYMBOL(pasemi_dma_free_ring);
/* pasemi_dma_start_chan - Start a DMA channel
* @chan: Channel to start
* @cmdsta: Additional CCMDSTA/TCMDSTA bits to write
*
* Enables (starts) a DMA channel with optional additional arguments.
*/
void pasemi_dma_start_chan(const struct pasemi_dmachan *chan, const u32 cmdsta)
{
if (chan->chan_type == RXCHAN)
pasemi_write_dma_reg(PAS_DMA_RXCHAN_CCMDSTA(chan->chno),
cmdsta | PAS_DMA_RXCHAN_CCMDSTA_EN);
else
pasemi_write_dma_reg(PAS_DMA_TXCHAN_TCMDSTA(chan->chno),
cmdsta | PAS_DMA_TXCHAN_TCMDSTA_EN);
}
EXPORT_SYMBOL(pasemi_dma_start_chan);
/* pasemi_dma_stop_chan - Stop a DMA channel
* @chan: Channel to stop
*
* Stops (disables) a DMA channel. This is done by setting the ST bit in the
* CMDSTA register and waiting on the ACT (active) bit to clear, then
* finally disabling the whole channel.
*
* This function will only try for a short while for the channel to stop, if
* it doesn't it will return failure.
*
* Returns 1 on success, 0 on failure.
*/
#define MAX_RETRIES 5000
int pasemi_dma_stop_chan(const struct pasemi_dmachan *chan)
{
int reg, retries;
u32 sta;
if (chan->chan_type == RXCHAN) {
reg = PAS_DMA_RXCHAN_CCMDSTA(chan->chno);
pasemi_write_dma_reg(reg, PAS_DMA_RXCHAN_CCMDSTA_ST);
for (retries = 0; retries < MAX_RETRIES; retries++) {
sta = pasemi_read_dma_reg(reg);
if (!(sta & PAS_DMA_RXCHAN_CCMDSTA_ACT)) {
pasemi_write_dma_reg(reg, 0);
return 1;
}
cond_resched();
}
} else {
reg = PAS_DMA_TXCHAN_TCMDSTA(chan->chno);
pasemi_write_dma_reg(reg, PAS_DMA_TXCHAN_TCMDSTA_ST);
for (retries = 0; retries < MAX_RETRIES; retries++) {
sta = pasemi_read_dma_reg(reg);
if (!(sta & PAS_DMA_TXCHAN_TCMDSTA_ACT)) {
pasemi_write_dma_reg(reg, 0);
return 1;
}
cond_resched();
}
}
return 0;
}
EXPORT_SYMBOL(pasemi_dma_stop_chan);
/* pasemi_dma_alloc_buf - Allocate a buffer to use for DMA
* @chan: Channel to allocate for
* @size: Size of buffer in bytes
* @handle: DMA handle
*
* Allocate a buffer to be used by the DMA engine for read/write,
* similar to dma_alloc_coherent().
*
* Returns the virtual address of the buffer, or NULL in case of failure.
*/
void *pasemi_dma_alloc_buf(struct pasemi_dmachan *chan, int size,
dma_addr_t *handle)
{
return dma_alloc_coherent(&dma_pdev->dev, size, handle, GFP_KERNEL);
}
EXPORT_SYMBOL(pasemi_dma_alloc_buf);
/* pasemi_dma_free_buf - Free a buffer used for DMA
* @chan: Channel the buffer was allocated for
* @size: Size of buffer in bytes
* @handle: DMA handle
*
* Frees a previously allocated buffer.
*/
void pasemi_dma_free_buf(struct pasemi_dmachan *chan, int size,
dma_addr_t *handle)
{
dma_free_coherent(&dma_pdev->dev, size, handle, GFP_KERNEL);
}
EXPORT_SYMBOL(pasemi_dma_free_buf);
/* pasemi_dma_alloc_flag - Allocate a flag (event) for channel synchronization
*
* Allocates a flag for use with channel synchronization (event descriptors).
* Returns allocated flag (0-63), < 0 on error.
*/
int pasemi_dma_alloc_flag(void)
{
int bit;
retry:
bit = find_next_bit(flags_free, MAX_FLAGS, 0);
if (bit >= MAX_FLAGS)
return -ENOSPC;
if (!test_and_clear_bit(bit, flags_free))
goto retry;
return bit;
}
EXPORT_SYMBOL(pasemi_dma_alloc_flag);
/* pasemi_dma_free_flag - Deallocates a flag (event)
* @flag: Flag number to deallocate
*
* Frees up a flag so it can be reused for other purposes.
*/
void pasemi_dma_free_flag(int flag)
{
BUG_ON(test_bit(flag, flags_free));
BUG_ON(flag >= MAX_FLAGS);
set_bit(flag, flags_free);
}
EXPORT_SYMBOL(pasemi_dma_free_flag);
/* pasemi_dma_set_flag - Sets a flag (event) to 1
* @flag: Flag number to set active
*
* Sets the flag provided to 1.
*/
void pasemi_dma_set_flag(int flag)
{
BUG_ON(flag >= MAX_FLAGS);
if (flag < 32)
pasemi_write_dma_reg(PAS_DMA_TXF_SFLG0, 1 << flag);
else
pasemi_write_dma_reg(PAS_DMA_TXF_SFLG1, 1 << flag);
}
EXPORT_SYMBOL(pasemi_dma_set_flag);
/* pasemi_dma_clear_flag - Sets a flag (event) to 0
* @flag: Flag number to set inactive
*
* Sets the flag provided to 0.
*/
void pasemi_dma_clear_flag(int flag)
{
BUG_ON(flag >= MAX_FLAGS);
if (flag < 32)
pasemi_write_dma_reg(PAS_DMA_TXF_CFLG0, 1 << flag);
else
pasemi_write_dma_reg(PAS_DMA_TXF_CFLG1, 1 << flag);
}
EXPORT_SYMBOL(pasemi_dma_clear_flag);
/* pasemi_dma_alloc_fun - Allocate a function engine
*
* Allocates a function engine to use for crypto/checksum offload
* Returns allocated engine (0-8), < 0 on error.
*/
int pasemi_dma_alloc_fun(void)
{
int bit;
retry:
bit = find_next_bit(fun_free, MAX_FLAGS, 0);
if (bit >= MAX_FLAGS)
return -ENOSPC;
if (!test_and_clear_bit(bit, fun_free))
goto retry;
return bit;
}
EXPORT_SYMBOL(pasemi_dma_alloc_fun);
/* pasemi_dma_free_fun - Deallocates a function engine
* @flag: Engine number to deallocate
*
* Frees up a function engine so it can be used for other purposes.
*/
void pasemi_dma_free_fun(int fun)
{
BUG_ON(test_bit(fun, fun_free));
BUG_ON(fun >= MAX_FLAGS);
set_bit(fun, fun_free);
}
EXPORT_SYMBOL(pasemi_dma_free_fun);
static void *map_onedev(struct pci_dev *p, int index)
{
struct device_node *dn;
void __iomem *ret;
dn = pci_device_to_OF_node(p);
if (!dn)
goto fallback;
ret = of_iomap(dn, index);
if (!ret)
goto fallback;
return ret;
fallback:
/* This is hardcoded and ugly, but we have some firmware versions
* that don't provide the register space in the device tree. Luckily
* they are at well-known locations so we can just do the math here.
*/
return ioremap(0xe0000000 + (p->devfn << 12), 0x2000);
}
/* pasemi_dma_init - Initialize the PA Semi DMA library
*
* This function initializes the DMA library. It must be called before
* any other function in the library.
*
* Returns 0 on success, errno on failure.
*/
int pasemi_dma_init(void)
{
static DEFINE_SPINLOCK(init_lock);
struct pci_dev *iob_pdev;
struct pci_dev *pdev;
struct resource res;
struct device_node *dn;
int i, intf, err = 0;
unsigned long timeout;
u32 tmp;
if (!machine_is(pasemi))
return -ENODEV;
spin_lock(&init_lock);
/* Make sure we haven't already initialized */
if (dma_pdev)
goto out;
iob_pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa001, NULL);
if (!iob_pdev) {
BUG();
printk(KERN_WARNING "Can't find I/O Bridge\n");
err = -ENODEV;
goto out;
}
iob_regs = map_onedev(iob_pdev, 0);
dma_pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa007, NULL);
if (!dma_pdev) {
BUG();
printk(KERN_WARNING "Can't find DMA controller\n");
err = -ENODEV;
goto out;
}
dma_regs = map_onedev(dma_pdev, 0);
base_hw_irq = virq_to_hw(dma_pdev->irq);
pci_read_config_dword(dma_pdev, PAS_DMA_CAP_TXCH, &tmp);
num_txch = (tmp & PAS_DMA_CAP_TXCH_TCHN_M) >> PAS_DMA_CAP_TXCH_TCHN_S;
pci_read_config_dword(dma_pdev, PAS_DMA_CAP_RXCH, &tmp);
num_rxch = (tmp & PAS_DMA_CAP_RXCH_RCHN_M) >> PAS_DMA_CAP_RXCH_RCHN_S;
intf = 0;
for (pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa006, NULL);
pdev;
pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa006, pdev))
mac_regs[intf++] = map_onedev(pdev, 0);
pci_dev_put(pdev);
for (pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa005, NULL);
pdev;
pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa005, pdev))
mac_regs[intf++] = map_onedev(pdev, 0);
pci_dev_put(pdev);
dn = pci_device_to_OF_node(iob_pdev);
if (dn)
err = of_address_to_resource(dn, 1, &res);
if (!dn || err) {
/* Fallback for old firmware */
res.start = 0xfd800000;
res.end = res.start + 0x1000;
}
dma_status = __ioremap(res.start, resource_size(&res), 0);
pci_dev_put(iob_pdev);
for (i = 0; i < MAX_TXCH; i++)
__set_bit(i, txch_free);
for (i = 0; i < MAX_RXCH; i++)
__set_bit(i, rxch_free);
timeout = jiffies + HZ;
pasemi_write_dma_reg(PAS_DMA_COM_RXCMD, 0);
while (pasemi_read_dma_reg(PAS_DMA_COM_RXSTA) & 1) {
if (time_after(jiffies, timeout)) {
pr_warning("Warning: Could not disable RX section\n");
break;
}
}
timeout = jiffies + HZ;
pasemi_write_dma_reg(PAS_DMA_COM_TXCMD, 0);
while (pasemi_read_dma_reg(PAS_DMA_COM_TXSTA) & 1) {
if (time_after(jiffies, timeout)) {
pr_warning("Warning: Could not disable TX section\n");
break;
}
}
/* setup resource allocations for the different DMA sections */
tmp = pasemi_read_dma_reg(PAS_DMA_COM_CFG);
pasemi_write_dma_reg(PAS_DMA_COM_CFG, tmp | 0x18000000);
/* enable tx section */
pasemi_write_dma_reg(PAS_DMA_COM_TXCMD, PAS_DMA_COM_TXCMD_EN);
/* enable rx section */
pasemi_write_dma_reg(PAS_DMA_COM_RXCMD, PAS_DMA_COM_RXCMD_EN);
for (i = 0; i < MAX_FLAGS; i++)
__set_bit(i, flags_free);
for (i = 0; i < MAX_FUN; i++)
__set_bit(i, fun_free);
/* clear all status flags */
pasemi_write_dma_reg(PAS_DMA_TXF_CFLG0, 0xffffffff);
pasemi_write_dma_reg(PAS_DMA_TXF_CFLG1, 0xffffffff);
printk(KERN_INFO "PA Semi PWRficient DMA library initialized "
"(%d tx, %d rx channels)\n", num_txch, num_rxch);
out:
spin_unlock(&init_lock);
return err;
}
EXPORT_SYMBOL(pasemi_dma_init);
@@ -0,0 +1,342 @@
/*
* Copyright (C) 2006-2007 PA Semi, Inc
*
* Author: Olof Johansson, PA Semi
*
* Maintained by: Olof Johansson <olof@lixom.net>
*
* Based on drivers/net/fs_enet/mii-bitbang.c.
*
* 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/io.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/phy.h>
#include <linux/of_mdio.h>
#include <linux/of_platform.h>
#define DELAY 1
static void __iomem *gpio_regs;
struct gpio_priv {
int mdc_pin;
int mdio_pin;
int mdio_irqs[PHY_MAX_ADDR];
};
#define MDC_PIN(bus) (((struct gpio_priv *)bus->priv)->mdc_pin)
#define MDIO_PIN(bus) (((struct gpio_priv *)bus->priv)->mdio_pin)
static inline void mdio_lo(struct mii_bus *bus)
{
out_le32(gpio_regs+0x10, 1 << MDIO_PIN(bus));
}
static inline void mdio_hi(struct mii_bus *bus)
{
out_le32(gpio_regs, 1 << MDIO_PIN(bus));
}
static inline void mdc_lo(struct mii_bus *bus)
{
out_le32(gpio_regs+0x10, 1 << MDC_PIN(bus));
}
static inline void mdc_hi(struct mii_bus *bus)
{
out_le32(gpio_regs, 1 << MDC_PIN(bus));
}
static inline void mdio_active(struct mii_bus *bus)
{
out_le32(gpio_regs+0x20, (1 << MDC_PIN(bus)) | (1 << MDIO_PIN(bus)));
}
static inline void mdio_tristate(struct mii_bus *bus)
{
out_le32(gpio_regs+0x30, (1 << MDIO_PIN(bus)));
}
static inline int mdio_read(struct mii_bus *bus)
{
return !!(in_le32(gpio_regs+0x40) & (1 << MDIO_PIN(bus)));
}
static void clock_out(struct mii_bus *bus, int bit)
{
if (bit)
mdio_hi(bus);
else
mdio_lo(bus);
udelay(DELAY);
mdc_hi(bus);
udelay(DELAY);
mdc_lo(bus);
}
/* Utility to send the preamble, address, and register (common to read and write). */
static void bitbang_pre(struct mii_bus *bus, int read, u8 addr, u8 reg)
{
int i;
/* CFE uses a really long preamble (40 bits). We'll do the same. */
mdio_active(bus);
for (i = 0; i < 40; i++) {
clock_out(bus, 1);
}
/* send the start bit (01) and the read opcode (10) or write (10) */
clock_out(bus, 0);
clock_out(bus, 1);
clock_out(bus, read);
clock_out(bus, !read);
/* send the PHY address */
for (i = 0; i < 5; i++) {
clock_out(bus, (addr & 0x10) != 0);
addr <<= 1;
}
/* send the register address */
for (i = 0; i < 5; i++) {
clock_out(bus, (reg & 0x10) != 0);
reg <<= 1;
}
}
static int gpio_mdio_read(struct mii_bus *bus, int phy_id, int location)
{
u16 rdreg;
int ret, i;
u8 addr = phy_id & 0xff;
u8 reg = location & 0xff;
bitbang_pre(bus, 1, addr, reg);
/* tri-state our MDIO I/O pin so we can read */
mdio_tristate(bus);
udelay(DELAY);
mdc_hi(bus);
udelay(DELAY);
mdc_lo(bus);
/* read 16 bits of register data, MSB first */
rdreg = 0;
for (i = 0; i < 16; i++) {
mdc_lo(bus);
udelay(DELAY);
mdc_hi(bus);
udelay(DELAY);
mdc_lo(bus);
udelay(DELAY);
rdreg <<= 1;
rdreg |= mdio_read(bus);
}
mdc_hi(bus);
udelay(DELAY);
mdc_lo(bus);
udelay(DELAY);
ret = rdreg;
return ret;
}
static int gpio_mdio_write(struct mii_bus *bus, int phy_id, int location, u16 val)
{
int i;
u8 addr = phy_id & 0xff;
u8 reg = location & 0xff;
u16 value = val & 0xffff;
bitbang_pre(bus, 0, addr, reg);
/* send the turnaround (10) */
mdc_lo(bus);
mdio_hi(bus);
udelay(DELAY);
mdc_hi(bus);
udelay(DELAY);
mdc_lo(bus);
mdio_lo(bus);
udelay(DELAY);
mdc_hi(bus);
udelay(DELAY);
/* write 16 bits of register data, MSB first */
for (i = 0; i < 16; i++) {
mdc_lo(bus);
if (value & 0x8000)
mdio_hi(bus);
else
mdio_lo(bus);
udelay(DELAY);
mdc_hi(bus);
udelay(DELAY);
value <<= 1;
}
/*
* Tri-state the MDIO line.
*/
mdio_tristate(bus);
mdc_lo(bus);
udelay(DELAY);
mdc_hi(bus);
udelay(DELAY);
return 0;
}
static int gpio_mdio_reset(struct mii_bus *bus)
{
/*nothing here - dunno how to reset it*/
return 0;
}
static int __devinit gpio_mdio_probe(struct platform_device *ofdev)
{
struct device *dev = &ofdev->dev;
struct device_node *np = ofdev->dev.of_node;
struct mii_bus *new_bus;
struct gpio_priv *priv;
const unsigned int *prop;
int err;
err = -ENOMEM;
priv = kzalloc(sizeof(struct gpio_priv), GFP_KERNEL);
if (!priv)
goto out;
new_bus = mdiobus_alloc();
if (!new_bus)
goto out_free_priv;
new_bus->name = "pasemi gpio mdio bus";
new_bus->read = &gpio_mdio_read;
new_bus->write = &gpio_mdio_write;
new_bus->reset = &gpio_mdio_reset;
prop = of_get_property(np, "reg", NULL);
snprintf(new_bus->id, MII_BUS_ID_SIZE, "%x", *prop);
new_bus->priv = priv;
new_bus->irq = priv->mdio_irqs;
prop = of_get_property(np, "mdc-pin", NULL);
priv->mdc_pin = *prop;
prop = of_get_property(np, "mdio-pin", NULL);
priv->mdio_pin = *prop;
new_bus->parent = dev;
dev_set_drvdata(dev, new_bus);
err = of_mdiobus_register(new_bus, np);
if (err != 0) {
printk(KERN_ERR "%s: Cannot register as MDIO bus, err %d\n",
new_bus->name, err);
goto out_free_irq;
}
return 0;
out_free_irq:
kfree(new_bus);
out_free_priv:
kfree(priv);
out:
return err;
}
static int gpio_mdio_remove(struct platform_device *dev)
{
struct mii_bus *bus = dev_get_drvdata(&dev->dev);
mdiobus_unregister(bus);
dev_set_drvdata(&dev->dev, NULL);
kfree(bus->priv);
bus->priv = NULL;
mdiobus_free(bus);
return 0;
}
static struct of_device_id gpio_mdio_match[] =
{
{
.compatible = "gpio-mdio",
},
{},
};
MODULE_DEVICE_TABLE(of, gpio_mdio_match);
static struct platform_driver gpio_mdio_driver =
{
.probe = gpio_mdio_probe,
.remove = gpio_mdio_remove,
.driver = {
.name = "gpio-mdio-bitbang",
.owner = THIS_MODULE,
.of_match_table = gpio_mdio_match,
},
};
int gpio_mdio_init(void)
{
struct device_node *np;
np = of_find_compatible_node(NULL, NULL, "1682m-gpio");
if (!np)
np = of_find_compatible_node(NULL, NULL,
"pasemi,pwrficient-gpio");
if (!np)
return -ENODEV;
gpio_regs = of_iomap(np, 0);
of_node_put(np);
if (!gpio_regs)
return -ENODEV;
return platform_driver_register(&gpio_mdio_driver);
}
module_init(gpio_mdio_init);
void gpio_mdio_exit(void)
{
platform_driver_unregister(&gpio_mdio_driver);
if (gpio_regs)
iounmap(gpio_regs);
}
module_exit(gpio_mdio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Olof Johansson <olof@lixom.net>");
MODULE_DESCRIPTION("Driver for MDIO over GPIO on PA Semi PWRficient-based boards");
+102
View File
@@ -0,0 +1,102 @@
/*
* Copyright (C) 2006-2007 PA Semi, Inc
*
* Maintained by: Olof Johansson <olof@lixom.net>
*
* 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#undef DEBUG
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/machdep.h>
#include <asm/reg.h>
#include <asm/smp.h>
#include "pasemi.h"
struct sleep_mode {
char *name;
void (*entry)(void);
};
static struct sleep_mode modes[] = {
{ .name = "spin", .entry = &idle_spin },
{ .name = "doze", .entry = &idle_doze },
};
static int current_mode = 0;
static int pasemi_system_reset_exception(struct pt_regs *regs)
{
/* If we were woken up from power savings, we need to return
* to the calling function, since nip is not saved across
* all modes.
*/
if (regs->msr & SRR1_WAKEMASK)
regs->nip = regs->link;
switch (regs->msr & SRR1_WAKEMASK) {
case SRR1_WAKEEE:
do_IRQ(regs);
break;
case SRR1_WAKEDEC:
timer_interrupt(regs);
break;
default:
/* do system reset */
return 0;
}
/* Set higher astate since we come out of power savings at 0 */
restore_astate(hard_smp_processor_id());
/* everything handled */
regs->msr |= MSR_RI;
return 1;
}
static int __init pasemi_idle_init(void)
{
#ifndef CONFIG_PPC_PASEMI_CPUFREQ
printk(KERN_WARNING "No cpufreq driver, powersavings modes disabled\n");
current_mode = 0;
#endif
ppc_md.system_reset_exception = pasemi_system_reset_exception;
ppc_md.power_save = modes[current_mode].entry;
printk(KERN_INFO "Using PA6T idle loop (%s)\n", modes[current_mode].name);
return 0;
}
machine_late_initcall(pasemi, pasemi_idle_init);
static int __init idle_param(char *p)
{
int i;
for (i = 0; i < ARRAY_SIZE(modes); i++) {
if (!strcmp(modes[i].name, p)) {
current_mode = i;
break;
}
}
return 0;
}
early_param("idle", idle_param);
@@ -0,0 +1,264 @@
/*
* Copyright (C) 2005-2008, PA Semi, Inc
*
* Maintained by: Olof Johansson <olof@lixom.net>
*
* 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#undef DEBUG
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/pci.h>
#include <asm/iommu.h>
#include <asm/machdep.h>
#include <asm/abs_addr.h>
#include <asm/firmware.h>
#define IOBMAP_PAGE_SHIFT 12
#define IOBMAP_PAGE_SIZE (1 << IOBMAP_PAGE_SHIFT)
#define IOBMAP_PAGE_MASK (IOBMAP_PAGE_SIZE - 1)
#define IOB_BASE 0xe0000000
#define IOB_SIZE 0x3000
/* Configuration registers */
#define IOBCAP_REG 0x40
#define IOBCOM_REG 0x100
/* Enable IOB address translation */
#define IOBCOM_ATEN 0x00000100
/* Address decode configuration register */
#define IOB_AD_REG 0x14c
/* IOBCOM_AD_REG fields */
#define IOB_AD_VGPRT 0x00000e00
#define IOB_AD_VGAEN 0x00000100
/* Direct mapping settings */
#define IOB_AD_MPSEL_MASK 0x00000030
#define IOB_AD_MPSEL_B38 0x00000000
#define IOB_AD_MPSEL_B40 0x00000010
#define IOB_AD_MPSEL_B42 0x00000020
/* Translation window size / enable */
#define IOB_AD_TRNG_MASK 0x00000003
#define IOB_AD_TRNG_256M 0x00000000
#define IOB_AD_TRNG_2G 0x00000001
#define IOB_AD_TRNG_128G 0x00000003
#define IOB_TABLEBASE_REG 0x154
/* Base of the 64 4-byte L1 registers */
#define IOB_XLT_L1_REGBASE 0x2b00
/* Register to invalidate TLB entries */
#define IOB_AT_INVAL_TLB_REG 0x2d00
/* The top two bits of the level 1 entry contains valid and type flags */
#define IOBMAP_L1E_V 0x40000000
#define IOBMAP_L1E_V_B 0x80000000
/* For big page entries, the bottom two bits contains flags */
#define IOBMAP_L1E_BIG_CACHED 0x00000002
#define IOBMAP_L1E_BIG_PRIORITY 0x00000001
/* For regular level 2 entries, top 2 bits contain valid and cache flags */
#define IOBMAP_L2E_V 0x80000000
#define IOBMAP_L2E_V_CACHED 0xc0000000
static void __iomem *iob;
static u32 iob_l1_emptyval;
static u32 iob_l2_emptyval;
static u32 *iob_l2_base;
static struct iommu_table iommu_table_iobmap;
static int iommu_table_iobmap_inited;
static int iobmap_build(struct iommu_table *tbl, long index,
long npages, unsigned long uaddr,
enum dma_data_direction direction,
struct dma_attrs *attrs)
{
u32 *ip;
u32 rpn;
unsigned long bus_addr;
pr_debug("iobmap: build at: %lx, %lx, addr: %lx\n", index, npages, uaddr);
bus_addr = (tbl->it_offset + index) << IOBMAP_PAGE_SHIFT;
ip = ((u32 *)tbl->it_base) + index;
while (npages--) {
rpn = virt_to_abs(uaddr) >> IOBMAP_PAGE_SHIFT;
*(ip++) = IOBMAP_L2E_V | rpn;
/* invalidate tlb, can be optimized more */
out_le32(iob+IOB_AT_INVAL_TLB_REG, bus_addr >> 14);
uaddr += IOBMAP_PAGE_SIZE;
bus_addr += IOBMAP_PAGE_SIZE;
}
return 0;
}
static void iobmap_free(struct iommu_table *tbl, long index,
long npages)
{
u32 *ip;
unsigned long bus_addr;
pr_debug("iobmap: free at: %lx, %lx\n", index, npages);
bus_addr = (tbl->it_offset + index) << IOBMAP_PAGE_SHIFT;
ip = ((u32 *)tbl->it_base) + index;
while (npages--) {
*(ip++) = iob_l2_emptyval;
/* invalidate tlb, can be optimized more */
out_le32(iob+IOB_AT_INVAL_TLB_REG, bus_addr >> 14);
bus_addr += IOBMAP_PAGE_SIZE;
}
}
static void iommu_table_iobmap_setup(void)
{
pr_debug(" -> %s\n", __func__);
iommu_table_iobmap.it_busno = 0;
iommu_table_iobmap.it_offset = 0;
/* it_size is in number of entries */
iommu_table_iobmap.it_size = 0x80000000 >> IOBMAP_PAGE_SHIFT;
/* Initialize the common IOMMU code */
iommu_table_iobmap.it_base = (unsigned long)iob_l2_base;
iommu_table_iobmap.it_index = 0;
/* XXXOJN tune this to avoid IOB cache invals.
* Should probably be 8 (64 bytes)
*/
iommu_table_iobmap.it_blocksize = 4;
iommu_init_table(&iommu_table_iobmap, 0);
pr_debug(" <- %s\n", __func__);
}
static void pci_dma_bus_setup_pasemi(struct pci_bus *bus)
{
pr_debug("pci_dma_bus_setup, bus %p, bus->self %p\n", bus, bus->self);
if (!iommu_table_iobmap_inited) {
iommu_table_iobmap_inited = 1;
iommu_table_iobmap_setup();
}
}
static void pci_dma_dev_setup_pasemi(struct pci_dev *dev)
{
pr_debug("pci_dma_dev_setup, dev %p (%s)\n", dev, pci_name(dev));
#if !defined(CONFIG_PPC_PASEMI_IOMMU_DMA_FORCE)
/* For non-LPAR environment, don't translate anything for the DMA
* engine. The exception to this is if the user has enabled
* CONFIG_PPC_PASEMI_IOMMU_DMA_FORCE at build time.
*/
if (dev->vendor == 0x1959 && dev->device == 0xa007 &&
!firmware_has_feature(FW_FEATURE_LPAR)) {
dev->dev.archdata.dma_ops = &dma_direct_ops;
return;
}
#endif
set_iommu_table_base(&dev->dev, &iommu_table_iobmap);
}
int __init iob_init(struct device_node *dn)
{
unsigned long tmp;
u32 regword;
int i;
pr_debug(" -> %s\n", __func__);
/* Allocate a spare page to map all invalid IOTLB pages. */
tmp = memblock_alloc(IOBMAP_PAGE_SIZE, IOBMAP_PAGE_SIZE);
if (!tmp)
panic("IOBMAP: Cannot allocate spare page!");
/* Empty l1 is marked invalid */
iob_l1_emptyval = 0;
/* Empty l2 is mapped to dummy page */
iob_l2_emptyval = IOBMAP_L2E_V | (tmp >> IOBMAP_PAGE_SHIFT);
iob = ioremap(IOB_BASE, IOB_SIZE);
if (!iob)
panic("IOBMAP: Cannot map registers!");
/* setup direct mapping of the L1 entries */
for (i = 0; i < 64; i++) {
/* Each L1 covers 32MB, i.e. 8K entries = 32K of ram */
regword = IOBMAP_L1E_V | (__pa(iob_l2_base + i*0x2000) >> 12);
out_le32(iob+IOB_XLT_L1_REGBASE+i*4, regword);
}
/* set 2GB translation window, based at 0 */
regword = in_le32(iob+IOB_AD_REG);
regword &= ~IOB_AD_TRNG_MASK;
regword |= IOB_AD_TRNG_2G;
out_le32(iob+IOB_AD_REG, regword);
/* Enable translation */
regword = in_le32(iob+IOBCOM_REG);
regword |= IOBCOM_ATEN;
out_le32(iob+IOBCOM_REG, regword);
pr_debug(" <- %s\n", __func__);
return 0;
}
/* These are called very early. */
void __init iommu_init_early_pasemi(void)
{
int iommu_off;
#ifndef CONFIG_PPC_PASEMI_IOMMU
iommu_off = 1;
#else
iommu_off = of_chosen &&
of_get_property(of_chosen, "linux,iommu-off", NULL);
#endif
if (iommu_off)
return;
iob_init(NULL);
ppc_md.pci_dma_dev_setup = pci_dma_dev_setup_pasemi;
ppc_md.pci_dma_bus_setup = pci_dma_bus_setup_pasemi;
ppc_md.tce_build = iobmap_build;
ppc_md.tce_free = iobmap_free;
set_pci_dma_ops(&dma_iommu_ops);
}
void __init alloc_iobmap_l2(void)
{
#ifndef CONFIG_PPC_PASEMI_IOMMU
return;
#endif
/* For 2G space, 8x64 pages (2^21 bytes) is max total l2 size */
iob_l2_base = (u32 *)abs_to_virt(memblock_alloc_base(1UL<<21, 1UL<<21, 0x80000000));
printk(KERN_INFO "IOBMAP L2 allocated at: %p\n", iob_l2_base);
}
@@ -0,0 +1,94 @@
/*
* Copyright (C) 2007 PA Semi, Inc
*
* Parts based on arch/powerpc/sysdev/fsl_soc.c:
*
* 2006 (c) MontaVista Software, 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.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/of.h>
#include <linux/i2c.h>
#ifdef CONFIG_I2C_BOARDINFO
/* The below is from fsl_soc.c. It's copied because since there are no
* official bus bindings at this time it doesn't make sense to share across
* the platforms, even though they happen to be common.
*/
struct i2c_driver_device {
char *of_device;
char *i2c_type;
};
static struct i2c_driver_device i2c_devices[] __initdata = {
{"dallas,ds1338", "ds1338"},
};
static int __init find_i2c_driver(struct device_node *node,
struct i2c_board_info *info)
{
int i;
for (i = 0; i < ARRAY_SIZE(i2c_devices); i++) {
if (!of_device_is_compatible(node, i2c_devices[i].of_device))
continue;
if (strlcpy(info->type, i2c_devices[i].i2c_type,
I2C_NAME_SIZE) >= I2C_NAME_SIZE)
return -ENOMEM;
return 0;
}
return -ENODEV;
}
static int __init pasemi_register_i2c_devices(void)
{
struct pci_dev *pdev;
struct device_node *adap_node;
struct device_node *node;
pdev = NULL;
while ((pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa003, pdev))) {
adap_node = pci_device_to_OF_node(pdev);
if (!adap_node)
continue;
node = NULL;
while ((node = of_get_next_child(adap_node, node))) {
struct i2c_board_info info = {};
const u32 *addr;
int len;
addr = of_get_property(node, "reg", &len);
if (!addr || len < sizeof(int) ||
*addr > (1 << 10) - 1) {
printk(KERN_WARNING
"pasemi_register_i2c_devices: "
"invalid i2c device entry\n");
continue;
}
info.irq = irq_of_parse_and_map(node, 0);
if (info.irq == NO_IRQ)
info.irq = -1;
if (find_i2c_driver(node, &info) < 0)
continue;
info.addr = *addr;
i2c_register_board_info(PCI_FUNC(pdev->devfn), &info,
1);
}
}
return 0;
}
device_initcall(pasemi_register_i2c_devices);
#endif
@@ -0,0 +1,34 @@
#ifndef _PASEMI_PASEMI_H
#define _PASEMI_PASEMI_H
extern unsigned long pas_get_boot_time(void);
extern void pas_pci_init(void);
extern void __devinit pas_pci_irq_fixup(struct pci_dev *dev);
extern void __devinit pas_pci_dma_dev_setup(struct pci_dev *dev);
extern void __iomem *pasemi_pci_getcfgaddr(struct pci_dev *dev, int offset);
extern void __init alloc_iobmap_l2(void);
extern void __init pasemi_map_registers(void);
/* Power savings modes, implemented in asm */
extern void idle_spin(void);
extern void idle_doze(void);
/* Restore astate to last set */
#ifdef CONFIG_PPC_PASEMI_CPUFREQ
extern int check_astate(void);
extern void restore_astate(int cpu);
#else
static inline int check_astate(void)
{
/* Always return >0 so we never power save */
return 1;
}
static inline void restore_astate(int cpu)
{
}
#endif
#endif /* _PASEMI_PASEMI_H */
+241
View File
@@ -0,0 +1,241 @@
/*
* Copyright (C) 2006 PA Semi, Inc
*
* Authors: Kip Walker, PA Semi
* Olof Johansson, PA Semi
*
* Maintained by: Olof Johansson <olof@lixom.net>
*
* Based on arch/powerpc/platforms/maple/pci.c
*
* 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/kernel.h>
#include <linux/pci.h>
#include <asm/pci-bridge.h>
#include <asm/machdep.h>
#include <asm/ppc-pci.h>
#define PA_PXP_CFA(bus, devfn, off) (((bus) << 20) | ((devfn) << 12) | (off))
static inline int pa_pxp_offset_valid(u8 bus, u8 devfn, int offset)
{
/* Device 0 Function 0 is special: It's config space spans function 1 as
* well, so allow larger offset. It's really a two-function device but the
* second function does not probe.
*/
if (bus == 0 && devfn == 0)
return offset < 8192;
else
return offset < 4096;
}
static void volatile __iomem *pa_pxp_cfg_addr(struct pci_controller *hose,
u8 bus, u8 devfn, int offset)
{
return hose->cfg_data + PA_PXP_CFA(bus, devfn, offset);
}
static inline int is_root_port(int busno, int devfn)
{
return ((busno == 0) && (PCI_FUNC(devfn) < 4) &&
((PCI_SLOT(devfn) == 16) || (PCI_SLOT(devfn) == 17)));
}
static inline int is_5945_reg(int reg)
{
return (((reg >= 0x18) && (reg < 0x34)) ||
((reg >= 0x158) && (reg < 0x178)));
}
static int workaround_5945(struct pci_bus *bus, unsigned int devfn,
int offset, int len, u32 *val)
{
struct pci_controller *hose;
void volatile __iomem *addr, *dummy;
int byte;
u32 tmp;
if (!is_root_port(bus->number, devfn) || !is_5945_reg(offset))
return 0;
hose = pci_bus_to_host(bus);
addr = pa_pxp_cfg_addr(hose, bus->number, devfn, offset & ~0x3);
byte = offset & 0x3;
/* Workaround bug 5945: write 0 to a dummy register before reading,
* and write back what we read. We must read/write the full 32-bit
* contents so we need to shift and mask by hand.
*/
dummy = pa_pxp_cfg_addr(hose, bus->number, devfn, 0x10);
out_le32(dummy, 0);
tmp = in_le32(addr);
out_le32(addr, tmp);
switch (len) {
case 1:
*val = (tmp >> (8*byte)) & 0xff;
break;
case 2:
if (byte == 0)
*val = tmp & 0xffff;
else
*val = (tmp >> 16) & 0xffff;
break;
default:
*val = tmp;
break;
}
return 1;
}
static int pa_pxp_read_config(struct pci_bus *bus, unsigned int devfn,
int offset, int len, u32 *val)
{
struct pci_controller *hose;
void volatile __iomem *addr;
hose = pci_bus_to_host(bus);
if (!hose)
return PCIBIOS_DEVICE_NOT_FOUND;
if (!pa_pxp_offset_valid(bus->number, devfn, offset))
return PCIBIOS_BAD_REGISTER_NUMBER;
if (workaround_5945(bus, devfn, offset, len, val))
return PCIBIOS_SUCCESSFUL;
addr = pa_pxp_cfg_addr(hose, bus->number, devfn, offset);
/*
* Note: the caller has already checked that offset is
* suitably aligned and that len is 1, 2 or 4.
*/
switch (len) {
case 1:
*val = in_8(addr);
break;
case 2:
*val = in_le16(addr);
break;
default:
*val = in_le32(addr);
break;
}
return PCIBIOS_SUCCESSFUL;
}
static int pa_pxp_write_config(struct pci_bus *bus, unsigned int devfn,
int offset, int len, u32 val)
{
struct pci_controller *hose;
void volatile __iomem *addr;
hose = pci_bus_to_host(bus);
if (!hose)
return PCIBIOS_DEVICE_NOT_FOUND;
if (!pa_pxp_offset_valid(bus->number, devfn, offset))
return PCIBIOS_BAD_REGISTER_NUMBER;
addr = pa_pxp_cfg_addr(hose, bus->number, devfn, offset);
/*
* Note: the caller has already checked that offset is
* suitably aligned and that len is 1, 2 or 4.
*/
switch (len) {
case 1:
out_8(addr, val);
break;
case 2:
out_le16(addr, val);
break;
default:
out_le32(addr, val);
break;
}
return PCIBIOS_SUCCESSFUL;
}
static struct pci_ops pa_pxp_ops = {
.read = pa_pxp_read_config,
.write = pa_pxp_write_config,
};
static void __init setup_pa_pxp(struct pci_controller *hose)
{
hose->ops = &pa_pxp_ops;
hose->cfg_data = ioremap(0xe0000000, 0x10000000);
}
static int __init pas_add_bridge(struct device_node *dev)
{
struct pci_controller *hose;
pr_debug("Adding PCI host bridge %s\n", dev->full_name);
hose = pcibios_alloc_controller(dev);
if (!hose)
return -ENOMEM;
hose->first_busno = 0;
hose->last_busno = 0xff;
setup_pa_pxp(hose);
printk(KERN_INFO "Found PA-PXP PCI host bridge.\n");
/* Interpret the "ranges" property */
pci_process_bridge_OF_ranges(hose, dev, 1);
return 0;
}
void __init pas_pci_init(void)
{
struct device_node *np, *root;
root = of_find_node_by_path("/");
if (!root) {
printk(KERN_CRIT "pas_pci_init: can't find root "
"of device tree\n");
return;
}
for (np = NULL; (np = of_get_next_child(root, np)) != NULL;)
if (np->name && !strcmp(np->name, "pxp") && !pas_add_bridge(np))
of_node_get(np);
of_node_put(root);
/* Setup the linkage between OF nodes and PHBs */
pci_devs_phb_init();
}
void __iomem *pasemi_pci_getcfgaddr(struct pci_dev *dev, int offset)
{
struct pci_controller *hose;
hose = pci_bus_to_host(dev->bus);
return (void __iomem *)pa_pxp_cfg_addr(hose, dev->bus->number, dev->devfn, offset);
}
@@ -0,0 +1,89 @@
/*
* Copyright (C) 2006-2007 PA Semi, Inc
*
* Maintained by: Olof Johansson <olof@lixom.net>
*
* 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <asm/processor.h>
#include <asm/page.h>
#include <asm/ppc_asm.h>
#include <asm/cputable.h>
#include <asm/cache.h>
#include <asm/thread_info.h>
#include <asm/asm-offsets.h>
/* Power savings opcodes since not all binutils have them at this time */
#define DOZE .long 0x4c000324
#define NAP .long 0x4c000364
#define SLEEP .long 0x4c0003a4
#define RVW .long 0x4c0003e4
/* Common sequence to do before going to any of the
* powersavings modes.
*/
#define PRE_SLEEP_SEQUENCE \
std r3,8(r1); \
ptesync ; \
ld r3,8(r1); \
1: cmpd r3,r3; \
bne 1b
_doze:
PRE_SLEEP_SEQUENCE
DOZE
b .
_GLOBAL(idle_spin)
blr
_GLOBAL(idle_doze)
LOAD_REG_ADDR(r3, _doze)
b sleep_common
/* Add more modes here later */
sleep_common:
mflr r0
std r0, 16(r1)
stdu r1,-64(r1)
#ifdef CONFIG_PPC_PASEMI_CPUFREQ
std r3, 48(r1)
/* Only do power savings when in astate 0 */
bl .check_astate
cmpwi r3,0
bne 1f
ld r3, 48(r1)
#endif
LOAD_REG_IMMEDIATE(r6,MSR_DR|MSR_IR|MSR_ME|MSR_EE)
mfmsr r4
andc r5,r4,r6
mtmsrd r5,0
mtctr r3
bctrl
mtmsrd r4,0
1: addi r1,r1,64
ld r0,16(r1)
mtlr r0
blr
@@ -0,0 +1,448 @@
/*
* Copyright (C) 2006-2007 PA Semi, Inc
*
* Authors: Kip Walker, PA Semi
* Olof Johansson, PA Semi
*
* Maintained by: Olof Johansson <olof@lixom.net>
*
* Based on arch/powerpc/platforms/maple/setup.c
*
* 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/console.h>
#include <linux/export.h>
#include <linux/pci.h>
#include <linux/of_platform.h>
#include <linux/gfp.h>
#include <asm/prom.h>
#include <asm/iommu.h>
#include <asm/machdep.h>
#include <asm/mpic.h>
#include <asm/smp.h>
#include <asm/time.h>
#include <asm/mmu.h>
#include <asm/debug.h>
#include <pcmcia/ss.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/ds.h>
#include "pasemi.h"
/* SDC reset register, must be pre-mapped at reset time */
static void __iomem *reset_reg;
/* Various error status registers, must be pre-mapped at MCE time */
#define MAX_MCE_REGS 32
struct mce_regs {
char *name;
void __iomem *addr;
};
static struct mce_regs mce_regs[MAX_MCE_REGS];
static int num_mce_regs;
static int nmi_virq = NO_IRQ;
static void pas_restart(char *cmd)
{
/* Need to put others cpu in hold loop so they're not sleeping */
smp_send_stop();
udelay(10000);
printk("Restarting...\n");
while (1)
out_le32(reset_reg, 0x6000000);
}
#ifdef CONFIG_SMP
static arch_spinlock_t timebase_lock;
static unsigned long timebase;
static void __devinit pas_give_timebase(void)
{
unsigned long flags;
local_irq_save(flags);
hard_irq_disable();
arch_spin_lock(&timebase_lock);
mtspr(SPRN_TBCTL, TBCTL_FREEZE);
isync();
timebase = get_tb();
arch_spin_unlock(&timebase_lock);
while (timebase)
barrier();
mtspr(SPRN_TBCTL, TBCTL_RESTART);
local_irq_restore(flags);
}
static void __devinit pas_take_timebase(void)
{
while (!timebase)
smp_rmb();
arch_spin_lock(&timebase_lock);
set_tb(timebase >> 32, timebase & 0xffffffff);
timebase = 0;
arch_spin_unlock(&timebase_lock);
}
struct smp_ops_t pas_smp_ops = {
.probe = smp_mpic_probe,
.message_pass = smp_mpic_message_pass,
.kick_cpu = smp_generic_kick_cpu,
.setup_cpu = smp_mpic_setup_cpu,
.give_timebase = pas_give_timebase,
.take_timebase = pas_take_timebase,
};
#endif /* CONFIG_SMP */
void __init pas_setup_arch(void)
{
#ifdef CONFIG_SMP
/* Setup SMP callback */
smp_ops = &pas_smp_ops;
#endif
/* Lookup PCI hosts */
pas_pci_init();
#ifdef CONFIG_DUMMY_CONSOLE
conswitchp = &dummy_con;
#endif
/* Remap SDC register for doing reset */
/* XXXOJN This should maybe come out of the device tree */
reset_reg = ioremap(0xfc101100, 4);
}
static int __init pas_setup_mce_regs(void)
{
struct pci_dev *dev;
int reg;
/* Remap various SoC status registers for use by the MCE handler */
reg = 0;
dev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa00a, NULL);
while (dev && reg < MAX_MCE_REGS) {
mce_regs[reg].name = kasprintf(GFP_KERNEL,
"mc%d_mcdebug_errsta", reg);
mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x730);
dev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa00a, dev);
reg++;
}
dev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa001, NULL);
if (dev && reg+4 < MAX_MCE_REGS) {
mce_regs[reg].name = "iobdbg_IntStatus1";
mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x438);
reg++;
mce_regs[reg].name = "iobdbg_IOCTbusIntDbgReg";
mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x454);
reg++;
mce_regs[reg].name = "iobiom_IntStatus";
mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0xc10);
reg++;
mce_regs[reg].name = "iobiom_IntDbgReg";
mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0xc1c);
reg++;
}
dev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa009, NULL);
if (dev && reg+2 < MAX_MCE_REGS) {
mce_regs[reg].name = "l2csts_IntStatus";
mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x200);
reg++;
mce_regs[reg].name = "l2csts_Cnt";
mce_regs[reg].addr = pasemi_pci_getcfgaddr(dev, 0x214);
reg++;
}
num_mce_regs = reg;
return 0;
}
machine_device_initcall(pasemi, pas_setup_mce_regs);
static __init void pas_init_IRQ(void)
{
struct device_node *np;
struct device_node *root, *mpic_node;
unsigned long openpic_addr;
const unsigned int *opprop;
int naddr, opplen;
int mpic_flags;
const unsigned int *nmiprop;
struct mpic *mpic;
mpic_node = NULL;
for_each_node_by_type(np, "interrupt-controller")
if (of_device_is_compatible(np, "open-pic")) {
mpic_node = np;
break;
}
if (!mpic_node)
for_each_node_by_type(np, "open-pic") {
mpic_node = np;
break;
}
if (!mpic_node) {
printk(KERN_ERR
"Failed to locate the MPIC interrupt controller\n");
return;
}
/* Find address list in /platform-open-pic */
root = of_find_node_by_path("/");
naddr = of_n_addr_cells(root);
opprop = of_get_property(root, "platform-open-pic", &opplen);
if (!opprop) {
printk(KERN_ERR "No platform-open-pic property.\n");
of_node_put(root);
return;
}
openpic_addr = of_read_number(opprop, naddr);
printk(KERN_DEBUG "OpenPIC addr: %lx\n", openpic_addr);
mpic_flags = MPIC_LARGE_VECTORS | MPIC_NO_BIAS | MPIC_NO_RESET;
nmiprop = of_get_property(mpic_node, "nmi-source", NULL);
if (nmiprop)
mpic_flags |= MPIC_ENABLE_MCK;
mpic = mpic_alloc(mpic_node, openpic_addr,
mpic_flags, 0, 0, "PASEMI-OPIC");
BUG_ON(!mpic);
mpic_assign_isu(mpic, 0, mpic->paddr + 0x10000);
mpic_init(mpic);
/* The NMI/MCK source needs to be prio 15 */
if (nmiprop) {
nmi_virq = irq_create_mapping(NULL, *nmiprop);
mpic_irq_set_priority(nmi_virq, 15);
irq_set_irq_type(nmi_virq, IRQ_TYPE_EDGE_RISING);
mpic_unmask_irq(irq_get_irq_data(nmi_virq));
}
of_node_put(mpic_node);
of_node_put(root);
}
static void __init pas_progress(char *s, unsigned short hex)
{
printk("[%04x] : %s\n", hex, s ? s : "");
}
static int pas_machine_check_handler(struct pt_regs *regs)
{
int cpu = smp_processor_id();
unsigned long srr0, srr1, dsisr;
int dump_slb = 0;
int i;
srr0 = regs->nip;
srr1 = regs->msr;
if (nmi_virq != NO_IRQ && mpic_get_mcirq() == nmi_virq) {
printk(KERN_ERR "NMI delivered\n");
debugger(regs);
mpic_end_irq(irq_get_irq_data(nmi_virq));
goto out;
}
dsisr = mfspr(SPRN_DSISR);
printk(KERN_ERR "Machine Check on CPU %d\n", cpu);
printk(KERN_ERR "SRR0 0x%016lx SRR1 0x%016lx\n", srr0, srr1);
printk(KERN_ERR "DSISR 0x%016lx DAR 0x%016lx\n", dsisr, regs->dar);
printk(KERN_ERR "BER 0x%016lx MER 0x%016lx\n", mfspr(SPRN_PA6T_BER),
mfspr(SPRN_PA6T_MER));
printk(KERN_ERR "IER 0x%016lx DER 0x%016lx\n", mfspr(SPRN_PA6T_IER),
mfspr(SPRN_PA6T_DER));
printk(KERN_ERR "Cause:\n");
if (srr1 & 0x200000)
printk(KERN_ERR "Signalled by SDC\n");
if (srr1 & 0x100000) {
printk(KERN_ERR "Load/Store detected error:\n");
if (dsisr & 0x8000)
printk(KERN_ERR "D-cache ECC double-bit error or bus error\n");
if (dsisr & 0x4000)
printk(KERN_ERR "LSU snoop response error\n");
if (dsisr & 0x2000) {
printk(KERN_ERR "MMU SLB multi-hit or invalid B field\n");
dump_slb = 1;
}
if (dsisr & 0x1000)
printk(KERN_ERR "Recoverable Duptags\n");
if (dsisr & 0x800)
printk(KERN_ERR "Recoverable D-cache parity error count overflow\n");
if (dsisr & 0x400)
printk(KERN_ERR "TLB parity error count overflow\n");
}
if (srr1 & 0x80000)
printk(KERN_ERR "Bus Error\n");
if (srr1 & 0x40000) {
printk(KERN_ERR "I-side SLB multiple hit\n");
dump_slb = 1;
}
if (srr1 & 0x20000)
printk(KERN_ERR "I-cache parity error hit\n");
if (num_mce_regs == 0)
printk(KERN_ERR "No MCE registers mapped yet, can't dump\n");
else
printk(KERN_ERR "SoC debug registers:\n");
for (i = 0; i < num_mce_regs; i++)
printk(KERN_ERR "%s: 0x%08x\n", mce_regs[i].name,
in_le32(mce_regs[i].addr));
if (dump_slb) {
unsigned long e, v;
int i;
printk(KERN_ERR "slb contents:\n");
for (i = 0; i < mmu_slb_size; i++) {
asm volatile("slbmfee %0,%1" : "=r" (e) : "r" (i));
asm volatile("slbmfev %0,%1" : "=r" (v) : "r" (i));
printk(KERN_ERR "%02d %016lx %016lx\n", i, e, v);
}
}
out:
/* SRR1[62] is from MSR[62] if recoverable, so pass that back */
return !!(srr1 & 0x2);
}
static void __init pas_init_early(void)
{
iommu_init_early_pasemi();
}
#ifdef CONFIG_PCMCIA
static int pcmcia_notify(struct notifier_block *nb, unsigned long action,
void *data)
{
struct device *dev = data;
struct device *parent;
struct pcmcia_device *pdev = to_pcmcia_dev(dev);
/* We are only intereted in device addition */
if (action != BUS_NOTIFY_ADD_DEVICE)
return 0;
parent = pdev->socket->dev.parent;
/* We know electra_cf devices will always have of_node set, since
* electra_cf is an of_platform driver.
*/
if (!parent->of_node)
return 0;
if (!of_device_is_compatible(parent->of_node, "electra-cf"))
return 0;
/* We use the direct ops for localbus */
dev->archdata.dma_ops = &dma_direct_ops;
return 0;
}
static struct notifier_block pcmcia_notifier = {
.notifier_call = pcmcia_notify,
};
static inline void pasemi_pcmcia_init(void)
{
extern struct bus_type pcmcia_bus_type;
bus_register_notifier(&pcmcia_bus_type, &pcmcia_notifier);
}
#else
static inline void pasemi_pcmcia_init(void)
{
}
#endif
static struct of_device_id pasemi_bus_ids[] = {
/* Unfortunately needed for legacy firmwares */
{ .type = "localbus", },
{ .type = "sdc", },
/* These are the proper entries, which newer firmware uses */
{ .compatible = "pasemi,localbus", },
{ .compatible = "pasemi,sdc", },
{},
};
static int __init pasemi_publish_devices(void)
{
pasemi_pcmcia_init();
/* Publish OF platform devices for SDC and other non-PCI devices */
of_platform_bus_probe(NULL, pasemi_bus_ids, NULL);
return 0;
}
machine_device_initcall(pasemi, pasemi_publish_devices);
/*
* Called very early, MMU is off, device-tree isn't unflattened
*/
static int __init pas_probe(void)
{
unsigned long root = of_get_flat_dt_root();
if (!of_flat_dt_is_compatible(root, "PA6T-1682M") &&
!of_flat_dt_is_compatible(root, "pasemi,pwrficient"))
return 0;
hpte_init_native();
alloc_iobmap_l2();
return 1;
}
define_machine(pasemi) {
.name = "PA Semi PWRficient",
.probe = pas_probe,
.setup_arch = pas_setup_arch,
.init_early = pas_init_early,
.init_IRQ = pas_init_IRQ,
.get_irq = mpic_get_irq,
.restart = pas_restart,
.get_boot_time = pas_get_boot_time,
.calibrate_decr = generic_calibrate_decr,
.progress = pas_progress,
.machine_check_exception = pas_machine_check_handler,
};
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2006 PA Semi, Inc
*
* Maintained by: Olof Johansson <olof@lixom.net>
*
* 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/time.h>
#include <asm/time.h>
unsigned long __init pas_get_boot_time(void)
{
/* Let's just return a fake date right now */
return mktime(2006, 1, 1, 12, 0, 0);
}