Re: [PATCH v2 2/3] drivers/irqchip: Add Actions external interrupts support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, 12 Nov 2018 10:32:51 +0000,
Parthiban Nallathambi <pn@xxxxxxx> wrote:
> 
> 
> 
> On 11/8/18 6:03 PM, Marc Zyngier wrote:
> > On 26/08/18 16:20, Parthiban Nallathambi wrote:
> >> Hello Marc,
> >> 
> >> Thanks for your feedback.
> >> 
> >> On 8/13/18 1:46 PM, Marc Zyngier wrote:
> >>> On 12/08/18 13:22, Parthiban Nallathambi wrote:
> >>>> Actions Semi Owl family SoC's S500, S700 and S900 provides support
> >>>> for 3 external interrupt controllers through SIRQ pins.
> >>>> 
> >>>> Each line can be independently configured as interrupt and triggers
> >>>> on either of the edges (raising or falling) or either of the levels
> >>>> (high or low) . Each line can also be masked independently.
> >>>> 
> >>>> Signed-off-by: Parthiban Nallathambi <pn@xxxxxxx>
> >>>> Signed-off-by: Saravanan Sekar <sravanhome@xxxxxxxxx>
> >>>> ---
> >>>>   drivers/irqchip/Makefile       |   1 +
> >>>>   drivers/irqchip/irq-owl-sirq.c | 305 +++++++++++++++++++++++++++++++++++++++++
> >>>>   2 files changed, 306 insertions(+)
> >>>>   create mode 100644 drivers/irqchip/irq-owl-sirq.c
> >>>> 
> >>>> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> >>>> index 15f268f646bf..072c4409e7c4 100644
> >>>> --- a/drivers/irqchip/Makefile
> >>>> +++ b/drivers/irqchip/Makefile
> >>>> @@ -7,6 +7,7 @@ obj-$(CONFIG_ATH79)			+= irq-ath79-misc.o
> >>>>   obj-$(CONFIG_ARCH_BCM2835)		+= irq-bcm2835.o
> >>>>   obj-$(CONFIG_ARCH_BCM2835)		+= irq-bcm2836.o
> >>>>   obj-$(CONFIG_ARCH_EXYNOS)		+= exynos-combiner.o
> >>>> +obj-$(CONFIG_ARCH_ACTIONS)		+= irq-owl-sirq.o
> >>>>   obj-$(CONFIG_FARADAY_FTINTC010)		+= irq-ftintc010.o
> >>>>   obj-$(CONFIG_ARCH_HIP04)		+= irq-hip04.o
> >>>>   obj-$(CONFIG_ARCH_LPC32XX)		+= irq-lpc32xx.o
> >>>> diff --git a/drivers/irqchip/irq-owl-sirq.c b/drivers/irqchip/irq-owl-sirq.c
> >>>> new file mode 100644
> >>>> index 000000000000..b69301388300
> >>>> --- /dev/null
> >>>> +++ b/drivers/irqchip/irq-owl-sirq.c
> >>>> @@ -0,0 +1,305 @@
> >>>> +// SPDX-License-Identifier: GPL-2.0+
> >>>> +/*
> >>>> + *
> >>>> + * Actions Semi Owl SoCs SIRQ interrupt controller driver
> >>>> + *
> >>>> + * Copyright (C) 2014 Actions Semi Inc.
> >>>> + * David Liu <liuwei@xxxxxxxxxxxxxxxx>
> >>>> + *
> >>>> + * Author: Parthiban Nallathambi <pn@xxxxxxx>
> >>>> + * Author: Saravanan Sekar <sravanhome@xxxxxxxxx>
> >>>> + */
> >>>> +
> >>>> +#include <linux/interrupt.h>
> >>>> +#include <linux/irqchip.h>
> >>>> +#include <linux/of_irq.h>
> >>>> +#include <linux/of_address.h>
> >>>> +
> >>>> +#include <dt-bindings/interrupt-controller/arm-gic.h>
> >>>> +
> >>>> +#define INTC_GIC_INTERRUPT_PIN		13
> >>> 
> >>> Why isn't that coming from the DT?
> >> 
> >> DT numbering is taken irqchip local, by which hwirq is directly used to
> >> calculate the offset into register when it is shared. Even if this is
> >> directly from DT, I need the value to offset into the register. So maintianed
> >> inside the driver.
> > 
> > This is normally shown as a property from DT, and is relative to the
> > parent irqchip. And I don't understand what you mean by "offset into the
> > register". The only use of this is to allocate the corresponding GIC
> 
> We have two SoC's (s500, s700) with shared external interrupt control
> register and one (s900) with dedicated register for each external
> interrupt line. So the DT property "actions,sirq-offset" was introduced
> to access the register.
> 
> In case of s500, s700 when it's shared, the idea is to use the "hwirq"
> variable value to offset into the control register INTC_EXTCTL. Even if
> 3 cell GIC value is directly used like
> 
> interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
> 
> then hwirq - 13 is needed internally everywhere in this driver.
> 
> In short, this value is defined inside driver for the ease of referring
> the offset with the register.
> 
> Yes, it is possible to change the driver logic and use 3 cell interrupts
> from DT.
> 
> > interrupt, and this definitely shouldn't be harcoded.
> > 
> >> 
> >> Should it make sense to move it to DT and use another macro (different name)
> >> for offsetting?
> >> 
> >>> 
> >>>> +#define INTC_EXTCTL_PENDING		BIT(0)
> >>>> +#define INTC_EXTCTL_CLK_SEL		BIT(4)
> >>>> +#define INTC_EXTCTL_EN			BIT(5)
> >>>> +#define	INTC_EXTCTL_TYPE_MASK		GENMASK(6, 7)
> >>>> +#define	INTC_EXTCTL_TYPE_HIGH		0
> >>>> +#define	INTC_EXTCTL_TYPE_LOW		BIT(6)
> >>>> +#define	INTC_EXTCTL_TYPE_RISING		BIT(7)
> >>>> +#define	INTC_EXTCTL_TYPE_FALLING	(BIT(6) | BIT(7))
> >>>> +
> >>>> +#define get_sirq_offset(x)	chip_data->sirq[x].offset
> >>>> +
> >>>> +/* Per SIRQ data */
> >>>> +struct owl_sirq {
> >>>> +	u16 offset;
> >>>> +	/* software is responsible to clear interrupt pending bit when
> >>>> +	 * type is edge triggered. This value is for per SIRQ line.
> >>>> +	 */
> >>> 
> >>> Please follow the normal multi-line comment style:
> >>> 
> >>> /*
> >>>   * This is a comment, starting with a capital letter and ending with
> >>>   * a full stop.
> >>>   */
> >> 
> >> Sure, thanks.
> >> 
> >>> 
> >>>> +	bool type_edge;
> >>>> +};
> >>>> +
> >>>> +struct owl_sirq_chip_data {
> >>>> +	void __iomem *base;
> >>>> +	raw_spinlock_t lock;
> >>>> +	/* some SoC's share the register for all SIRQ lines, so maintain
> >>>> +	 * register is shared or not here. This value is from DT.
> >>>> +	 */
> >>>> +	bool shared_reg;
> >>>> +	struct owl_sirq *sirq;
> >>> 
> >>> Given that this driver handles at most 3 interrupts, do we need the
> >>> overhead of a pointer and an additional allocation, while we could store
> >>> all of this data in the space taken by the pointer itself?
> >>> 
> >>> Something like:
> >>> 
> >>> 	u16 offset[3];
> >>> 	u8  trigger; // Bit mask indicating edge-triggered interrupts
> >>> 
> >>> and we're done.
> >> 
> >> Sure, I will modify with one allocation.
> >> 
> >>> 
> >>>> +};
> >>>> +
> >>>> +static struct owl_sirq_chip_data *sirq_data;
> >>>> +
> >>>> +static unsigned int sirq_read_extctl(struct irq_data *data)
> >>> 
> >>> Why isn't this taking a struct owl_sirq_chip_data as a parameter instead
> >>> of always passing irq_data?
> >>> 
> >>> Also, this should return a well defined size, which "unsigned int"
> >>> isn't. Make that u32.
> >> 
> >> Sure, will adapt this.
> >> 
> >>> 
> >>>> +{
> >>>> +	struct owl_sirq_chip_data *chip_data = data->chip_data;
> >>>> +	unsigned int val;
> >>> 
> >>> u32;
> >> 
> >> Sure.
> >> 
> >>> 
> >>>> +
> >>>> +	val = readl_relaxed(chip_data->base + get_sirq_offset(data->hwirq));
> >>>> +	if (chip_data->shared_reg)
> >>>> +		val = (val >> (2 - data->hwirq) * 8) & 0xff;
> >>>> +
> >>>> +	return val;
> >>>> +}
> >>>> +
> >>>> +static void sirq_write_extctl(struct irq_data *data, unsigned int extctl)
> >>> 
> >>> Same comments.
> >> 
> >> Sure.
> >> 
> >>> 
> >>>> +{
> >>>> +	struct owl_sirq_chip_data *chip_data = data->chip_data;
> >>>> +	unsigned int val;
> >>> 
> >>> u32;
> >> 
> >> Sure.
> >> 
> >>> 
> >>>> +
> >>>> +	if (chip_data->shared_reg) {
> >>>> +		val = readl_relaxed(chip_data->base +
> >>>> +				get_sirq_offset(data->hwirq));
> >>> 
> >>> Single line, please.
> >> 
> >> Sure.
> >> 
> >>> 
> >>>> +		val &= ~(0xff << (2 - data->hwirq) * 8);
> >>>> +		extctl &= 0xff;
> >>>> +		extctl = (extctl << (2 - data->hwirq) * 8) | val;
> >>>> +	}
> >>>> +
> >>>> +	writel_relaxed(extctl, chip_data->base +
> >>>> +			get_sirq_offset(data->hwirq));
> >>> 
> >>> Single line.
> >> 
> >> Sure.
> >> 
> >>> 
> >>>> +}
> >>>> +
> >>>> +static void owl_sirq_ack(struct irq_data *data)
> >>>> +{
> >>>> +	struct owl_sirq_chip_data *chip_data = data->chip_data;
> >>>> +	unsigned int extctl;
> >>>> +	unsigned long flags;
> >>>> +
> >>>> +	/* software must clear external interrupt pending, when interrupt type
> >>>> +	 * is edge triggered, so we need per SIRQ based clearing.
> >>>> +	 */
> >>>> +	if (chip_data->sirq[data->hwirq].type_edge) {
> >>>> +		raw_spin_lock_irqsave(&chip_data->lock, flags);
> >>>> +
> >>>> +		extctl = sirq_read_extctl(data);
> >>>> +		extctl |= INTC_EXTCTL_PENDING;
> >>>> +		sirq_write_extctl(data, extctl);
> >>>> +
> >>>> +		raw_spin_unlock_irqrestore(&chip_data->lock, flags);
> >>> 
> >>> It would make a lot more sense if the lock was taken inside the accessor
> >>> so that the rest of the driver doesn't have to deal with it. Something
> >>> along of the line of:
> >>> 
> >>> static void sirq_clear_set_extctl(struct owl_sirq_chip_data *d,
> >>>                                    u32 clear, u32 set)
> >>> {
> >>> 	unsigned long flags;
> >>> 	u32 val;
> >>> 
> >>> 	raw_spin_lock_irqsave(&d->lock, flags);
> >>> 	val = sirq_read_extctl(d);
> >>> 	val &= ~clear;
> >>> 	val |= set;
> >>> 	sirq_write_extctl(d, val);
> >>> 	raw_spin_unlock_irqrestore(&d->lock, flags)
> >>> }
> >>> 
> >>> And use that throughout the driver.
> >> 
> >> Thanks for sharing the function with lock, will update it.
> >> 
> >>> 
> >>>> +	}
> >>>> +	irq_chip_ack_parent(data);
> >>> 
> >>> That being said, I'm terribly sceptical about this whole function. At
> >>> the end of the day, the flow handler used by the GIC is
> >>> handle_fasteoi_irq, which doesn't call the ack callback at all. So how
> >>> does this work?
> >> 
> >> That's my mistake. I will move this function for ".irq_eoi". Will that be fine?
> >> In short, all the devices/interrupt controller connected to sirq lines are level
> >> triggered in my board. So, I couldn't test this part last time.
> > 
> > If you don't have any way to test it, is it worth it to have that code
> > in? I'd prefer you add code that actually works, even if that's for a
> > subset of the capability of the HW, rather than add code that cannot be
> > exercised.
> 
> Ok, it wasn't case now. I have the hardware connected on these lines and
> tested ok with the implementation moved to .irq.eoi.
> 
> > 
> >> 
> >>> 
> >>>> +}
> >>>> +
> >>>> +static void owl_sirq_mask(struct irq_data *data)
> >>>> +{
> >>>> +	struct owl_sirq_chip_data *chip_data = data->chip_data;
> >>>> +	unsigned int extctl;
> >>>> +	unsigned long flags;
> >>>> +
> >>>> +	raw_spin_lock_irqsave(&chip_data->lock, flags);
> >>>> +
> >>>> +	extctl = sirq_read_extctl(data);
> >>>> +	extctl &= ~(INTC_EXTCTL_EN);
> >>>> +	sirq_write_extctl(data, extctl);
> >>>> +
> >>>> +	raw_spin_unlock_irqrestore(&chip_data->lock, flags);
> >>>> +	irq_chip_mask_parent(data);
> >>>> +}
> >>>> +
> >>>> +static void owl_sirq_unmask(struct irq_data *data)
> >>>> +{
> >>>> +	struct owl_sirq_chip_data *chip_data = data->chip_data;
> >>>> +	unsigned int extctl;
> >>>> +	unsigned long flags;
> >>>> +
> >>>> +	raw_spin_lock_irqsave(&chip_data->lock, flags);
> >>>> +
> >>>> +	extctl = sirq_read_extctl(data);
> >>>> +	extctl |= INTC_EXTCTL_EN;
> >>>> +	sirq_write_extctl(data, extctl);
> >>>> +
> >>>> +	raw_spin_unlock_irqrestore(&chip_data->lock, flags);
> >>>> +	irq_chip_unmask_parent(data);
> >>>> +}
> >>>> +
> >>>> +/* PAD_PULLCTL needs to be defined in pinctrl */
> >>>> +static int owl_sirq_set_type(struct irq_data *data, unsigned int flow_type)
> >>>> +{
> >>>> +	struct owl_sirq_chip_data *chip_data = data->chip_data;
> >>>> +	unsigned int extctl, type;
> >>>> +	unsigned long flags;
> >>>> +
> >>>> +	switch (flow_type) {
> >>>> +	case IRQF_TRIGGER_LOW:
> >>>> +		type = INTC_EXTCTL_TYPE_LOW;
> >>>> +		break;
> >>>> +	case IRQF_TRIGGER_HIGH:
> >>>> +		type = INTC_EXTCTL_TYPE_HIGH;
> >>>> +		break;
> >>>> +	case IRQF_TRIGGER_FALLING:
> >>>> +		type = INTC_EXTCTL_TYPE_FALLING;
> >>>> +		chip_data->sirq[data->hwirq].type_edge = true;
> >>>> +		break;
> >>>> +	case IRQF_TRIGGER_RISING:
> >>>> +		type = INTC_EXTCTL_TYPE_RISING;
> >>>> +		chip_data->sirq[data->hwirq].type_edge = true;
> >>>> +		break;
> >>> 
> >>> So let's say I configure an interrupt as edge, then switch it to level.
> >>> The edge setting remains and bad things will happen.
> >> 
> >> Ok, I will update the value to false for edge cases.
> >> 
> >>> 
> >>>> +	default:
> >>>> +		return  -EINVAL;
> >>>> +	}
> >>>> +
> >>>> +	raw_spin_lock_irqsave(&chip_data->lock, flags);
> >>>> +
> >>>> +	extctl = sirq_read_extctl(data);
> >>>> +	extctl &= ~INTC_EXTCTL_TYPE_MASK;
> >>>> +	extctl |= type;
> >>>> +	sirq_write_extctl(data, extctl);
> >>>> +
> >>>> +	raw_spin_unlock_irqrestore(&chip_data->lock, flags);
> >>>> +	data = data->parent_data;
> >>>> +	return irq_chip_set_type_parent(data, flow_type);
> >>>> +}
> >>>> +
> >>>> +static struct irq_chip owl_sirq_chip = {
> >>>> +	.name		= "owl-sirq",
> >>>> +	.irq_ack	= owl_sirq_ack,
> >>>> +	.irq_mask	= owl_sirq_mask,
> >>>> +	.irq_unmask	= owl_sirq_unmask,
> >>>> +	.irq_set_type	= owl_sirq_set_type,
> >>>> +	.irq_eoi	= irq_chip_eoi_parent,
> >>>> +	.irq_retrigger	= irq_chip_retrigger_hierarchy,
> >>>> +};
> >>>> +
> >>>> +static int owl_sirq_domain_alloc(struct irq_domain *domain, unsigned int virq,
> >>>> +				 unsigned int nr_irqs, void *arg)
> >>>> +{
> >>>> +	struct irq_fwspec *fwspec = arg;
> >>>> +	struct irq_fwspec parent_fwspec = {
> >>>> +		.param_count	= 3,
> >>>> +		.param[0]	= GIC_SPI,
> >>>> +		.param[1]	= fwspec->param[0] + INTC_GIC_INTERRUPT_PIN,
> >>>> +		.param[2]	= fwspec->param[1],
> >>> 
> >>> param[2] is supposed to be the trigger configuration. Your driver
> >>> supports LEVEL_LOW and EDGE_FALLING, which the GIC cannot handle. And
> >>> yet you're passing it directly.
> >> 
> >> That's my mistake. I will translate and restrict LEVEL_HIGH and EDGE_RISING
> >> for GIC here. Thanks.
> >> 
> >>> 
> >>>> +		.fwnode		= domain->parent->fwnode,
> >>>> +	};
> >>>> +
> >>>> +	if (WARN_ON(nr_irqs != 1))
> >>>> +		return -EINVAL;
> >>>> +
> >>>> +	irq_domain_set_hwirq_and_chip(domain, virq, fwspec->param[0],
> >>>> +				      &owl_sirq_chip,
> >>>> +				      domain->host_data);
> >>>> +
> >>>> +	return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
> >>>> +					    &parent_fwspec);
> >>>> +}
> >>>> +
> >>>> +static const struct irq_domain_ops sirq_domain_ops = {
> >>>> +	.alloc	= owl_sirq_domain_alloc,
> >>>> +	.free	= irq_domain_free_irqs_common,
> >>> 
> >>> No translation method? Again, how does this work?
> >> 
> >> Missed this part, I will update this next version.
> >> 
> >>> 
> >>>> +};
> >>>> +
> >>>> +static void owl_sirq_clk_init(int offset, int hwirq)
> >>>> +{
> >>>> +	unsigned int val;
> >>>> +
> >>>> +	/* register default clock is 32Khz, change to 24Mhz only when defined */
> >>>> +	val = readl_relaxed(sirq_data->base + offset);
> >>>> +	if (sirq_data->shared_reg)
> >>>> +		val |= INTC_EXTCTL_CLK_SEL << (2 - hwirq) * 8;
> >>>> +	else
> >>>> +		val |= INTC_EXTCTL_CLK_SEL;
> >>>> +
> >>>> +	writel_relaxed(val, sirq_data->base + offset);
> >>>> +}
> >>> 
> >>> I've asked questions about this in the first review, and you didn't
> >>> answer. Why is it even configurable? How do you choose the sample rate?
> >>> What's the drawback of always setting it one way or the other?
> >> 
> >> The provision for selecting sampling rate here seems meant for power
> >> management, which I wasn't aware of. So this configuration doesn't need
> >> to come from DT.
> >> 
> >> Possibly this needs to be implemented as "syscore_ops" suspend and resume
> >> calls. Should I register this as "register_syscore_ops" or leaving 32MHz
> >> is fine?
> > 
> > I think this should be entirely hidden from the interrupt controller,
> > and set by firmware or by the platform clock setup.
> 
> Agreed!
> 
> > 
> >> 
> >>> 
> >>>> +
> >>>> +static int __init owl_sirq_of_init(struct device_node *node,
> >>>> +					struct device_node *parent)
> >>>> +{
> >>>> +	struct irq_domain *domain, *domain_parent;
> >>>> +	int ret = 0, i, sirq_cnt = 0;
> >>>> +	struct owl_sirq_chip_data *chip_data;
> >>>> +
> >>>> +	sirq_cnt = of_property_count_u32_elems(node, "actions,sirq-offset");
> >>>> +	if (sirq_cnt <= 0) {
> >>>> +		pr_err("owl_sirq: register offset not specified\n");
> >>>> +		return -EINVAL;
> >>>> +	}
> >>>> +
> >>>> +	chip_data = kzalloc(sizeof(*chip_data), GFP_KERNEL);
> >>>> +	if (!chip_data)
> >>>> +		return -ENOMEM;
> >>>> +	sirq_data = chip_data;
> >>>> +
> >>>> +	chip_data->sirq = kcalloc(sirq_cnt, sizeof(*chip_data->sirq),
> >>>> +				GFP_KERNEL);
> >>>> +	if (!chip_data->sirq)
> >>>> +		goto out_free;
> >>>> +
> >>>> +	raw_spin_lock_init(&chip_data->lock);
> >>>> +	chip_data->base = of_iomap(node, 0);
> >>>> +	if (!chip_data->base) {
> >>>> +		pr_err("owl_sirq: unable to map sirq register\n");
> >>>> +		ret = -ENXIO;
> >>>> +		goto out_free;
> >>>> +	}
> >>>> +
> >>>> +	chip_data->shared_reg = of_property_read_bool(node,
> >>>> +						"actions,sirq-shared-reg");
> >>>> +	for (i = 0; i < sirq_cnt; i++) {
> >>>> +		u32 value;
> >>>> +
> >>>> +		ret = of_property_read_u32_index(node, "actions,sirq-offset",
> >>>> +						i, &value);
> >>>> +		if (ret)
> >>>> +			goto out_unmap;
> >>>> +
> >>>> +		get_sirq_offset(i) = (u16)value;
> >>>> +
> >>>> +		ret = of_property_read_u32_index(node, "actions,sirq-clk-sel",
> >>>> +						i, &value);
> >>>> +		if (ret || !value)
> >>>> +			continue;
> >>>> +
> >>>> +		/* external interrupt controller can be either connect to 32Khz/
> >>>> +		 * 24Mhz external/internal clock. This shall be configured for
> >>>> +		 * per SIRQ line. It can be defined from DT, failing defaults to
> >>>> +		 * 24Mhz clock.
> >>>> +		 */
> >>>> +		owl_sirq_clk_init(get_sirq_offset(i), i);
> >>>> +	}
> >>>> +
> >>>> +	domain_parent = irq_find_host(parent);
> >>>> +	if (!domain_parent) {
> >>>> +		pr_err("owl_sirq: interrupt-parent not found\n");
> >>>> +		goto out_unmap;
> >>>> +	}
> >>>> +
> >>>> +	domain = irq_domain_add_hierarchy(domain_parent, 0,
> >>>> +			sirq_cnt, node,
> >>>> +			&sirq_domain_ops, chip_data);
> >>>> +	if (!domain) {
> >>>> +		ret = -ENOMEM;
> >>>> +		goto out_unmap;
> >>>> +	}
> >>>> +
> >>>> +	return 0;
> >>>> +
> >>>> +out_unmap:
> >>>> +	iounmap(chip_data->base);
> >>>> +out_free:
> >>>> +	kfree(chip_data);
> >>>> +	kfree(chip_data->sirq);
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +IRQCHIP_DECLARE(owl_sirq, "actions,owl-sirq", owl_sirq_of_init);
> >>>> 
> >>> 
> >>> As it stands, this driver is nowhere near ready. I don't even understand
> >>> how edge signalling works. Also, I'd appreciate if you could answer my
> >>> comments before respining another version.
> >> 
> >> As the previous version wasn't based on hierarchy, which I was working on
> >> after your feedback. Apologize!
> > 
> > I must say I've lost track of this driver a while ago. Can you please
> > send whatever you have come up with, and we'll take it from there.
> 
> Sure, there is one thing changed, tested the eoi part with the PMIC
> connected on this interrupt line and it works.
> 
> Should I send v3 using 3 interrupt cells and re-define the usage of
> hwirq to offsetting?

I don't really care how this is expressed in the device-tree (Rob can
certainly help you define something that works), but I don't want to
see hardcoded values in the driver.

Thanks,

	M.

-- 
Jazz is not dead, it just smell funny.



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux