* [PATCH 3/6] ARM: ux500: Add support for ST-Ericsson's u9540 SoC
From: Jean-Christophe PLAGNIOL-VILLARD @ 2012-10-11 18:11 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20121011092039.GV9707@gmail.com>
On 10:20 Thu 11 Oct , Lee Jones wrote:
> On Thu, 11 Oct 2012, Jean-Christophe PLAGNIOL-VILLARD wrote:
>
> > On 08:38 Thu 11 Oct , Lee Jones wrote:
> > > On Wed, 10 Oct 2012, Jean-Christophe PLAGNIOL-VILLARD wrote:
> > >
> > > > On 16:18 Wed 10 Oct , Lee Jones wrote:
> > > > > First level board support for the u9540.
> > > > >
> > > > > Acked-by: Linus Walleij <linus.walleij@linaro.org>
> > > > > Signed-off-by: Lee Jones <lee.jones@linaro.org>
> > > > > ---
> > > > > arch/arm/mach-ux500/cpu-db8500.c | 14 ++++++++++++++
> > > > > 1 file changed, 14 insertions(+)
> > > > >
> > > > > diff --git a/arch/arm/mach-ux500/cpu-db8500.c b/arch/arm/mach-ux500/cpu-db8500.c
> > > > > index 3d8e321..6b7f970 100644
> > > > > --- a/arch/arm/mach-ux500/cpu-db8500.c
> > > > > +++ b/arch/arm/mach-ux500/cpu-db8500.c
> > > > > @@ -17,6 +17,7 @@
> > > > > #include <linux/platform_device.h>
> > > > > #include <linux/io.h>
> > > > > #include <linux/mfd/abx500/ab8500.h>
> > > > > +#include <linux/mfd/dbx500-prcmu.h>
> > > > > #include <linux/of.h>
> > > > > #include <linux/of_platform.h>
> > > > > #include <linux/regulator/machine.h>
> > > > > @@ -319,6 +320,8 @@ static void __init u8500_init_machine(void)
> > > > > snowball_pinmaps_init();
> > > > > else if (of_machine_is_compatible("st-ericsson,hrefv60+"))
> > > > > hrefv60_pinmaps_init();
> > > > > + else if (of_machine_is_compatible("st-ericsson,u9540")) {}
> > > > > + /* TODO: Add u9540 pinmaps. */
> > > > do you via DT no here
> > >
> > > We don't support pinctrl yet. It will come, it's just not available yet.
> > so stop to add more and update the drviers
>
> We'll get round to it.
>
> We have a set priority list. Neither you nor I have control over that.
yes but code mainline is not driver be politics but by code review
so no do the effort to add the dt to the pinctrl it's 12 hours work so you have
no excuse to delay it again
>
> > and u9540 is a SoC name not a baord name
>
> It's both. The u9540 board is affectionately called the u9540 board.
>
> > > > > /* TODO: Export SoC, USB, cpu-freq and DMA40 */
> > > > > parent = u8500_of_init_devices();
> > > > > @@ -331,6 +334,7 @@ static const char * u8500_dt_board_compat[] = {
> > > > > "calaosystems,snowball-a9500",
> > > > > "st-ericsson,hrefv60+",
> > > > > "st-ericsson,mop500",
> > > > > + "st-ericsson,u9540",
> > > > stop to add more compatible sue a more generic one
> > >
> > > Not entirely sure what you mean here.
> > >
> > > These are the only four boards we officially support.
> > >
> > > There are other uXXXX boards that we do not.
> > you do not need it just mach a global compatbile
>
> Why? What difference does it make? I could understand if we
> had a long list of supported boards, but I think this is move
> verbose and descriptive.
this is already the case simplify it and give a good example in the mainline
code as people just do copy & paste usualy
>
> > > > > NULL,
> > > > > };
> > > > >
> > > > > @@ -345,4 +349,14 @@ DT_MACHINE_START(U8500_DT, "ST-Ericsson U8500 platform (Device Tree Support)")
> > > > > .dt_compat = u8500_dt_board_compat,
> > > > > MACHINE_END
> > > > >
> > > > > +DT_MACHINE_START(U9540_DT, "ST-Ericsson 9540 platform (Device Tree Support)")
> > > > > + .map_io = u8500_map_io,
> > > > > + .init_irq = ux500_init_irq,
> > > > > + .timer = &ux500_timer,
> > > > > + .handle_irq = gic_handle_irq,
> > > > > + .init_machine = u8500_init_machine,
> > > > > + .init_late = NULL,
> > > > > + .dt_compat = u8500_dt_board_compat,
> > > > > +MACHINE_END
> > > > really?
> > > > no need drop it for a more generic on
> > >
> > > This may look the same as the previous MACHINE_START now, but
> > > it will have differences in the future as more u9540 support is
> > > upstreamed. I think it should stick around as a separate entity
> > > for the time being. If we can consolidate the init functions at
> > > a later date, believe me I will make sure it happens.
no sorry add it when it's need not now
Best Regards,
J.
^ permalink raw reply
* [PATCH 4/6] ARM: ux500: Add UART support to the u9540 Device Tree
From: Jean-Christophe PLAGNIOL-VILLARD @ 2012-10-11 18:06 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20121011092222.GW9707@gmail.com>
On 10:22 Thu 11 Oct , Lee Jones wrote:
> On Thu, 11 Oct 2012, Jean-Christophe PLAGNIOL-VILLARD wrote:
>
> > On 08:29 Thu 11 Oct , Lee Jones wrote:
> > > On Wed, 10 Oct 2012, Jean-Christophe PLAGNIOL-VILLARD wrote:
> > >
> > > > On 16:18 Wed 10 Oct , Lee Jones wrote:
> > > > > Add the 3 UART nodes required to enable serial ports on the u9540.
> > > > >
> > > > > Acked-by: Linus Walleij <linus.walleij@linaro.org>
> > > > > Signed-off-by: Lee Jones <lee.jones@linaro.org>
> > > > > ---
> > > > > arch/arm/boot/dts/u9540.dts | 13 ++++++++++++-
> > > > dts? it's dtsi
> > >
> > > No it's not.
> > so the name of the file is wrong s u9540 is a SoC name
>
> The board could actually be called either ccu9540, or just
> u9540. I picked one and went with it. The full name is the
> u9540 development board.
so use ccu9540 as u9540 is the soc name so if we use u9540 as compatible we
expect to mach the soc and not a board
Best Regards,
J.
^ permalink raw reply
* [PATCH 1/2] ARM: nomadik: switch over to using the FSMC driver
From: Jean-Christophe PLAGNIOL-VILLARD @ 2012-10-11 18:04 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1349953632.12014.8.camel@sauron.fi.intel.com>
On 14:07 Thu 11 Oct , Artem Bityutskiy wrote:
> On Thu, 2012-10-04 at 09:28 +0200, Linus Walleij wrote:
> > The Nomadik NAND driver is really just a subset of the existing
> > FSMC driver, so let's switch over to using that driver instead,
> > since it handles more variants of this chip. The callbacks for
> > setting up the chip is doing stuff now handled by the FSMC
> > driver.
> >
> > Cc: Alessandro Rubini <rubini@unipv.it>
> > Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
>
> Pushed both to l2-mtd.git, thanks!
wait on this one Linus said he will send an other version based on my patch
for fmsc
Best Regards,
J.
>
> --
> Best Regards,
> Artem Bityutskiy
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* [PATCH 07/11] fsmc/nand: Provide contiguous buffers to dma
From: viresh kumar @ 2012-10-11 17:07 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CACRpkdYjA8QUh4HqvE1rsnXcGA4WafTkMq_wLNJ__gXEnEYrow@mail.gmail.com>
On Thu, Oct 11, 2012 at 9:36 PM, Linus Walleij <linus.walleij@linaro.org> wrote:
> On Thu, Oct 11, 2012 at 6:15 AM, viresh kumar <viresh.kumar@linaro.org> wrote:
>> I pray that i am wrong here, otherwise i would be thrown out from
s/am/am not/ :(
>> the maintainers list for this driver :)
>>
>> dma_sync_single_for_device() is not doing anything on the buffer, but
>> on the LLI item. Actually it is flushing LLI struct so that DMA h/w can get
>> the correct values.
>
> Sorry no, I'm the one who's wrong...
Glad to hear that. :)
It happens very few times in one's lifetime, that a beginner like me is correct
and an expert like you is not. Just kidding :)
> And the block layer of the subsystem should take care
> of only handing the driver buffers that are contiguous
But why should that be a constraint on block layer? It is working in virtual
space and is concerned about that onlly.
--
viresh
^ permalink raw reply
* alignment faults in 3.6
From: Catalin Marinas @ 2012-10-11 16:59 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20121011103257.GO4625@n2100.arm.linux.org.uk>
On 11 October 2012 11:32, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Thu, Oct 11, 2012 at 12:22:06PM +0200, Eric Dumazet wrote:
>> I took a look, and I dont see why/how gcc could use a ldm instruction
>>
>> Doing so assumed the alignment of the structure was 8 bytes, but its
>> not.
>>
>> Networking stack mandates that IP headers are aligned on 4 bytes
>> boundaries, not 8 bytes.
>
> Err, no. ldm is "load multiple" not "load double". It loads multiple
> 32-bit registers, and its requirement for non-faulting behaviour is for
> the pointer to be 4 byte aligned. However, "load double" requires 8
> byte alignment.
It got better with ARMv6 where LDRD/STRD only require 4 byte alignment
(the only 8 byte alignment is required by LDREXD/STREXD). But on ARMv5
LDRD/STRD 8 byte alignment is indeed required (otherwise
unpredictable).
--
Catalin
^ permalink raw reply
* [PATCH 7/7] ARM: tegra30: cpuidle: add LP2 driver for CPU0
From: Colin Cross @ 2012-10-11 16:48 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <5076F5CB.4020200@wwwdotorg.org>
On Thu, Oct 11, 2012 at 9:37 AM, Stephen Warren <swarren@wwwdotorg.org> wrote:
> On 10/11/2012 05:24 AM, Joseph Lo wrote:
>> On Wed, 2012-10-10 at 06:49 +0800, Stephen Warren wrote:
>>> On 10/08/2012 04:26 AM, Joseph Lo wrote:
>>>> The cpuidle LP2 is a power gating idle mode. It support power gating
>>>> vdd_cpu rail after all cpu cores in LP2. For Tegra30, the CPU0 must
>>>> be last one to go into LP2. We need to take care and make sure whole
>>>> secondary CPUs were in LP2 by checking CPU and power gate status.
>>>> After that, the CPU0 can go into LP2 safely. Then power gating the
>>>> CPU rail.
<snip>
>>>> @@ -85,16 +108,22 @@ static int __cpuinit tegra30_idle_lp2(struct cpuidle_device *dev,
>>>> int index)
>>>> {
>>>> bool entered_lp2 = false;
>>>> + bool last_cpu;
>>>>
>>>> local_fiq_disable();
>>>>
>>>> + last_cpu = tegra_set_cpu_in_lp2(dev->cpu);
>>>> + if (dev->cpu == 0) {
>>>> + if (last_cpu)
>>>> + entered_lp2 = tegra30_idle_enter_lp2_cpu_0(dev, drv,
>>>> + index);
>>>> + else
>>>> + cpu_do_idle();
>>>> + } else {
>>>> entered_lp2 = tegra30_idle_enter_lp2_cpu_n(dev, drv, index);
>>>> + }
>>>
>>> Hmm. That means that if the last CPU to enter LP2 is e.g. CPU1, then
>>> even though all CPUs are now in LP2, the complex as a whole doesn't
>>> enter LP2. Is there a way to make the cluster as a whole enter LP2 in
>>> this case? Isn't that what coupled cpuidle is for?
>>
>> It may look like the coupled cpuidle can satisfy the usage here. But it
>> didn't. Please check the criteria of coupled cpuidle.
>
> What about the first part of the question. What happens if:
>
> CPU3 enters LP2
> CPU2 enters LP2
> CPU0 enters LP2
> CPU1 enters LP2
>
> Since CPU1 is not CPU0, tegra30_idle_enter_lp2_cpu_n() is called, and
> hence I think the whole CPU complex is never rail-gated (just each CPU
> is power-gated) even though all CPUs are in LP2 and the complex could be
> rail-gated. Isn't this missing out on power-savings?
>
> So, we either need to:
>
> a) Make tegra30_idle_enter_lp2_cpu_n() rail-gate if the last CPU is
> entering LP2, and then I'm not sure the implementation would be any
> different to tegra30_idle_enter_lp2_cpu_0, would it?
>
> b) If CPUn can't trigger rail-gating, then when CPUn is the last to
> enter LP2 of the whole complex, it needs to IPI to CPU0 to tell it to
> rail-gate, and simply power-gate itself. I believe this IPI interaction
> is exactly what coupled cpuidle is about, isn't it?
>
>> /*
>> * To use coupled cpuidle states, a cpuidle driver must:
>> *
>> * Set struct cpuidle_device.coupled_cpus to the mask of all
>> * coupled cpus, usually the same as cpu_possible_mask if all cpus
>> * are part of the same cluster. The coupled_cpus mask must be
>> * set in the struct cpuidle_device for each cpu.
>> *
>> * Set struct cpuidle_device.safe_state to a state that is not a
>> * coupled state. This is usually WFI.
>> *
>> * Set CPUIDLE_FLAG_COUPLED in struct cpuidle_state.flags for each
>> * state that affects multiple cpus.
>> *
>> * Provide a struct cpuidle_state.enter function for each state
>> * that affects multiple cpus. This function is guaranteed to be
>> * called on all cpus at approximately the same time. The driver
>> * should ensure that the cpus all abort together if any cpu tries
>> * to abort once the function is called. The function should return
>> * with interrupts still disabled.
>> */
>>
>> The Tegra30 can support the secondary CPUs go into LP2 (power-gate)
>> independently.
>
> I think that just means that the safe state for CPUn (i.e. not CPU0) can
> do better than WFI on Tegra30, even though it can't on Tegra20.
Exactly.
>> The limitation of the CPU0 is the CPU0 must be the last
>> one to go into LP2 to shut off CPU rail.
>>
>> It also no need for every CPU to leave LP2 at the same time. The CPU0
>> would be always the first one that woken up from LP2. But all the other
>> secondary CPUs can still keep in LP2. One of the secondary CPUs can also
>> be woken up alone, if the CPU0 already up.
>
> That seems like an implementation detail. Perhaps coupled cpuidle needs
> to be enhanced to best support Tegra30?
As is, coupled cpuidle will work on Tegra30, but it will unnecessarily
wake up the secondary cpus during the transitions to off and back on
again. Those cpus will immediately go back to single-cpu LP2, so it
may not be a big deal, but there is a small optimization I've
discussed with a few other people that could avoid waking them up. I
suggest adding an extra pre-idle hook to the Tegra30 that is called by
coupled cpuidle on the last cpu to go down. It would return a cpumask
of cpus that have been prepared for idle by guaranteeing that they
will not wake up from an interrupt, and therefore don't need to be
woken up for the transitions. I haven't worked with a cpu that needs
this optimization yet, so I haven't done it.
^ permalink raw reply
* [PATCH v7] Initialize USB on dm365 EVM
From: Constantine Shulyupin @ 2012-10-11 16:44 UTC (permalink / raw)
To: linux-arm-kernel
From: Constantine Shulyupin <const@MakeLinux.com>
Call USB initialization davinci_setup_usb from board initialization dm365_evm_init.
Tested with OTG configuration, usb gadget g_zero on DM365 EVM connected to PC.
Note: register USB_PHY_CTRL must have flag USBPHY_CLKFREQ_24MHZ
References:
Original patch by miguel.aguilar at ridgerun.com three years ago:
- http://www.mail-archive.com/davinci-linux-open-source at linux.davincidsp.com/msg14741.html
Signed-off-by: Constantine Shulyupin <const@MakeLinux.com>
---
Changelog
Changes since v6
- patch splitted accordinly request of Sergei
- this split contains call to davinci_setup_usb from dm365_evm_init
Changes since v5 http://www.spinics.net/lists/kernel/msg1413120.html
accordingy feedback of nsekhar at ti.com http://www.spinics.net/lists/kernel/msg1414914.html
- phy configuration moved to drivers/usb/musb/davinci.c
- USB_OTG configuration is submitted in separated patch: http://www.spinics.net/lists/kernel/msg1414964.html
- Setting current limit to 1000 mA. Any way the current is limited to 510 mA in davinci_setup_usb.
Changes since v4 http://www.spinics.net/lists/kernel/msg1412995.html
- removed fix of dev_info in musb_init_controller
Changes since v3 http://www.spinics.net/lists/kernel/msg1412544.html:
- removed optional altering of pr_info
Changes since v1 http://marc.info/?l=linux-kernel&m=130894150803661&w=2:
- removed optional code and reordered
- removed alternation of GPIO33, which is multiplexed with DRVVBUS, because is not need for peripheral USB
This patch is based on code from projects Arago, Angstom and RidgeRun.
---
arch/arm/mach-davinci/board-dm365-evm.c | 2 ++
diff --git a/arch/arm/mach-davinci/board-dm365-evm.c b/arch/arm/mach-davinci/board-dm365-evm.c
index 688a9c5..ba5ffc1 100644
--- a/arch/arm/mach-davinci/board-dm365-evm.c
+++ b/arch/arm/mach-davinci/board-dm365-evm.c
@@ -38,6 +38,7 @@
#include <mach/mmc.h>
#include <mach/nand.h>
#include <mach/keyscan.h>
+#include <mach/usb.h>
#include <media/tvp514x.h>
@@ -610,6 +611,7 @@ static __init void dm365_evm_init(void)
dm365_init_spi0(BIT(0), dm365_evm_spi_info,
ARRAY_SIZE(dm365_evm_spi_info));
+ davinci_setup_usb(1000, 8);
}
MACHINE_START(DAVINCI_DM365_EVM, "DaVinci DM365 EVM")
--
1.7.9.5
^ permalink raw reply related
* [PATCH v2 1/4] net: mvneta: driver for Marvell Armada 370/XP network unit
From: Thomas Petazzoni @ 2012-10-11 16:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <5076E9F1.60200@gmail.com>
Rob,
On Thu, 11 Oct 2012 10:46:57 -0500, Rob Herring wrote:
> > +Required properties:
> > +- compatible: should be "marvell,neta".
>
> This should be more specific such as "marvell,armada-xp-neta".
>
> Or use 370 instead of xp. It should be which ever chip came first.
>
> > +- reg: address and length of the register set for the device.
> > +- interrupts: interrupt for the device
> > +- phy-mode: String, operation mode of the PHY interface. Supported
> > + values are "sgmii" and "rmii".
> > +- phy-addr: Integer, address of the PHY.
> > +- device_type: should be "network".
>
> Drop this. device_type is not used for FDT.
>
> > +- clock-frequency: frequency of the peripheral clock of the SoC.
> > +
> > +Example:
> > +
> > +eth at d0070000 {
>
> Should be ethernet at ...
Thanks for your comments. Will fix in v3.
Best regards,
Thomas
--
Thomas Petazzoni, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
^ permalink raw reply
* [PATCH 7/7] ARM: tegra30: cpuidle: add LP2 driver for CPU0
From: Stephen Warren @ 2012-10-11 16:37 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1349954685.19413.207.camel@jlo-ubuntu-64.nvidia.com>
On 10/11/2012 05:24 AM, Joseph Lo wrote:
> On Wed, 2012-10-10 at 06:49 +0800, Stephen Warren wrote:
>> On 10/08/2012 04:26 AM, Joseph Lo wrote:
>>> The cpuidle LP2 is a power gating idle mode. It support power gating
>>> vdd_cpu rail after all cpu cores in LP2. For Tegra30, the CPU0 must
>>> be last one to go into LP2. We need to take care and make sure whole
>>> secondary CPUs were in LP2 by checking CPU and power gate status.
>>> After that, the CPU0 can go into LP2 safely. Then power gating the
>>> CPU rail.
>>
>>> diff --git a/arch/arm/mach-tegra/cpuidle-tegra30.c b/arch/arm/mach-tegra/cpuidle-tegra30.c
>>
>>> +static bool tegra30_idle_enter_lp2_cpu_0(struct cpuidle_device *dev,
>>> + struct cpuidle_driver *drv,
>>> + int index)
>>> +{
>>> + struct cpuidle_state *state = &drv->states[index];
>>> + u32 cpu_on_time = state->exit_latency;
>>> + u32 cpu_off_time = state->target_residency - state->exit_latency;
>>> +
>>> + if (num_online_cpus() > 1 && !tegra_cpu_rail_off_ready()) {
>>
>> Should that be || not &&?
>>
>> Isn't the "num_online_cpus() > 1" condition effectively checked at the
>> call site, i.e. in tegra30_idle_lp2() below via the if (last_cpu) check?
>>
>
> Should be "&&" here.
> Because we need to check if there are still multi CPUs online, then we
> need to make sure all the secondary CPUs be power gated first. After all
> the secondary CPUs been power gated, the CPU0 could go into LP2 and the
> CPU rail could be shut off.
> If all the secondary CPUs been hot plugged, then the "num_online_cpus()
>> 1" would be always false. Then the CPU0 can go into LP2 directly.
>
> So it was used to check are there multi cpus online or not? It's
> difference with the last_cpu check below. The last_cpu was used to check
> all the CPUs were in LP2 process or not. If the CPU0 is the last one
> went into LP2 process, then it would be true.
>
> So the point here is. We can avoid to check the power status of the
> secodarys CPUs if they be unplugged.
OK, so this condition is about ignoring the result of
tegra_cpu_rail_off_ready() if there is only 1 CPU online. That makes
sense, since we know in that case there cannot be any other CPUs to
check if they're in LP2 or not.
But what about the case where 2 CPUs are online and 2 offline. In that
case, num_online_cpus() > 1, so the call to tegra_cpu_rail_off_ready()
is skipped. Yet, with 2 CPUs online, we do need to check whichever other
CPU is online to see if it's in LP2 or not.
I think what we need to do is the following:
cpus_in_lp2_mask = generate_mask_from_pmc_registers();
if (cpus_in_lp2_mask != cpus_online_mask) {
cpu_do_idle();
return;
}
enter lp2;
right?
>>> @@ -85,16 +108,22 @@ static int __cpuinit tegra30_idle_lp2(struct cpuidle_device *dev,
>>> int index)
>>> {
>>> bool entered_lp2 = false;
>>> + bool last_cpu;
>>>
>>> local_fiq_disable();
>>>
>>> + last_cpu = tegra_set_cpu_in_lp2(dev->cpu);
>>> + if (dev->cpu == 0) {
>>> + if (last_cpu)
>>> + entered_lp2 = tegra30_idle_enter_lp2_cpu_0(dev, drv,
>>> + index);
>>> + else
>>> + cpu_do_idle();
>>> + } else {
>>> entered_lp2 = tegra30_idle_enter_lp2_cpu_n(dev, drv, index);
>>> + }
>>
>> Hmm. That means that if the last CPU to enter LP2 is e.g. CPU1, then
>> even though all CPUs are now in LP2, the complex as a whole doesn't
>> enter LP2. Is there a way to make the cluster as a whole enter LP2 in
>> this case? Isn't that what coupled cpuidle is for?
>
> It may look like the coupled cpuidle can satisfy the usage here. But it
> didn't. Please check the criteria of coupled cpuidle.
What about the first part of the question. What happens if:
CPU3 enters LP2
CPU2 enters LP2
CPU0 enters LP2
CPU1 enters LP2
Since CPU1 is not CPU0, tegra30_idle_enter_lp2_cpu_n() is called, and
hence I think the whole CPU complex is never rail-gated (just each CPU
is power-gated) even though all CPUs are in LP2 and the complex could be
rail-gated. Isn't this missing out on power-savings?
So, we either need to:
a) Make tegra30_idle_enter_lp2_cpu_n() rail-gate if the last CPU is
entering LP2, and then I'm not sure the implementation would be any
different to tegra30_idle_enter_lp2_cpu_0, would it?
b) If CPUn can't trigger rail-gating, then when CPUn is the last to
enter LP2 of the whole complex, it needs to IPI to CPU0 to tell it to
rail-gate, and simply power-gate itself. I believe this IPI interaction
is exactly what coupled cpuidle is about, isn't it?
> /*
> * To use coupled cpuidle states, a cpuidle driver must:
> *
> * Set struct cpuidle_device.coupled_cpus to the mask of all
> * coupled cpus, usually the same as cpu_possible_mask if all cpus
> * are part of the same cluster. The coupled_cpus mask must be
> * set in the struct cpuidle_device for each cpu.
> *
> * Set struct cpuidle_device.safe_state to a state that is not a
> * coupled state. This is usually WFI.
> *
> * Set CPUIDLE_FLAG_COUPLED in struct cpuidle_state.flags for each
> * state that affects multiple cpus.
> *
> * Provide a struct cpuidle_state.enter function for each state
> * that affects multiple cpus. This function is guaranteed to be
> * called on all cpus at approximately the same time. The driver
> * should ensure that the cpus all abort together if any cpu tries
> * to abort once the function is called. The function should return
> * with interrupts still disabled.
> */
>
> The Tegra30 can support the secondary CPUs go into LP2 (power-gate)
> independently.
I think that just means that the safe state for CPUn (i.e. not CPU0) can
do better than WFI on Tegra30, even though it can't on Tegra20.
> The limitation of the CPU0 is the CPU0 must be the last
> one to go into LP2 to shut off CPU rail.
>
> It also no need for every CPU to leave LP2 at the same time. The CPU0
> would be always the first one that woken up from LP2. But all the other
> secondary CPUs can still keep in LP2. One of the secondary CPUs can also
> be woken up alone, if the CPU0 already up.
That seems like an implementation detail. Perhaps coupled cpuidle needs
to be enhanced to best support Tegra30?
>>> diff --git a/arch/arm/mach-tegra/pm.c b/arch/arm/mach-tegra/pm.c
>>
>>> +static void set_power_timers(unsigned long us_on, unsigned long us_off)
>>
>>> + if (tegra_pclk == NULL) {
>>> + tegra_pclk = clk_get_sys(NULL, "pclk");
>>> + if (IS_ERR(tegra_pclk)) {
>>> + /*
>>> + * pclk not been init or not exist.
>>> + * Use sclk to take the place of it.
>>> + * The default setting was pclk=sclk.
>>> + */
>>> + tegra_pclk = clk_get_sys(NULL, "sclk");
>>> + }
>>> + }
>>
>> That's a little odd. Surely the HW has pclk or it doesn't? Why use
>> different clocks at different times for what is apparently the same thing?
>
> It just because the "pclk" is not available on the Tegra30's clock
> framework but Tegra20 right now.
We should just fix that instead of working around it then. I assume it's
a simple matter of adding the appropriate clock definition?
^ permalink raw reply
* [PATCH v7] Enable USB peripheral mode on dm365 EVM
From: Constantine Shulyupin @ 2012-10-11 16:34 UTC (permalink / raw)
To: linux-arm-kernel
From: Constantine Shulyupin <const@MakeLinux.com>
Sets USB PHY clock source to 24 MHz clock.
Tested with OTG configuration, usb gadget g_zero on DM365 EVM connected to PC.
To active the patch need to call davinci_setup_usb from dm365_evm_init
References:
Definition of USB_PHY_CTRL and PHYCLKFREQ:
- http://www.makelinux.com/lib/ti/DM36x_ARM/doc-141
Original patch by miguel.aguilar at ridgerun.com three years ago:
- http://www.mail-archive.com/davinci-linux-open-source at linux.davincidsp.com/msg14741.html
Signed-off-by: Constantine Shulyupin <const@MakeLinux.com>
---
Note:
Changelog
Changes since v6
- moved call to davinci_setup_usb from dm365_evm_init to another patch accordinly request of Sergei
Changes since v5 http://www.spinics.net/lists/kernel/msg1413120.html
accordingy feedback of nsekhar at ti.com http://www.spinics.net/lists/kernel/msg1414914.html
- phy configuration moved to drivers/usb/musb/davinci.c
- USB_OTG configuration is submitted in separated patch: http://www.spinics.net/lists/kernel/msg1414964.html
- Setting current limit to 1000 mA. Any way the current is limited to 510 mA in davinci_setup_usb.
Changes since v4 http://www.spinics.net/lists/kernel/msg1412995.html
- removed fix of dev_info in musb_init_controller
Changes since v3 http://www.spinics.net/lists/kernel/msg1412544.html:
- removed optional altering of pr_info
Changes since v1 http://marc.info/?l=linux-kernel&m=130894150803661&w=2:
- removed optional code and reordered
- removed alternation of GPIO33, which is multiplexed with DRVVBUS, because is not need for peripheral USB
This patch is based on code from projects Arago, Angstom and RidgeRun.
---
drivers/usb/musb/davinci.c | 3 +++
drivers/usb/musb/davinci.h | 1 +
diff --git a/drivers/usb/musb/davinci.c b/drivers/usb/musb/davinci.c
index 472c8b4..af09ebf 100644
--- a/drivers/usb/musb/davinci.c
+++ b/drivers/usb/musb/davinci.c
@@ -428,6 +428,9 @@ static int davinci_musb_init(struct musb *musb)
__raw_writel(deepsleep, DM355_DEEPSLEEP);
}
+ if (machine_is_davinci_dm365_evm())
+ writel(readl(USB_PHY_CTRL) | USBPHY_CLKFREQ_24MHZ, USB_PHY_CTRL);
+
/* reset the controller */
musb_writel(tibase, DAVINCI_USB_CTRL_REG, 0x1);
diff --git a/drivers/usb/musb/davinci.h b/drivers/usb/musb/davinci.h
index 371baa0..e737d97 100644
--- a/drivers/usb/musb/davinci.h
+++ b/drivers/usb/musb/davinci.h
@@ -16,6 +16,7 @@
/* Integrated highspeed/otg PHY */
#define USBPHY_CTL_PADDR 0x01c40034
+#define USBPHY_CLKFREQ_24MHZ BIT(13)
#define USBPHY_DATAPOL BIT(11) /* (dm355) switch D+/D- */
#define USBPHY_PHYCLKGD BIT(8)
#define USBPHY_SESNDEN BIT(7) /* v(sess_end) comparator */
--
1.7.9.5
^ permalink raw reply related
* [PATCH 3/7] ARM: tegra30: cpuidle: add LP2 driver for secondary CPUs
From: Stephen Warren @ 2012-10-11 16:24 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1349946918.19413.130.camel@jlo-ubuntu-64.nvidia.com>
On 10/11/2012 03:15 AM, Joseph Lo wrote:
> On Wed, 2012-10-10 at 06:38 +0800, Stephen Warren wrote:
>> On 10/08/2012 04:26 AM, Joseph Lo wrote:
>>> This supports power-gated (LP2) idle on secondary CPUs for Tegra30.
>>> The secondary CPUs can go into LP2 state independently. When CPU goes
>>> into LP2 state, it saves it's state and puts itself to flow controlled
>>> WFI state. After that, it will been power gated.
>>
>>> diff --git a/arch/arm/mach-tegra/cpuidle-tegra30.c b/arch/arm/mach-tegra/cpuidle-tegra30.c
>>
>>> static struct cpuidle_driver tegra_idle_driver = {
>>> .name = "tegra_idle",
>>> .owner = THIS_MODULE,
>>> .en_core_tk_irqen = 1,
>>> - .state_count = 1,
>>> + .state_count = 2,
>>
>> Doesn't that assignment need to be ifdef'd just like the array entry
>> setup below:
>>
>>> .states = {
>>> [0] = ARM_CPUIDLE_WFI_STATE_PWR(600),
>>> +#ifdef CONFIG_PM_SLEEP
>>> + [1] = {
>>> + .enter = tegra30_idle_lp2,
>>> + .exit_latency = 2000,
>>> + .target_residency = 2200,
>>> + .power_usage = 0,
>>> + .flags = CPUIDLE_FLAG_TIME_VALID,
>>> + .name = "LP2",
>>> + .desc = "CPU power-gate",
>>> + },
>>> +#endif
>>> },
>>> };
>>
>>> @@ -41,6 +114,10 @@ int __init tegra30_cpuidle_init(void)
>>> struct cpuidle_device *dev;
>>> struct cpuidle_driver *drv = &tegra_idle_driver;
>>>
>>> +#ifndef CONFIG_PM_SLEEP
>>> + drv->state_count = 1; /* support clockgating only */
>>> +#endif
>>
>> Oh, I see it's done here. Just fixing the static initialization seems a
>> lot simpler?
>>
> OK. Will do.
>
>>> diff --git a/arch/arm/mach-tegra/pm.c b/arch/arm/mach-tegra/pm.c
>>
>>> +void __cpuinit tegra_clear_cpu_in_lp2(int cpu)
>>> +{
>>> + spin_lock(&tegra_lp2_lock);
>>> + BUG_ON(!cpumask_test_cpu(cpu, &tegra_in_lp2));
>>> + cpumask_clear_cpu(cpu, &tegra_in_lp2);
>>> +
>>> + /*
>>> + * Update the IRAM copy used by the reset handler. The IRAM copy
>>> + * can't use used directly by cpumask_clear_cpu() because it uses
>>> + * LDREX/STREX which requires the addressed location to be inner
>>> + * cacheable and sharable which IRAM isn't.
>>> + */
>>> + writel(tegra_in_lp2.bits[0], tegra_cpu_lp2_mask);
>>> + dsb();
>>
>> Why not /just/ store the data in IRAM, and read/write directly to it,
>> rather than maintaining an SDRAM-based copy of it?
>>
>> Then, wouldn't the body of this function be simply:
>>
>> spin_lock();
>> BUG_ON(!(tegra_cpu_lp2_mask & BIT(cpu)));
>> tegra_cpu_lp2_mask |= BIT(cpu);
>> spin_unlock();
>>
>
> It may not simple like this. To maintain it identical to a cpumask. It
> may look likes below. Because I need to compare it with cpu_online_mask.
Oh, the comparison against cpu_online_mask() is what I was missing. I
guess that offline CPUs don't go into LP2, so you can't just check that
tegra_cpu_lp2_mask == (1 << num_cpus()) - 1.
One way to avoid that might be to maintain a cpu_in_lp2_count variable
alongside the mask, and simply compare that against num_online_cpus()
rather than comparing the two masks. At least that would avoid the
following line:
>>> + writel(tegra_in_lp2.bits[0], tegra_cpu_lp2_mask);
... making use of knowledge of the internal structure of the struct
cpumask type.
However, given the comparison requirement, either way is probably fine.
^ permalink raw reply
* alignment faults in 3.6
From: Måns Rullgård @ 2012-10-11 16:18 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <AE90C24D6B3A694183C094C60CF0A2F6026B703E@saturn3.aculab.com>
"David Laight" <David.Laight@ACULAB.COM> writes:
>> Not exactly. It is asked to to perform 2 32-bit loads which are combined
>> into a single ldm (load multiple) which cannot handle unaligned
>> accesses. Here's a simple example that does the same thing:
>>
>> void test(char * buf)
>> {
>> printf("%d, %d\n", *((unsigned int *)&buf[0]), *((unsigned int *)&buf[4]));
>> }
>
> Have you actually looked at what an ARM processor traditionally did
> with misaligned memory reads?
> While useful, it probably wasn't what was intended.
>
> Actually, and IIRC, some very recent ARM cpus will do the 'expected'
> thing for single-word loads from misaligned addesses.
What various CPUs do with unaligned accesses is not the issue here. The
casts in the code above act as a promise to the compiler that the
address is in fact properly align for the pointer type.
> However they almost certainly won't for ldm/stm.
>
> The 'ldm' optimisation for adjacent memory loads is also dubious.
There is nothing whatsoever dubious about the compiler using the most
efficient instruction sequence to accomplish what the code asks for.
> On at least some ARMs it is very slow (might only be strongarms).
The compiler will pick instructions suitable for the CPU you specify.
>> So I guess the only ABI legal unaligned access is in a packed struct.
>
> Correct. And you mustn't try casting the address, the compiler is
> allowed to remember where it came from.
> (This causes a lot of grief...)
It is only a problem when you try to outsmart the compiler.
> If you are targeting the ARM cpu that can do misaligned transfers,
> then gcc should generate single instructions for misaligned structure
> members, and never do the 'ldm' optimisations.
That is exactly how gcc works.
> But, the IP header is expected to be aligned.
Everything tells the compiler the struct is perfectly aligned. When the
buggy driver passes a misaligned pointer, bad things happen.
--
M?ns Rullg?rd
mans at mansr.com
^ permalink raw reply
* alignment faults in 3.6
From: Eric Dumazet @ 2012-10-11 16:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <5076E472.8030703@gmail.com>
On Thu, 2012-10-11 at 10:23 -0500, Rob Herring wrote:
> On 10/11/2012 08:47 AM, Eric Dumazet wrote:
> > Compiler is asked to perform a 32bit load, it does it.
>
> Not exactly. It is asked to to perform 2 32-bit loads which are combined
> into a single ldm (load multiple) which cannot handle unaligned
> accesses. Here's a simple example that does the same thing:
Thats simply not true. You are severely mistaken.
ldm does a load of seeral 32bit words.
And the compiler would not use it if the alignment was not matching the
prereq (alignment >= 4)
>
> void test(char * buf)
> {
> printf("%d, %d\n", *((unsigned int *)&buf[0]), *((unsigned int *)&buf[4]));
> }
But you completely miss the fact that network doesnt pass a "char *buf"
but a "be32 *buf". Your example is not relevant at all.
So the compiler is absolutely right, and network stack is _right_ too.
The prereq is that IP header are aligned to 4 bytes boundary.
Denying this fact is not going to help you
>
> So I guess the only ABI legal unaligned access is in a packed struct.
>
> > There is no questionable optimization here. Really.
> > Please stop pretending this, this makes no sense.
>
> I'm not the one calling the networking stack bad code.
Once you understand the issues, you can explain us where is the bad
code. But given you say "Bug is in compiler, and/or network stack, but
my driver is fine", its not very wise.
For the moment, the bug is in your driver.
>
> I can fix my h/w, so I'll stop caring about this. Others can all get
> bitten by this new behavior in gcc 4.7.
Again compiler is fine. How should we say that so that you stop your
rants ?
Stop trying to find an excuse, dont try to fool us, this is really
embarrassing. Just fix the driver, there is no shame to fix an error.
^ permalink raw reply
* ARM_ATAG_DTB_COMPAT fails on fdt_ro.c compiling
From: Stephen Warren @ 2012-10-11 16:08 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20121011072428.GA26298@S2101-09.ap.freescale.net>
On 10/11/2012 01:24 AM, Shawn Guo wrote:
> On Tue, Oct 09, 2012 at 01:21:02PM -0300, Fabio Estevam wrote:
>> On Tue, Oct 9, 2012 at 1:15 PM, Stephen Warren <swarren@wwwdotorg.org> wrote:
>>
>>>>> I just tried building mainline now. It is at commit f5a246eab9.
>>>>>
>>>> The build still fails for me at this commit.
>>>
>>> I can't reproduce this.
>>
>> Same here. It builds fine for me.
>>
> Thanks all for confirming it works.
>
> I just figured it out on my end. Here is how it gets broken for me.
>
> - Before commit cd29672 (dtc: import latest upstream dtc) gets in,
> I did a build without O=<output_dir>. It results in a copy of those
> libfdt files in arch/arm/boot/compressed.
>
> - After the commit gets in, I start a build with O=imx parameter. When
> compiling imx/arch/arm/boot/compressed/fdt_ro.c, the #include <libfdt.h>
> in fdt_ro.c seems to match the one left in arch/arm/boot/compressed/
> from last build rather than imx/arch/arm/boot/compressed/libfdt.h.
>
> Cleaning the libfdt copies in arch/arm/boot/compressed fixes the
> problem.
> Not sure why it's not "libfdt.h" but <libfdt.h> is used as
> the inclusion though, since the .h and .c are in the same folder.
I imagine that's because the libfdt code is set up to support being
built as a standalone library. In that case, <fdt.h> and <libfdt.h> are
presumably the public interface to the library, and would hence be
installed into /usr/include or similar. Still, I guess the libfdt source
itself would still work with "libfdt.h" rather than <libfdt.h>.
Note that looking at git history, fdt_ro.c included <libfdt.h> even
before cd29672 (dtc: import latest upstream dtc), and
arch/arm/boot/compressed/Makefile included fdt_ro.c before that commit,
so I'm not sure why the problem only shows up now? Perhaps anything that
caused your objects to be stale, and hence get rebuilt, would have
caused the issue.
^ permalink raw reply
* [PATCH 07/11] fsmc/nand: Provide contiguous buffers to dma
From: Linus Walleij @ 2012-10-11 16:06 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAOh2x=ksMr7VRCnpF-rK+Mesg1p4Td++SQ-t=ShrguRRVWSSSQ@mail.gmail.com>
On Thu, Oct 11, 2012 at 6:15 AM, viresh kumar <viresh.kumar@linaro.org> wrote:
> On Wed, Oct 10, 2012 at 10:37 PM, Linus Walleij
> <linus.walleij@linaro.org> wrote:
>> dma_sync_single_for_device() is translating the virtual
>> address to physical for every chunk BTW.
>
> I pray that i am wrong here, otherwise i would be thrown out from
> the maintainers list for this driver :)
>
> dma_sync_single_for_device() is not doing anything on the buffer, but
> on the LLI item. Actually it is flushing LLI struct so that DMA h/w can get
> the correct values.
Sorry no, I'm the one who's wrong...
So the DMA engine memcpy() is not mapping virt->phys
but expects physical addresses to be provided.
So dma_map_single() needs to be called on the stuff
passed in to dev->device_prep_dma_memcpy().
And currently there is indeed a dma_map_single() in
dma_xfer() in fsmc_nand.c which should work just fine.
dma_map_single() will only work if the buffer is
physically contiguous.
And the block layer of the subsystem should take care
of only handing the driver buffers that are contiguous
I think? Not that I'm an expert here ... more some
guesswork :-/
Artem will know I hope!
Yours,
Linus Walleij
^ permalink raw reply
* [PATCH] ARM: decompressor: clear SCTLR.A bit for v7 cores
From: Nicolas Pitre @ 2012-10-11 15:58 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20121011135925.GR4625@n2100.arm.linux.org.uk>
On Thu, 11 Oct 2012, Russell King - ARM Linux wrote:
> On Thu, Oct 11, 2012 at 08:31:47AM -0500, Rob Herring wrote:
> > This only affects v7 cores. It should not vary for v7 cores as unaligned
> > access is a required feature. So how is it going to vary on v7 CPUs?
> > We've got bigger problems if there are v7 cores that don't handle
> > unaligned accesses.
>
> Oh, and this gives me a third reason to NAK this patch. Why only ensure
> that the A bit is clear for v7 CPUs? Why not v6, v5, v4 too? Why does
> ARMv7 get this special treatment?
As I said, gcc knows that ARMv7 can perform word sized accesses even
with misaligned pointers. So when it is passed a pointer marked with
the packed attribute, it will generate a series of byte accesses when
compiling for anything but ARMv7, and use a single ldr or str when
compiling for ARMv7.
Nicolas
^ permalink raw reply
* [PATCH 4/8] cpufreq: db8500: Fetch cpufreq table from platform data
From: Lee Jones @ 2012-10-11 15:49 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20121011134140.GA15428@gmail.com>
On Thu, 11 Oct 2012, Lee Jones wrote:
> > -static struct cpufreq_frequency_table freq_table[] = {
> > - [0] = {
> > - .index = 0,
> > - .frequency = 200000,
> > - },
> > - [1] = {
> > - .index = 1,
> > - .frequency = 400000,
> > - },
> > - [2] = {
> > - .index = 2,
> > - .frequency = 800000,
> > - },
> > - [3] = {
> > - /* Used for MAX_OPP, if available */
> > - .index = 3,
> > - .frequency = CPUFREQ_TABLE_END,
> > - },
> > - [4] = {
> > - .index = 4,
> > - .frequency = CPUFREQ_TABLE_END,
> > - },
> > -};
>
> So where has this table gone? Am I missing a patch?
Ah, I see it now. Nice!
--
Lee Jones
Linaro ST-Ericsson Landing Team Lead
Linaro.org ? Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply
* [PATCH v2 1/4] net: mvneta: driver for Marvell Armada 370/XP network unit
From: Rob Herring @ 2012-10-11 15:46 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1349969282-12676-2-git-send-email-thomas.petazzoni@free-electrons.com>
On 10/11/2012 10:27 AM, Thomas Petazzoni wrote:
> This patch contains a new network driver for the network unit of the
> ARM Marvell Armada 370 and the Armada XP. Both SoCs use the PJ4B
> processor, a Marvell-developed ARM core that implements the ARMv7
> instruction set.
> diff --git a/Documentation/devicetree/bindings/net/marvell-neta.txt b/Documentation/devicetree/bindings/net/marvell-neta.txt
> new file mode 100644
> index 0000000..a031978
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/marvell-neta.txt
> @@ -0,0 +1,24 @@
> +* Marvell Armada 370 / Armada XP Ethernet Controller (NETA)
> +
> +Required properties:
> +- compatible: should be "marvell,neta".
This should be more specific such as "marvell,armada-xp-neta".
Or use 370 instead of xp. It should be which ever chip came first.
> +- reg: address and length of the register set for the device.
> +- interrupts: interrupt for the device
> +- phy-mode: String, operation mode of the PHY interface. Supported
> + values are "sgmii" and "rmii".
> +- phy-addr: Integer, address of the PHY.
> +- device_type: should be "network".
Drop this. device_type is not used for FDT.
> +- clock-frequency: frequency of the peripheral clock of the SoC.
> +
> +Example:
> +
> +eth at d0070000 {
Should be ethernet at ...
Rob
^ permalink raw reply
* [PATCH] ARM: decompressor: clear SCTLR.A bit for v7 cores
From: Nicolas Pitre @ 2012-10-11 15:44 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20121011134130.GQ4625@n2100.arm.linux.org.uk>
On Thu, 11 Oct 2012, Russell King - ARM Linux wrote:
> On Thu, Oct 11, 2012 at 08:31:47AM -0500, Rob Herring wrote:
> > On 10/11/2012 08:09 AM, Russell King - ARM Linux wrote:
> > > On Thu, Oct 11, 2012 at 07:43:22AM -0500, Rob Herring wrote:
> > >> The contents of this were already reviewed on this thread, so I sent this
> > >> to the patch system and this was Russell's reply:
> > >
> > > So that's why I couldn't find it - the mailing list thread has a different
> > > subject line to the patch. Don't do that. Given the amount of list
> > > traffic we have today, that's as good as not having been posted at all.
> > >
> > >>> NAK for two reasons.
> > >>>
> > >>> 1. It hasn't been on the list (I can't find a match for "clear SCTLR.A"
> > >>> in my mailbox)
> > >>>
> > >>> 2. The behaviour of unaligned accesses vary depending on CPU. Some
> > >>> fix-up the access, others load the word and then rotate it. If we have
> > >>> decompressors which perform unaligned accesses, we need to fix this
> > >>> properly to avoid the CPU specific behaviour, rather than tweaking
> > >>> control bits to hide the problem.
> > >>
> > >> I'm simply matching the behavior of the kernel itself. The A bit is cleared
> > >> for v7 kernels and compilers only generate unaligned accesses for v7.
> > >> Without this the initial state of the A bit is undefined as a bootloader
> > >> could have cleared it already. We should document the required state or set
> > >> it to what we want.
> > >
> > > Irrespective of this, (2) still stands. Unaligned accesses in the
> > > decompressor without a fixup (which will be very hard to provide)
> > > will return different data depending on the CPU as I mention in point
> > > 2.
> >
> > This only affects v7 cores. It should not vary for v7 cores as unaligned
> > access is a required feature. So how is it going to vary on v7 CPUs?
> > We've got bigger problems if there are v7 cores that don't handle
> > unaligned accesses.
>
> Rob,
>
> Your patch may only affect v7 cores, but you've raised the issue of the
> decompressor performing unaligned accesses in general. Shall I re-repeat
> my point over that or is the problem here going to finally sink in?
The decompressor is not performing direct unaligned accesses. It uses
the get_unaligned() and put_unaligned() accessors. That means that
we're in control of how this is happening.
So let's talk about the how. On pre ARMv7, those accesses are performed
with a series of byte accesses. When compiling for ARMv7, gcc knows and
that the hardware can do unaligned accesses, and it does optimize its
output by using ldr/str instructions. But the A bit has to be cleared
in that case, and only in that case. This is why the patch clears the A
bit only for ARMv7.
So this patch is only setting up the hardware to match gcc's
expectations when generating code from the use of get_unaligned() and
put_unaligned() when optimizing for ARMv7.
As always, any code doing unaligned access and _not_ using those
accessors is broken.
Nicolas
^ permalink raw reply
* alignment faults in 3.6
From: David Laight @ 2012-10-11 15:39 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <5076E472.8030703@gmail.com>
> Not exactly. It is asked to to perform 2 32-bit loads which are combined
> into a single ldm (load multiple) which cannot handle unaligned
> accesses. Here's a simple example that does the same thing:
>
> void test(char * buf)
> {
> printf("%d, %d\n", *((unsigned int *)&buf[0]), *((unsigned int *)&buf[4]));
> }
Have you actually looked at what an ARM processor traditionally did
with misaligned memory reads?
While useful, it probably wasn't what was intended.
Actually, and IIRC, some very recent ARM cpus will do the 'expected'
thing for single-word loads from misaligned addesses.
However they almost certainly won't for ldm/stm.
The 'ldm' optimisation for adjacent memory loads is also dubious.
On at least some ARMs it is very slow (might only be strongarms).
> So I guess the only ABI legal unaligned access is in a packed struct.
Correct. And you mustn't try casting the address, the compiler is
allowed to remember where it came from.
(This causes a lot of grief...)
If you are targeting the ARM cpu that can do misaligned transfers,
then gcc should generate single instructions for misaligned structure
members, and never do the 'ldm' optimisations.
But, the IP header is expected to be aligned.
David
^ permalink raw reply
* [PATCH] Boottime: A tool for automatic measurement of kernel/bootloader boot time
From: Lee Jones @ 2012-10-11 15:36 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAH9NwWe+s1p73Jwa=s5aY_KjGS2nvP8LR2YH3i5gtpFMUYMiuw@mail.gmail.com>
On Thu, 11 Oct 2012, Christian Gmeiner wrote:
> 2012/10/11 Lee Jones <lee.jones@linaro.org>:
> > From: Jonas Aaberg <jonas.aberg@stericsson.com>
> >
> > The overhead is very low and the results will be found under
> > sysfs/bootime, as well as detailed results in debugfs under
> > boottime/. The bootgraph* files are compatible with
> > scripts/bootgraph.pl. The reason for this patch is to provide
> > data (sysfs/boottime) suitable for automatic testcases as
> > well as help for developers to reduce the boot time (debugfs).
> >
>
> Nice idea... what about x86?
What about it?
You want to extend the functionality to include it? :)
--
Lee Jones
Linaro ST-Ericsson Landing Team Lead
Linaro.org ? Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply
* [PATCH v2 4/4] arm: mvebu: enable Ethernet controllers on Armada 370/XP eval boards
From: Thomas Petazzoni @ 2012-10-11 15:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1349969282-12676-1-git-send-email-thomas.petazzoni@free-electrons.com>
This patch enables the two network interfaces of the Armada 370
official Marvell evaluation platform, and the four network interfaces
of the Armada XP official Marvell evaluation platform.
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
---
arch/arm/boot/dts/armada-370-db.dts | 12 ++++++++++++
arch/arm/boot/dts/armada-xp-db.dts | 24 ++++++++++++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/arch/arm/boot/dts/armada-370-db.dts b/arch/arm/boot/dts/armada-370-db.dts
index fffd5c2..bb18b0d 100644
--- a/arch/arm/boot/dts/armada-370-db.dts
+++ b/arch/arm/boot/dts/armada-370-db.dts
@@ -38,5 +38,17 @@
clock-frequency = <600000000>;
status = "okay";
};
+ eth at d0070000 {
+ clock-frequency = <200000000>;
+ status = "okay";
+ phy-mode = "rgmii";
+ phy-addr = <0>;
+ };
+ eth at d0074000 {
+ clock-frequency = <200000000>;
+ status = "okay";
+ phy-mode = "rgmii";
+ phy-addr = <1>;
+ };
};
};
diff --git a/arch/arm/boot/dts/armada-xp-db.dts b/arch/arm/boot/dts/armada-xp-db.dts
index f97040d..d1a4173 100644
--- a/arch/arm/boot/dts/armada-xp-db.dts
+++ b/arch/arm/boot/dts/armada-xp-db.dts
@@ -46,5 +46,29 @@
clock-frequency = <250000000>;
status = "okay";
};
+ eth at d0070000 {
+ clock-frequency = <250000000>;
+ status = "okay";
+ phy-mode = "rgmii";
+ phy-addr = <0>;
+ };
+ eth at d0074000 {
+ clock-frequency = <250000000>;
+ status = "okay";
+ phy-mode = "rgmii";
+ phy-addr = <1>;
+ };
+ eth at d0030000 {
+ clock-frequency = <250000000>;
+ status = "okay";
+ phy-mode = "sgmii";
+ phy-addr = <25>;
+ };
+ eth at d0034000 {
+ clock-frequency = <250000000>;
+ status = "okay";
+ phy-mode = "sgmii";
+ phy-addr = <27>;
+ };
};
};
--
1.7.9.5
^ permalink raw reply related
* [PATCH v2 3/4] arm: mvebu: add Ethernet controllers using mvneta driver for Armada 370/XP
From: Thomas Petazzoni @ 2012-10-11 15:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1349969282-12676-1-git-send-email-thomas.petazzoni@free-electrons.com>
The Armada 370 SoC has two network units, while the Armada XP has four
network units. The first two network units are common to both the
Armada XP and Armada 370, so they are added to armada-370-xp.dtsi,
while the other two network units are specific to the Armada XP and
therefore added to armada-xp.dtsi.
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
---
arch/arm/boot/dts/armada-370-xp.dtsi | 16 ++++++++++++++++
arch/arm/boot/dts/armada-xp.dtsi | 16 ++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/arch/arm/boot/dts/armada-370-xp.dtsi b/arch/arm/boot/dts/armada-370-xp.dtsi
index 6b6b932..52af710 100644
--- a/arch/arm/boot/dts/armada-370-xp.dtsi
+++ b/arch/arm/boot/dts/armada-370-xp.dtsi
@@ -63,6 +63,22 @@
reg = <0xd0020300 0x30>;
interrupts = <37>, <38>, <39>, <40>;
};
+
+ eth at d0070000 {
+ compatible = "marvell,neta";
+ reg = <0xd0070000 0x2500>;
+ interrupts = <8>;
+ device_type = "network";
+ status = "disabled";
+ };
+
+ eth at d0074000 {
+ compatible = "marvell,neta";
+ reg = <0xd0074000 0x2500>;
+ interrupts = <10>;
+ device_type = "network";
+ status = "disabled";
+ };
};
};
diff --git a/arch/arm/boot/dts/armada-xp.dtsi b/arch/arm/boot/dts/armada-xp.dtsi
index 71d6b5d..59167dc 100644
--- a/arch/arm/boot/dts/armada-xp.dtsi
+++ b/arch/arm/boot/dts/armada-xp.dtsi
@@ -51,5 +51,21 @@
compatible = "marvell,armada-370-xp-system-controller";
reg = <0xd0018200 0x500>;
};
+
+ eth at d0030000 {
+ compatible = "marvell,neta";
+ reg = <0xd0030000 0x2500>;
+ interrupts = <12>;
+ device_type = "network";
+ status = "disabled";
+ };
+
+ eth at d0034000 {
+ compatible = "marvell,neta";
+ reg = <0xd0034000 0x2500>;
+ interrupts = <14>;
+ device_type = "network";
+ status = "disabled";
+ };
};
};
--
1.7.9.5
^ permalink raw reply related
* [PATCH v2 2/4] net: mvneta: update MAINTAINERS file for the mvneta maintainers
From: Thomas Petazzoni @ 2012-10-11 15:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1349969282-12676-1-git-send-email-thomas.petazzoni@free-electrons.com>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
---
MAINTAINERS | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 9a6c4da..163533c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4438,6 +4438,12 @@ S: Maintained
F: drivers/net/ethernet/marvell/mv643xx_eth.*
F: include/linux/mv643xx.h
+MARVELL MVNETA ETHERNET DRIVER
+M: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+L: netdev at vger.kernel.org
+S: Maintained
+F: drivers/net/ethernet/marvell/mvneta.*
+
MARVELL MWIFIEX WIRELESS DRIVER
M: Bing Zhao <bzhao@marvell.com>
L: linux-wireless at vger.kernel.org
--
1.7.9.5
^ permalink raw reply related
* [PATCH v2 1/4] net: mvneta: driver for Marvell Armada 370/XP network unit
From: Thomas Petazzoni @ 2012-10-11 15:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1349969282-12676-1-git-send-email-thomas.petazzoni@free-electrons.com>
This patch contains a new network driver for the network unit of the
ARM Marvell Armada 370 and the Armada XP. Both SoCs use the PJ4B
processor, a Marvell-developed ARM core that implements the ARMv7
instruction set.
Compared to previous ARM Marvell SoCs (Kirkwood, Orion, Discovery),
the network unit in Armada 370 and Armada XP is highly different. This
is the reason why this new 'mvneta' driver is needed, while the older
ARM Marvell SoCs use the 'mv643xx_eth' driver.
Here is an overview of the most important hardware changes that
require a new, specific, driver for the network unit of Armada 370/XP:
- The new network unit has a completely different design and layout
for the RX and TX descriptors. They are now organized as a simple
array (each RX and TX queue has base address and size of this
array) rather than a linked list as in the old SoCs.
- The new network unit has a different RXQ and TXQ management: this
management is done using special read/write counter registers,
while in the Old SocS, it was done using the Ownership bit in RX
and TX descriptors.
- The new network unit has different interrupt registers
- The new network unit way of cleaning of interrupts is not done by
writing to the cause register, but by updating per-queue counters
- The new network unit has different GMAC registers (link, speed,
duplex configuration) and different WRR registers.
- The new network unit has lots of new units like PnC (Parser and
Classifier), PMT, BM (Memory Buffer Management), xPON, and more.
The driver proposed in the current patch only handles the basic
features. Additional hardware features will progressively be supported
as needed.
This code has originally been written by Rami Rosen
<rosenr@marvell.com>, and then reviewed and cleaned up by Thomas
Petazzoni <thomas.petazzoni@free-electrons.com>.
Signed-off-by: Rami Rosen <rosenr@marvell.com>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
---
.../devicetree/bindings/net/marvell-neta.txt | 24 +
drivers/net/ethernet/marvell/Kconfig | 12 +
drivers/net/ethernet/marvell/Makefile | 1 +
drivers/net/ethernet/marvell/mvneta.c | 3054 ++++++++++++++++++++
4 files changed, 3091 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/marvell-neta.txt
create mode 100644 drivers/net/ethernet/marvell/mvneta.c
diff --git a/Documentation/devicetree/bindings/net/marvell-neta.txt b/Documentation/devicetree/bindings/net/marvell-neta.txt
new file mode 100644
index 0000000..a031978
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/marvell-neta.txt
@@ -0,0 +1,24 @@
+* Marvell Armada 370 / Armada XP Ethernet Controller (NETA)
+
+Required properties:
+- compatible: should be "marvell,neta".
+- reg: address and length of the register set for the device.
+- interrupts: interrupt for the device
+- phy-mode: String, operation mode of the PHY interface. Supported
+ values are "sgmii" and "rmii".
+- phy-addr: Integer, address of the PHY.
+- device_type: should be "network".
+- clock-frequency: frequency of the peripheral clock of the SoC.
+
+Example:
+
+eth at d0070000 {
+ compatible = "marvell,neta";
+ reg = <0xd0070000 0x2500>;
+ interrupts = <8>;
+ device_type = "network";
+ clock-frequency = <250000000>;
+ status = "okay";
+ phy-mode = "sgmii";
+ phy-addr = <25>;
+};
diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig
index 0029934..7bdc5da 100644
--- a/drivers/net/ethernet/marvell/Kconfig
+++ b/drivers/net/ethernet/marvell/Kconfig
@@ -18,6 +18,18 @@ config NET_VENDOR_MARVELL
if NET_VENDOR_MARVELL
+config MVNETA
+ tristate "Marvell Armada 370/XP network interface support"
+ depends on MACH_ARMADA_370_XP
+ select PHYLIB
+ ---help---
+ This driver supports the network interface units in the
+ Marvell ARMADA XP and ARMADA 370 SoC family.
+
+ Note that this driver is distinct from the mv643xx_eth
+ driver, which should be used for the older Marvell SoCs
+ (Dove, Orion, Discovery, Kirkwood).
+
config MV643XX_ETH
tristate "Marvell Discovery (643XX) and Orion ethernet support"
depends on (MV64X60 || PPC32 || PLAT_ORION) && INET
diff --git a/drivers/net/ethernet/marvell/Makefile b/drivers/net/ethernet/marvell/Makefile
index 57e3234..a13f9b9 100644
--- a/drivers/net/ethernet/marvell/Makefile
+++ b/drivers/net/ethernet/marvell/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
obj-$(CONFIG_PXA168_ETH) += pxa168_eth.o
obj-$(CONFIG_SKGE) += skge.o
obj-$(CONFIG_SKY2) += sky2.o
+obj-$(CONFIG_MVNETA) += mvneta.o
diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
new file mode 100644
index 0000000..4f7fe08
--- /dev/null
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -0,0 +1,3054 @@
+/*
+ * Driver for Marvell NETA network card for Armada XP and Armada 370 SoCs.
+ *
+ * Copyright (C) 2012 Marvell
+ *
+ * Rami Rosen <rosenr@marvell.com>
+ * Thomas Petazzoni <thomas.petazzoni@free-electrons.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/version.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/platform_device.h>
+#include <linux/skbuff.h>
+#include <linux/inetdevice.h>
+#include <linux/mbus.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_net.h>
+#include <linux/of_address.h>
+#include <linux/phy.h>
+
+/* Registers */
+#define MVNETA_RXQ_CONFIG_REG(q) (0x1400 + ((q) << 2))
+#define MVNETA_RXQ_HW_BUF_ALLOC BIT(1)
+#define MVNETA_RXQ_PKT_OFFSET_ALL_MASK (0xf << 8)
+#define MVNETA_RXQ_PKT_OFFSET_MASK(offs) ((offs) << 8)
+#define MVNETA_RXQ_THRESHOLD_REG(q) (0x14c0 + ((q) << 2))
+#define MVNETA_RXQ_NON_OCCUPIED(v) ((v) << 16)
+#define MVNETA_RXQ_BASE_ADDR_REG(q) (0x1480 + ((q) << 2))
+#define MVNETA_RXQ_SIZE_REG(q) (0x14a0 + ((q) << 2))
+#define MVNETA_RXQ_BUF_SIZE_SHIFT 19
+#define MVNETA_RXQ_BUF_SIZE_MASK (0x1fff << 19)
+#define MVNETA_RXQ_STATUS_REG(q) (0x14e0 + ((q) << 2))
+#define MVNETA_RXQ_OCCUPIED_ALL_MASK 0x3fff
+#define MVNETA_RXQ_STATUS_UPDATE_REG(q) (0x1500 + ((q) << 2))
+#define MVNETA_RXQ_ADD_NON_OCCUPIED_SHIFT 16
+#define MVNETA_RXQ_ADD_NON_OCCUPIED_MAX 255
+#define MVNETA_PORT_RX_RESET 0x1cc0
+#define MVNETA_PORT_RX_DMA_RESET BIT(0)
+#define MVNETA_PHY_ADDR 0x2000
+#define MVNETA_PHY_ADDR_MASK 0x1f
+#define MVNETA_SMI 0x2004
+#define MVNETA_SMI_DATA_SHIFT 0
+#define MVNETA_SMI_PHY_ADDR_SHIFT 16
+#define MVNETA_SMI_PHY_REG_SHIFT 21
+#define MVNETA_SMI_READ_OPERATION BIT(26)
+#define MVNETA_SMI_WRITE_OPERATION 0
+#define MVNETA_SMI_READ_VALID BIT(27)
+#define MVNETA_SMI_BUSY BIT(28)
+#define MVNETA_MBUS_RETRY 0x2010
+#define MVNETA_UNIT_INTR_CAUSE 0x2080
+#define MVNETA_UNIT_CONTROL 0x20B0
+#define MVNETA_PHY_POLLING_ENABLE BIT(1)
+#define MVNETA_WIN_BASE(w) (0x2200 + ((w) << 3))
+#define MVNETA_WIN_SIZE(w) (0x2204 + ((w) << 3))
+#define MVNETA_WIN_REMAP(w) (0x2280 + ((w) << 2))
+#define MVNETA_BASE_ADDR_ENABLE 0x2290
+#define MVNETA_PORT_CONFIG 0x2400
+#define MVNETA_UNI_PROMISC_MODE BIT(0)
+#define MVNETA_DEF_RXQ(q) ((q) << 1)
+#define MVNETA_DEF_RXQ_ARP(q) ((q) << 4)
+#define MVNETA_TX_UNSET_ERR_SUM BIT(12)
+#define MVNETA_DEF_RXQ_TCP(q) ((q) << 16)
+#define MVNETA_DEF_RXQ_UDP(q) ((q) << 19)
+#define MVNETA_DEF_RXQ_BPDU(q) ((q) << 22)
+#define MVNETA_RX_CSUM_WITH_PSEUDO_HDR BIT(25)
+#define MVNETA_PORT_CONFIG_DEFL_VALUE(q) (MVNETA_DEF_RXQ(q) | \
+ MVNETA_DEF_RXQ_ARP(q) | \
+ MVNETA_DEF_RXQ_TCP(q) | \
+ MVNETA_DEF_RXQ_UDP(q) | \
+ MVNETA_DEF_RXQ_BPDU(q) | \
+ MVNETA_TX_UNSET_ERR_SUM | \
+ MVNETA_RX_CSUM_WITH_PSEUDO_HDR)
+#define MVNETA_PORT_CONFIG_EXTEND 0x2404
+#define MVNETA_MAC_ADDR_LOW 0x2414
+#define MVNETA_MAC_ADDR_HIGH 0x2418
+#define MVNETA_SDMA_CONFIG 0x241c
+#define MVNETA_SDMA_BRST_SIZE_16 4
+#define MVNETA_NO_DESC_SWAP 0x0
+#define MVNETA_RX_BRST_SZ_MASK(burst) ((burst) << 1)
+#define MVNETA_RX_NO_DATA_SWAP BIT(4)
+#define MVNETA_TX_NO_DATA_SWAP BIT(5)
+#define MVNETA_TX_BRST_SZ_MASK(burst) ((burst) << 22)
+#define MVNETA_PORT_STATUS 0x2444
+#define MVNETA_TX_IN_PRGRS BIT(1)
+#define MVNETA_TX_FIFO_EMPTY BIT(8)
+#define MVNETA_RX_MIN_FRAME_SIZE 0x247c
+#define MVNETA_TYPE_PRIO 0x24bc
+#define MVNETA_FORCE_UNI BIT(21)
+#define MVNETA_TXQ_CMD_1 0x24e4
+#define MVNETA_TXQ_CMD 0x2448
+#define MVNETA_TXQ_DISABLE_SHIFT 8
+#define MVNETA_TXQ_ENABLE_MASK 0x000000ff
+#define MVNETA_ACC_MODE 0x2500
+#define MVNETA_CPU_MAP(cpu) (0x2540 + ((cpu) << 2))
+#define MVNETA_CPU_RXQ_ACCESS_ALL_MASK 0x000000ff
+#define MVNETA_CPU_TXQ_ACCESS_ALL_MASK 0x0000ff00
+#define MVNETA_RXQ_TIME_COAL_REG(q) (0x2580 + ((q) << 2))
+#define MVNETA_INTR_NEW_CAUSE 0x25a0
+#define MVNETA_RX_INTR_MASK(nr_rxqs) (((1 << nr_rxqs) - 1) << 8)
+#define MVNETA_INTR_NEW_MASK 0x25a4
+#define MVNETA_INTR_OLD_CAUSE 0x25a8
+#define MVNETA_INTR_OLD_MASK 0x25ac
+#define MVNETA_INTR_MISC_CAUSE 0x25b0
+#define MVNETA_INTR_MISC_MASK 0x25b4
+#define MVNETA_INTR_ENABLE 0x25b8
+#define MVNETA_TXQ_INTR_ENABLE_ALL_MASK 0x0000ff00
+#define MVNETA_RXQ_INTR_ENABLE_ALL_MASK 0xff000000
+#define MVNETA_RXQ_CMD 0x2680
+#define MVNETA_RXQ_DISABLE_SHIFT 8
+#define MVNETA_RXQ_ENABLE_MASK 0x000000ff
+#define MVETH_TXQ_TOKEN_COUNT_REG(q) (0x2700 + ((q) << 4))
+#define MVETH_TXQ_TOKEN_CFG_REG(q) (0x2704 + ((q) << 4))
+#define MVNETA_GMAC_CTRL_0 0x2c00
+#define MVNETA_GMAC_MAX_RX_SIZE_SHIFT 2
+#define MVNETA_GMAC_MAX_RX_SIZE_MASK 0x7ffc
+#define MVNETA_GMAC0_PORT_ENABLE BIT(0)
+#define MVNETA_GMAC_CTRL_2 0x2c08
+#define MVNETA_GMAC2_PSC_ENABLE BIT(3)
+#define MVNETA_GMAC2_PORT_RGMII BIT(4)
+#define MVNETA_GMAC2_PORT_RESET BIT(6)
+#define MVNETA_GMAC_STATUS 0x2c10
+#define MVNETA_GMAC_LINK_UP BIT(0)
+#define MVNETA_GMAC_SPEED_1000 BIT(1)
+#define MVNETA_GMAC_SPEED_100 BIT(2)
+#define MVNETA_GMAC_FULL_DUPLEX BIT(3)
+#define MVNETA_GMAC_RX_FLOW_CTRL_ENABLE BIT(4)
+#define MVNETA_GMAC_TX_FLOW_CTRL_ENABLE BIT(5)
+#define MVNETA_GMAC_RX_FLOW_CTRL_ACTIVE BIT(6)
+#define MVNETA_GMAC_TX_FLOW_CTRL_ACTIVE BIT(7)
+#define MVNETA_GMAC_AUTONEG_CONFIG 0x2c0c
+#define MVNETA_GMAC_FORCE_LINK_DOWN BIT(0)
+#define MVNETA_GMAC_FORCE_LINK_PASS BIT(1)
+#define MVNETA_GMAC_CONFIG_MII_SPEED BIT(5)
+#define MVNETA_GMAC_CONFIG_GMII_SPEED BIT(6)
+#define MVNETA_GMAC_CONFIG_FULL_DUPLEX BIT(12)
+#define MVNETA_MIB_COUNTERS_BASE 0x3080
+#define MVNETA_MIB_LATE_COLLISION 0x7c
+#define MVNETA_DA_FILT_SPEC_MCAST 0x3400
+#define MVNETA_DA_FILT_OTH_MCAST 0x3500
+#define MVNETA_DA_FILT_UCAST_BASE 0x3600
+#define MVNETA_TXQ_BASE_ADDR_REG(q) (0x3c00 + ((q) << 2))
+#define MVNETA_TXQ_SIZE_REG(q) (0x3c20 + ((q) << 2))
+#define MVNETA_TXQ_SENT_THRESH_ALL_MASK 0x3fff0000
+#define MVNETA_TXQ_SENT_THRESH_MASK(coal) ((coal) << 16)
+#define MVNETA_TXQ_UPDATE_REG(q) (0x3c60 + ((q) << 2))
+#define MVNETA_TXQ_DEC_SENT_SHIFT 16
+#define MVNETA_TXQ_STATUS_REG(q) (0x3c40 + ((q) << 2))
+#define MVNETA_TXQ_SENT_DESC_SHIFT 16
+#define MVNETA_TXQ_SENT_DESC_MASK 0x3fff0000
+#define MVNETA_PORT_TX_RESET 0x3cf0
+#define MVNETA_PORT_TX_DMA_RESET BIT(0)
+#define MVNETA_TX_MTU 0x3e0c
+#define MVNETA_TX_TOKEN_SIZE 0x3e14
+#define MVNETA_TX_TOKEN_SIZE_MAX 0xffffffff
+#define MVNETA_TXQ_TOKEN_SIZE_REG(q) (0x3e40 + ((q) << 2))
+#define MVNETA_TXQ_TOKEN_SIZE_MAX 0x7fffffff
+
+#define MVNETA_CAUSE_TXQ_SENT_DESC_ALL_MASK 0xff
+
+/* Descriptor ring Macros */
+#define MVNETA_QUEUE_NEXT_DESC(q, index) \
+ (((index) < (q)->last_desc) ? ((index) + 1) : 0)
+
+/* Various constants */
+
+/* Coalescing */
+#define MVNETA_TXDONE_COAL_PKTS 16
+#define MVNETA_RX_COAL_PKTS 32
+#define MVNETA_RX_COAL_USEC 100
+
+/* Timer */
+#define MVNETA_TX_DONE_TIMER_PERIOD 10
+
+/* Napi polling weight */
+#define MVNETA_RX_POLL_WEIGHT 64
+
+#define MVNETA_MH_SIZE 2
+
+#define MVNETA_CPU_D_CACHE_LINE_SIZE 32
+#define MVNETA_ETH_CRC_SIZE 4
+#define MVNETA_TX_CSUM_MAX_SIZE 9800
+#define MVNETA_ACC_MODE_EXT 1
+
+/* Timeout constants */
+#define MVNETA_TX_DISABLE_TIMEOUT_MSEC 1000
+#define MVNETA_RX_DISABLE_TIMEOUT_MSEC 1000
+#define MVNETA_TX_FIFO_EMPTY_TIMEOUT 10000
+
+#define MVNETA_TX_MTU_MAX 0x3ffff
+
+/* Max number of Rx descriptors */
+#define MVNETA_MAX_RXD 128
+
+/* Max number of Tx descriptors */
+#define MVNETA_MAX_TXD 532
+
+/* descriptor aligned size */
+#define MVNETA_DESC_ALIGNED_SIZE 32
+
+#define MVNETA_RX_PKT_SIZE(mtu) \
+ ALIGN((mtu) + 2 + 4 + ETH_HLEN + 4, MVNETA_CPU_D_CACHE_LINE_SIZE)
+
+#define MVNETA_RX_BUF_SIZE(pkt_size) ((pkt_size) + NET_SKB_PAD)
+
+struct mvneta_stats {
+ struct u64_stats_sync syncp;
+ u64 packets;
+ u64 bytes;
+};
+
+struct mvneta_port {
+ /* Packet size in bytes */
+ int pkt_size;
+
+ /* Base virtual address of the Ethernet controller registers */
+ void __iomem *base;
+
+ /* Array of RX queues */
+ struct mvneta_rx_queue *rxqs;
+
+ /* Array of TX queues */
+ struct mvneta_tx_queue *txqs;
+
+ /* Timer */
+ struct timer_list tx_done_timer;
+
+ /* Back pointer to the Linux network interface device */
+ struct net_device *dev;
+
+ u32 cause_rx_tx[CONFIG_NR_CPUS];
+ struct napi_struct napi;
+
+ /* Flags */
+ unsigned long flags;
+#define MVNETA_F_TX_DONE_TIMER_BIT 0
+
+ /* Napi weight */
+ int weight;
+
+ /* Core clock [Hz] */
+ unsigned int clk_rate;
+ u8 mcast_count[256];
+ u16 tx_ring_size;
+ u16 rx_ring_size;
+ struct mvneta_stats tx_stats;
+ struct mvneta_stats rx_stats;
+
+ struct mii_bus *mii_bus;
+ struct phy_device *phy_dev;
+ phy_interface_t phy_interface;
+ unsigned int link;
+ unsigned int duplex;
+ unsigned int speed;
+};
+
+/*
+ * The mvneta_tx_desc and mvneta_rx_desc structures describe the
+ * layout of the transmit and reception DMA descriptors, and their
+ * layout is therefore defined by the hardware design
+ */
+struct mvneta_tx_desc {
+ u32 command; /* Options used by HW for packet transmitting.*/
+#define MVNETA_TX_L3_OFF_SHIFT 0
+#define MVNETA_TX_IP_HLEN_SHIFT 8
+#define MVNETA_TX_L4_UDP BIT(16)
+#define MVNETA_TX_L3_IP6 BIT(17)
+#define MVNETA_TXD_IP_CSUM BIT(18)
+#define MVNETA_TXD_Z_PAD BIT(19)
+#define MVNETA_TXD_L_DESC BIT(20)
+#define MVNETA_TXD_F_DESC BIT(21)
+#define MVNETA_TXD_FLZ_DESC (MVNETA_TXD_Z_PAD | \
+ MVNETA_TXD_L_DESC | \
+ MVNETA_TXD_F_DESC)
+#define MVNETA_TX_L4_CSUM_FULL BIT(30)
+#define MVNETA_TX_L4_CSUM_NOT BIT(31)
+
+ u16 reserverd1; /* csum_l4 (for future use) */
+ u16 data_size; /* Data size of transmitted packet in bytes */
+ u32 buf_phys_addr; /* Physical addr of transmitted buffer */
+ u32 reserved2; /* hw_cmd - (for future use, PMT) */
+ u32 reserved3[4]; /* Reserved - (for future use) */
+};
+
+struct mvneta_rx_desc {
+ u32 status; /* Info about received packet */
+#define MVNETA_RXD_ERR_CRC 0x0
+#define MVNETA_RXD_ERR_SUMMARY BIT(16)
+#define MVNETA_RXD_ERR_OVERRUN BIT(17)
+#define MVNETA_RXD_ERR_LEN BIT(18)
+#define MVNETA_RXD_ERR_RESOURCE (BIT(17) | BIT(18))
+#define MVNETA_RXD_ERR_CODE_MASK (BIT(17) | BIT(18))
+#define MVNETA_RXD_L3_IP4 BIT(25)
+#define MVNETA_RXD_FIRST_LAST_DESC (BIT(26) | BIT(27))
+#define MVNETA_RXD_L4_CSUM_OK BIT(30)
+
+ u16 reserved1; /* pnc_info - (for future use, PnC) */
+ u16 data_size; /* Size of received packet in bytes */
+ u32 buf_phys_addr; /* Physical address of the buffer */
+ u32 reserved2; /* pnc_flow_id (for future use, PnC) */
+ u32 buf_cookie; /* cookie for access to RX buffer in rx path */
+ u16 reserved3; /* prefetch_cmd, for future use */
+ u16 reserved4; /* csum_l4 - (for future use, PnC) */
+ u32 reserved5; /* pnc_extra PnC (for future use, PnC) */
+ u32 reserved6; /* hw_cmd (for future use, PnC and HWF) */
+};
+
+struct mvneta_tx_queue {
+ /* Number of this TX queue, in the range 0-7 */
+ u8 id;
+
+ /* Number of TX DMA descriptors in the descriptor ring */
+ int size;
+
+ /* Index of last TX DMA descriptor in the descriptor ring */
+ int count;
+
+ /* Array of transmitted skb */
+ struct sk_buff **tx_skb;
+
+ /* Index of last TX DMA descriptor that was inserted */
+ int txq_put_index;
+
+ /* Index of the TX DMA descriptor to be cleaned up */
+ int txq_get_index;
+
+ u32 done_pkts_coal;
+
+ /* Virtual address of the TX DMA descriptors array */
+ struct mvneta_tx_desc *descs;
+
+ /* DMA address of the TX DMA descriptors array */
+ dma_addr_t descs_phys;
+
+ /* Index of the last TX DMA descriptor */
+ int last_desc;
+
+ /* Index of the next TX DMA descriptor to process */
+ int next_desc_to_proc;
+};
+
+struct mvneta_rx_queue {
+ /* rx queue number, in the range 0-7 */
+ u8 id;
+
+ /* num of rx descriptors in the rx descriptor ring */
+ int size;
+
+ /* counter of times when mvneta_refill() failed */
+ int missed;
+
+ u32 pkts_coal;
+ u32 time_coal;
+
+ /* Virtual address of the RX DMA descriptors array */
+ struct mvneta_rx_desc *descs;
+
+ /* DMA address of the RX DMA descriptors array */
+ dma_addr_t descs_phys;
+
+ /* Index of the last RX DMA descriptor */
+ int last_desc;
+
+ /* Index of the next RX DMA descriptor to process */
+ int next_desc_to_proc;
+};
+
+static int mvneta_rxq_number = 8;
+static int mvneta_txq_number = 8;
+
+static int mvneta_rxq_def;
+static int mvneta_txq_def;
+
+#define MVNETA_DRIVER_NAME "mvneta"
+#define MVNETA_DRIVER_VERSION "1.0"
+
+/* Utility/helper methods */
+
+/* Write helper method */
+static void mvreg_write(struct mvneta_port *pp, u32 offset, u32 data)
+{
+ writel(data, pp->base + offset);
+}
+
+/* Read helper method */
+static u32 mvreg_read(struct mvneta_port *pp, u32 offset)
+{
+ return readl(pp->base + offset);
+}
+
+/* Increment txq get counter */
+static void mvneta_txq_inc_get(struct mvneta_tx_queue *txq)
+{
+ txq->txq_get_index++;
+ if (txq->txq_get_index == txq->size)
+ txq->txq_get_index = 0;
+}
+
+/* Increment txq put counter */
+static void mvneta_txq_inc_put(struct mvneta_tx_queue *txq)
+{
+ txq->txq_put_index++;
+ if (txq->txq_put_index == txq->size)
+ txq->txq_put_index = 0;
+}
+
+
+/* Clear all MIB counters */
+static void mvneta_mib_counters_clear(struct mvneta_port *pp)
+{
+ int i;
+ u32 dummy;
+
+ /* Perform dummy reads from MIB counters */
+ for (i = 0; i < MVNETA_MIB_LATE_COLLISION; i += 4)
+ dummy = mvreg_read(pp, (MVNETA_MIB_COUNTERS_BASE + i));
+}
+
+/* Get System Network Statistics */
+struct rtnl_link_stats64 *mvneta_get_stats64(struct net_device *dev,
+ struct rtnl_link_stats64 *stats)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+ unsigned int start;
+
+ memset(stats, 0, sizeof(struct rtnl_link_stats64));
+
+ do {
+ start = u64_stats_fetch_begin_bh(&pp->rx_stats.syncp);
+ stats->rx_packets = pp->rx_stats.packets;
+ stats->rx_bytes = pp->rx_stats.bytes;
+ } while (u64_stats_fetch_retry_bh(&pp->rx_stats.syncp, start));
+
+
+ do {
+ start = u64_stats_fetch_begin_bh(&pp->tx_stats.syncp);
+ stats->tx_packets = pp->tx_stats.packets;
+ stats->tx_bytes = pp->tx_stats.bytes;
+ } while (u64_stats_fetch_retry_bh(&pp->tx_stats.syncp, start));
+
+ stats->rx_errors = dev->stats.rx_errors;
+ stats->rx_dropped = dev->stats.rx_dropped;
+
+ stats->tx_dropped = dev->stats.tx_dropped;
+
+ return stats;
+
+}
+
+/* Rx descriptors helper methods */
+
+/* Add number of descriptors ready to receive new packets */
+static void mvneta_rxq_non_occup_desc_add(struct mvneta_port *pp,
+ struct mvneta_rx_queue *rxq,
+ int ndescs)
+{
+ /* Only MVNETA_RXQ_ADD_NON_OCCUPIED_MAX (255) descriptors can
+ * be added at once */
+ while (ndescs > MVNETA_RXQ_ADD_NON_OCCUPIED_MAX) {
+ mvreg_write(pp, MVNETA_RXQ_STATUS_UPDATE_REG(rxq->id),
+ (MVNETA_RXQ_ADD_NON_OCCUPIED_MAX <<
+ MVNETA_RXQ_ADD_NON_OCCUPIED_SHIFT));
+ ndescs -= MVNETA_RXQ_ADD_NON_OCCUPIED_MAX;
+ }
+
+ mvreg_write(pp, MVNETA_RXQ_STATUS_UPDATE_REG(rxq->id),
+ (ndescs << MVNETA_RXQ_ADD_NON_OCCUPIED_SHIFT));
+}
+
+/* Get number of RX descriptors occupied by received packets */
+static int mvneta_rxq_busy_desc_num_get(struct mvneta_port *pp,
+ struct mvneta_rx_queue *rxq)
+{
+ u32 val;
+
+ val = mvreg_read(pp, MVNETA_RXQ_STATUS_REG(rxq->id));
+ return val & MVNETA_RXQ_OCCUPIED_ALL_MASK;
+}
+
+/*
+ * Update num of rx desc called upon return from rx path or
+ * from mvneta_rxq_drop_pkts().
+ */
+static void mvneta_rxq_desc_num_update(struct mvneta_port *pp,
+ struct mvneta_rx_queue *rxq,
+ int rx_done, int rx_filled)
+{
+ u32 val;
+
+ if ((rx_done <= 0xff) && (rx_filled <= 0xff)) {
+ val = rx_done |
+ (rx_filled << MVNETA_RXQ_ADD_NON_OCCUPIED_SHIFT);
+ mvreg_write(pp, MVNETA_RXQ_STATUS_UPDATE_REG(rxq->id), val);
+ return;
+ }
+
+ /* Only 255 descriptors can be added at once */
+ while ((rx_done > 0) || (rx_filled > 0)) {
+ if (rx_done <= 0xff) {
+ val = rx_done;
+ rx_done = 0;
+ } else {
+ val = 0xff;
+ rx_done -= 0xff;
+ }
+ if (rx_filled <= 0xff) {
+ val |= rx_filled
+ << MVNETA_RXQ_ADD_NON_OCCUPIED_SHIFT;
+ rx_filled = 0;
+ } else {
+ val |= 0xff << MVNETA_RXQ_ADD_NON_OCCUPIED_SHIFT;
+ rx_filled -= 0xff;
+ }
+ mvreg_write(pp, MVNETA_RXQ_STATUS_UPDATE_REG(rxq->id), val);
+ }
+}
+
+/* Get pointer to next RX descriptor to be processed by SW */
+static struct mvneta_rx_desc *
+mvneta_rxq_next_desc_get(struct mvneta_rx_queue *rxq)
+{
+ int rx_desc = rxq->next_desc_to_proc;
+
+ rxq->next_desc_to_proc =
+ MVNETA_QUEUE_NEXT_DESC(rxq, rx_desc);
+
+ return rxq->descs + rx_desc;
+}
+
+/* Change maximum receive size of the port. */
+static void mvneta_max_rx_size_set(struct mvneta_port *pp, int max_rx_size)
+{
+ u32 val;
+
+ val = mvreg_read(pp, MVNETA_GMAC_CTRL_0);
+ val &= ~MVNETA_GMAC_MAX_RX_SIZE_MASK;
+ val |= (((max_rx_size - MVNETA_MH_SIZE) / 2)
+ << MVNETA_GMAC_MAX_RX_SIZE_SHIFT);
+ mvreg_write(pp, MVNETA_GMAC_CTRL_0, val);
+}
+
+
+/* Set rx queue offset */
+static int mvneta_rxq_offset_set(struct mvneta_port *pp,
+ struct mvneta_rx_queue *rxq,
+ int offset)
+{
+ u32 val;
+
+ val = mvreg_read(pp, MVNETA_RXQ_CONFIG_REG(rxq->id));
+ val &= ~MVNETA_RXQ_PKT_OFFSET_ALL_MASK;
+
+ /* Offset is in */
+ val |= MVNETA_RXQ_PKT_OFFSET_MASK(offset >> 3);
+ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val);
+
+ return 0;
+}
+
+
+/* Tx descriptors helper methods */
+
+/* Update HW with number of TX descriptors to be sent */
+static void mvneta_txq_pend_desc_add(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq,
+ int pend_desc)
+{
+ u32 val;
+
+ /* Only 255 descriptors can be added at once ; Assume caller
+ process TX desriptors in quanta less than 256 */
+ val = pend_desc;
+ mvreg_write(pp, MVNETA_TXQ_UPDATE_REG(txq->id), val);
+}
+
+/* Get pointer to next TX descriptor to be processed (send) by HW */
+static struct mvneta_tx_desc *
+mvneta_txq_next_desc_get(struct mvneta_tx_queue *txq)
+{
+ int tx_desc = txq->next_desc_to_proc;
+ txq->next_desc_to_proc =
+ MVNETA_QUEUE_NEXT_DESC(txq, tx_desc);
+
+ return txq->descs + tx_desc;
+}
+
+/* Release the last allocated TX descriptor. Useful to handle DMA
+ * mapping failures in the TX path. */
+static void mvneta_txq_desc_put(struct mvneta_tx_queue *txq)
+{
+ if (txq->next_desc_to_proc == 0)
+ txq->next_desc_to_proc = txq->last_desc - 1;
+ else
+ txq->next_desc_to_proc--;
+}
+
+/* Set rxq buf size */
+static void mvneta_rxq_buf_size_set(struct mvneta_port *pp,
+ struct mvneta_rx_queue *rxq,
+ int buf_size)
+{
+ u32 val;
+
+ val = mvreg_read(pp, MVNETA_RXQ_SIZE_REG(rxq->id));
+
+ val &= ~MVNETA_RXQ_BUF_SIZE_MASK;
+ val |= ((buf_size >> 3) << MVNETA_RXQ_BUF_SIZE_SHIFT);
+
+ mvreg_write(pp, MVNETA_RXQ_SIZE_REG(rxq->id), val);
+}
+
+/* Disable buffer management (BM) */
+static void mvneta_rxq_bm_disable(struct mvneta_port *pp,
+ struct mvneta_rx_queue *rxq)
+{
+ u32 val;
+
+ val = mvreg_read(pp, MVNETA_RXQ_CONFIG_REG(rxq->id));
+ val &= ~MVNETA_RXQ_HW_BUF_ALLOC;
+ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val);
+}
+
+
+
+/* Sets the RGMII Enable bit (RGMIIEn) in port MAC control register */
+static void __devinit mvneta_gmac_rgmii_set(struct mvneta_port *pp, int enable)
+{
+ u32 val;
+
+ val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
+
+ if (enable)
+ val |= MVNETA_GMAC2_PORT_RGMII;
+ else
+ val &= ~MVNETA_GMAC2_PORT_RGMII;
+
+ mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
+}
+
+/* Config SGMII port */
+static void __devinit mvneta_port_sgmii_config(struct mvneta_port *pp)
+{
+ u32 val;
+
+ val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
+ val |= MVNETA_GMAC2_PSC_ENABLE;
+ mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
+}
+
+/* Start the Ethernet port RX and TX activity */
+static void mvneta_port_up(struct mvneta_port *pp)
+{
+ int queue;
+ u32 q_map;
+
+ /* Enable all initialized TXs. */
+ mvneta_mib_counters_clear(pp);
+ q_map = 0;
+ for (queue = 0; queue < mvneta_txq_number; queue++) {
+ struct mvneta_tx_queue *txq = &pp->txqs[queue];
+ if (txq->descs != NULL)
+ q_map |= (1 << queue);
+ }
+ mvreg_write(pp, MVNETA_TXQ_CMD, q_map);
+
+ /* Enable all initialized RXQs. */
+ q_map = 0;
+ for (queue = 0; queue < mvneta_rxq_number; queue++) {
+ struct mvneta_rx_queue *rxq = &pp->rxqs[queue];
+ if (rxq->descs != NULL)
+ q_map |= (1 << queue);
+ }
+
+ mvreg_write(pp, MVNETA_RXQ_CMD, q_map);
+}
+
+/* Stop the Ethernet port activity */
+static void mvneta_port_down(struct mvneta_port *pp)
+{
+ u32 val;
+ int count;
+
+ /* Stop Rx port activity. Check port Rx activity. */
+ val = (mvreg_read(pp, MVNETA_RXQ_CMD))
+ & MVNETA_RXQ_ENABLE_MASK;
+ /* Issue stop command for active channels only */
+ if (val != 0)
+ mvreg_write(pp, MVNETA_RXQ_CMD,
+ val << MVNETA_RXQ_DISABLE_SHIFT);
+
+ /* Wait for all Rx activity to terminate. */
+ count = 0;
+ do {
+ if (count >= MVNETA_RX_DISABLE_TIMEOUT_MSEC) {
+ netdev_warn(pp->dev,
+ "TIMEOUT for RX stopped ! rx_queue_cmd: 0x08%x\n",
+ val);
+ break;
+ }
+ mdelay(1);
+ count++;
+
+ val = mvreg_read(pp, MVNETA_RXQ_CMD);
+ } while (val & 0xff);
+
+ /* Stop Tx port activity. Check port Tx activity. Issue stop
+ command for active channels only */
+ val = (mvreg_read(pp, MVNETA_TXQ_CMD)) & MVNETA_TXQ_ENABLE_MASK;
+
+ if (val != 0)
+ mvreg_write(pp, MVNETA_TXQ_CMD,
+ (val << MVNETA_TXQ_DISABLE_SHIFT));
+
+ /* Wait for all Tx activity to terminate. */
+ count = 0;
+ do {
+ if (count >= MVNETA_TX_DISABLE_TIMEOUT_MSEC) {
+ netdev_warn(pp->dev,
+ "TIMEOUT for TX stopped tx_queue_cmd - 0x%08x\n",
+ val);
+ break;
+ }
+ mdelay(1);
+ count++;
+
+ /* Check TX Command reg that all Txqs are stopped */
+ val = mvreg_read(pp, MVNETA_TXQ_CMD);
+
+ } while (val & 0xff);
+
+ /* Double check to verify that TX FIFO is empty */
+ count = 0;
+ do {
+ if (count >= MVNETA_TX_FIFO_EMPTY_TIMEOUT) {
+ netdev_warn(pp->dev,
+ "TX FIFO empty timeout status=0x08%x",
+ val);
+ break;
+ }
+ mdelay(1);
+ count++;
+
+ val = mvreg_read(pp, MVNETA_PORT_STATUS);
+ } while (!(val & MVNETA_TX_FIFO_EMPTY) &&
+ (val & MVNETA_TX_IN_PRGRS));
+
+ udelay(200);
+}
+
+/* Enable the port by setting the port enable bit of the MAC control register */
+static void mvneta_port_enable(struct mvneta_port *pp)
+{
+ u32 val;
+
+ /* Enable port */
+ val = mvreg_read(pp, MVNETA_GMAC_CTRL_0);
+ val |= MVNETA_GMAC0_PORT_ENABLE;
+ mvreg_write(pp, MVNETA_GMAC_CTRL_0, val);
+}
+
+/* Disable the port and wait for about 200 usec before retuning */
+static void mvneta_port_disable(struct mvneta_port *pp)
+{
+ u32 val;
+
+ /* Reset the Enable bit in the Serial Control Register */
+ val = mvreg_read(pp, MVNETA_GMAC_CTRL_0);
+ val &= ~(MVNETA_GMAC0_PORT_ENABLE);
+ mvreg_write(pp, MVNETA_GMAC_CTRL_0, val);
+
+ udelay(200);
+}
+
+/* Multicast tables methods */
+
+/* Set all entries in Unicast MAC Table; queue==-1 means reject all */
+static void mvneta_set_ucast_table(struct mvneta_port *pp, int queue)
+{
+ int offset;
+ u32 val;
+
+ if (queue == -1) {
+ val = 0;
+ } else {
+ val = (((0x01 | (queue << 1)) << 0) |
+ ((0x01 | (queue << 1)) << 8) |
+ ((0x01 | (queue << 1)) << 16) |
+ ((0x01 | (queue << 1)) << 24));
+ }
+
+ for (offset = 0; offset <= 0xc; offset += 4)
+ mvreg_write(pp, MVNETA_DA_FILT_UCAST_BASE + offset, val);
+}
+
+/* Set all entries in Special Multicast MAC Table; queue==-1 means reject all */
+static void mvneta_set_special_mcast_table(struct mvneta_port *pp, int queue)
+{
+ int offs;
+ u32 val;
+
+ if (queue == -1) {
+ val = 0;
+ } else {
+ val = (((0x01 | (queue << 1)) << 0) |
+ ((0x01 | (queue << 1)) << 8) |
+ ((0x01 | (queue << 1)) << 16) |
+ ((0x01 | (queue << 1)) << 24));
+ }
+
+ for (offs = 0; offs <= 0xfc; offs += 4)
+ mvreg_write(pp, (MVNETA_DA_FILT_SPEC_MCAST + offs), val);
+
+}
+
+/* Set all entries in Other Multicast MAC Table. queue==-1 means reject all */
+static void mvneta_set_other_mcast_table(struct mvneta_port *pp, int queue)
+{
+ int offset;
+ u32 val;
+
+ if (queue == -1) {
+ memset(pp->mcast_count, 0, sizeof(pp->mcast_count));
+ val = 0;
+ } else {
+ memset(pp->mcast_count, 1, sizeof(pp->mcast_count));
+ val = (((0x01 | (queue << 1)) << 0) |
+ ((0x01 | (queue << 1)) << 8) |
+ ((0x01 | (queue << 1)) << 16) |
+ ((0x01 | (queue << 1)) << 24));
+ }
+
+ for (offset = 0; offset <= 0xfc; offset += 4)
+ mvreg_write(pp, (MVNETA_DA_FILT_OTH_MCAST + offset), val);
+}
+
+/*
+ * Reset the port to its default state (in terms of interrupt
+ * cause/mask, MAC tables, RX/TX desciptor rings, PHY, etc.)
+ */
+static void mvneta_defaults_set(struct mvneta_port *pp)
+{
+ int cpu;
+ int queue;
+ u32 val;
+
+ /* Clear all Cause registers */
+ mvreg_write(pp, MVNETA_INTR_NEW_CAUSE, 0);
+ mvreg_write(pp, MVNETA_INTR_OLD_CAUSE, 0);
+ mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0);
+
+ /* Mask all interrupts */
+ mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0);
+ mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0);
+ mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0);
+ mvreg_write(pp, MVNETA_INTR_ENABLE, 0);
+
+ /* Enable MBUS Retry bit16 */
+ mvreg_write(pp, MVNETA_MBUS_RETRY, 0x20);
+
+ /* Set CPU queue access map - all CPUs have access to all RX
+ queues and to all TX queues */
+ for (cpu = 0; cpu < CONFIG_NR_CPUS; cpu++)
+ mvreg_write(pp, MVNETA_CPU_MAP(cpu),
+ (MVNETA_CPU_RXQ_ACCESS_ALL_MASK |
+ MVNETA_CPU_TXQ_ACCESS_ALL_MASK));
+
+ /* Reset RX and TX DMAs */
+ mvreg_write(pp, MVNETA_PORT_RX_RESET, MVNETA_PORT_RX_DMA_RESET);
+ mvreg_write(pp, MVNETA_PORT_TX_RESET, MVNETA_PORT_TX_DMA_RESET);
+
+ /* Disable Legacy WRR, Disable EJP, Release from reset */
+ mvreg_write(pp, MVNETA_TXQ_CMD_1, 0);
+ for (queue = 0; queue < mvneta_txq_number; queue++) {
+ mvreg_write(pp, MVETH_TXQ_TOKEN_COUNT_REG(queue), 0);
+ mvreg_write(pp, MVETH_TXQ_TOKEN_CFG_REG(queue), 0);
+ }
+
+ mvreg_write(pp, MVNETA_PORT_TX_RESET, 0);
+ mvreg_write(pp, MVNETA_PORT_RX_RESET, 0);
+
+ /* Set Port Acceleration Mode */
+ val = MVNETA_ACC_MODE_EXT;
+ mvreg_write(pp, MVNETA_ACC_MODE, val);
+
+ /* Update val of portCfg register accordingly with all RxQueue types */
+ val = MVNETA_PORT_CONFIG_DEFL_VALUE(mvneta_rxq_def);
+ mvreg_write(pp, MVNETA_PORT_CONFIG, val);
+
+ val = 0;
+ mvreg_write(pp, MVNETA_PORT_CONFIG_EXTEND, val);
+ mvreg_write(pp, MVNETA_RX_MIN_FRAME_SIZE, 64);
+
+ /* Build PORT_SDMA_CONFIG_REG */
+ val = 0;
+
+ /* Default burst size */
+ val |= MVNETA_TX_BRST_SZ_MASK(MVNETA_SDMA_BRST_SIZE_16);
+ val |= MVNETA_RX_BRST_SZ_MASK(MVNETA_SDMA_BRST_SIZE_16);
+
+ val |= (MVNETA_RX_NO_DATA_SWAP | MVNETA_TX_NO_DATA_SWAP |
+ MVNETA_NO_DESC_SWAP);
+
+ /* Assign port SDMA configuration */
+ mvreg_write(pp, MVNETA_SDMA_CONFIG, val);
+
+ mvneta_set_ucast_table(pp, -1);
+ mvneta_set_special_mcast_table(pp, -1);
+ mvneta_set_other_mcast_table(pp, -1);
+
+ /* Set port interrupt enable register - default enable all */
+ mvreg_write(pp, MVNETA_INTR_ENABLE,
+ (MVNETA_RXQ_INTR_ENABLE_ALL_MASK
+ | MVNETA_TXQ_INTR_ENABLE_ALL_MASK));
+}
+
+/* Set max sizes for tx queues */
+static void mvneta_txq_max_tx_size_set(struct mvneta_port *pp, int max_tx_size)
+
+{
+ u32 val, size, mtu;
+ int queue;
+
+ mtu = max_tx_size * 8;
+ if (mtu > MVNETA_TX_MTU_MAX)
+ mtu = MVNETA_TX_MTU_MAX;
+
+ /* Set MTU */
+ val = mvreg_read(pp, MVNETA_TX_MTU);
+ val &= ~MVNETA_TX_MTU_MAX;
+ val |= mtu;
+ mvreg_write(pp, MVNETA_TX_MTU, val);
+
+ /* TX token size and all TXQs token size must be larger that MTU */
+ val = mvreg_read(pp, MVNETA_TX_TOKEN_SIZE);
+
+ size = val & MVNETA_TX_TOKEN_SIZE_MAX;
+ if (size < mtu) {
+ size = mtu;
+ val &= ~MVNETA_TX_TOKEN_SIZE_MAX;
+ val |= size;
+ mvreg_write(pp, MVNETA_TX_TOKEN_SIZE, val);
+ }
+ for (queue = 0; queue < mvneta_txq_number; queue++) {
+ val = mvreg_read(pp, MVNETA_TXQ_TOKEN_SIZE_REG(queue));
+
+ size = val & MVNETA_TXQ_TOKEN_SIZE_MAX;
+ if (size < mtu) {
+ size = mtu;
+ val &= ~MVNETA_TXQ_TOKEN_SIZE_MAX;
+ val |= size;
+ mvreg_write(pp, MVNETA_TXQ_TOKEN_SIZE_REG(queue), val);
+ }
+ }
+}
+
+/* Set unicast address */
+static int mvneta_set_ucast_addr(struct mvneta_port *pp, u8 last_nibble,
+ int queue)
+{
+ unsigned int unicast_reg;
+ unsigned int tbl_offset;
+ unsigned int reg_offset;
+
+ /* Locate the Unicast table entry */
+ last_nibble = (0xf & last_nibble);
+
+ /* offset from unicast tbl base */
+ tbl_offset = (last_nibble / 4) * 4;
+
+ /* offset within the above reg */
+ reg_offset = last_nibble % 4;
+
+ unicast_reg = mvreg_read(pp, (MVNETA_DA_FILT_UCAST_BASE + tbl_offset));
+
+ if (queue == -1) {
+ /* Clear accepts frame bit@specified unicast DA tbl entry */
+ unicast_reg &= ~(0xff << (8 * reg_offset));
+ } else {
+ unicast_reg &= ~(0xff << (8 * reg_offset));
+ unicast_reg |= ((0x01 | (queue << 1)) << (8 * reg_offset));
+ }
+
+ mvreg_write(pp, (MVNETA_DA_FILT_UCAST_BASE + tbl_offset), unicast_reg);
+ return 1;
+}
+
+/* Set mac address */
+static int mvneta_mac_addr_set(struct mvneta_port *pp, unsigned char *addr,
+ int queue)
+{
+ unsigned int mac_h;
+ unsigned int mac_l;
+
+ if (queue >= 1) {
+ netdev_err(pp->dev, "RX queue #%d is out of range\n", queue);
+ return -EINVAL;
+ }
+
+ if (queue != -1) {
+ mac_l = (addr[4] << 8) | (addr[5]);
+ mac_h = (addr[0] << 24) | (addr[1] << 16) |
+ (addr[2] << 8) | (addr[3] << 0);
+
+ mvreg_write(pp, MVNETA_MAC_ADDR_LOW, mac_l);
+ mvreg_write(pp, MVNETA_MAC_ADDR_HIGH, mac_h);
+ }
+
+ /* Accept frames of this address */
+ mvneta_set_ucast_addr(pp, addr[5], queue);
+
+ return 0;
+}
+
+/* Mask interrupts */
+static void mvneta_interrupts_mask(void *priv)
+{
+ struct mvneta_port *pp = priv;
+
+ /* Mask all ethernet port interrupts */
+ mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0);
+ mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0);
+ mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0);
+}
+
+/* Unmask interrupts */
+static void mvneta_interrupts_unmask(void *priv)
+{
+ struct mvneta_port *pp = priv;
+
+ mvreg_write(pp, MVNETA_INTR_NEW_MASK,
+ MVNETA_RX_INTR_MASK(mvneta_rxq_number));
+}
+
+/*
+ * Set the number of packets that will be received before
+ * RX interrupt will be generated by HW.
+ */
+static void mvneta_rx_pkts_coal_set(struct mvneta_port *pp,
+ struct mvneta_rx_queue *rxq, u32 value)
+{
+ mvreg_write(pp, MVNETA_RXQ_THRESHOLD_REG(rxq->id),
+ (value | MVNETA_RXQ_NON_OCCUPIED(0)));
+ rxq->pkts_coal = value;
+}
+
+/*
+ * Set the time delay in usec before
+ * RX interrupt will be generated by HW.
+ */
+static void mvneta_rx_time_coal_set(struct mvneta_port *pp,
+ struct mvneta_rx_queue *rxq, u32 value)
+{
+ u32 val = (pp->clk_rate / 1000000) * value;
+
+ mvreg_write(pp, MVNETA_RXQ_TIME_COAL_REG(rxq->id), val);
+ rxq->time_coal = value;
+}
+
+/* Set threshold for TX_DONE pkts coalescing */
+static void mvneta_tx_done_pkts_coal_set(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq, u32 value)
+{
+ u32 val;
+
+ val = mvreg_read(pp, MVNETA_TXQ_SIZE_REG(txq->id));
+
+ val &= ~MVNETA_TXQ_SENT_THRESH_ALL_MASK;
+ val |= MVNETA_TXQ_SENT_THRESH_MASK(value);
+
+ mvreg_write(pp, MVNETA_TXQ_SIZE_REG(txq->id), val);
+
+ txq->done_pkts_coal = value;
+}
+
+/* Trigger tx done timer in MVNETA_TX_DONE_TIMER_PERIOD msecs */
+static void mvneta_add_tx_done_timer(struct mvneta_port *pp)
+{
+ if (test_and_set_bit(MVNETA_F_TX_DONE_TIMER_BIT, &pp->flags) == 0) {
+ pp->tx_done_timer.expires = jiffies +
+ msecs_to_jiffies(MVNETA_TX_DONE_TIMER_PERIOD);
+ add_timer(&pp->tx_done_timer);
+ }
+}
+
+
+/* Handle rx descriptor fill by setting buf_cookie and buf_phys_addr */
+static void mvneta_rx_desc_fill(struct mvneta_rx_desc *rx_desc,
+ u32 phys_addr, u32 cookie)
+{
+ rx_desc->buf_cookie = cookie;
+ rx_desc->buf_phys_addr = phys_addr;
+}
+
+/* Decrement sent descriptors counter */
+static void mvneta_txq_sent_desc_dec(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq,
+ int sent_desc)
+{
+ u32 val;
+
+ /* Only 255 TX descriptors can be updated at once */
+ while (sent_desc > 0xff) {
+ val = (0xff << MVNETA_TXQ_DEC_SENT_SHIFT);
+ mvreg_write(pp, MVNETA_TXQ_UPDATE_REG(txq->id), val);
+ sent_desc = sent_desc - 0xff;
+ }
+
+ val = (sent_desc << MVNETA_TXQ_DEC_SENT_SHIFT);
+ mvreg_write(pp, MVNETA_TXQ_UPDATE_REG(txq->id), val);
+}
+
+/* Get number of TX descriptors already sent by HW */
+static int mvneta_txq_sent_desc_num_get(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq)
+{
+ u32 val;
+ int sent_desc;
+
+ val = mvreg_read(pp, MVNETA_TXQ_STATUS_REG(txq->id));
+ sent_desc = (val & MVNETA_TXQ_SENT_DESC_MASK) >>
+ MVNETA_TXQ_SENT_DESC_SHIFT;
+
+ return sent_desc;
+}
+
+/*
+ * Get number of sent descriptors and decrement counter.
+ * The number of sent descriptors is returned.
+ */
+static int mvneta_txq_sent_desc_proc(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq)
+{
+ int sent_desc;
+
+ /* Get number of sent descriptors */
+ sent_desc = mvneta_txq_sent_desc_num_get(pp, txq);
+
+ /* Decrement sent descriptors counter */
+ if (sent_desc)
+ mvneta_txq_sent_desc_dec(pp, txq, sent_desc);
+
+ return sent_desc;
+}
+
+/* Set TXQ descriptors fields relevant for CSUM calculation */
+static u32 mvneta_txq_desc_csum(int l3_offs, int l3_proto,
+ int ip_hdr_len, int l4_proto)
+{
+ u32 command;
+
+ /* Fields: L3_offset, IP_hdrlen, L3_type, G_IPv4_chk,
+ G_L4_chk, L4_type; required only for checksum
+ calculation */
+ command = (l3_offs << MVNETA_TX_L3_OFF_SHIFT);
+ command |= (ip_hdr_len << MVNETA_TX_IP_HLEN_SHIFT);
+
+ if (l3_proto == swab16(ETH_P_IP))
+ command |= MVNETA_TXD_IP_CSUM;
+ else
+ command |= MVNETA_TX_L3_IP6;
+
+ if (l4_proto == IPPROTO_TCP)
+ command |= MVNETA_TX_L4_CSUM_FULL;
+ else if (l4_proto == IPPROTO_UDP)
+ command |= (MVNETA_TX_L4_UDP | MVNETA_TX_L4_CSUM_FULL);
+ else
+ command |= MVNETA_TX_L4_CSUM_NOT;
+
+ return command;
+}
+
+/* Display more error info */
+static void mvneta_rx_error(struct mvneta_port *pp,
+ struct mvneta_rx_desc *rx_desc)
+{
+ u32 status = rx_desc->status;
+
+ if ((status & MVNETA_RXD_FIRST_LAST_DESC)
+ != MVNETA_RXD_FIRST_LAST_DESC) {
+ netdev_err(pp->dev,
+ "bad rx status %08x (buffer oversize), size=%d\n",
+ rx_desc->status, rx_desc->data_size);
+ return;
+ }
+
+ switch (status & MVNETA_RXD_ERR_CODE_MASK) {
+ case MVNETA_RXD_ERR_CRC:
+ netdev_err(pp->dev, "bad rx status %08x (crc error), size=%d\n",
+ status, rx_desc->data_size);
+ break;
+ case MVNETA_RXD_ERR_OVERRUN:
+ netdev_err(pp->dev, "bad rx status %08x (overrun error), size=%d\n",
+ status, rx_desc->data_size);
+ break;
+ case MVNETA_RXD_ERR_LEN:
+ netdev_err(pp->dev, "bad rx status %08x (max frame length error), size=%d\n",
+ status, rx_desc->data_size);
+ break;
+ case MVNETA_RXD_ERR_RESOURCE:
+ netdev_err(pp->dev, "bad rx status %08x (resource error), size=%d\n",
+ status, rx_desc->data_size);
+ break;
+ }
+}
+
+/* Handle RX checksum offload */
+static void mvneta_rx_csum(struct mvneta_port *pp,
+ struct mvneta_rx_desc *rx_desc,
+ struct sk_buff *skb)
+{
+ if ((rx_desc->status & MVNETA_RXD_L3_IP4) &&
+ (rx_desc->status & MVNETA_RXD_L4_CSUM_OK)) {
+ skb->csum = 0;
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ return;
+ }
+
+ skb->ip_summed = CHECKSUM_NONE;
+}
+
+/* Return tx queue pointer (find last set bit) according to causeTxDone reg */
+static struct mvneta_tx_queue *mvneta_tx_done_policy(struct mvneta_port *pp,
+ u32 cause)
+{
+ int queue;
+ queue = fls(cause) - 1;
+ if (queue < 0 || queue >= mvneta_txq_number)
+ return NULL;
+ return &pp->txqs[queue];
+}
+
+/* Free tx queue skbuffs */
+static void mvneta_txq_bufs_free(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq, int num)
+{
+ struct sk_buff *skb;
+ int i;
+ struct mvneta_tx_desc *tx_desc;
+ for (i = 0; i < num; i++) {
+ skb = txq->tx_skb[txq->txq_get_index];
+ tx_desc = txq->descs + txq->txq_get_index;
+
+ mvneta_txq_inc_get(txq);
+
+ if (!skb)
+ continue;
+ if (tx_desc) {
+ dma_unmap_single(pp->dev->dev.parent,
+ tx_desc->buf_phys_addr,
+ tx_desc->data_size,
+ DMA_TO_DEVICE);
+ dev_kfree_skb_any(skb);
+ }
+ }
+}
+
+/* Handle end of transmission */
+static int mvneta_txq_done(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq)
+{
+ int tx_done;
+
+ tx_done = mvneta_txq_sent_desc_proc(pp, txq);
+ if (tx_done == 0)
+ return tx_done;
+ mvneta_txq_bufs_free(pp, txq, tx_done);
+
+ txq->count -= tx_done;
+
+ return tx_done;
+}
+
+/* Refill processing */
+static int mvneta_rx_refill(struct mvneta_port *pp,
+ struct mvneta_rx_desc *rx_desc)
+
+{
+ unsigned long phys_addr;
+ struct sk_buff *skb;
+
+ skb = netdev_alloc_skb(pp->dev, pp->pkt_size);
+ if (!skb)
+ return 1;
+
+ phys_addr = dma_map_single(pp->dev->dev.parent, skb->head,
+ MVNETA_RX_BUF_SIZE(pp->pkt_size),
+ DMA_FROM_DEVICE);
+ if (unlikely(dma_mapping_error(pp->dev->dev.parent,
+ phys_addr))) {
+ dev_kfree_skb_irq(skb);
+ return 1;
+ }
+
+ mvneta_rx_desc_fill(rx_desc, phys_addr, (u32)skb);
+
+ return 0;
+}
+
+/* Handle tx checksum */
+static u32 mvneta_skb_tx_csum(struct mvneta_port *pp, struct sk_buff *skb)
+{
+ if (skb->ip_summed == CHECKSUM_PARTIAL) {
+ int ip_hdr_len = 0;
+ u8 l4_proto;
+
+ if (skb->protocol == htons(ETH_P_IP)) {
+ struct iphdr *ip4h = ip_hdr(skb);
+
+ /* Calculate IPv4 checksum and L4 checksum */
+ ip_hdr_len = ip4h->ihl;
+ l4_proto = ip4h->protocol;
+ } else if (skb->protocol == htons(ETH_P_IPV6)) {
+ struct ipv6hdr *ip6h = ipv6_hdr(skb);
+
+ /* Read l4_protocol from one of IPv6 extra headers */
+ if (skb_network_header_len(skb) > 0)
+ ip_hdr_len = (skb_network_header_len(skb) >> 2);
+ l4_proto = ip6h->nexthdr;
+ } else
+ return MVNETA_TX_L4_CSUM_NOT;
+
+ return mvneta_txq_desc_csum(skb_network_offset(skb),
+ skb->protocol, ip_hdr_len, l4_proto);
+ }
+
+ return MVNETA_TX_L4_CSUM_NOT;
+}
+
+/*
+ * Returns rx queue pointer (find last set bit) according to causeRxTx
+ * value
+ */
+static struct mvneta_rx_queue *mvneta_rx_policy(struct mvneta_port *pp,
+ u32 cause)
+{
+ int queue = fls(cause >> 8) - 1;
+ if (queue < 0 || queue >= mvneta_rxq_number)
+ return NULL;
+ return &pp->rxqs[queue];
+}
+
+/* Drop packets received by the RXQ and free buffers */
+static void mvneta_rxq_drop_pkts(struct mvneta_port *pp,
+ struct mvneta_rx_queue *rxq)
+{
+ struct mvneta_rx_desc *rx_desc;
+ struct sk_buff *skb;
+ int rx_done, i;
+
+ rx_done = mvneta_rxq_busy_desc_num_get(pp, rxq);
+ for (i = 0; i < rxq->size; i++) {
+ rx_desc = rxq->descs + i;
+ skb = (struct sk_buff *)rx_desc->buf_cookie;
+ dev_kfree_skb_any(skb);
+ dma_unmap_single(pp->dev->dev.parent, rx_desc->buf_phys_addr,
+ rx_desc->data_size, DMA_FROM_DEVICE);
+
+
+ }
+ if (rx_done)
+ mvneta_rxq_desc_num_update(pp, rxq, rx_done, rx_done);
+}
+
+
+/* Main rx processing */
+static int mvneta_rx(struct mvneta_port *pp, int rx_todo,
+ struct mvneta_rx_queue *rxq)
+{
+ struct net_device *dev = pp->dev;
+
+ int rx_done, rx_filled, err;
+ struct mvneta_rx_desc *rx_desc;
+ u32 rx_status;
+ int rx_bytes;
+ struct sk_buff *skb;
+
+ /* Get number of received packets */
+ rx_done = mvneta_rxq_busy_desc_num_get(pp, rxq);
+
+ if (rx_todo > rx_done)
+ rx_todo = rx_done;
+
+ rx_done = 0;
+ rx_filled = 0;
+
+ /* Fairness NAPI loop */
+ while (rx_done < rx_todo) {
+ rx_desc = mvneta_rxq_next_desc_get(rxq);
+ prefetch(rx_desc);
+ rx_done++;
+ rx_filled++;
+ rx_status = rx_desc->status;
+ skb = (struct sk_buff *)rx_desc->buf_cookie;
+
+ if (((rx_status & MVNETA_RXD_FIRST_LAST_DESC)
+ != MVNETA_RXD_FIRST_LAST_DESC)
+ || (rx_status & MVNETA_RXD_ERR_SUMMARY)) {
+ dev->stats.rx_errors++;
+ mvneta_rx_error(pp, rx_desc);
+ mvneta_rx_desc_fill(rx_desc, rx_desc->buf_phys_addr,
+ (u32)skb);
+ continue;
+ }
+
+ dma_unmap_single(pp->dev->dev.parent, rx_desc->buf_phys_addr,
+ rx_desc->data_size, DMA_FROM_DEVICE);
+
+ rx_bytes = rx_desc->data_size -
+ (MVNETA_ETH_CRC_SIZE + MVNETA_MH_SIZE);
+ u64_stats_update_begin(&pp->rx_stats.syncp);
+ pp->rx_stats.packets++;
+ pp->rx_stats.bytes += rx_bytes;
+ u64_stats_update_end(&pp->rx_stats.syncp);
+
+ /* Linux processing */
+ skb->data += MVNETA_MH_SIZE;
+ skb->tail += (rx_bytes + MVNETA_MH_SIZE);
+ skb->len = rx_bytes;
+
+ skb->protocol = eth_type_trans(skb, dev);
+
+ mvneta_rx_csum(pp, rx_desc, skb);
+
+ if (dev->features & NETIF_F_GRO)
+ napi_gro_receive(&pp->napi, skb);
+ else
+ netif_receive_skb(skb);
+
+ /* Refill processing */
+ err = mvneta_rx_refill(pp, rx_desc);
+ if (err) {
+ netdev_err(pp->dev, "Linux processing - Can't refill\n");
+ rxq->missed++;
+ rx_filled--;
+ }
+ }
+
+ /* Update rxq management counters */
+ mvneta_rxq_desc_num_update(pp, rxq, rx_done, rx_filled);
+
+ return rx_done;
+}
+
+/* Handle tx fragmentation processing */
+static int mvneta_tx_frag_process(struct mvneta_port *pp, struct sk_buff *skb,
+ struct mvneta_tx_queue *txq)
+{
+ int i, j;
+ struct mvneta_tx_desc *tx_desc;
+ skb_frag_t *frag;
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ frag = &skb_shinfo(skb)->frags[i];
+
+ tx_desc = mvneta_txq_next_desc_get(txq);
+ tx_desc->data_size = frag->size;
+
+ tx_desc->buf_phys_addr =
+ dma_map_single(pp->dev->dev.parent,
+ page_address(frag->page.p) +
+ frag->page_offset, tx_desc->data_size,
+ DMA_TO_DEVICE);
+
+ if (dma_mapping_error(pp->dev->dev.parent,
+ tx_desc->buf_phys_addr)) {
+ mvneta_txq_desc_put(txq);
+ goto error;
+ }
+
+ if (i == (skb_shinfo(skb)->nr_frags - 1)) {
+ /* Last descriptor */
+ tx_desc->command = (MVNETA_TXD_L_DESC |
+ MVNETA_TXD_Z_PAD);
+
+ txq->tx_skb[txq->txq_put_index] = skb;
+
+ mvneta_txq_inc_put(txq);
+ } else {
+ /* Descriptor in the middle: Not First, Not Last */
+ tx_desc->command = 0;
+
+ txq->tx_skb[txq->txq_put_index] = NULL;
+ mvneta_txq_inc_put(txq);
+ }
+ }
+
+ return 0;
+
+error:
+ /* Release all descriptors that were used to map fragments of
+ * this packet, as well as the corresponding DMA mappings */
+ for (j = i - 1; j >= 0; j--) {
+ tx_desc = txq->descs + j;
+ dma_unmap_single(pp->dev->dev.parent,
+ tx_desc->buf_phys_addr,
+ tx_desc->data_size,
+ DMA_TO_DEVICE);
+ mvneta_txq_desc_put(txq);
+ }
+
+ return -ENOMEM;
+}
+
+/* Main tx processing */
+static int mvneta_tx(struct sk_buff *skb, struct net_device *dev)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+ int frags = 0;
+ int res = NETDEV_TX_OK;
+ u32 tx_cmd;
+ struct mvneta_tx_queue *txq = &pp->txqs[mvneta_txq_def];
+ struct mvneta_tx_desc *tx_desc;
+
+ if (!netif_running(dev))
+ goto out;
+
+ frags = skb_shinfo(skb)->nr_frags + 1;
+
+ /* Are there enough TX descriptors to send packet ? */
+ if ((txq->count + frags) >= txq->size) {
+ frags = 0;
+ res = NETDEV_TX_BUSY;
+ goto out;
+ }
+
+ /* Get a descriptor for the first part of the packet */
+ tx_desc = mvneta_txq_next_desc_get(txq);
+
+ tx_cmd = mvneta_skb_tx_csum(pp, skb);
+
+ tx_desc->data_size = skb_headlen(skb);
+
+ tx_desc->buf_phys_addr = dma_map_single(dev->dev.parent, skb->data,
+ tx_desc->data_size,
+ DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(dev->dev.parent,
+ tx_desc->buf_phys_addr))) {
+ mvneta_txq_desc_put(txq);
+ frags = 0;
+ res = NETDEV_TX_BUSY;
+ goto out;
+ }
+
+ if (frags == 1) {
+ /* First and Last descriptor */
+ tx_cmd |= MVNETA_TXD_FLZ_DESC;
+ tx_desc->command = tx_cmd;
+ txq->tx_skb[txq->txq_put_index] = skb;
+ mvneta_txq_inc_put(txq);
+ } else {
+ /* First but not Last */
+ tx_cmd |= MVNETA_TXD_F_DESC;
+ txq->tx_skb[txq->txq_put_index] = NULL;
+ mvneta_txq_inc_put(txq);
+ tx_desc->command = tx_cmd;
+ /* Continue with other skb fragments */
+ if (mvneta_tx_frag_process(pp, skb, txq)) {
+ dma_unmap_single(dev->dev.parent,
+ tx_desc->buf_phys_addr,
+ tx_desc->data_size,
+ DMA_TO_DEVICE);
+ mvneta_txq_desc_put(txq);
+ frags = 0;
+ res = NETDEV_TX_BUSY;
+ goto out;
+ }
+ }
+
+ txq->count += frags;
+ mvneta_txq_pend_desc_add(pp, txq, frags);
+
+out:
+ if (frags > 0) {
+ u64_stats_update_begin(&pp->tx_stats.syncp);
+ pp->tx_stats.packets++;
+ pp->tx_stats.bytes += skb->len;
+ u64_stats_update_end(&pp->tx_stats.syncp);
+
+ } else {
+ dev->stats.tx_dropped++;
+ dev_kfree_skb_any(skb);
+ }
+
+ if (txq->count >= MVNETA_TXDONE_COAL_PKTS)
+ mvneta_txq_done(pp, txq);
+
+ /* If after calling mvneta_txq_done, count equals
+ frags, we need to set the timer */
+ if (txq->count == frags && frags > 0)
+ mvneta_add_tx_done_timer(pp);
+
+ return res;
+}
+
+
+/* Free tx resources, when resetting a port */
+static void mvneta_txq_done_force(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq)
+
+{
+ int tx_done = txq->count;
+ mvneta_txq_bufs_free(pp, txq, tx_done);
+
+ /* reset txq */
+ txq->count = 0;
+ txq->txq_put_index = 0;
+ txq->txq_get_index = 0;
+}
+
+/* handle tx done - called from tx done timer callback */
+static u32 mvneta_tx_done_gbe(struct mvneta_port *pp, u32 cause_tx_done,
+ int *tx_todo)
+{
+ struct mvneta_tx_queue *txq;
+ u32 tx_done = 0;
+ struct netdev_queue *nq;
+
+ *tx_todo = 0;
+ while (cause_tx_done != 0) {
+ txq = mvneta_tx_done_policy(pp, cause_tx_done);
+ if (!txq)
+ break;
+
+ nq = netdev_get_tx_queue(pp->dev, txq->id);
+ __netif_tx_lock(nq, smp_processor_id());
+
+ if (txq->count) {
+ tx_done += mvneta_txq_done(pp, txq);
+ *tx_todo += txq->count;
+ }
+
+ __netif_tx_unlock(nq);
+ cause_tx_done &= ~((1 << txq->id));
+ }
+
+ return tx_done;
+}
+
+/*
+ * Compute crc8 of the specified address, using a unique algorithm ,
+ * according to hw spec, different than generic crc8 algorithm
+ */
+static int mvneta_addr_crc(unsigned char *addr)
+{
+ int crc = 0;
+ int i;
+
+ for (i = 0; i < ETH_ALEN; i++) {
+ int j;
+
+ crc = (crc ^ addr[i]) << 8;
+ for (j = 7; j >= 0; j--) {
+ if (crc & (0x100 << j))
+ crc ^= 0x107 << j;
+ }
+ }
+
+ return crc;
+}
+
+/* This method controls the net device special MAC multicast support.
+ * The Special Multicast Table for MAC addresses supports MAC of the form
+ * 0x01-00-5E-00-00-XX (where XX is between 0x00 and 0xFF).
+ * The MAC DA[7:0] bits are used as a pointer to the Special Multicast
+ * Table entries in the DA-Filter table. This method set the Special
+ * Multicast Table appropriate entry.
+ */
+static void mvneta_set_special_mcast_addr(struct mvneta_port *pp,
+ unsigned char last_byte,
+ int queue)
+{
+ unsigned int smc_table_reg;
+ unsigned int tbl_offset;
+ unsigned int reg_offset;
+
+ /* Register offset from SMC table base */
+ tbl_offset = (last_byte / 4);
+ /* Entry offset within the above reg */
+ reg_offset = last_byte % 4;
+
+ smc_table_reg = mvreg_read(pp, (MVNETA_DA_FILT_SPEC_MCAST
+ + tbl_offset * 4));
+
+ if (queue == -1)
+ smc_table_reg &= ~(0xff << (8 * reg_offset));
+ else {
+ smc_table_reg &= ~(0xff << (8 * reg_offset));
+ smc_table_reg |= ((0x01 | (queue << 1)) << (8 * reg_offset));
+ }
+
+ mvreg_write(pp, MVNETA_DA_FILT_SPEC_MCAST + tbl_offset * 4,
+ smc_table_reg);
+}
+
+/* This method controls the network device Other MAC multicast support.
+ * The Other Multicast Table is used for multicast of another type.
+ * A CRC-8 is used as an index to the Other Multicast Table entries
+ * in the DA-Filter table.
+ * The method gets the CRC-8 value from the calling routine and
+ * sets the Other Multicast Table appropriate entry according to the
+ * specified CRC-8 .
+ */
+static void mvneta_set_other_mcast_addr(struct mvneta_port *pp,
+ unsigned char crc8,
+ int queue)
+{
+ unsigned int omc_table_reg;
+ unsigned int tbl_offset;
+ unsigned int reg_offset;
+
+ tbl_offset = (crc8 / 4) * 4; /* Register offset from OMC table base */
+ reg_offset = crc8 % 4; /* Entry offset within the above reg */
+
+ omc_table_reg = mvreg_read(pp, MVNETA_DA_FILT_OTH_MCAST + tbl_offset);
+
+ if (queue == -1) {
+ /* Clear accepts frame bit@specified Other DA table entry */
+ omc_table_reg &= ~(0xff << (8 * reg_offset));
+ } else {
+ omc_table_reg &= ~(0xff << (8 * reg_offset));
+ omc_table_reg |= ((0x01 | (queue << 1)) << (8 * reg_offset));
+ }
+
+ mvreg_write(pp, MVNETA_DA_FILT_OTH_MCAST + tbl_offset, omc_table_reg);
+}
+
+/* The network device supports multicast using two tables:
+ * 1) Special Multicast Table for MAC addresses of the form
+ * 0x01-00-5E-00-00-XX (where XX is between 0x00 and 0xFF).
+ * The MAC DA[7:0] bits are used as a pointer to the Special Multicast
+ * Table entries in the DA-Filter table.
+ * 2) Other Multicast Table for multicast of another type. A CRC-8 value
+ * is used as an index to the Other Multicast Table entries in the
+ * DA-Filter table.
+ */
+static int mvneta_mcast_addr_set(struct mvneta_port *pp, unsigned char *p_addr,
+ int queue)
+{
+ unsigned char crc_result = 0;
+
+ if (memcmp(p_addr, "\x01\x00\x5e\x00\x00", 5) == 0) {
+ mvneta_set_special_mcast_addr(pp, p_addr[5], queue);
+ return 0;
+ }
+
+ crc_result = mvneta_addr_crc(p_addr);
+ if (queue == -1) {
+ if (pp->mcast_count[crc_result] == 0) {
+ netdev_info(pp->dev, "No valid Mcast for crc8=0x%02x\n",
+ crc_result);
+ return -EINVAL;
+ }
+
+ pp->mcast_count[crc_result]--;
+ if (pp->mcast_count[crc_result] != 0) {
+ netdev_info(pp->dev,
+ "After delete there are %d valid Mcast for crc8=0x%02x\n",
+ pp->mcast_count[crc_result], crc_result);
+ return -EINVAL;
+ }
+ } else
+ pp->mcast_count[crc_result]++;
+
+ mvneta_set_other_mcast_addr(pp, crc_result, queue);
+
+ return 0;
+}
+
+/* Configure Fitering mode of Ethernet port */
+static void mvneta_rx_unicast_promisc_set(struct mvneta_port *pp,
+ int is_promisc)
+{
+ u32 port_cfg_reg, val;
+
+ port_cfg_reg = mvreg_read(pp, MVNETA_PORT_CONFIG);
+
+ val = mvreg_read(pp, MVNETA_TYPE_PRIO);
+
+ /* Set / Clear UPM bit in port configuration register */
+ if (is_promisc) {
+ /* Accept all Unicast addresses */
+ port_cfg_reg |= MVNETA_UNI_PROMISC_MODE;
+ val |= MVNETA_FORCE_UNI;
+ mvreg_write(pp, MVNETA_MAC_ADDR_LOW, 0xffff);
+ mvreg_write(pp, MVNETA_MAC_ADDR_HIGH, 0xffffffff);
+ } else {
+ /* Reject all Unicast addresses */
+ port_cfg_reg &= ~MVNETA_UNI_PROMISC_MODE;
+ val &= ~MVNETA_FORCE_UNI;
+ }
+
+ mvreg_write(pp, MVNETA_PORT_CONFIG, port_cfg_reg);
+ mvreg_write(pp, MVNETA_TYPE_PRIO, val);
+}
+
+/* register unicast and multicast addresses */
+static void mvneta_set_rx_mode(struct net_device *dev)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+ struct netdev_hw_addr *ha;
+ int queue = 0;
+
+ if (dev->flags & IFF_PROMISC) {
+ /* Accept all: Multicast + Unicast */
+ mvneta_rx_unicast_promisc_set(pp, 1);
+ mvneta_set_ucast_table(pp, queue);
+ mvneta_set_special_mcast_table(pp, queue);
+ mvneta_set_other_mcast_table(pp, queue);
+ } else {
+ /* Accept single Unicast */
+ mvneta_rx_unicast_promisc_set(pp, 0);
+ mvneta_set_ucast_table(pp, -1);
+ if ((mvneta_mac_addr_set(pp, dev->dev_addr, queue)) != 0)
+ netdev_err(dev, "mvneta_mac_addr_set failed\n");
+
+ if (dev->flags & IFF_ALLMULTI) {
+ /* Accept all multicast */
+ mvneta_set_special_mcast_table(pp, queue);
+ mvneta_set_other_mcast_table(pp, queue);
+ } else {
+ /* Accept only initialized multicast */
+ mvneta_set_special_mcast_table(pp, -1);
+ mvneta_set_other_mcast_table(pp, -1);
+
+ if (!netdev_mc_empty(dev)) {
+ netdev_for_each_mc_addr(ha, dev) {
+ mvneta_mcast_addr_set(pp, ha->addr,
+ queue);
+ }
+ }
+ }
+ }
+}
+
+/* Interrupt handling - the callback for request_irq() */
+static irqreturn_t mvneta_isr(int irq, void *dev_id)
+{
+ struct mvneta_port *pp = (struct mvneta_port *)dev_id;
+
+ /* Mask all interrupts */
+ mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0);
+
+ /* Verify that the device not already on the polling list */
+ if (napi_schedule_prep(&pp->napi))
+ __napi_schedule(&pp->napi);
+
+ return IRQ_HANDLED;
+}
+
+/* NAPI handler
+ * Bits 0 - 7 of the causeRxTx register indicate that are transmitted
+ * packets on the corresponding TXQ (Bit 0 is for TX queue 1).
+ * Bits 8 -15 of the cause Rx Tx register indicate that are received
+ * packets on the corresponding RXQ (Bit 8 is for RX queue 0).
+ * Each CPU has its own causeRxTx register
+ */
+static int mvneta_poll(struct napi_struct *napi, int budget)
+{
+ int rx_done = 0;
+ u32 cause_rx_tx;
+ unsigned long flags;
+ struct mvneta_port *pp = netdev_priv(napi->dev);
+
+ if (!netif_running(pp->dev)) {
+ napi_complete(napi);
+ return rx_done;
+ }
+
+ /* Read cause register */
+ cause_rx_tx = mvreg_read(pp, MVNETA_INTR_NEW_CAUSE) &
+ MVNETA_RX_INTR_MASK(mvneta_rxq_number);
+
+ /* TBD: For the case where the last mvneta_poll did not process
+ all RX packets */
+ cause_rx_tx |= pp->cause_rx_tx[smp_processor_id()];
+ if (mvneta_rxq_number > 1) {
+ while ((cause_rx_tx != 0) && (budget > 0)) {
+ int count;
+ struct mvneta_rx_queue *rxq;
+ /* get rx queue number from cause_rx_tx */
+ rxq = mvneta_rx_policy(pp, cause_rx_tx);
+ if (!rxq)
+ break;
+
+ /* process the packet in that rx queue */
+ count = mvneta_rx(pp, budget, rxq);
+ rx_done += count;
+ budget -= count;
+ if (budget > 0) {
+ /* set off the rx bit of the corresponding bit
+ in the cause rx tx register, so that next
+ iteration will find the next rx queue where
+ packets are received on */
+ cause_rx_tx &= ~((1 << rxq->id) << 8);
+ }
+ }
+ } else {
+ rx_done = mvneta_rx(pp, budget, &pp->rxqs[mvneta_rxq_def]);
+ budget -= rx_done;
+ }
+
+ if (budget > 0) {
+ cause_rx_tx = 0;
+ napi_complete(napi);
+ local_irq_save(flags);
+ mvreg_write(pp, MVNETA_INTR_NEW_MASK,
+ MVNETA_RX_INTR_MASK(mvneta_rxq_number));
+ local_irq_restore(flags);
+ }
+
+ pp->cause_rx_tx[smp_processor_id()] = cause_rx_tx;
+ return rx_done;
+}
+
+/* tx done timer callback */
+static void mvneta_tx_done_timer_callback(unsigned long data)
+{
+ struct net_device *dev = (struct net_device *)data;
+ struct mvneta_port *pp = netdev_priv(dev);
+ int tx_done = 0, tx_todo = 0;
+
+ if (!netif_running(dev))
+ return ;
+
+ clear_bit(MVNETA_F_TX_DONE_TIMER_BIT, &pp->flags);
+
+ tx_done = mvneta_tx_done_gbe(pp,
+ (((1 << mvneta_txq_number) - 1) &
+ MVNETA_CAUSE_TXQ_SENT_DESC_ALL_MASK),
+ &tx_todo);
+ if (tx_todo > 0)
+ mvneta_add_tx_done_timer(pp);
+}
+
+/* Handle rxq fill: allocates rxq skbs; called when initializing a port */
+static int mvneta_rxq_fill(struct mvneta_port *pp, struct mvneta_rx_queue *rxq,
+ int num)
+{
+ int i;
+ struct net_device *dev = pp->dev;
+
+ for (i = 0; i < num; i++) {
+ struct sk_buff *skb;
+ struct mvneta_rx_desc *rx_desc;
+ unsigned long phys_addr;
+
+ skb = dev_alloc_skb(pp->pkt_size);
+ if (!skb) {
+ netdev_err(dev, "%s:rxq %d, %d of %d buffs filled\n",
+ __func__, rxq->id, i, num);
+ break;
+ }
+
+ rx_desc = rxq->descs + i;
+ memset(rx_desc, 0, sizeof(struct mvneta_rx_desc));
+ phys_addr = dma_map_single(dev->dev.parent, skb->head,
+ MVNETA_RX_BUF_SIZE(pp->pkt_size),
+ DMA_FROM_DEVICE);
+ if (unlikely(dma_mapping_error(dev->dev.parent, phys_addr))) {
+ dev_kfree_skb(skb);
+ break;
+ }
+
+ mvneta_rx_desc_fill(rx_desc, phys_addr, (u32)skb);
+ }
+
+ /* Add this number of RX descriptors as non occupied (ready to
+ get packets) */
+ mvneta_rxq_non_occup_desc_add(pp, rxq, i);
+
+ return i;
+}
+
+/* Free all packets pending transmit from all TXQs and reset TX port */
+static void mvneta_tx_reset(struct mvneta_port *pp)
+{
+ int queue;
+
+ /* free the skb's in the hal tx ring */
+ for (queue = 0; queue < mvneta_txq_number; queue++)
+ mvneta_txq_done_force(pp, &pp->txqs[queue]);
+
+ mvreg_write(pp, MVNETA_PORT_TX_RESET, MVNETA_PORT_TX_DMA_RESET);
+ mvreg_write(pp, MVNETA_PORT_TX_RESET, 0);
+}
+
+static void mvneta_rx_reset(struct mvneta_port *pp)
+{
+ mvreg_write(pp, MVNETA_PORT_RX_RESET, MVNETA_PORT_RX_DMA_RESET);
+ mvreg_write(pp, MVNETA_PORT_RX_RESET, 0);
+}
+
+/* Rx/Tx queue initialization/cleanup methods */
+
+/* Create a specified RX queue */
+static int mvneta_rxq_init(struct mvneta_port *pp,
+ struct mvneta_rx_queue *rxq)
+
+{
+ rxq->size = pp->rx_ring_size;
+
+ /* Allocate memory for RX descriptors */
+ rxq->descs = dma_alloc_coherent(pp->dev->dev.parent,
+ rxq->size * MVNETA_DESC_ALIGNED_SIZE,
+ &rxq->descs_phys,
+ GFP_KERNEL);
+ if (rxq->descs == NULL) {
+ netdev_err(pp->dev,
+ "rxQ=%d: Can't allocate %d bytes for %d RX descr\n",
+ rxq->id, rxq->size * MVNETA_DESC_ALIGNED_SIZE,
+ rxq->size);
+ return -ENOMEM;
+ }
+
+ BUG_ON(rxq->descs !=
+ PTR_ALIGN(rxq->descs, MVNETA_CPU_D_CACHE_LINE_SIZE));
+
+ rxq->last_desc = rxq->size - 1;
+
+ /* Set Rx descriptors queue starting address */
+ mvreg_write(pp, MVNETA_RXQ_BASE_ADDR_REG(rxq->id), rxq->descs_phys);
+ mvreg_write(pp, MVNETA_RXQ_SIZE_REG(rxq->id), rxq->size);
+
+ /* Set Offset */
+ mvneta_rxq_offset_set(pp, rxq, NET_SKB_PAD);
+
+ /* Set coalescing pkts and time */
+ mvneta_rx_pkts_coal_set(pp, rxq, rxq->pkts_coal);
+ mvneta_rx_time_coal_set(pp, rxq, rxq->time_coal);
+
+ /* Fill RXQ with buffers from RX pool */
+ mvneta_rxq_buf_size_set(pp, rxq, MVNETA_RX_BUF_SIZE(pp->pkt_size));
+ mvneta_rxq_bm_disable(pp, rxq);
+ mvneta_rxq_fill(pp, rxq, rxq->size);
+
+ return 0;
+}
+
+/* Cleanup Rx queue */
+static void mvneta_rxq_deinit(struct mvneta_port *pp,
+ struct mvneta_rx_queue *rxq)
+{
+ mvneta_rxq_drop_pkts(pp, rxq);
+
+ if (rxq->descs)
+ dma_free_coherent(pp->dev->dev.parent,
+ rxq->size * MVNETA_DESC_ALIGNED_SIZE,
+ rxq->descs,
+ rxq->descs_phys);
+
+ rxq->descs = NULL;
+ rxq->last_desc = 0;
+ rxq->next_desc_to_proc = 0;
+ rxq->descs_phys = 0;
+}
+
+/* Create and initialize a tx queue */
+static int mvneta_txq_init(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq)
+{
+ txq->size = pp->tx_ring_size;
+
+ /* Allocate memory for TX descriptors */
+ txq->descs = dma_alloc_coherent(pp->dev->dev.parent,
+ txq->size * MVNETA_DESC_ALIGNED_SIZE,
+ &txq->descs_phys,
+ DMA_BIDIRECTIONAL);
+ if (txq->descs == NULL) {
+ netdev_err(pp->dev,
+ "txQ=%d: Can't allocate %d bytes for %d TX descr\n",
+ txq->id, txq->size * MVNETA_DESC_ALIGNED_SIZE,
+ txq->size);
+ return -ENOMEM;
+ }
+
+ /* Make sure descriptor address is cache line size aligned */
+ BUG_ON(txq->descs !=
+ PTR_ALIGN(txq->descs, MVNETA_CPU_D_CACHE_LINE_SIZE));
+
+ txq->last_desc = txq->size - 1;
+
+ /* Set maximum bandwidth for enabled TXQs */
+ mvreg_write(pp, MVETH_TXQ_TOKEN_CFG_REG(txq->id), 0x03ffffff);
+ mvreg_write(pp, MVETH_TXQ_TOKEN_COUNT_REG(txq->id), 0x3fffffff);
+
+ /* Set Tx descriptors queue starting address */
+ mvreg_write(pp, MVNETA_TXQ_BASE_ADDR_REG(txq->id), txq->descs_phys);
+ mvreg_write(pp, MVNETA_TXQ_SIZE_REG(txq->id), txq->size);
+
+ txq->tx_skb = kmalloc(txq->size * sizeof(*txq->tx_skb),
+ GFP_KERNEL);
+ if (txq->tx_skb == NULL) {
+ dma_free_coherent(pp->dev->dev.parent,
+ txq->size * MVNETA_DESC_ALIGNED_SIZE,
+ txq->descs, txq->descs_phys);
+ return -ENOMEM;
+ }
+ mvneta_tx_done_pkts_coal_set(pp, txq, txq->done_pkts_coal);
+
+ return 0;
+}
+
+/* Free allocated resources when mvneta_txq_init() fails to allocate memory*/
+static void mvneta_txq_deinit(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq)
+{
+ kfree(txq->tx_skb);
+
+ if (txq->descs)
+ dma_free_coherent(pp->dev->dev.parent,
+ txq->size * MVNETA_DESC_ALIGNED_SIZE,
+ txq->descs,
+ txq->descs_phys);
+
+ txq->descs = NULL;
+ txq->last_desc = 0;
+ txq->next_desc_to_proc = 0;
+ txq->descs_phys = 0;
+
+ /* Set minimum bandwidth for disabled TXQs */
+ mvreg_write(pp, MVETH_TXQ_TOKEN_CFG_REG(txq->id), 0);
+ mvreg_write(pp, MVETH_TXQ_TOKEN_COUNT_REG(txq->id), 0);
+
+ /* Set Tx descriptors queue starting address and size */
+ mvreg_write(pp, MVNETA_TXQ_BASE_ADDR_REG(txq->id), 0);
+ mvreg_write(pp, MVNETA_TXQ_SIZE_REG(txq->id), 0);
+}
+
+/* Cleanup all Tx queues */
+static void mvneta_cleanup_txqs(struct mvneta_port *pp)
+{
+ int queue;
+ for (queue = 0; queue < mvneta_txq_number; queue++)
+ mvneta_txq_deinit(pp, &pp->txqs[queue]);
+}
+
+/* Cleanup all Rx queues */
+static void mvneta_cleanup_rxqs(struct mvneta_port *pp)
+{
+ int queue;
+ for (queue = 0; queue < mvneta_rxq_number; queue++)
+ mvneta_rxq_deinit(pp, &pp->rxqs[queue]);
+}
+
+
+/* Init all Rx queues */
+static int mvneta_setup_rxqs(struct mvneta_port *pp)
+{
+ int queue;
+
+ for (queue = 0; queue < mvneta_rxq_number; queue++) {
+ int err = mvneta_rxq_init(pp, &pp->rxqs[queue]);
+ if (err) {
+ netdev_err(pp->dev,
+ "%s: can't create RxQ rxq=%d\n",
+ __func__, queue);
+ mvneta_cleanup_rxqs(pp);
+ return -ENODEV;
+ }
+ }
+
+ return 0;
+}
+
+/* Init all tx queues */
+static int mvneta_setup_txqs(struct mvneta_port *pp)
+{
+ int queue;
+
+ for (queue = 0; queue < mvneta_txq_number; queue++) {
+ int err = mvneta_txq_init(pp, &pp->txqs[queue]);
+ if (err) {
+ netdev_err(pp->dev,
+ "%s: can't create TxQ txq=%d\n",
+ __func__, queue);
+ mvneta_cleanup_txqs(pp);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+/* Start the port, connect to port interrupt line, unmask interrupts */
+static void mvneta_start_dev(struct mvneta_port *pp)
+{
+ mvneta_max_rx_size_set(pp, pp->pkt_size);
+ mvneta_txq_max_tx_size_set(pp, pp->pkt_size);
+
+ /* start the Rx/Tx activity */
+ mvneta_port_enable(pp);
+
+ /* Enable polling on the port */
+ napi_enable(&pp->napi);
+
+ /* Unmask interrupts */
+ mvneta_interrupts_unmask(pp);
+ smp_call_function_many(cpu_online_mask,
+ mvneta_interrupts_unmask,
+ pp, 1);
+
+ phy_start(pp->phy_dev);
+
+ netif_tx_start_all_queues(pp->dev);
+}
+
+/* Stop the port, free port interrupt line */
+static void mvneta_stop_dev(struct mvneta_port *pp)
+{
+ phy_stop(pp->phy_dev);
+
+ napi_disable(&pp->napi);
+
+ /* Stop upper layer */
+ netif_carrier_off(pp->dev);
+
+ mvneta_port_down(pp);
+ netif_tx_stop_all_queues(pp->dev);
+
+ /* Stop the port activity */
+ mvneta_port_disable(pp);
+
+ /* Clear all ethernet port interrupts */
+ mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0);
+ mvreg_write(pp, MVNETA_INTR_OLD_CAUSE, 0);
+
+ /* Mask all interrupts */
+ mvneta_interrupts_mask(pp);
+ smp_call_function_many(cpu_online_mask, mvneta_interrupts_mask,
+ pp, 1);
+
+ /* Reset TX port here. */
+ mvneta_tx_reset(pp);
+ mvneta_rx_reset(pp);
+}
+
+
+/* tx timeout callback - display a message and stop/start the network device */
+static void mvneta_tx_timeout(struct net_device *dev)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+ netdev_info(dev, "TX timeout\n");
+ mvneta_stop_dev(pp);
+ mvneta_start_dev(pp);
+}
+
+/* Return positive if MTU is valid */
+static int mvneta_check_mtu_valid(struct net_device *dev, int mtu)
+{
+ if (mtu < 68) {
+ netdev_err(dev, "cannot change mtu to less than 68\n");
+ return -EINVAL;
+ }
+
+ if (mtu > 9676 /* 9700 - 20 and rounding to 8 */) {
+ netdev_info(dev, "Illegal MTU value %d, round to 9676", mtu);
+ mtu = 9676;
+ }
+
+ if (!IS_ALIGNED(MVNETA_RX_PKT_SIZE(mtu), 8)) {
+ netdev_info(dev, "Illegal MTU value %d, rounding to %d",
+ mtu, ALIGN(MVNETA_RX_PKT_SIZE(mtu), 8));
+ mtu = ALIGN(MVNETA_RX_PKT_SIZE(mtu), 8);
+ }
+
+ return mtu;
+}
+
+/* Change the device mtu */
+static int mvneta_change_mtu(struct net_device *dev, int mtu)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+ int ret;
+
+ mtu = mvneta_check_mtu_valid(dev, mtu);
+ if (mtu < 0)
+ return -EINVAL;
+
+ dev->mtu = mtu;
+
+ if (!netif_running(dev))
+ return 0;
+
+ /*
+ * The interface is running, so we have to force a
+ * reallocation of the RXQs
+ */
+ mvneta_stop_dev(pp);
+
+ mvneta_cleanup_txqs(pp);
+ mvneta_cleanup_rxqs(pp);
+
+ pp->pkt_size = MVNETA_RX_PKT_SIZE(pp->dev->mtu);
+
+ ret = mvneta_setup_rxqs(pp);
+ if (ret) {
+ netdev_err(pp->dev, "unable to setup rxqs after MTU change\n");
+ return ret;
+ }
+
+ mvneta_setup_txqs(pp);
+
+ mvneta_start_dev(pp);
+ mvneta_port_up(pp);
+
+ return 0;
+}
+
+/* Handle setting mac address */
+static int mvneta_set_mac_addr(struct net_device *dev, void *addr)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+ u8 *mac = addr + 2;
+ int i, ret;
+
+ if (netif_running(dev))
+ return -EBUSY;
+
+ /* Remove previous address table entry */
+ ret = mvneta_mac_addr_set(pp, dev->dev_addr, -1);
+ if (ret < 0)
+ return ret;
+
+ /* Set new addr in hw */
+ ret = mvneta_mac_addr_set(pp, mac, mvneta_rxq_def);
+ if (ret < 0)
+ return ret;
+
+ /* Set addr in the device */
+ for (i = 0; i < ETH_ALEN; i++)
+ dev->dev_addr[i] = mac[i];
+
+ return 0;
+}
+
+/* MDIO / phylib functions */
+
+static int mvneta_mdio_read(struct mii_bus *bus, int mii_id,
+ int regnum)
+{
+ struct mvneta_port *pp = bus->priv;
+ int count;
+ u32 val;
+
+ /* Wait for the SMI register to be ready for another
+ * operation */
+ count = 0;
+ while (1) {
+ val = mvreg_read(pp, MVNETA_SMI);
+ if (!(val & MVNETA_SMI_BUSY))
+ break;
+
+ if (count > 100) {
+ netdev_err(pp->dev, "Timeout: SMI busy for too long\n");
+ return -ETIMEDOUT;
+ }
+
+ udelay(10);
+ count++;
+ }
+
+ mvreg_write(pp, MVNETA_SMI,
+ ((mii_id << MVNETA_SMI_PHY_ADDR_SHIFT) |
+ (regnum << MVNETA_SMI_PHY_REG_SHIFT) |
+ MVNETA_SMI_READ_OPERATION));
+
+ /* Wait for the value to become available */
+ count = 0;
+ while (1) {
+ val = mvreg_read(pp, MVNETA_SMI);
+ if (val & MVNETA_SMI_READ_VALID)
+ break;
+
+ if (count > 100) {
+ netdev_err(pp->dev, "Timeout when reading PHY\n");
+ return -ETIMEDOUT;
+ }
+
+ udelay(10);
+ count++;
+ }
+
+ return val & 0xFFFF;
+}
+
+static int mvneta_mdio_write(struct mii_bus *bus, int mii_id,
+ int regnum, u16 value)
+{
+ struct mvneta_port *pp = bus->priv;
+ int count;
+ u32 val;
+
+ /* Wait for the SMI register to be ready for another
+ * operation */
+ count = 0;
+ while (1) {
+ val = mvreg_read(pp, MVNETA_SMI);
+ if (!(val & MVNETA_SMI_BUSY))
+ break;
+
+ if (count > 100) {
+ netdev_err(pp->dev, "Timeout: SMI busy for too long\n");
+ return -ETIMEDOUT;
+ }
+
+ udelay(10);
+ count++;
+ }
+
+ mvreg_write(pp, MVNETA_SMI,
+ ((mii_id << MVNETA_SMI_PHY_ADDR_SHIFT) |
+ (regnum << MVNETA_SMI_PHY_REG_SHIFT) |
+ MVNETA_SMI_WRITE_OPERATION |
+ (value << MVNETA_SMI_DATA_SHIFT)));
+
+ return 0;
+}
+
+static int mvneta_mdio_reset(struct mii_bus *bus)
+{
+ return 0;
+}
+
+static void mvneta_adjust_link(struct net_device *ndev)
+{
+ struct mvneta_port *pp = netdev_priv(ndev);
+ struct phy_device *phydev = pp->phy_dev;
+ int status_change = 0;
+
+ if (phydev->link) {
+ if ((pp->speed != phydev->speed) ||
+ (pp->duplex != phydev->duplex)) {
+ u32 val;
+
+ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+ val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED |
+ MVNETA_GMAC_CONFIG_GMII_SPEED |
+ MVNETA_GMAC_CONFIG_FULL_DUPLEX);
+
+ if (phydev->duplex)
+ val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+
+ if (phydev->speed == SPEED_1000)
+ val |= MVNETA_GMAC_CONFIG_GMII_SPEED;
+ else
+ val |= MVNETA_GMAC_CONFIG_MII_SPEED;
+
+ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+
+ pp->duplex = phydev->duplex;
+ pp->speed = phydev->speed;
+ }
+ }
+
+ if (phydev->link != pp->link) {
+ if (!phydev->link) {
+ pp->duplex = -1;
+ pp->speed = 0;
+ }
+
+ pp->link = phydev->link;
+ status_change = 1;
+ }
+
+ if (status_change) {
+ if (phydev->link) {
+ u32 val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+ val |= (MVNETA_GMAC_FORCE_LINK_PASS |
+ MVNETA_GMAC_FORCE_LINK_DOWN);
+ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+ mvneta_port_up(pp);
+ netdev_info(pp->dev, "link up\n");
+ } else {
+ mvneta_port_down(pp);
+ netdev_info(pp->dev, "link down\n");
+ }
+ }
+}
+
+static int mvneta_mdio_probe(struct mvneta_port *pp)
+{
+ int i, ret;
+ struct phy_device *phy_dev = NULL;
+
+ for (i = 0; i < PHY_MAX_ADDR; i++) {
+ if (pp->mii_bus->phy_map[i] &&
+ pp->mii_bus->phy_map[i]->phy_id != 0) {
+ phy_dev = pp->mii_bus->phy_map[i];
+ break;
+ }
+ }
+
+ if (!phy_dev) {
+ netdev_err(pp->dev, "no PHY found\n");
+ return -ENODEV;
+ }
+
+ ret = phy_connect_direct(pp->dev, phy_dev, mvneta_adjust_link, 0,
+ pp->phy_interface);
+ if (ret) {
+ netdev_err(pp->dev, "could not attach to PHY\n");
+ return ret;
+ }
+
+ phy_dev->supported &= PHY_GBIT_FEATURES;
+ phy_dev->advertising = phy_dev->supported;
+
+ pp->phy_dev = phy_dev;
+ pp->link = 0;
+ pp->duplex = 0;
+ pp->speed = 0;
+
+ return 0;
+}
+
+static void mvneta_mdio_remove(struct mvneta_port *pp)
+{
+ phy_disconnect(pp->phy_dev);
+ pp->phy_dev = NULL;
+}
+
+static int mvneta_open(struct net_device *dev)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+ int ret = 0;
+
+ ret = mvneta_mac_addr_set(pp, dev->dev_addr, mvneta_rxq_def);
+ if (ret < 0) {
+ netdev_err(dev, "mvneta_mac_addr_set failed\n");
+ goto mac_addr_set_failure;
+ }
+
+ pp->pkt_size = MVNETA_RX_PKT_SIZE(pp->dev->mtu);
+
+ ret = mvneta_setup_rxqs(pp);
+ if (ret)
+ goto rxqs_setup_failure;
+
+ ret = mvneta_setup_txqs(pp);
+ if (ret)
+ goto txqs_setup_failure;
+
+ /* Connect to port interrupt line */
+ ret = request_irq(pp->dev->irq, mvneta_isr, IRQF_DISABLED,
+ MVNETA_DRIVER_NAME, pp);
+ if (ret) {
+ netdev_err(pp->dev, "cannot request irq %d\n", pp->dev->irq);
+ goto request_irq_failure;
+ }
+
+ /* In default link is down */
+ netif_carrier_off(pp->dev);
+
+ ret = mvneta_mdio_probe(pp);
+ if (ret < 0) {
+ netdev_err(dev, "cannot probe MDIO bus\n");
+ goto mdio_probe_failure;
+ }
+
+ mvneta_start_dev(pp);
+
+ return 0;
+
+mdio_probe_failure:
+ free_irq(pp->dev->irq, pp);
+request_irq_failure:
+ mvneta_cleanup_txqs(pp);
+txqs_setup_failure:
+ mvneta_cleanup_rxqs(pp);
+rxqs_setup_failure:
+mac_addr_set_failure:
+ return ret;
+}
+
+static int mvneta_stop(struct net_device *dev)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+ mvneta_stop_dev(pp);
+
+ mvneta_cleanup_rxqs(pp);
+ mvneta_cleanup_txqs(pp);
+
+ del_timer(&pp->tx_done_timer);
+ clear_bit(MVNETA_F_TX_DONE_TIMER_BIT, &pp->flags);
+
+ free_irq(pp->dev->irq, pp);
+
+ mvneta_mdio_remove(pp);
+ return 0;
+}
+
+/* Ethtool methods */
+
+/* Get settings (phy address, speed) for ethtools */
+int mvneta_ethtool_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+
+ if (!pp->phy_dev)
+ return -ENODEV;
+
+ return phy_ethtool_gset(pp->phy_dev, cmd);
+}
+
+/* Set settings (phy address, speed) for ethtools */
+int mvneta_ethtool_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+ int ret;
+
+ if (!pp->phy_dev)
+ return -ENODEV;
+
+ ret = phy_ethtool_sset(pp->phy_dev, cmd);
+ netdev_info(dev, "ethtool_sset returned %d\n", ret);
+ return ret;
+}
+
+/* Set interrupt coalescing for ethtools */
+static int mvneta_ethtool_set_coalesce(struct net_device *dev,
+ struct ethtool_coalesce *c)
+{
+ int queue;
+ struct mvneta_port *pp = netdev_priv(dev);
+
+ for (queue = 0; queue < mvneta_rxq_number; queue++) {
+ struct mvneta_rx_queue *rxq = &pp->rxqs[queue];
+ rxq->time_coal = c->rx_coalesce_usecs;
+ rxq->pkts_coal = c->rx_max_coalesced_frames;
+ mvneta_rx_pkts_coal_set(pp, rxq, rxq->pkts_coal);
+ mvneta_rx_time_coal_set(pp, rxq, rxq->time_coal);
+ }
+
+ for (queue = 0; queue < mvneta_txq_number; queue++) {
+ struct mvneta_tx_queue *txq = &pp->txqs[queue];
+ txq->done_pkts_coal = c->tx_max_coalesced_frames;
+ mvneta_tx_done_pkts_coal_set(pp, txq, txq->done_pkts_coal);
+ }
+
+ return 0;
+}
+
+/* get coalescing for ethtools */
+static int mvneta_ethtool_get_coalesce(struct net_device *dev,
+ struct ethtool_coalesce *c)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+
+ c->rx_coalesce_usecs = pp->rxqs[0].time_coal;
+ c->rx_max_coalesced_frames = pp->rxqs[0].pkts_coal;
+
+ c->tx_max_coalesced_frames = pp->txqs[0].done_pkts_coal;
+ return 0;
+}
+
+
+static void mvneta_ethtool_get_drvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *drvinfo)
+{
+ strlcpy(drvinfo->driver, MVNETA_DRIVER_NAME,
+ sizeof(drvinfo->driver));
+ strlcpy(drvinfo->version, MVNETA_DRIVER_VERSION,
+ sizeof(drvinfo->version));
+ strlcpy(drvinfo->bus_info, dev_name(&dev->dev),
+ sizeof(drvinfo->bus_info));
+}
+
+
+static void mvneta_ethtool_get_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring)
+{
+ struct mvneta_port *pp = netdev_priv(netdev);
+
+ ring->rx_max_pending = MVNETA_MAX_RXD;
+ ring->tx_max_pending = MVNETA_MAX_TXD;
+ ring->rx_pending = pp->rx_ring_size;
+ ring->tx_pending = pp->tx_ring_size;
+}
+
+static int mvneta_ethtool_set_ringparam(struct net_device *dev,
+ struct ethtool_ringparam *ring)
+{
+ struct mvneta_port *pp = netdev_priv(dev);
+
+ if ((ring->rx_pending == 0) || (ring->tx_pending == 0))
+ return -EINVAL;
+ pp->rx_ring_size = ring->rx_pending < MVNETA_MAX_RXD ?
+ ring->rx_pending : MVNETA_MAX_RXD;
+ pp->tx_ring_size = ring->tx_pending < MVNETA_MAX_TXD ?
+ ring->tx_pending : MVNETA_MAX_TXD;
+
+ if (netif_running(dev)) {
+ mvneta_stop(dev);
+ if (mvneta_open(dev)) {
+ netdev_err(dev,
+ "error on opening device after ring param change\n");
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+static const struct net_device_ops mvneta_netdev_ops = {
+ .ndo_open = mvneta_open,
+ .ndo_stop = mvneta_stop,
+ .ndo_start_xmit = mvneta_tx,
+ .ndo_set_rx_mode = mvneta_set_rx_mode,
+ .ndo_set_mac_address = mvneta_set_mac_addr,
+ .ndo_change_mtu = mvneta_change_mtu,
+ .ndo_tx_timeout = mvneta_tx_timeout,
+ .ndo_get_stats64 = mvneta_get_stats64,
+};
+
+const struct ethtool_ops mvneta_eth_tool_ops = {
+ .get_link = ethtool_op_get_link,
+ .get_settings = mvneta_ethtool_get_settings,
+ .set_settings = mvneta_ethtool_set_settings,
+ .set_coalesce = mvneta_ethtool_set_coalesce,
+ .get_coalesce = mvneta_ethtool_get_coalesce,
+ .get_drvinfo = mvneta_ethtool_get_drvinfo,
+ .get_ringparam = mvneta_ethtool_get_ringparam,
+ .set_ringparam = mvneta_ethtool_set_ringparam,
+};
+
+/* Initialize hw */
+static int __devinit mvneta_init(struct mvneta_port *pp, int phy_addr)
+{
+ int queue, i, ret = 0;
+
+ /* Disable port */
+ mvneta_port_disable(pp);
+
+ /* Set port default values */
+ mvneta_defaults_set(pp);
+
+ pp->txqs = kzalloc(mvneta_txq_number * sizeof(struct mvneta_tx_queue),
+ GFP_KERNEL);
+ if (!pp->txqs) {
+ netdev_err(pp->dev, "out of memory in allocating tx queue\n");
+ ret = -ENOMEM;
+ goto txqs_alloc_failure;
+ }
+
+ /* Initialize TX descriptor rings */
+ for (queue = 0; queue < mvneta_txq_number; queue++) {
+ struct mvneta_tx_queue *txq = &pp->txqs[queue];
+ txq->id = queue;
+ txq->size = pp->tx_ring_size;
+ txq->done_pkts_coal = MVNETA_TXDONE_COAL_PKTS;
+ }
+
+ pp->rxqs = kzalloc(mvneta_rxq_number *
+ sizeof(struct mvneta_rx_queue), GFP_KERNEL);
+ if (!pp->rxqs) {
+ netdev_err(pp->dev, "out of memory in allocating rx queue\n");
+ ret = -ENOMEM;
+ goto rxqs_alloc_failure;
+ }
+
+ /* Create Rx descriptor rings */
+ for (queue = 0; queue < mvneta_rxq_number; queue++) {
+ struct mvneta_rx_queue *rxq = &pp->rxqs[queue];
+ rxq->id = queue;
+ rxq->size = pp->rx_ring_size;
+ rxq->pkts_coal = MVNETA_RX_COAL_PKTS;
+ rxq->time_coal = MVNETA_RX_COAL_USEC;
+ }
+
+ pp->mii_bus = mdiobus_alloc();
+ if (!pp->mii_bus) {
+ netdev_err(pp->dev, "Cannot allocate MDIO bus\n");
+ ret = -ENOMEM;
+ goto mdiobus_alloc_failure;
+ }
+
+ pp->mii_bus->name = "mvneta_mii_bus";
+ pp->mii_bus->read = mvneta_mdio_read;
+ pp->mii_bus->write = mvneta_mdio_write;
+ pp->mii_bus->reset = mvneta_mdio_reset;
+ snprintf(pp->mii_bus->id, MII_BUS_ID_SIZE, "%s-mii",
+ dev_name(pp->dev->dev.parent));
+ pp->mii_bus->priv = pp;
+ pp->mii_bus->parent = pp->dev->dev.parent;
+ pp->mii_bus->phy_mask = ~(1 << phy_addr);
+
+ pp->mii_bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL);
+ if (!pp->mii_bus->irq) {
+ netdev_err(pp->dev, "Cannot allocate PHY IRQ array\n");
+ ret = -ENOMEM;
+ goto mdiobus_irq_alloc_failure;
+ }
+
+ for (i = 0; i < PHY_MAX_ADDR; i++)
+ pp->mii_bus->irq[i] = PHY_POLL;
+
+ ret = mdiobus_register(pp->mii_bus);
+ if (ret < 0) {
+ netdev_err(pp->dev, "Cannot register MDIO bus (%d)\n", ret);
+ goto mdiobus_register_failure;
+ }
+
+ return 0;
+
+mdiobus_register_failure:
+ kfree(pp->mii_bus->irq);
+mdiobus_irq_alloc_failure:
+ mdiobus_free(pp->mii_bus);
+mdiobus_alloc_failure:
+ kfree(pp->rxqs);
+rxqs_alloc_failure:
+ kfree(pp->txqs);
+txqs_alloc_failure:
+ return ret;
+}
+
+static void __devexit mvneta_deinit(struct mvneta_port *pp)
+{
+ mdiobus_unregister(pp->mii_bus);
+ kfree(pp->mii_bus->irq);
+ mdiobus_free(pp->mii_bus);
+ kfree(pp->txqs);
+ kfree(pp->rxqs);
+}
+
+/* platform glue : initialize decoding windows */
+static void __devinit mvneta_conf_mbus_windows(struct mvneta_port *pp,
+ const struct mbus_dram_target_info *dram)
+{
+ u32 win_enable;
+ u32 win_protect;
+ int i;
+
+ for (i = 0; i < 6; i++) {
+ mvreg_write(pp, MVNETA_WIN_BASE(i), 0);
+ mvreg_write(pp, MVNETA_WIN_SIZE(i), 0);
+
+ if (i < 4)
+ mvreg_write(pp, MVNETA_WIN_REMAP(i), 0);
+ }
+
+ win_enable = 0x3f;
+ win_protect = 0;
+
+ for (i = 0; i < dram->num_cs; i++) {
+ const struct mbus_dram_window *cs = dram->cs + i;
+ mvreg_write(pp, MVNETA_WIN_BASE(i),
+ (cs->base & 0xffff0000) |
+ (cs->mbus_attr << 8) |
+ dram->mbus_dram_target_id);
+
+ mvreg_write(pp, MVNETA_WIN_SIZE(i),
+ (cs->size - 1) & 0xffff0000);
+
+ win_enable &= ~(1 << i);
+ win_protect |= 3 << (2 * i);
+ }
+
+ mvreg_write(pp, MVNETA_BASE_ADDR_ENABLE, win_enable);
+}
+
+/* Power up the port */
+static void __devinit mvneta_port_power_up(struct mvneta_port *pp, int phy_mode)
+{
+ u32 val;
+
+ /* MAC Cause register should be cleared */
+ mvreg_write(pp, MVNETA_UNIT_INTR_CAUSE, 0);
+
+ if (phy_mode == PHY_INTERFACE_MODE_SGMII)
+ mvneta_port_sgmii_config(pp);
+
+ mvneta_gmac_rgmii_set(pp, 1);
+
+ /* Cancel Port Reset */
+ val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
+ val &= ~MVNETA_GMAC2_PORT_RESET;
+ mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
+
+ while ((mvreg_read(pp, MVNETA_GMAC_CTRL_2) &
+ MVNETA_GMAC2_PORT_RESET) != 0)
+ continue;
+}
+
+/* Device initialization routine */
+static int __devinit mvneta_probe(struct platform_device *pdev)
+{
+ int err = -EINVAL;
+ struct mvneta_port *pp;
+ struct net_device *dev;
+ u32 phy_addr, clk_rate;
+ int phy_mode;
+ const char *mac_addr;
+ const struct mbus_dram_target_info *dram_target_info;
+ struct device_node *dn = pdev->dev.of_node;
+
+ dev = alloc_etherdev_mq(sizeof(struct mvneta_port), 8);
+ if (!dev)
+ return -ENOMEM;
+
+ dev->irq = irq_of_parse_and_map(dn, 0);
+ if (dev->irq == 0) {
+ err = -EINVAL;
+ goto err_irq;
+ }
+
+ if (of_property_read_u32(dn, "phy-addr", &phy_addr) != 0) {
+ dev_err(&pdev->dev, "could not read phy-addr\n");
+ err = -ENODEV;
+ goto err_node;
+ }
+
+ phy_mode = of_get_phy_mode(dn);
+ if (phy_mode < 0) {
+ dev_err(&pdev->dev, "wrong phy-mode\n");
+ err = -EINVAL;
+ goto err_node;
+ }
+
+ if (of_property_read_u32(dn, "clock-frequency", &clk_rate) != 0) {
+ dev_err(&pdev->dev, "could not read clock-frequency\n");
+ err = -EINVAL;
+ goto err_node;
+ }
+
+ mac_addr = of_get_mac_address(dn);
+
+ if (!mac_addr || !is_valid_ether_addr(mac_addr))
+ eth_hw_addr_random(dev);
+ else
+ memcpy(dev->dev_addr, mac_addr, 6);
+
+ dev->tx_queue_len = MVNETA_MAX_TXD;
+ dev->watchdog_timeo = 5 * HZ;
+ dev->netdev_ops = &mvneta_netdev_ops;
+
+ SET_ETHTOOL_OPS(dev, &mvneta_eth_tool_ops);
+
+ pp = netdev_priv(dev);
+
+ pp->tx_done_timer.function = mvneta_tx_done_timer_callback;
+ init_timer(&pp->tx_done_timer);
+ clear_bit(MVNETA_F_TX_DONE_TIMER_BIT, &pp->flags);
+
+ pp->weight = MVNETA_RX_POLL_WEIGHT;
+ pp->clk_rate = clk_rate;
+ pp->phy_interface = phy_mode;
+
+ pp->base = of_iomap(dn, 0);
+ if (pp->base == NULL) {
+ err = -ENOMEM;
+ goto err_node;
+ }
+
+ pp->tx_done_timer.data = (unsigned long)dev;
+
+ pp->tx_ring_size = MVNETA_MAX_TXD;
+ pp->rx_ring_size = MVNETA_MAX_RXD;
+
+ pp->dev = dev;
+ SET_NETDEV_DEV(dev, &pdev->dev);
+
+ if (mvneta_init(pp, phy_addr)) {
+ dev_err(&pdev->dev, "can't init eth hal\n");
+ err = -ENODEV;
+ goto err_base;
+ }
+ mvneta_port_power_up(pp, phy_mode);
+
+ dram_target_info = mv_mbus_dram_info();
+ if (dram_target_info)
+ mvneta_conf_mbus_windows(pp, dram_target_info);
+
+ netif_napi_add(dev, &pp->napi, mvneta_poll, pp->weight);
+
+ if (register_netdev(dev)) {
+ dev_err(&pdev->dev, "failed to register\n");
+ err = ENOMEM;
+ goto err_base;
+ }
+
+ dev->features = NETIF_F_SG | NETIF_F_IP_CSUM;
+ dev->hw_features = NETIF_F_SG | NETIF_F_IP_CSUM;
+ dev->priv_flags |= IFF_UNICAST_FLT;
+
+ dev_info(&pdev->dev, "%s, mac: %pM\n", dev->name, dev->dev_addr);
+
+ platform_set_drvdata(pdev, pp->dev);
+
+ return 0;
+err_base:
+ iounmap(pp->base);
+err_node:
+ irq_dispose_mapping(dev->irq);
+err_irq:
+ free_netdev(dev);
+ return err;
+}
+
+/* Device removal routine */
+static int __devexit mvneta_remove(struct platform_device *pdev)
+{
+ struct net_device *dev = platform_get_drvdata(pdev);
+ struct mvneta_port *pp = netdev_priv(dev);
+
+ iounmap(pp->base);
+
+ unregister_netdev(dev);
+ irq_dispose_mapping(dev->irq);
+ free_netdev(dev);
+ mvneta_deinit(pp);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static const struct of_device_id mvneta_match[] = {
+ { .compatible = "marvell,neta" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mvneta_match);
+
+static struct platform_driver mvneta_driver = {
+ .probe = mvneta_probe,
+ .remove = __devexit_p(mvneta_remove),
+ .driver = {
+ .name = MVNETA_DRIVER_NAME,
+ .of_match_table = mvneta_match,
+ },
+};
+
+module_platform_driver(mvneta_driver);
+
+MODULE_DESCRIPTION("Marvell NETA Ethernet Driver - www.marvell.com");
+MODULE_AUTHOR("Rami Rosen <rosenr@marvell.com>, Thomas Petazzoni <thomas.petazzoni@free-electrons.com>");
+MODULE_LICENSE("GPL");
+
+module_param(mvneta_rxq_number, int, S_IRUGO);
+module_param(mvneta_txq_number, int, S_IRUGO);
+
+module_param(mvneta_rxq_def, int, S_IRUGO);
+module_param(mvneta_txq_def, int, S_IRUGO);
+
--
1.7.9.5
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox