forked from Ivasoft/openwrt
oxnas: kill old oxnas target
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,297 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Broadcom
|
||||
* Copyright (C) 2012 Stephen Warren
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/stringify.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/io.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <mach/utils.h>
|
||||
|
||||
#define MHZ (1000 * 1000)
|
||||
|
||||
struct clk_oxnas_pllb {
|
||||
struct clk_hw hw;
|
||||
struct device_node *devnode;
|
||||
struct reset_control *rstc;
|
||||
};
|
||||
|
||||
#define to_clk_oxnas_pllb(_hw) container_of(_hw, struct clk_oxnas_pllb, hw)
|
||||
|
||||
static unsigned long plla_clk_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
unsigned long fin = parent_rate;
|
||||
unsigned long pll0;
|
||||
unsigned long fbdiv, refdiv, outdiv;
|
||||
|
||||
pll0 = readl_relaxed(SYS_CTRL_PLLA_CTRL0);
|
||||
refdiv = (pll0 >> PLLA_REFDIV_SHIFT) & PLLA_REFDIV_MASK;
|
||||
refdiv += 1;
|
||||
outdiv = (pll0 >> PLLA_OUTDIV_SHIFT) & PLLA_OUTDIV_MASK;
|
||||
outdiv += 1;
|
||||
fbdiv = readl_relaxed(SYS_CTRL_PLLA_CTRL1);
|
||||
|
||||
/* seems we will not be here when pll is bypassed, so ignore this
|
||||
* case */
|
||||
|
||||
return fin / MHZ * fbdiv / (refdiv * outdiv) / 32768 * MHZ;
|
||||
}
|
||||
|
||||
static const char *pll_clk_parents[] = {
|
||||
"oscillator",
|
||||
};
|
||||
|
||||
static struct clk_ops plla_ops = {
|
||||
.recalc_rate = plla_clk_recalc_rate,
|
||||
};
|
||||
|
||||
static struct clk_init_data clk_plla_init = {
|
||||
.name = "plla",
|
||||
.ops = &plla_ops,
|
||||
.parent_names = pll_clk_parents,
|
||||
.num_parents = ARRAY_SIZE(pll_clk_parents),
|
||||
};
|
||||
|
||||
static struct clk_hw plla_hw = {
|
||||
.init = &clk_plla_init,
|
||||
};
|
||||
|
||||
static int pllb_clk_is_prepared(struct clk_hw *hw)
|
||||
{
|
||||
struct clk_oxnas_pllb *pllb = to_clk_oxnas_pllb(hw);
|
||||
|
||||
return !!pllb->rstc;
|
||||
}
|
||||
|
||||
static int pllb_clk_prepare(struct clk_hw *hw)
|
||||
{
|
||||
struct clk_oxnas_pllb *pllb = to_clk_oxnas_pllb(hw);
|
||||
|
||||
pllb->rstc = of_reset_control_get(pllb->devnode, NULL);
|
||||
|
||||
return IS_ERR(pllb->rstc) ? PTR_ERR(pllb->rstc) : 0;
|
||||
}
|
||||
|
||||
static void pllb_clk_unprepare(struct clk_hw *hw)
|
||||
{
|
||||
struct clk_oxnas_pllb *pllb = to_clk_oxnas_pllb(hw);
|
||||
|
||||
BUG_ON(IS_ERR(pllb->rstc));
|
||||
|
||||
reset_control_put(pllb->rstc);
|
||||
pllb->rstc = NULL;
|
||||
}
|
||||
|
||||
static int pllb_clk_enable(struct clk_hw *hw)
|
||||
{
|
||||
struct clk_oxnas_pllb *pllb = to_clk_oxnas_pllb(hw);
|
||||
|
||||
BUG_ON(IS_ERR(pllb->rstc));
|
||||
|
||||
/* put PLL into bypass */
|
||||
oxnas_register_set_mask(SEC_CTRL_PLLB_CTRL0, BIT(PLLB_BYPASS));
|
||||
wmb();
|
||||
udelay(10);
|
||||
reset_control_assert(pllb->rstc);
|
||||
udelay(10);
|
||||
/* set PLL B control information */
|
||||
writel((1 << PLLB_ENSAT) | (1 << PLLB_OUTDIV) | (2 << PLLB_REFDIV),
|
||||
SEC_CTRL_PLLB_CTRL0);
|
||||
reset_control_deassert(pllb->rstc);
|
||||
udelay(100);
|
||||
oxnas_register_clear_mask(SEC_CTRL_PLLB_CTRL0, BIT(PLLB_BYPASS));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pllb_clk_disable(struct clk_hw *hw)
|
||||
{
|
||||
struct clk_oxnas_pllb *pllb = to_clk_oxnas_pllb(hw);
|
||||
|
||||
BUG_ON(IS_ERR(pllb->rstc));
|
||||
|
||||
/* put PLL into bypass */
|
||||
oxnas_register_set_mask(SEC_CTRL_PLLB_CTRL0, BIT(PLLB_BYPASS));
|
||||
wmb();
|
||||
udelay(10);
|
||||
|
||||
reset_control_assert(pllb->rstc);
|
||||
}
|
||||
|
||||
static struct clk_ops pllb_ops = {
|
||||
.prepare = pllb_clk_prepare,
|
||||
.unprepare = pllb_clk_unprepare,
|
||||
.is_prepared = pllb_clk_is_prepared,
|
||||
.enable = pllb_clk_enable,
|
||||
.disable = pllb_clk_disable,
|
||||
};
|
||||
|
||||
static struct clk_init_data clk_pllb_init = {
|
||||
.name = "pllb",
|
||||
.ops = &pllb_ops,
|
||||
.parent_names = pll_clk_parents,
|
||||
.num_parents = ARRAY_SIZE(pll_clk_parents),
|
||||
};
|
||||
|
||||
|
||||
/* standard gate clock */
|
||||
struct clk_std {
|
||||
struct clk_hw hw;
|
||||
signed char bit;
|
||||
};
|
||||
|
||||
#define NUM_STD_CLKS 17
|
||||
#define to_stdclk(_hw) container_of(_hw, struct clk_std, hw)
|
||||
|
||||
static int std_clk_is_enabled(struct clk_hw *hw)
|
||||
{
|
||||
struct clk_std *std = to_stdclk(hw);
|
||||
|
||||
return readl_relaxed(SYSCTRL_CLK_STAT) & BIT(std->bit);
|
||||
}
|
||||
|
||||
static int std_clk_enable(struct clk_hw *hw)
|
||||
{
|
||||
struct clk_std *std = to_stdclk(hw);
|
||||
|
||||
writel(BIT(std->bit), SYS_CTRL_CLK_SET_CTRL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void std_clk_disable(struct clk_hw *hw)
|
||||
{
|
||||
struct clk_std *std = to_stdclk(hw);
|
||||
|
||||
writel(BIT(std->bit), SYS_CTRL_CLK_CLR_CTRL);
|
||||
}
|
||||
|
||||
static struct clk_ops std_clk_ops = {
|
||||
.enable = std_clk_enable,
|
||||
.disable = std_clk_disable,
|
||||
.is_enabled = std_clk_is_enabled,
|
||||
};
|
||||
|
||||
static const char *std_clk_parents[] = {
|
||||
"oscillator",
|
||||
};
|
||||
|
||||
static const char *eth_parents[] = {
|
||||
"gmacclk",
|
||||
};
|
||||
|
||||
#define DECLARE_STD_CLKP(__clk, __bit, __parent) \
|
||||
static struct clk_init_data clk_##__clk##_init = { \
|
||||
.name = __stringify(__clk), \
|
||||
.ops = &std_clk_ops, \
|
||||
.parent_names = __parent, \
|
||||
.num_parents = ARRAY_SIZE(__parent), \
|
||||
}; \
|
||||
\
|
||||
static struct clk_std clk_##__clk = { \
|
||||
.bit = __bit, \
|
||||
.hw = { \
|
||||
.init = &clk_##__clk##_init, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define DECLARE_STD_CLK(__clk, __bit) DECLARE_STD_CLKP(__clk, __bit, \
|
||||
std_clk_parents)
|
||||
|
||||
DECLARE_STD_CLK(leon, 0);
|
||||
DECLARE_STD_CLK(dma_sgdma, 1);
|
||||
DECLARE_STD_CLK(cipher, 2);
|
||||
DECLARE_STD_CLK(sd, 3);
|
||||
DECLARE_STD_CLK(sata, 4);
|
||||
DECLARE_STD_CLK(audio, 5);
|
||||
DECLARE_STD_CLK(usbmph, 6);
|
||||
DECLARE_STD_CLKP(etha, 7, eth_parents);
|
||||
DECLARE_STD_CLK(pciea, 8);
|
||||
DECLARE_STD_CLK(static, 9);
|
||||
DECLARE_STD_CLK(ethb, 10);
|
||||
DECLARE_STD_CLK(pcieb, 11);
|
||||
DECLARE_STD_CLK(ref600, 12);
|
||||
DECLARE_STD_CLK(usbdev, 13);
|
||||
|
||||
struct clk_hw *std_clk_hw_tbl[] = {
|
||||
&clk_leon.hw,
|
||||
&clk_dma_sgdma.hw,
|
||||
&clk_cipher.hw,
|
||||
&clk_sd.hw,
|
||||
&clk_sata.hw,
|
||||
&clk_audio.hw,
|
||||
&clk_usbmph.hw,
|
||||
&clk_etha.hw,
|
||||
&clk_pciea.hw,
|
||||
&clk_static.hw,
|
||||
&clk_ethb.hw,
|
||||
&clk_pcieb.hw,
|
||||
&clk_ref600.hw,
|
||||
&clk_usbdev.hw,
|
||||
};
|
||||
|
||||
struct clk *std_clk_tbl[ARRAY_SIZE(std_clk_hw_tbl)];
|
||||
|
||||
static struct clk_onecell_data std_clk_data;
|
||||
|
||||
void __init oxnas_init_stdclk(struct device_node *np)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(std_clk_hw_tbl); i++) {
|
||||
std_clk_tbl[i] = clk_register(NULL, std_clk_hw_tbl[i]);
|
||||
BUG_ON(IS_ERR(std_clk_tbl[i]));
|
||||
}
|
||||
std_clk_data.clks = std_clk_tbl;
|
||||
std_clk_data.clk_num = ARRAY_SIZE(std_clk_tbl);
|
||||
of_clk_add_provider(np, of_clk_src_onecell_get, &std_clk_data);
|
||||
}
|
||||
CLK_OF_DECLARE(oxnas_pllstd, "plxtech,nas782x-stdclk", oxnas_init_stdclk);
|
||||
|
||||
void __init oxnas_init_plla(struct device_node *np)
|
||||
{
|
||||
struct clk *clk;
|
||||
|
||||
clk = clk_register(NULL, &plla_hw);
|
||||
BUG_ON(IS_ERR(clk));
|
||||
/* mark it as enabled */
|
||||
clk_prepare_enable(clk);
|
||||
of_clk_add_provider(np, of_clk_src_simple_get, clk);
|
||||
}
|
||||
CLK_OF_DECLARE(oxnas_plla, "plxtech,nas782x-plla", oxnas_init_plla);
|
||||
|
||||
void __init oxnas_init_pllb(struct device_node *np)
|
||||
{
|
||||
struct clk *clk;
|
||||
struct clk_oxnas_pllb *pllb;
|
||||
|
||||
pllb = kmalloc(sizeof(*pllb), GFP_KERNEL);
|
||||
BUG_ON(!pllb);
|
||||
|
||||
pllb->hw.init = &clk_pllb_init;
|
||||
pllb->devnode = np;
|
||||
pllb->rstc = NULL;
|
||||
|
||||
clk = clk_register(NULL, &pllb->hw);
|
||||
BUG_ON(IS_ERR(clk));
|
||||
of_clk_add_provider(np, of_clk_src_simple_get, clk);
|
||||
}
|
||||
CLK_OF_DECLARE(oxnas_pllb, "plxtech,nas782x-pllb", oxnas_init_pllb);
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* arch/arm/mach-ox820/rps-time.c
|
||||
*
|
||||
* Copyright (C) 2009 Oxford Semiconductor Ltd
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/sched_clock.h>
|
||||
#include <mach/hardware.h>
|
||||
|
||||
enum {
|
||||
TIMER_LOAD = 0,
|
||||
TIMER_CURR = 4,
|
||||
TIMER_CTRL = 8,
|
||||
TIMER_CLRINT = 0xC,
|
||||
|
||||
TIMER_BITS = 24,
|
||||
|
||||
TIMER_MAX_VAL = (1 << TIMER_BITS) - 1,
|
||||
|
||||
TIMER_PERIODIC = (1 << 6),
|
||||
TIMER_ENABLE = (1 << 7),
|
||||
|
||||
TIMER_DIV1 = (0 << 2),
|
||||
TIMER_DIV16 = (1 << 2),
|
||||
TIMER_DIV256 = (2 << 2),
|
||||
|
||||
TIMER1_OFFSET = 0,
|
||||
TIMER2_OFFSET = 0x20,
|
||||
|
||||
};
|
||||
|
||||
static u64 notrace rps_read_sched_clock(void)
|
||||
{
|
||||
return ~readl_relaxed(RPSA_TIMER2_VAL);
|
||||
}
|
||||
|
||||
static void __init rps_clocksource_init(void __iomem *base, ulong ref_rate)
|
||||
{
|
||||
int ret;
|
||||
ulong clock_rate;
|
||||
/* use prescale 16 */
|
||||
clock_rate = ref_rate / 16;
|
||||
|
||||
iowrite32(TIMER_MAX_VAL, base + TIMER_LOAD);
|
||||
iowrite32(TIMER_PERIODIC | TIMER_ENABLE | TIMER_DIV16,
|
||||
base + TIMER_CTRL);
|
||||
|
||||
ret = clocksource_mmio_init(base + TIMER_CURR, "rps_clocksource_timer",
|
||||
clock_rate, 250, TIMER_BITS,
|
||||
clocksource_mmio_readl_down);
|
||||
if (ret)
|
||||
panic("can't register clocksource\n");
|
||||
|
||||
sched_clock_register(rps_read_sched_clock, TIMER_BITS, clock_rate);
|
||||
}
|
||||
|
||||
static void __init rps_timer_init(struct device_node *np)
|
||||
{
|
||||
struct clk *refclk;
|
||||
unsigned long ref_rate;
|
||||
void __iomem *base;
|
||||
|
||||
refclk = of_clk_get(np, 0);
|
||||
|
||||
if (IS_ERR(refclk) || clk_prepare_enable(refclk))
|
||||
panic("rps_timer_init: failed to get refclk\n");
|
||||
ref_rate = clk_get_rate(refclk);
|
||||
|
||||
base = of_iomap(np, 0);
|
||||
if (!base)
|
||||
panic("rps_timer_init: failed to map io\n");
|
||||
|
||||
rps_clocksource_init(base + TIMER2_OFFSET, ref_rate);
|
||||
}
|
||||
|
||||
CLOCKSOURCE_OF_DECLARE(nas782x, "plxtech,nas782x-rps-timer", rps_timer_init);
|
||||
@@ -1,145 +0,0 @@
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irqchip.h>
|
||||
|
||||
struct rps_chip_data {
|
||||
void __iomem *base;
|
||||
struct irq_chip chip;
|
||||
struct irq_domain *domain;
|
||||
} rps_data;
|
||||
|
||||
enum {
|
||||
RPS_IRQ_BASE = 64,
|
||||
RPS_IRQ_COUNT = 32,
|
||||
PRS_HWIRQ_BASE = 0,
|
||||
|
||||
RPS_STATUS = 0,
|
||||
RPS_RAW_STATUS = 4,
|
||||
RPS_UNMASK = 8,
|
||||
RPS_MASK = 0xc,
|
||||
};
|
||||
|
||||
/*
|
||||
* Routines to acknowledge, disable and enable interrupts
|
||||
*/
|
||||
static void rps_mask_irq(struct irq_data *d)
|
||||
{
|
||||
struct rps_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
u32 mask = BIT(d->hwirq);
|
||||
|
||||
iowrite32(mask, chip_data->base + RPS_MASK);
|
||||
}
|
||||
|
||||
static void rps_unmask_irq(struct irq_data *d)
|
||||
{
|
||||
struct rps_chip_data *chip_data = irq_data_get_irq_chip_data(d);
|
||||
u32 mask = BIT(d->hwirq);
|
||||
|
||||
iowrite32(mask, chip_data->base + RPS_UNMASK);
|
||||
}
|
||||
|
||||
static struct irq_chip rps_chip = {
|
||||
.name = "RPS",
|
||||
.irq_mask = rps_mask_irq,
|
||||
.irq_unmask = rps_unmask_irq,
|
||||
};
|
||||
|
||||
static int rps_irq_domain_xlate(struct irq_domain *d,
|
||||
struct device_node *controller,
|
||||
const u32 *intspec, unsigned int intsize,
|
||||
unsigned long *out_hwirq,
|
||||
unsigned int *out_type)
|
||||
{
|
||||
if (irq_domain_get_of_node(d) != controller)
|
||||
return -EINVAL;
|
||||
if (intsize < 1)
|
||||
return -EINVAL;
|
||||
|
||||
*out_hwirq = intspec[0];
|
||||
/* Honestly I do not know the type */
|
||||
*out_type = IRQ_TYPE_LEVEL_HIGH;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rps_irq_domain_map(struct irq_domain *d, unsigned int irq,
|
||||
irq_hw_number_t hw)
|
||||
{
|
||||
irq_set_chip_and_handler(irq, &rps_chip, handle_level_irq);
|
||||
irq_set_probe(irq);
|
||||
irq_set_chip_data(irq, d->host_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct irq_domain_ops rps_irq_domain_ops = {
|
||||
.map = rps_irq_domain_map,
|
||||
.xlate = rps_irq_domain_xlate,
|
||||
};
|
||||
|
||||
static void rps_handle_cascade_irq(struct irq_desc *desc)
|
||||
{
|
||||
struct rps_chip_data *chip_data = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
unsigned int cascade_irq, rps_irq;
|
||||
u32 status;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
|
||||
status = ioread32(chip_data->base + RPS_STATUS);
|
||||
rps_irq = __ffs(status);
|
||||
cascade_irq = irq_find_mapping(chip_data->domain, rps_irq);
|
||||
|
||||
if (unlikely(rps_irq >= RPS_IRQ_COUNT))
|
||||
handle_bad_irq(desc);
|
||||
else
|
||||
generic_handle_irq(cascade_irq);
|
||||
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
int __init rps_of_init(struct device_node *node, struct device_node *parent)
|
||||
{
|
||||
void __iomem *rps_base;
|
||||
int irq_start = RPS_IRQ_BASE;
|
||||
int irq_base;
|
||||
int irq;
|
||||
|
||||
if (WARN_ON(!node))
|
||||
return -ENODEV;
|
||||
|
||||
rps_base = of_iomap(node, 0);
|
||||
WARN(!rps_base, "unable to map rps registers\n");
|
||||
rps_data.base = rps_base;
|
||||
|
||||
irq_base = irq_alloc_descs(irq_start, 0, RPS_IRQ_COUNT, numa_node_id());
|
||||
if (IS_ERR_VALUE(irq_base)) {
|
||||
WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
|
||||
irq_start);
|
||||
irq_base = irq_start;
|
||||
}
|
||||
|
||||
rps_data.domain = irq_domain_add_legacy(node, RPS_IRQ_COUNT, irq_base,
|
||||
PRS_HWIRQ_BASE, &rps_irq_domain_ops, &rps_data);
|
||||
|
||||
if (WARN_ON(!rps_data.domain))
|
||||
return -ENOMEM;
|
||||
|
||||
if (parent) {
|
||||
irq = irq_of_parse_and_map(node, 0);
|
||||
if (irq_set_handler_data(irq, &rps_data) != 0)
|
||||
BUG();
|
||||
irq_set_chained_handler(irq, rps_handle_cascade_irq);
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
IRQCHIP_DECLARE(nas782x, "plxtech,nas782x-rps", rps_of_init);
|
||||
#endif
|
||||
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
* Oxford Semiconductor OXNAS NAND driver
|
||||
|
||||
* Copyright (C) 2016 Neil Armstrong <narmstrong@baylibre.com>
|
||||
* Heavily based on plat_nand.c :
|
||||
* Author: Vitaly Wool <vitalywool@gmail.com>
|
||||
* Copyright (C) 2013 Ma Haijun <mahaijuns@gmail.com>
|
||||
* Copyright (C) 2012 John Crispin <blogic@openwrt.org>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
/* Nand commands */
|
||||
#define OXNAS_NAND_CMD_ALE BIT(18)
|
||||
#define OXNAS_NAND_CMD_CLE BIT(19)
|
||||
|
||||
#define OXNAS_NAND_MAX_CHIPS 1
|
||||
|
||||
struct oxnas_nand {
|
||||
struct nand_hw_control base;
|
||||
void __iomem *io_base;
|
||||
struct clk *clk;
|
||||
struct nand_chip *chips[OXNAS_NAND_MAX_CHIPS];
|
||||
unsigned long ctrl;
|
||||
struct mtd_partition *partitions;
|
||||
int nr_partitions;
|
||||
};
|
||||
|
||||
static uint8_t oxnas_nand_read_byte(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
struct oxnas_nand *oxnas = nand_get_controller_data(chip);
|
||||
|
||||
return readb(oxnas->io_base);
|
||||
}
|
||||
|
||||
static void oxnas_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
struct oxnas_nand *oxnas = nand_get_controller_data(chip);
|
||||
|
||||
ioread8_rep(oxnas->io_base, buf, len);
|
||||
}
|
||||
|
||||
static void oxnas_nand_write_buf(struct mtd_info *mtd,
|
||||
const uint8_t *buf, int len)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
struct oxnas_nand *oxnas = nand_get_controller_data(chip);
|
||||
|
||||
iowrite8_rep(oxnas->io_base + oxnas->ctrl, buf, len);
|
||||
}
|
||||
|
||||
/* Single CS command control */
|
||||
static void oxnas_nand_cmd_ctrl(struct mtd_info *mtd, int cmd,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
struct oxnas_nand *oxnas = nand_get_controller_data(chip);
|
||||
|
||||
if (ctrl & NAND_CTRL_CHANGE) {
|
||||
if (ctrl & NAND_CLE)
|
||||
oxnas->ctrl = OXNAS_NAND_CMD_CLE;
|
||||
else if (ctrl & NAND_ALE)
|
||||
oxnas->ctrl = OXNAS_NAND_CMD_ALE;
|
||||
else
|
||||
oxnas->ctrl = 0;
|
||||
}
|
||||
|
||||
if (cmd != NAND_CMD_NONE)
|
||||
writeb(cmd, oxnas->io_base + oxnas->ctrl);
|
||||
}
|
||||
|
||||
/*
|
||||
* Probe for the NAND device.
|
||||
*/
|
||||
static int oxnas_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *nand_np;
|
||||
struct oxnas_nand *oxnas;
|
||||
struct nand_chip *chip;
|
||||
struct mtd_info *mtd;
|
||||
struct resource *res;
|
||||
int nchips = 0;
|
||||
int count = 0;
|
||||
int err = 0;
|
||||
|
||||
/* Allocate memory for the device structure (and zero it) */
|
||||
oxnas = devm_kzalloc(&pdev->dev, sizeof(struct nand_chip),
|
||||
GFP_KERNEL);
|
||||
if (!oxnas)
|
||||
return -ENOMEM;
|
||||
|
||||
nand_hw_control_init(&oxnas->base);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
oxnas->io_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(oxnas->io_base))
|
||||
return PTR_ERR(oxnas->io_base);
|
||||
|
||||
oxnas->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(oxnas->clk))
|
||||
oxnas->clk = NULL;
|
||||
|
||||
/* Only a single chip node is supported */
|
||||
count = of_get_child_count(np);
|
||||
if (count > 1)
|
||||
return -EINVAL;
|
||||
|
||||
clk_prepare_enable(oxnas->clk);
|
||||
device_reset_optional(&pdev->dev);
|
||||
|
||||
for_each_child_of_node(np, nand_np) {
|
||||
chip = devm_kzalloc(&pdev->dev, sizeof(struct nand_chip),
|
||||
GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
chip->controller = &oxnas->base;
|
||||
|
||||
nand_set_flash_node(chip, nand_np);
|
||||
nand_set_controller_data(chip, oxnas);
|
||||
|
||||
mtd = nand_to_mtd(chip);
|
||||
mtd->dev.parent = &pdev->dev;
|
||||
mtd->priv = chip;
|
||||
|
||||
chip->cmd_ctrl = oxnas_nand_cmd_ctrl;
|
||||
chip->read_buf = oxnas_nand_read_buf;
|
||||
chip->read_byte = oxnas_nand_read_byte;
|
||||
chip->write_buf = oxnas_nand_write_buf;
|
||||
chip->chip_delay = 30;
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
err = nand_scan(mtd, 1);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = mtd_device_register(mtd, NULL, 0);
|
||||
if (err) {
|
||||
nand_release(mtd);
|
||||
return err;
|
||||
}
|
||||
|
||||
oxnas->chips[nchips] = chip;
|
||||
++nchips;
|
||||
}
|
||||
|
||||
/* Exit if no chips found */
|
||||
if (!nchips)
|
||||
return -ENODEV;
|
||||
|
||||
platform_set_drvdata(pdev, oxnas);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int oxnas_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct oxnas_nand *oxnas = platform_get_drvdata(pdev);
|
||||
|
||||
if (oxnas->chips[0])
|
||||
nand_release(nand_to_mtd(oxnas->chips[0]));
|
||||
|
||||
clk_disable_unprepare(oxnas->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id oxnas_nand_match[] = {
|
||||
{ .compatible = "oxsemi,ox820-nand" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, oxnas_nand_match);
|
||||
|
||||
static struct platform_driver oxnas_nand_driver = {
|
||||
.probe = oxnas_nand_probe,
|
||||
.remove = oxnas_nand_remove,
|
||||
.driver = {
|
||||
.name = "oxnas_nand",
|
||||
.of_match_table = oxnas_nand_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(oxnas_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
|
||||
MODULE_DESCRIPTION("Oxnas NAND driver");
|
||||
MODULE_ALIAS("platform:oxnas_nand");
|
||||
@@ -1,145 +0,0 @@
|
||||
/* Copyright OpenWrt.org (C) 2015.
|
||||
* Copyright Altera Corporation (C) 2014. All rights reserved.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Adopted from dwmac-socfpga.c
|
||||
* Based on code found in mach-oxnas.c
|
||||
*/
|
||||
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_net.h>
|
||||
#include <linux/phy.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/stmmac.h>
|
||||
|
||||
#include <mach/hardware.h>
|
||||
|
||||
#include "stmmac.h"
|
||||
#include "stmmac_platform.h"
|
||||
|
||||
struct oxnas_gmac {
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
static int oxnas_gmac_init(struct platform_device *pdev, void *priv)
|
||||
{
|
||||
struct oxnas_gmac *bsp_priv = priv;
|
||||
int ret = 0;
|
||||
unsigned value;
|
||||
|
||||
ret = device_reset(&pdev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (IS_ERR(bsp_priv->clk))
|
||||
return PTR_ERR(bsp_priv->clk);
|
||||
clk_prepare_enable(bsp_priv->clk);
|
||||
|
||||
value = readl(SYS_CTRL_GMAC_CTRL);
|
||||
|
||||
/* Enable GMII_GTXCLK to follow GMII_REFCLK, required for gigabit PHY */
|
||||
value |= BIT(SYS_CTRL_GMAC_CKEN_GTX);
|
||||
/* Use simple mux for 25/125 Mhz clock switching */
|
||||
value |= BIT(SYS_CTRL_GMAC_SIMPLE_MUX);
|
||||
/* set auto switch tx clock source */
|
||||
value |= BIT(SYS_CTRL_GMAC_AUTO_TX_SOURCE);
|
||||
/* enable tx & rx vardelay */
|
||||
value |= BIT(SYS_CTRL_GMAC_CKEN_TX_OUT);
|
||||
value |= BIT(SYS_CTRL_GMAC_CKEN_TXN_OUT);
|
||||
value |= BIT(SYS_CTRL_GMAC_CKEN_TX_IN);
|
||||
value |= BIT(SYS_CTRL_GMAC_CKEN_RX_OUT);
|
||||
value |= BIT(SYS_CTRL_GMAC_CKEN_RXN_OUT);
|
||||
value |= BIT(SYS_CTRL_GMAC_CKEN_RX_IN);
|
||||
writel(value, SYS_CTRL_GMAC_CTRL);
|
||||
|
||||
/* set tx & rx vardelay */
|
||||
value = 0;
|
||||
value |= SYS_CTRL_GMAC_TX_VARDELAY(4);
|
||||
value |= SYS_CTRL_GMAC_TXN_VARDELAY(2);
|
||||
value |= SYS_CTRL_GMAC_RX_VARDELAY(10);
|
||||
value |= SYS_CTRL_GMAC_RXN_VARDELAY(8);
|
||||
writel(value, SYS_CTRL_GMAC_DELAY_CTRL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void oxnas_gmac_exit(struct platform_device *pdev, void *priv)
|
||||
{
|
||||
struct reset_control *rstc;
|
||||
|
||||
clk_disable_unprepare(priv);
|
||||
devm_clk_put(&pdev->dev, priv);
|
||||
|
||||
rstc = reset_control_get(&pdev->dev, NULL);
|
||||
if (!IS_ERR(rstc)) {
|
||||
reset_control_assert(rstc);
|
||||
reset_control_put(rstc);
|
||||
}
|
||||
}
|
||||
|
||||
static int oxnas_gmac_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct plat_stmmacenet_data *plat_dat;
|
||||
struct stmmac_resources stmmac_res;
|
||||
int ret;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct oxnas_gmac *bsp_priv;
|
||||
|
||||
bsp_priv = devm_kzalloc(dev, sizeof(*bsp_priv), GFP_KERNEL);
|
||||
if (!bsp_priv)
|
||||
return -ENOMEM;
|
||||
bsp_priv->clk = devm_clk_get(dev, "gmac");
|
||||
if (IS_ERR(bsp_priv->clk))
|
||||
return PTR_ERR(bsp_priv->clk);
|
||||
|
||||
ret = stmmac_get_platform_resources(pdev, &stmmac_res);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
plat_dat = stmmac_probe_config_dt(pdev, &stmmac_res.mac);
|
||||
if (IS_ERR(plat_dat))
|
||||
return PTR_ERR(plat_dat);
|
||||
|
||||
plat_dat->bsp_priv = bsp_priv;
|
||||
plat_dat->init = oxnas_gmac_init;
|
||||
plat_dat->exit = oxnas_gmac_exit;
|
||||
|
||||
ret = oxnas_gmac_init(pdev, bsp_priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return stmmac_dvr_probe(dev, plat_dat, &stmmac_res);
|
||||
}
|
||||
|
||||
static const struct of_device_id oxnas_gmac_match[] = {
|
||||
{ .compatible = "plxtech,nas782x-gmac" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, oxnas_gmac_match);
|
||||
|
||||
static struct platform_driver oxnas_gmac_driver = {
|
||||
.probe = oxnas_gmac_probe,
|
||||
.remove = stmmac_pltfr_remove,
|
||||
.driver = {
|
||||
.name = "oxnas-gmac",
|
||||
.pm = &stmmac_pltfr_pm_ops,
|
||||
.of_match_table = oxnas_gmac_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(oxnas_gmac_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
@@ -1,676 +0,0 @@
|
||||
/*
|
||||
* PCIe driver for PLX NAS782X SoCs
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mbus.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/reset.h>
|
||||
#include <mach/iomap.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <mach/utils.h>
|
||||
|
||||
#define VERSION_ID_MAGIC 0x082510b5
|
||||
#define LINK_UP_TIMEOUT_SECONDS 1
|
||||
#define NUM_CONTROLLERS 1
|
||||
|
||||
enum {
|
||||
PCIE_DEVICE_TYPE_MASK = 0x0F,
|
||||
PCIE_DEVICE_TYPE_ENDPOINT = 0,
|
||||
PCIE_DEVICE_TYPE_LEGACY_ENDPOINT = 1,
|
||||
PCIE_DEVICE_TYPE_ROOT = 4,
|
||||
|
||||
PCIE_LTSSM = BIT(4),
|
||||
PCIE_READY_ENTR_L23 = BIT(9),
|
||||
PCIE_LINK_UP = BIT(11),
|
||||
PCIE_OBTRANS = BIT(12),
|
||||
};
|
||||
|
||||
enum {
|
||||
HCSL_BIAS_ON = BIT(0),
|
||||
HCSL_PCIE_EN = BIT(1),
|
||||
HCSL_PCIEA_EN = BIT(2),
|
||||
HCSL_PCIEB_EN = BIT(3),
|
||||
};
|
||||
|
||||
enum {
|
||||
/* pcie phy reg offset */
|
||||
PHY_ADDR = 0,
|
||||
PHY_DATA = 4,
|
||||
/* phy data reg bits */
|
||||
READ_EN = BIT(16),
|
||||
WRITE_EN = BIT(17),
|
||||
CAP_DATA = BIT(18),
|
||||
};
|
||||
|
||||
/* core config registers */
|
||||
enum {
|
||||
PCI_CONFIG_VERSION_DEVICEID = 0,
|
||||
PCI_CONFIG_COMMAND_STATUS = 4,
|
||||
};
|
||||
|
||||
/* inbound config registers */
|
||||
enum {
|
||||
IB_ADDR_XLATE_ENABLE = 0xFC,
|
||||
|
||||
/* bits */
|
||||
ENABLE_IN_ADDR_TRANS = BIT(0),
|
||||
};
|
||||
|
||||
/* outbound config registers, offset relative to PCIE_POM0_MEM_ADDR */
|
||||
enum {
|
||||
PCIE_POM0_MEM_ADDR = 0,
|
||||
PCIE_POM1_MEM_ADDR = 4,
|
||||
PCIE_IN0_MEM_ADDR = 8,
|
||||
PCIE_IN1_MEM_ADDR = 12,
|
||||
PCIE_IN_IO_ADDR = 16,
|
||||
PCIE_IN_CFG0_ADDR = 20,
|
||||
PCIE_IN_CFG1_ADDR = 24,
|
||||
PCIE_IN_MSG_ADDR = 28,
|
||||
PCIE_IN0_MEM_LIMIT = 32,
|
||||
PCIE_IN1_MEM_LIMIT = 36,
|
||||
PCIE_IN_IO_LIMIT = 40,
|
||||
PCIE_IN_CFG0_LIMIT = 44,
|
||||
PCIE_IN_CFG1_LIMIT = 48,
|
||||
PCIE_IN_MSG_LIMIT = 52,
|
||||
PCIE_AHB_SLAVE_CTRL = 56,
|
||||
|
||||
PCIE_SLAVE_BE_SHIFT = 22,
|
||||
};
|
||||
|
||||
#define ADDR_VAL(val) ((val) & 0xFFFF)
|
||||
#define DATA_VAL(val) ((val) & 0xFFFF)
|
||||
|
||||
#define PCIE_SLAVE_BE(val) ((val) << PCIE_SLAVE_BE_SHIFT)
|
||||
#define PCIE_SLAVE_BE_MASK PCIE_SLAVE_BE(0xF)
|
||||
|
||||
struct oxnas_pcie_shared {
|
||||
/* seems all access are serialized, no lock required */
|
||||
int refcount;
|
||||
};
|
||||
|
||||
/* Structure representing one PCIe interfaces */
|
||||
struct oxnas_pcie {
|
||||
void __iomem *cfgbase;
|
||||
void __iomem *base;
|
||||
void __iomem *inbound;
|
||||
void __iomem *outbound;
|
||||
void __iomem *pcie_ctrl;
|
||||
|
||||
int haslink;
|
||||
struct platform_device *pdev;
|
||||
struct resource io;
|
||||
struct resource cfg;
|
||||
struct resource pre_mem; /* prefetchable */
|
||||
struct resource non_mem; /* non-prefetchable */
|
||||
struct resource busn; /* max available bus numbers */
|
||||
int card_reset; /* gpio pin, optional */
|
||||
unsigned hcsl_en; /* hcsl pci enable bit */
|
||||
struct clk *clk;
|
||||
struct clk *busclk; /* for pcie bus, actually the PLLB */
|
||||
void *private_data[1];
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
static struct oxnas_pcie_shared pcie_shared = {
|
||||
.refcount = 0,
|
||||
};
|
||||
|
||||
static inline struct oxnas_pcie *sys_to_pcie(struct pci_sys_data *sys)
|
||||
{
|
||||
return sys->private_data;
|
||||
}
|
||||
|
||||
|
||||
static inline void set_out_lanes(struct oxnas_pcie *pcie, unsigned lanes)
|
||||
{
|
||||
oxnas_register_value_mask(pcie->outbound + PCIE_AHB_SLAVE_CTRL,
|
||||
PCIE_SLAVE_BE_MASK, PCIE_SLAVE_BE(lanes));
|
||||
wmb();
|
||||
}
|
||||
|
||||
static int oxnas_pcie_link_up(struct oxnas_pcie *pcie)
|
||||
{
|
||||
unsigned long end;
|
||||
|
||||
/* Poll for PCIE link up */
|
||||
end = jiffies + (LINK_UP_TIMEOUT_SECONDS * HZ);
|
||||
while (!time_after(jiffies, end)) {
|
||||
if (readl(pcie->pcie_ctrl) & PCIE_LINK_UP)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __init oxnas_pcie_setup_hw(struct oxnas_pcie *pcie)
|
||||
{
|
||||
/* We won't have any inbound address translation. This allows PCI
|
||||
* devices to access anywhere in the AHB address map. Might be regarded
|
||||
* as a bit dangerous, but let's get things working before we worry
|
||||
* about that
|
||||
*/
|
||||
oxnas_register_clear_mask(pcie->inbound + IB_ADDR_XLATE_ENABLE,
|
||||
ENABLE_IN_ADDR_TRANS);
|
||||
wmb();
|
||||
|
||||
/*
|
||||
* Program outbound translation windows
|
||||
*
|
||||
* Outbound window is what is referred to as "PCI client" region in HRM
|
||||
*
|
||||
* Could use the larger alternative address space to get >>64M regions
|
||||
* for graphics cards etc., but will not bother at this point.
|
||||
*
|
||||
* IP bug means that AMBA window size must be a power of 2
|
||||
*
|
||||
* Set mem0 window for first 16MB of outbound window non-prefetchable
|
||||
* Set mem1 window for second 16MB of outbound window prefetchable
|
||||
* Set io window for next 16MB of outbound window
|
||||
* Set cfg0 for final 1MB of outbound window
|
||||
*
|
||||
* Ignore mem1, cfg1 and msg windows for now as no obvious use cases for
|
||||
* 820 that would need them
|
||||
*
|
||||
* Probably ideally want no offset between mem0 window start as seen by
|
||||
* ARM and as seen on PCI bus and get Linux to assign memory regions to
|
||||
* PCI devices using the same "PCI client" region start address as seen
|
||||
* by ARM
|
||||
*/
|
||||
|
||||
/* Set PCIeA mem0 region to be 1st 16MB of the 64MB PCIeA window */
|
||||
writel_relaxed(pcie->non_mem.start, pcie->outbound + PCIE_IN0_MEM_ADDR);
|
||||
writel_relaxed(pcie->non_mem.end, pcie->outbound + PCIE_IN0_MEM_LIMIT);
|
||||
writel_relaxed(pcie->non_mem.start, pcie->outbound + PCIE_POM0_MEM_ADDR);
|
||||
|
||||
/* Set PCIeA mem1 region to be 2nd 16MB of the 64MB PCIeA window */
|
||||
writel_relaxed(pcie->pre_mem.start, pcie->outbound + PCIE_IN1_MEM_ADDR);
|
||||
writel_relaxed(pcie->pre_mem.end, pcie->outbound + PCIE_IN1_MEM_LIMIT);
|
||||
writel_relaxed(pcie->pre_mem.start, pcie->outbound + PCIE_POM1_MEM_ADDR);
|
||||
|
||||
/* Set PCIeA io to be third 16M region of the 64MB PCIeA window*/
|
||||
writel_relaxed(pcie->io.start, pcie->outbound + PCIE_IN_IO_ADDR);
|
||||
writel_relaxed(pcie->io.end, pcie->outbound + PCIE_IN_IO_LIMIT);
|
||||
|
||||
/* Set PCIeA cgf0 to be last 16M region of the 64MB PCIeA window*/
|
||||
writel_relaxed(pcie->cfg.start, pcie->outbound + PCIE_IN_CFG0_ADDR);
|
||||
writel_relaxed(pcie->cfg.end, pcie->outbound + PCIE_IN_CFG0_LIMIT);
|
||||
wmb();
|
||||
|
||||
/* Enable outbound address translation */
|
||||
oxnas_register_set_mask(pcie->pcie_ctrl, PCIE_OBTRANS);
|
||||
wmb();
|
||||
|
||||
/*
|
||||
* Program PCIe command register for core to:
|
||||
* enable memory space
|
||||
* enable bus master
|
||||
* enable io
|
||||
*/
|
||||
writel_relaxed(7, pcie->base + PCI_CONFIG_COMMAND_STATUS);
|
||||
/* which is which */
|
||||
wmb();
|
||||
}
|
||||
|
||||
static unsigned oxnas_pcie_cfg_to_offset(
|
||||
struct pci_sys_data *sys,
|
||||
unsigned char bus_number,
|
||||
unsigned int devfn,
|
||||
int where)
|
||||
{
|
||||
unsigned int function = PCI_FUNC(devfn);
|
||||
unsigned int slot = PCI_SLOT(devfn);
|
||||
unsigned char bus_number_offset;
|
||||
|
||||
bus_number_offset = bus_number - sys->busnr;
|
||||
|
||||
/*
|
||||
* We'll assume for now that the offset, function, slot, bus encoding
|
||||
* should map onto linear, contiguous addresses in PCIe config space,
|
||||
* albeit that the majority will be unused as only slot 0 is valid for
|
||||
* any PCIe bus and most devices have only function 0
|
||||
*
|
||||
* Could be that PCIe in fact works by not encoding the slot number into
|
||||
* the config space address as it's known that only slot 0 is valid.
|
||||
* We'll have to experiment if/when we get a PCIe switch connected to
|
||||
* the PCIe host
|
||||
*/
|
||||
return (bus_number_offset << 20) | (slot << 15) | (function << 12) |
|
||||
(where & ~3);
|
||||
}
|
||||
|
||||
/* PCI configuration space write function */
|
||||
static int oxnas_pcie_wr_conf(struct pci_bus *bus, u32 devfn,
|
||||
int where, int size, u32 val)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct oxnas_pcie *pcie = sys_to_pcie(bus->sysdata);
|
||||
unsigned offset;
|
||||
u32 value;
|
||||
u32 lanes;
|
||||
|
||||
/* Only a single device per bus for PCIe point-to-point links */
|
||||
if (PCI_SLOT(devfn) > 0)
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
|
||||
if (!pcie->haslink)
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
|
||||
offset = oxnas_pcie_cfg_to_offset(bus->sysdata, bus->number, devfn,
|
||||
where);
|
||||
|
||||
value = val << (8 * (where & 3));
|
||||
lanes = (0xf >> (4-size)) << (where & 3);
|
||||
/* it race with mem and io write, but the possibility is low, normally
|
||||
* all config writes happens at driver initialize stage, wont interleave
|
||||
* with others.
|
||||
* and many pcie cards use dword (4bytes) access mem/io access only,
|
||||
* so not bother to copy that ugly work-around now. */
|
||||
spin_lock_irqsave(&pcie->lock, flags);
|
||||
set_out_lanes(pcie, lanes);
|
||||
writel_relaxed(value, pcie->cfgbase + offset);
|
||||
set_out_lanes(pcie, 0xf);
|
||||
spin_unlock_irqrestore(&pcie->lock, flags);
|
||||
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
/* PCI configuration space read function */
|
||||
static int oxnas_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where,
|
||||
int size, u32 *val)
|
||||
{
|
||||
struct oxnas_pcie *pcie = sys_to_pcie(bus->sysdata);
|
||||
unsigned offset;
|
||||
u32 value;
|
||||
u32 left_bytes, right_bytes;
|
||||
|
||||
/* Only a single device per bus for PCIe point-to-point links */
|
||||
if (PCI_SLOT(devfn) > 0) {
|
||||
*val = 0xffffffff;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (!pcie->haslink) {
|
||||
*val = 0xffffffff;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
|
||||
offset = oxnas_pcie_cfg_to_offset(bus->sysdata, bus->number, devfn,
|
||||
where);
|
||||
value = readl_relaxed(pcie->cfgbase + offset);
|
||||
left_bytes = where & 3;
|
||||
right_bytes = 4 - left_bytes - size;
|
||||
value <<= right_bytes * 8;
|
||||
value >>= (left_bytes + right_bytes) * 8;
|
||||
*val = value;
|
||||
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
static struct pci_ops oxnas_pcie_ops = {
|
||||
.read = oxnas_pcie_rd_conf,
|
||||
.write = oxnas_pcie_wr_conf,
|
||||
};
|
||||
|
||||
static int __init oxnas_pcie_setup(int nr, struct pci_sys_data *sys)
|
||||
{
|
||||
struct oxnas_pcie *pcie = sys_to_pcie(sys);
|
||||
|
||||
pci_add_resource_offset(&sys->resources, &pcie->non_mem, sys->mem_offset);
|
||||
pci_add_resource_offset(&sys->resources, &pcie->pre_mem, sys->mem_offset);
|
||||
pci_add_resource_offset(&sys->resources, &pcie->io, sys->io_offset);
|
||||
pci_add_resource(&sys->resources, &pcie->busn);
|
||||
if (sys->busnr == 0) { /* default one */
|
||||
sys->busnr = pcie->busn.start;
|
||||
}
|
||||
/* do not use devm_ioremap_resource, it does not like cfg resource */
|
||||
pcie->cfgbase = devm_ioremap(&pcie->pdev->dev, pcie->cfg.start,
|
||||
resource_size(&pcie->cfg));
|
||||
if (!pcie->cfgbase)
|
||||
return -ENOMEM;
|
||||
|
||||
oxnas_pcie_setup_hw(pcie);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void __init oxnas_pcie_enable(struct device *dev, struct oxnas_pcie *pcie)
|
||||
{
|
||||
struct hw_pci hw;
|
||||
int i;
|
||||
|
||||
memset(&hw, 0, sizeof(hw));
|
||||
for (i = 0; i < NUM_CONTROLLERS; i++)
|
||||
pcie->private_data[i] = pcie;
|
||||
|
||||
hw.nr_controllers = NUM_CONTROLLERS;
|
||||
/* I think use stack pointer is a bad idea though it is valid in this case */
|
||||
hw.private_data = pcie->private_data;
|
||||
hw.setup = oxnas_pcie_setup;
|
||||
hw.map_irq = of_irq_parse_and_map_pci;
|
||||
hw.ops = &oxnas_pcie_ops;
|
||||
|
||||
/* pass dev to maintain of tree, interrupt mapping rely on this */
|
||||
pci_common_init_dev(dev, &hw);
|
||||
}
|
||||
|
||||
void oxnas_pcie_init_shared_hw(struct platform_device *pdev,
|
||||
void __iomem *phybase)
|
||||
{
|
||||
struct reset_control *rstc;
|
||||
int ret;
|
||||
|
||||
/* generate clocks from HCSL buffers, shared parts */
|
||||
writel(HCSL_BIAS_ON|HCSL_PCIE_EN, SYS_CTRL_HCSL_CTRL);
|
||||
|
||||
/* Ensure PCIe PHY is properly reset */
|
||||
rstc = reset_control_get(&pdev->dev, "phy");
|
||||
if (IS_ERR(rstc)) {
|
||||
ret = PTR_ERR(rstc);
|
||||
} else {
|
||||
ret = reset_control_reset(rstc);
|
||||
reset_control_put(rstc);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "phy reset failed %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Enable PCIe Pre-Emphasis: What these value means? */
|
||||
|
||||
writel(ADDR_VAL(0x0014), phybase + PHY_ADDR);
|
||||
writel(DATA_VAL(0xce10) | CAP_DATA, phybase + PHY_DATA);
|
||||
writel(DATA_VAL(0xce10) | WRITE_EN, phybase + PHY_DATA);
|
||||
|
||||
writel(ADDR_VAL(0x2004), phybase + PHY_ADDR);
|
||||
writel(DATA_VAL(0x82c7) | CAP_DATA, phybase + PHY_DATA);
|
||||
writel(DATA_VAL(0x82c7) | WRITE_EN, phybase + PHY_DATA);
|
||||
}
|
||||
|
||||
static int oxnas_pcie_shared_init(struct platform_device *pdev)
|
||||
{
|
||||
if (++pcie_shared.refcount == 1) {
|
||||
/* we are the first */
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
void __iomem *phy = of_iomap(np, 2);
|
||||
if (!phy) {
|
||||
--pcie_shared.refcount;
|
||||
return -ENOMEM;
|
||||
}
|
||||
oxnas_pcie_init_shared_hw(pdev, phy);
|
||||
iounmap(phy);
|
||||
return 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* maybe we will call it when enter low power state */
|
||||
static void oxnas_pcie_shared_deinit(struct platform_device *pdev)
|
||||
{
|
||||
if (--pcie_shared.refcount == 0) {
|
||||
/* no cleanup needed */;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int __init
|
||||
oxnas_pcie_map_registers(struct platform_device *pdev,
|
||||
struct device_node *np,
|
||||
struct oxnas_pcie *pcie)
|
||||
{
|
||||
struct resource regs;
|
||||
int ret = 0;
|
||||
u32 outbound_ctrl_offset;
|
||||
u32 pcie_ctrl_offset;
|
||||
|
||||
/* 2 is reserved for shared phy */
|
||||
ret = of_address_to_resource(np, 0, ®s);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
pcie->base = devm_ioremap_resource(&pdev->dev, ®s);
|
||||
if (!pcie->base)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_address_to_resource(np, 1, ®s);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
pcie->inbound = devm_ioremap_resource(&pdev->dev, ®s);
|
||||
if (!pcie->inbound)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
if (of_property_read_u32(np, "plxtech,pcie-outbound-offset",
|
||||
&outbound_ctrl_offset))
|
||||
return -EINVAL;
|
||||
/* SYSCRTL is shared by too many drivers, so is mapped by board file */
|
||||
pcie->outbound = IOMEM(OXNAS_SYSCRTL_BASE_VA + outbound_ctrl_offset);
|
||||
|
||||
if (of_property_read_u32(np, "plxtech,pcie-ctrl-offset",
|
||||
&pcie_ctrl_offset))
|
||||
return -EINVAL;
|
||||
pcie->pcie_ctrl = IOMEM(OXNAS_SYSCRTL_BASE_VA + pcie_ctrl_offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init oxnas_pcie_init_res(struct platform_device *pdev,
|
||||
struct oxnas_pcie *pcie,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct of_pci_range range;
|
||||
struct of_pci_range_parser parser;
|
||||
int ret;
|
||||
|
||||
if (of_pci_range_parser_init(&parser, np))
|
||||
return -EINVAL;
|
||||
|
||||
/* Get the I/O and memory ranges from DT */
|
||||
for_each_of_pci_range(&parser, &range) {
|
||||
|
||||
unsigned long restype = range.flags & IORESOURCE_TYPE_BITS;
|
||||
if (restype == IORESOURCE_IO) {
|
||||
of_pci_range_to_resource(&range, np, &pcie->io);
|
||||
pcie->io.name = "I/O";
|
||||
}
|
||||
if (restype == IORESOURCE_MEM) {
|
||||
if (range.flags & IORESOURCE_PREFETCH) {
|
||||
of_pci_range_to_resource(&range, np, &pcie->pre_mem);
|
||||
pcie->pre_mem.name = "PRE MEM";
|
||||
} else {
|
||||
of_pci_range_to_resource(&range, np, &pcie->non_mem);
|
||||
pcie->non_mem.name = "NON MEM";
|
||||
}
|
||||
|
||||
}
|
||||
if (restype == 0)
|
||||
of_pci_range_to_resource(&range, np, &pcie->cfg);
|
||||
}
|
||||
|
||||
/* Get the bus range */
|
||||
ret = of_pci_parse_bus_range(np, &pcie->busn);
|
||||
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to parse bus-range property: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pcie->card_reset = of_get_gpio(np, 0);
|
||||
if (pcie->card_reset < 0)
|
||||
dev_info(&pdev->dev, "card reset gpio pin not exists\n");
|
||||
|
||||
if (of_property_read_u32(np, "plxtech,pcie-hcsl-bit", &pcie->hcsl_en))
|
||||
return -EINVAL;
|
||||
|
||||
pcie->clk = of_clk_get_by_name(np, "pcie");
|
||||
if (IS_ERR(pcie->clk)) {
|
||||
return PTR_ERR(pcie->clk);
|
||||
}
|
||||
|
||||
pcie->busclk = of_clk_get_by_name(np, "busclk");
|
||||
if (IS_ERR(pcie->busclk)) {
|
||||
clk_put(pcie->clk);
|
||||
return PTR_ERR(pcie->busclk);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void oxnas_pcie_init_hw(struct platform_device *pdev,
|
||||
struct oxnas_pcie *pcie)
|
||||
{
|
||||
u32 version_id;
|
||||
int ret;
|
||||
|
||||
clk_prepare_enable(pcie->busclk);
|
||||
|
||||
/* reset PCIe cards use hard-wired gpio pin */
|
||||
if (pcie->card_reset >= 0 &&
|
||||
!gpio_direction_output(pcie->card_reset, 0)) {
|
||||
wmb();
|
||||
mdelay(10);
|
||||
/* must tri-state the pin to pull it up */
|
||||
gpio_direction_input(pcie->card_reset);
|
||||
wmb();
|
||||
mdelay(100);
|
||||
}
|
||||
|
||||
oxnas_register_set_mask(SYS_CTRL_HCSL_CTRL, BIT(pcie->hcsl_en));
|
||||
|
||||
/* core */
|
||||
ret = device_reset(&pdev->dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "core reset failed %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Start PCIe core clocks */
|
||||
clk_prepare_enable(pcie->clk);
|
||||
|
||||
version_id = readl_relaxed(pcie->base + PCI_CONFIG_VERSION_DEVICEID);
|
||||
dev_info(&pdev->dev, "PCIe version/deviceID 0x%x\n", version_id);
|
||||
|
||||
if (version_id != VERSION_ID_MAGIC) {
|
||||
dev_info(&pdev->dev, "PCIe controller not found\n");
|
||||
pcie->haslink = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/* allow entry to L23 state */
|
||||
oxnas_register_set_mask(pcie->pcie_ctrl, PCIE_READY_ENTR_L23);
|
||||
|
||||
/* Set PCIe core into RootCore mode */
|
||||
oxnas_register_value_mask(pcie->pcie_ctrl, PCIE_DEVICE_TYPE_MASK,
|
||||
PCIE_DEVICE_TYPE_ROOT);
|
||||
wmb();
|
||||
|
||||
/* Bring up the PCI core */
|
||||
oxnas_register_set_mask(pcie->pcie_ctrl, PCIE_LTSSM);
|
||||
wmb();
|
||||
}
|
||||
|
||||
static int __init oxnas_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct oxnas_pcie *pcie;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
int ret;
|
||||
|
||||
pcie = devm_kzalloc(&pdev->dev, sizeof(struct oxnas_pcie),
|
||||
GFP_KERNEL);
|
||||
if (!pcie)
|
||||
return -ENOMEM;
|
||||
|
||||
pcie->pdev = pdev;
|
||||
pcie->haslink = 1;
|
||||
spin_lock_init(&pcie->lock);
|
||||
|
||||
ret = oxnas_pcie_init_res(pdev, pcie, np);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (pcie->card_reset >= 0) {
|
||||
ret = gpio_request_one(pcie->card_reset, GPIOF_DIR_IN,
|
||||
dev_name(&pdev->dev));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "cannot request gpio pin %d\n",
|
||||
pcie->card_reset);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = oxnas_pcie_map_registers(pdev, np, pcie);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "cannot map registers\n");
|
||||
goto err_free_gpio;
|
||||
}
|
||||
|
||||
ret = oxnas_pcie_shared_init(pdev);
|
||||
if (ret)
|
||||
goto err_free_gpio;
|
||||
|
||||
/* if hw not found, haslink cleared */
|
||||
oxnas_pcie_init_hw(pdev, pcie);
|
||||
|
||||
if (pcie->haslink && oxnas_pcie_link_up(pcie)) {
|
||||
pcie->haslink = 1;
|
||||
dev_info(&pdev->dev, "link up\n");
|
||||
} else {
|
||||
pcie->haslink = 0;
|
||||
dev_info(&pdev->dev, "link down\n");
|
||||
}
|
||||
/* should we register our controller even when pcie->haslink is 0 ? */
|
||||
/* register the controller with framework */
|
||||
oxnas_pcie_enable(&pdev->dev, pcie);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_gpio:
|
||||
if (pcie->card_reset)
|
||||
gpio_free(pcie->card_reset);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id oxnas_pcie_of_match_table[] = {
|
||||
{ .compatible = "plxtech,nas782x-pcie", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, oxnas_pcie_of_match_table);
|
||||
|
||||
static struct platform_driver oxnas_pcie_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "oxnas-pcie",
|
||||
.of_match_table =
|
||||
of_match_ptr(oxnas_pcie_of_match_table),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init oxnas_pcie_init(void)
|
||||
{
|
||||
return platform_driver_probe(&oxnas_pcie_driver,
|
||||
oxnas_pcie_probe);
|
||||
}
|
||||
|
||||
subsys_initcall(oxnas_pcie_init);
|
||||
|
||||
MODULE_AUTHOR("Ma Haijun <mahaijuns@gmail.com>");
|
||||
MODULE_DESCRIPTION("NAS782x PCIe driver");
|
||||
MODULE_LICENSE("GPLv2");
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* 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/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reset-controller.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <mach/hardware.h>
|
||||
|
||||
static int ox820_reset_reset(struct reset_controller_dev *rcdev,
|
||||
unsigned long id)
|
||||
{
|
||||
writel(BIT(id), SYS_CTRL_RST_SET_CTRL);
|
||||
writel(BIT(id), SYS_CTRL_RST_CLR_CTRL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ox820_reset_assert(struct reset_controller_dev *rcdev,
|
||||
unsigned long id)
|
||||
{
|
||||
writel(BIT(id), SYS_CTRL_RST_SET_CTRL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ox820_reset_deassert(struct reset_controller_dev *rcdev,
|
||||
unsigned long id)
|
||||
{
|
||||
writel(BIT(id), SYS_CTRL_RST_CLR_CTRL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct reset_control_ops ox820_reset_ops = {
|
||||
.reset = ox820_reset_reset,
|
||||
.assert = ox820_reset_assert,
|
||||
.deassert = ox820_reset_deassert,
|
||||
};
|
||||
|
||||
static const struct of_device_id ox820_reset_dt_ids[] = {
|
||||
{ .compatible = "plxtech,nas782x-reset", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ox820_reset_dt_ids);
|
||||
|
||||
struct reset_controller_dev rcdev;
|
||||
|
||||
static int ox820_reset_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct reset_controller_dev *rcdev;
|
||||
|
||||
rcdev = devm_kzalloc(&pdev->dev, sizeof(*rcdev), GFP_KERNEL);
|
||||
if (!rcdev)
|
||||
return -ENOMEM;
|
||||
|
||||
/* note: reset controller is statically mapped */
|
||||
|
||||
rcdev->owner = THIS_MODULE;
|
||||
rcdev->nr_resets = 32;
|
||||
rcdev->ops = &ox820_reset_ops;
|
||||
rcdev->of_node = pdev->dev.of_node;
|
||||
reset_controller_register(rcdev);
|
||||
platform_set_drvdata(pdev, rcdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ox820_reset_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct reset_controller_dev *rcdev = platform_get_drvdata(pdev);
|
||||
|
||||
reset_controller_unregister(rcdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ox820_reset_driver = {
|
||||
.probe = ox820_reset_probe,
|
||||
.remove = ox820_reset_remove,
|
||||
.driver = {
|
||||
.name = "ox820-reset",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = ox820_reset_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init ox820_reset_init(void)
|
||||
{
|
||||
return platform_driver_probe(&ox820_reset_driver,
|
||||
ox820_reset_probe);
|
||||
}
|
||||
/*
|
||||
* reset controller does not support probe deferral, so it has to be
|
||||
* initialized before any user, in particular, PCIE uses subsys_initcall.
|
||||
*/
|
||||
arch_initcall(ox820_reset_init);
|
||||
|
||||
MODULE_AUTHOR("Ma Haijun");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -1,316 +0,0 @@
|
||||
/*
|
||||
* drivers/usb/host/ehci-oxnas.c
|
||||
*
|
||||
* Tzachi Perelstein <tzachi@marvell.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/hcd.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/reset.h>
|
||||
#include <mach/hardware.h>
|
||||
#include <mach/utils.h>
|
||||
|
||||
#include "ehci.h"
|
||||
|
||||
struct oxnas_hcd {
|
||||
struct clk *clk;
|
||||
struct clk *refsrc;
|
||||
struct clk *phyref;
|
||||
int use_pllb;
|
||||
int use_phya;
|
||||
struct reset_control *rst_host;
|
||||
struct reset_control *rst_phya;
|
||||
struct reset_control *rst_phyb;
|
||||
};
|
||||
|
||||
#define DRIVER_DESC "Oxnas On-Chip EHCI Host Controller"
|
||||
|
||||
static struct hc_driver __read_mostly oxnas_hc_driver;
|
||||
|
||||
static void start_oxnas_usb_ehci(struct oxnas_hcd *oxnas)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
if (oxnas->use_pllb) {
|
||||
/* enable pllb */
|
||||
clk_prepare_enable(oxnas->refsrc);
|
||||
/* enable ref600 */
|
||||
clk_prepare_enable(oxnas->phyref);
|
||||
/* 600MHz pllb divider for 12MHz */
|
||||
writel(PLLB_DIV_INT(50) | PLLB_DIV_FRAC(0),
|
||||
SEC_CTRL_PLLB_DIV_CTRL);
|
||||
|
||||
} else {
|
||||
/* ref 300 divider for 12MHz */
|
||||
writel(REF300_DIV_INT(25) | REF300_DIV_FRAC(0),
|
||||
SYS_CTRL_REF300_DIV);
|
||||
}
|
||||
|
||||
/* Ensure the USB block is properly reset */
|
||||
reset_control_reset(oxnas->rst_host);
|
||||
reset_control_reset(oxnas->rst_phya);
|
||||
reset_control_reset(oxnas->rst_phyb);
|
||||
|
||||
/* Force the high speed clock to be generated all the time, via serial
|
||||
programming of the USB HS PHY */
|
||||
writel((2UL << USBHSPHY_TEST_ADD) |
|
||||
(0xe0UL << USBHSPHY_TEST_DIN), SYS_CTRL_USBHSPHY_CTRL);
|
||||
|
||||
writel((1UL << USBHSPHY_TEST_CLK) |
|
||||
(2UL << USBHSPHY_TEST_ADD) |
|
||||
(0xe0UL << USBHSPHY_TEST_DIN), SYS_CTRL_USBHSPHY_CTRL);
|
||||
|
||||
writel((0xfUL << USBHSPHY_TEST_ADD) |
|
||||
(0xaaUL << USBHSPHY_TEST_DIN), SYS_CTRL_USBHSPHY_CTRL);
|
||||
|
||||
writel((1UL << USBHSPHY_TEST_CLK) |
|
||||
(0xfUL << USBHSPHY_TEST_ADD) |
|
||||
(0xaaUL << USBHSPHY_TEST_DIN), SYS_CTRL_USBHSPHY_CTRL);
|
||||
|
||||
if (oxnas->use_pllb) /* use pllb clock */
|
||||
writel(USB_CLK_INTERNAL | USB_INT_CLK_PLLB, SYS_CTRL_USB_CTRL);
|
||||
else /* use ref300 derived clock */
|
||||
writel(USB_CLK_INTERNAL | USB_INT_CLK_REF300,
|
||||
SYS_CTRL_USB_CTRL);
|
||||
|
||||
if (oxnas->use_phya) {
|
||||
/* Configure USB PHYA as a host */
|
||||
reg = readl(SYS_CTRL_USB_CTRL);
|
||||
reg &= ~USBAMUX_DEVICE;
|
||||
writel(reg, SYS_CTRL_USB_CTRL);
|
||||
}
|
||||
|
||||
/* Enable the clock to the USB block */
|
||||
clk_prepare_enable(oxnas->clk);
|
||||
}
|
||||
|
||||
static void stop_oxnas_usb_ehci(struct oxnas_hcd *oxnas)
|
||||
{
|
||||
reset_control_assert(oxnas->rst_host);
|
||||
reset_control_assert(oxnas->rst_phya);
|
||||
reset_control_assert(oxnas->rst_phyb);
|
||||
|
||||
if (oxnas->use_pllb) {
|
||||
clk_disable_unprepare(oxnas->phyref);
|
||||
clk_disable_unprepare(oxnas->refsrc);
|
||||
}
|
||||
clk_disable_unprepare(oxnas->clk);
|
||||
}
|
||||
|
||||
static int ehci_oxnas_reset(struct usb_hcd *hcd)
|
||||
{
|
||||
#define txttfill_tuning reserved2[0]
|
||||
|
||||
struct ehci_hcd *ehci;
|
||||
u32 tmp;
|
||||
int retval = ehci_setup(hcd);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
ehci = hcd_to_ehci(hcd);
|
||||
tmp = ehci_readl(ehci, &ehci->regs->txfill_tuning);
|
||||
tmp &= ~0x00ff0000;
|
||||
tmp |= 0x003f0000; /* set burst pre load count to 0x40 (63 * 4 bytes) */
|
||||
tmp |= 0x16; /* set sheduler overhead to 22 * 1.267us (HS) or 22 * 6.33us (FS/LS)*/
|
||||
ehci_writel(ehci, tmp, &ehci->regs->txfill_tuning);
|
||||
|
||||
tmp = ehci_readl(ehci, &ehci->regs->txttfill_tuning);
|
||||
tmp |= 0x2; /* set sheduler overhead to 2 * 6.333us */
|
||||
ehci_writel(ehci, tmp, &ehci->regs->txttfill_tuning);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int ehci_oxnas_drv_probe(struct platform_device *ofdev)
|
||||
{
|
||||
struct device_node *np = ofdev->dev.of_node;
|
||||
struct usb_hcd *hcd;
|
||||
struct ehci_hcd *ehci;
|
||||
struct resource res;
|
||||
struct oxnas_hcd *oxnas;
|
||||
int irq, err;
|
||||
struct reset_control *rstc;
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
if (!ofdev->dev.dma_mask)
|
||||
ofdev->dev.dma_mask = &ofdev->dev.coherent_dma_mask;
|
||||
if (!ofdev->dev.coherent_dma_mask)
|
||||
ofdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
|
||||
|
||||
hcd = usb_create_hcd(&oxnas_hc_driver, &ofdev->dev,
|
||||
dev_name(&ofdev->dev));
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
|
||||
err = of_address_to_resource(np, 0, &res);
|
||||
if (err)
|
||||
goto err_res;
|
||||
|
||||
hcd->rsrc_start = res.start;
|
||||
hcd->rsrc_len = resource_size(&res);
|
||||
|
||||
hcd->regs = devm_ioremap_resource(&ofdev->dev, &res);
|
||||
if (IS_ERR(hcd->regs)) {
|
||||
dev_err(&ofdev->dev, "devm_ioremap_resource failed\n");
|
||||
err = PTR_ERR(hcd->regs);
|
||||
goto err_ioremap;
|
||||
}
|
||||
|
||||
oxnas = (struct oxnas_hcd *)hcd_to_ehci(hcd)->priv;
|
||||
|
||||
oxnas->use_pllb = of_property_read_bool(np, "plxtech,ehci_use_pllb");
|
||||
oxnas->use_phya = of_property_read_bool(np, "plxtech,ehci_use_phya");
|
||||
|
||||
oxnas->clk = of_clk_get_by_name(np, "usb");
|
||||
if (IS_ERR(oxnas->clk)) {
|
||||
err = PTR_ERR(oxnas->clk);
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
if (oxnas->use_pllb) {
|
||||
oxnas->refsrc = of_clk_get_by_name(np, "refsrc");
|
||||
if (IS_ERR(oxnas->refsrc)) {
|
||||
err = PTR_ERR(oxnas->refsrc);
|
||||
goto err_refsrc;
|
||||
}
|
||||
oxnas->phyref = of_clk_get_by_name(np, "phyref");
|
||||
if (IS_ERR(oxnas->refsrc)) {
|
||||
err = PTR_ERR(oxnas->refsrc);
|
||||
goto err_phyref;
|
||||
}
|
||||
|
||||
} else {
|
||||
oxnas->refsrc = NULL;
|
||||
oxnas->phyref = NULL;
|
||||
}
|
||||
|
||||
rstc = devm_reset_control_get(&ofdev->dev, "host");
|
||||
if (IS_ERR(rstc)) {
|
||||
err = PTR_ERR(rstc);
|
||||
goto err_rst;
|
||||
}
|
||||
oxnas->rst_host = rstc;
|
||||
|
||||
rstc = devm_reset_control_get(&ofdev->dev, "phya");
|
||||
if (IS_ERR(rstc)) {
|
||||
err = PTR_ERR(rstc);
|
||||
goto err_rst;
|
||||
}
|
||||
oxnas->rst_phya = rstc;
|
||||
|
||||
rstc = devm_reset_control_get(&ofdev->dev, "phyb");
|
||||
if (IS_ERR(rstc)) {
|
||||
err = PTR_ERR(rstc);
|
||||
goto err_rst;
|
||||
}
|
||||
oxnas->rst_phyb = rstc;
|
||||
|
||||
irq = irq_of_parse_and_map(np, 0);
|
||||
if (!irq) {
|
||||
dev_err(&ofdev->dev, "irq_of_parse_and_map failed\n");
|
||||
err = -EBUSY;
|
||||
goto err_irq;
|
||||
}
|
||||
|
||||
hcd->has_tt = 1;
|
||||
ehci = hcd_to_ehci(hcd);
|
||||
ehci->caps = hcd->regs;
|
||||
|
||||
start_oxnas_usb_ehci(oxnas);
|
||||
|
||||
err = usb_add_hcd(hcd, irq, IRQF_SHARED);
|
||||
if (err)
|
||||
goto err_hcd;
|
||||
|
||||
return 0;
|
||||
|
||||
err_hcd:
|
||||
stop_oxnas_usb_ehci(oxnas);
|
||||
err_irq:
|
||||
err_rst:
|
||||
if (oxnas->phyref)
|
||||
clk_put(oxnas->phyref);
|
||||
err_phyref:
|
||||
if (oxnas->refsrc)
|
||||
clk_put(oxnas->refsrc);
|
||||
err_refsrc:
|
||||
clk_put(oxnas->clk);
|
||||
err_clk:
|
||||
err_ioremap:
|
||||
err_res:
|
||||
usb_put_hcd(hcd);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ehci_oxnas_drv_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
||||
struct oxnas_hcd *oxnas = (struct oxnas_hcd *)hcd_to_ehci(hcd)->priv;
|
||||
|
||||
usb_remove_hcd(hcd);
|
||||
if (oxnas->use_pllb) {
|
||||
clk_disable_unprepare(oxnas->phyref);
|
||||
clk_put(oxnas->phyref);
|
||||
clk_disable_unprepare(oxnas->refsrc);
|
||||
clk_put(oxnas->refsrc);
|
||||
}
|
||||
clk_disable_unprepare(oxnas->clk);
|
||||
usb_put_hcd(hcd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id oxnas_ehci_dt_ids[] = {
|
||||
{ .compatible = "plxtech,nas782x-ehci" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, oxnas_ehci_dt_ids);
|
||||
|
||||
static struct platform_driver ehci_oxnas_driver = {
|
||||
.probe = ehci_oxnas_drv_probe,
|
||||
.remove = ehci_oxnas_drv_remove,
|
||||
.shutdown = usb_hcd_platform_shutdown,
|
||||
.driver.name = "oxnas-ehci",
|
||||
.driver.of_match_table = oxnas_ehci_dt_ids,
|
||||
};
|
||||
|
||||
static const struct ehci_driver_overrides oxnas_overrides __initconst = {
|
||||
.reset = ehci_oxnas_reset,
|
||||
.extra_priv_size = sizeof(struct oxnas_hcd),
|
||||
};
|
||||
|
||||
static int __init ehci_oxnas_init(void)
|
||||
{
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
ehci_init_driver(&oxnas_hc_driver, &oxnas_overrides);
|
||||
return platform_driver_register(&ehci_oxnas_driver);
|
||||
}
|
||||
module_init(ehci_oxnas_init);
|
||||
|
||||
static void __exit ehci_oxnas_cleanup(void)
|
||||
{
|
||||
platform_driver_unregister(&ehci_oxnas_driver);
|
||||
}
|
||||
module_exit(ehci_oxnas_cleanup);
|
||||
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_ALIAS("platform:oxnas-ehci");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user