Re: [PATCH V1 1/1]mmc:sdhci-bayhub:fix Qualcomm sd host 845 SD card compatibility issue

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

 



On Wed 28 Jul 10:43 PDT 2021, chevron wrote:

> Improve the signal integrity for long SD bus trace by using 845+BH201 SD host redriver chip
> 

This is a large patch with a very short commit message, which likely is
the reason why you've not gotten any reviews.

What is this hardware? The commit message gives me a feeling that the
BH201 is an external redriver that has been paired with the SDM845
sometimes?

Please describe properly how the hardware setup looks like.


I did skim through the patch trying to figure out what you're doing and
gave you some feedback below, but this is far from a complete review.
And many of the things I point out repeats throughout the patch.

I think the first step is to figure out what this is and if your design
is the right one...

> Signed-off-by: chevron.li <chevron.li@xxxxxxxxxxxxxx>

Is your name really chevron.li? Perhaps Chevron Li?

> ---
> change in V1:

I'm not able to find v0, can you point me to it, because I would like to
understand why these changes was made:

> 1. copy Qualcomm driver base from sdhci-msm.c to sdhci-bayhub.c

Why do we need to duplicate all this code.

> 2. implement the BH201 chip initialization function at sdhci-bayhub.c

Submitting this as a large "copy sdhci-msm and implement BH201" makes it
impossible to figure out what the BH201 specific pieces (that needs the
review) are.

> 3. implement the BH201 chip power control function at sdhci-bayhub.c
> 4. implement the BH201 chip tuning function at sdhci-bayhub.c
> 5. re-implement mmc_rescan to match BH201 mode switch flow at sdhci-bayhub.c

If this is an external redriver, can this be paired with any other SDHCI
controller?

> ---
> ---
[..]
> diff --git a/drivers/mmc/host/sdhci-bayhub.c b/drivers/mmc/host/sdhci-bayhub.c
> new file mode 100644
> index 000000000000..75029470bd22
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-bayhub.c
> @@ -0,0 +1,6563 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Bayhub Technologies, Inc. BH201 SDHCI bridge IC for
> + * VENDOR SDHCI platform driver source file
> + *
> + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.

The SPDX header covers this, so don't repeat the license text here.

> + */
> +
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/delay.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/pm_opp.h>
> +#include <linux/slab.h>
> +#include <linux/iopoll.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/interconnect.h>
> +#include <linux/pinctrl/consumer.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/card.h>
> +#include <linux/mmc/sd.h>
> +#include <linux/io.h>
> +#include <linux/of_gpio.h>
> +
> +#include "sdhci-pltfm.h"
> +#include "cqhci.h"
> +#include "../core/core.h"
> +#include "../core/sd_ops.h"
> +#include "../core/sdio_ops.h"
> +#include "../core/mmc_ops.h"
> +#include "../core/sd.h"
> +#include "../core/bus.h"
> +#include "../core/host.h"
> +#include "../core/card.h"
> +#include "../core/pwrseq.h"
> +
> +#define TRUE	1
> +#define FALSE	0

Use "true" and "false"...

> +
> +#define	SD_FNC_AM_SDR50		0x2
> +#define	SD_FNC_AM_SDR104	0x3
> +#define BIT_PASS_MASK  (0x7ff)
> +#define SDR104_MANUAL_INJECT 0x3ff
> +#define SDR50_MANUAL_INJECT  0x77f
> +
[..]
> +#define TUNING_PHASE_SIZE	11
> +#define GGC_CFG_DATA {0x07000000, 0x07364022, 0x01015412, 0x01062400,\
> +	0x10400076, 0x00025432, 0x01046076, 0x62011000,\
> +	0x30503106, 0x64141711, 0x10057513, 0x00336200,\
> +	0x00020006, 0x40000400, 0x12200310, 0x3A314177}
> +
> +struct ggc_bus_mode_cfg_t {

Does "_t" say this is a struct? No need for that as "struct ggc..."
already says "it's a struct".

> +	u32 tx_selb_tb[TUNING_PHASE_SIZE];
> +	u32 all_selb_tb[TUNING_PHASE_SIZE];
> +	u32 tx_selb_failed_history;
> +	int bus_mode;
> +	int default_sela;
> +	int default_selb;
> +	u32 default_delaycode;
> +	u32 dll_voltage_unlock_cnt[4];
> +	u32 max_delaycode;
> +	u32 min_delaycode;
> +	u32 delaycode_narrowdown_index;
> +	u32 fail_phase;
> +};
> +
> +enum tuning_stat_et {

Does "_et" make this an enum?

> +	NO_TUNING = 0,
> +	OUTPUT_PASS_TYPE = 1,
> +	SET_PHASE_FAIL_TYPE = 2,
> +	TUNING_FAIL_TYPE = 3,
> +	READ_STATUS_FAIL_TYPE = 4,
> +	TUNING_CMD7_TIMEOUT = 5,
> +	RETUNING_CASE = 6,
> +};
> +
> +
> +struct t_gg_reg_strt {

What is a "t gg reg strt"?

> +	u32 ofs;
> +	u32 mask;
> +	u32 value;
> +};
> +
> +struct rl_bit_lct {
> +	u8 bits;
> +	u8 rl_bits;
> +};
> +
> +struct chk_type_t {

This only time this is used, it's there to pass 3 bools into a function.
Just use function arguments.

> +	u8 right_valid:1;
> +	u8 first_valid:1;
> +	u8 record_valid:1;
> +	u8 reserved:5;
> +};
> +
> +static const char *const op_dbg_str[] = {
> +	"no tuning",
> +	"pass",
> +	"set_phase_fail",
> +	"tuning fail",
> +	"read status fail",
> +	"tuning CMD7 timeout",
> +	"retuning case"
> +};
> +
> +struct ggc_platform_t {

What is this "ggc" and what "platform" does this represent?

> +	struct ggc_bus_mode_cfg_t sdr50;
> +	struct ggc_bus_mode_cfg_t sdr104;
> +	struct ggc_bus_mode_cfg_t *cur_bus_mode;
> +
> +	struct t_gg_reg_strt pha_stas_rx_low32;
> +	struct t_gg_reg_strt pha_stas_rx_high32;
> +	struct t_gg_reg_strt pha_stas_tx_low32;
> +	struct t_gg_reg_strt pha_stas_tx_high32;
> +	struct t_gg_reg_strt dll_sela_after_mask;
> +	struct t_gg_reg_strt dll_selb_after_mask;
> +
> +	struct t_gg_reg_strt dll_delay_100m_backup;
> +	struct t_gg_reg_strt dll_delay_200m_backup;
> +
> +	struct t_gg_reg_strt dll_sela_100m_cfg;
> +	struct t_gg_reg_strt dll_sela_200m_cfg;
> +	struct t_gg_reg_strt dll_selb_100m_cfg;
> +	struct t_gg_reg_strt dll_selb_200m_cfg;
> +	struct t_gg_reg_strt dll_selb_100m_cfg_en;
> +	struct t_gg_reg_strt dll_selb_200m_cfg_en;
> +	struct t_gg_reg_strt internl_tuning_en_100m;
> +	struct t_gg_reg_strt internl_tuning_en_200m;
> +	struct t_gg_reg_strt cmd19_cnt_cfg;
> +
> +	struct t_gg_reg_strt inject_failure_for_tuning_enable_cfg;
> +	struct t_gg_reg_strt inject_failure_for_200m_tuning_cfg;
> +	struct t_gg_reg_strt inject_failure_for_100m_tuning_cfg;
> +
> +	int def_sela_100m;
> +	int def_sela_200m;
> +	int def_selb_100m;
> +	int def_selb_200m;
> +
> +	u32 _gg_reg_cur[16];
> +	u8 _cur_read_buf[512];

Why does these have a '_' prefix?

> +
> +	bool dll_unlock_reinit_flg;
> +	u8 driver_strength_reinit_flg;
> +	bool tuning_cmd7_timeout_reinit_flg;
> +	u32 tuning_cmd7_timeout_reinit_cnt;
> +	u32 ggc_cur_sela;
> +	bool selx_tuning_done_flag;
> +	u32 ggc_cmd_tx_selb_failed_range;
> +	int ggc_sw_selb_tuning_first_selb;
> +	enum tuning_stat_et ggc_sela_tuning_result[11];
> +	int dll_voltage_scan_map[4];
> +	int cur_dll_voltage_idx;
> +	int sdr50_notuning_sela_inject_flag;
> +	int sdr50_notuning_crc_error_flag;
> +	u32 sdr50_notuning_sela_rx_inject;
> +	u32 bh201_sdr50_sela_sw_inject;
> +	u32 bh201_sdr50_selb_hw_inject;
> +	u32 bh201_sdr104_selb_hw_inject;
> +	u32 bh201_drive_strength;
> +	bool tuning_in_progress;
> +	int bh201_used;
> +	int pwr_gpio; /* External power enable pin for Redriver IC */
> +	int det_gpio;
> +};
[..]
> +static const struct sdhci_msm_bayhub_offset sdhci_msm_bayhub_v5_offset = {
> +	.core_mci_data_cnt = 0x35c,
> +	.core_mci_status = 0x324,
> +	.core_mci_fifo_cnt = 0x308,
> +	.core_mci_version = 0x318,
> +	.core_generics = 0x320,
> +	.core_testbus_config = 0x32c,
> +	.core_testbus_sel2_bit = 3,
> +	.core_testbus_ena = (1 << 31),
> +	.core_testbus_sel2 = (1 << 3),
> +	.core_pwrctl_status = 0x240,
> +	.core_pwrctl_mask = 0x244,
> +	.core_pwrctl_clear = 0x248,
> +	.core_pwrctl_ctl = 0x24c,
> +	.core_sdcc_debug_reg = 0x358,
> +	.core_dll_config = 0x200,
> +	.core_dll_status = 0x208,
> +	.core_vendor_spec = 0x20c,
> +	.core_vendor_spec_adma_err_addr0 = 0x214,
> +	.core_vendor_spec_adma_err_addr1 = 0x218,
> +	.core_vendor_spec_func2 = 0x210,
> +	.core_vendor_spec_capabilities0 = 0x21c,
> +	.core_ddr_200_cfg = 0x224,
> +	.core_vendor_spec3 = 0x250,
> +	.core_dll_config_2 = 0x254,
> +	.core_dll_config_3 = 0x258,
> +	.core_ddr_config = 0x25c,
> +	.core_dll_usr_ctl = 0x388,
> +};
> +
> +static const struct sdhci_msm_bayhub_offset sdhci_msm_bayhub_mci_offset = {

This is unused and as such you don't need the sdhci_msm_bayhub_offset
either.

> +	.core_hc_mode = 0x78,
> +	.core_mci_data_cnt = 0x30,
> +	.core_mci_status = 0x34,
> +	.core_mci_fifo_cnt = 0x44,
> +	.core_mci_version = 0x050,
> +	.core_generics = 0x70,
> +	.core_testbus_config = 0x0cc,
> +	.core_testbus_sel2_bit = 4,
> +	.core_testbus_ena = (1 << 3),
> +	.core_testbus_sel2 = (1 << 4),
> +	.core_pwrctl_status = 0xdc,
> +	.core_pwrctl_mask = 0xe0,
> +	.core_pwrctl_clear = 0xe4,
> +	.core_pwrctl_ctl = 0xe8,
> +	.core_sdcc_debug_reg = 0x124,
> +	.core_dll_config = 0x100,
> +	.core_dll_status = 0x108,
> +	.core_vendor_spec = 0x10c,
> +	.core_vendor_spec_adma_err_addr0 = 0x114,
> +	.core_vendor_spec_adma_err_addr1 = 0x118,
> +	.core_vendor_spec_func2 = 0x110,
> +	.core_vendor_spec_capabilities0 = 0x11c,
> +	.core_ddr_200_cfg = 0x184,
> +	.core_vendor_spec3 = 0x1b0,
> +	.core_dll_config_2 = 0x1b4,
> +	.core_ddr_config_old = 0x1b8,
> +	.core_ddr_config = 0x1bc,
> +};
> +
[..]
> +static const unsigned int freqs[] = { 400000, 300000, 200000, 100000 };
> +
> +static void cfg_bit_2_bt(int max_bit, int tar, int *byt, int *bit)
> +{
> +	struct rl_bit_lct cfg_bit_map[6] = {
> +		{0, 6},	{1, 5},	{2, 4},
> +		{3, 2},	{4, 1},	{5, 0},
> +	};

So you have an array of tuples, where the first entry in the tuple is
the index in the array? And then you only use rl_bits anyways...

How about:
	u8 bit_map[6] = { 6, 5, 4, 2, 1, 0 }

> +
> +	*byt = (max_bit - tar) / 6;
> +	*bit = cfg_bit_map[(max_bit - tar) % 6].rl_bits;
> +}
> +
> +static u32 cfg_read_bits_ofs_mask(u8 *cfg, struct t_gg_reg_strt *bts)
> +{
> +	u32 rv = 0;
> +	u32 msk = bts->mask;
> +	int byt = 0, bit = 0;
> +	int i = 0;
> +
> +	do {
> +		cfg_bit_2_bt(MAX_CFG_BIT_VAL, bts->ofs + i, &byt, &bit);

So "bt" is short for "byte" and "bts" is "bytes"?

> +		if (cfg[byt] & (1 << bit))
> +			rv |= 1 << i;
> +
> +		i++;
> +		msk >>= 1;
> +		if (msk == 0)
> +			break;

There's no case where mask is 0 (and I don't see why it would be), so

	while (msk) {
		...;
		msk >>= 1;
	}

Seems cleaner


But all of this would benefit from better naming and probably some
comments describing the actual format/structure of these bits and bytes.

> +	} while (1);
> +	return rv;
> +}
> +
> +static void get_default_setting(struct sdhci_host *host, u8 *data)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_msm_bayhub_host *vendor_host = sdhci_pltfm_priv(pltfm_host);
> +
> +	vendor_host->ggc.def_sela_100m =
> +		cfg_read_bits_ofs_mask(data, &vendor_host->ggc.dll_sela_100m_cfg);
> +	vendor_host->ggc.def_sela_200m =
> +		cfg_read_bits_ofs_mask(data, &vendor_host->ggc.dll_sela_200m_cfg);
> +	vendor_host->ggc.def_selb_100m =
> +		cfg_read_bits_ofs_mask(data, &vendor_host->ggc.dll_sela_100m_cfg);
> +	vendor_host->ggc.def_selb_200m =
> +		cfg_read_bits_ofs_mask(data, &vendor_host->ggc.dll_sela_200m_cfg);
> +}
> +
> +static void cfg_write_bits_ofs_mask(u8 *cfg,
> +		struct t_gg_reg_strt *bts, u32 w_value)
> +{
> +	u32 wv = w_value & bts->mask;
> +	u32 msk = bts->mask;
> +	int byt = 0, bit = 0;
> +	int i = 0;
> +
> +	do {
> +		cfg_bit_2_bt(MAX_CFG_BIT_VAL, bts->ofs + i, &byt, &bit);
> +		if (wv & 1)
> +			cfg[byt] |= (u8) (1 << bit);
> +		else
> +			cfg[byt] &= (u8) (~(1 << bit));
> +
> +		i++;
> +		wv >>= 1;
> +		msk >>= 1;
> +		if (msk == 0)
> +			break;
> +	} while (1);
> +}
> +
> +static void ram_bit_2_bt(int tar, int *byt, int *bit)

So the "ram" variant is just a regular bitmap with 8 bits per byte?
include/linux/bitmap.h gives your an interface for dealing with those.

> +{
> +	*byt = tar / 8;
> +	*bit = tar % 8;
> +}
> +
> +static void set_gg_reg_cur_val(struct ggc_platform_t  *ggc,
> +	u8 *data, u8 len)
> +{
> +	memcpy(&ggc->_gg_reg_cur[0], data, len);
> +}
> +
> +static void get_gg_reg_cur_val(struct ggc_platform_t  *ggc,
> +	u8 *data, u8 len)
> +{
> +	memcpy(data, &ggc->_gg_reg_cur[0], len);
> +}
> +
> +static void get_gg_reg_def(struct sdhci_host *host, u8 *data)
> +{
> +	u32 gg_sw_def[16] = GGC_CFG_DATA;
> +
> +	memcpy(data, (u8 *)&(gg_sw_def[0]), sizeof(gg_sw_def));

(u8 *)&(gg_sw_def[0])... The address of the first element, type casted
to a (u8*), then passed to a void *?

Isn't that the same thing as just typing "gg_sw_def"?


But why is GGC_CFG_DATA a define and not just a static const u32 array,
in which case you can replace this function with just a simple memcpy in
ggc_chip_init().

> +}
> +
> +static u32 read_ram_bits_ofs_mask(u8 *cfg, struct t_gg_reg_strt *bts)
> +{
> +	u32 rv = 0;
> +	u32 msk = bts->mask;
> +	int byt = 0, bit = 0;
> +	int i = 0;
> +
> +	do {
> +		ram_bit_2_bt(bts->ofs + i, &byt, &bit);
> +		if (cfg[byt] & (1 << bit))
> +			rv |= 1 << i;
> +
> +		i++;
> +		msk >>= 1;
> +		if (msk == 0)
> +			break;
> +
> +	} while (1);
> +	return rv;
> +}
> +
> +static void ggc_dll_voltage_init(struct sdhci_host *host)
> +{
> +	int i = 0;
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_msm_bayhub_host *vendor_host =
> +						sdhci_pltfm_priv(pltfm_host);
> +
> +	for (i = 0; i < 4; i++) {
> +		vendor_host->ggc.dll_voltage_scan_map[i] = 0;
> +		vendor_host->ggc.sdr50.dll_voltage_unlock_cnt[i] = 0;
> +		vendor_host->ggc.sdr104.dll_voltage_unlock_cnt[i] = 0;
> +	}
> +}
> +
> +static void ggc_chip_init(struct sdhci_host *host)
> +{
> +	u8 data[512] = { 0 };
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_msm_bayhub_host *vendor_host =
> +						sdhci_pltfm_priv(pltfm_host);
> +
> +	get_gg_reg_def(host, data);
> +	get_default_setting(host, data);
> +	set_gg_reg_cur_val(&vendor_host->ggc, data, 64);
> +}
> +
> +static int driver_send_command7(struct sdhci_host *host)
> +{
> +	int ret = 0;
> +	int err;
> +	struct mmc_host *mmc = host->mmc;
> +	struct mmc_command cmd = {0};
> +
> +	cmd.opcode = MMC_SELECT_CARD;
> +	cmd.arg = 0;
> +	cmd.flags = MMC_RSP_NONE | MMC_CMD_AC;
> +	err = mmc_wait_for_cmd(mmc, &cmd, 3);
> +	if (err)
> +		ret = 0;
> +	else
> +		ret = 1;
> +
> +	return ret;

return mmc_wait_for_cmd() seems more reasonable, but you don't check the
return value in any off the call sites...

> +}
> +
> +static void driver_send_command24(struct sdhci_host *host,
> +		u32 *cfg_data, int data_len)
> +{
> +	struct mmc_host *mmc = host->mmc;
> +	struct mmc_request mrq = {0};
> +	struct mmc_command cmd = { 0 };
> +	struct mmc_data data = { 0 };
> +	struct scatterlist sg;
> +	u8 *data1 = kzalloc(PAGE_SIZE, GFP_KERNEL);

This allocation needs a NULL check.

> +
> +	memcpy(data1, (u8 *)&(cfg_data[0]), data_len);
> +	sg_init_one(&sg, data1, 512);
> +
> +	cmd.opcode = MMC_WRITE_BLOCK;
> +	cmd.arg = 0;
> +	cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
> +	data.blksz = 512;
> +	data.blocks = 1;
> +	data.flags = MMC_DATA_WRITE;
> +	data.timeout_ns = 1000 * 1000 * 1000;
> +	data.sg = &sg;
> +	data.sg_len = 1;
> +	mrq.cmd = &cmd;
> +	mrq.data = &data;
> +	mrq.stop = NULL;
> +
> +	mmc_wait_for_req(mmc, &mrq);
> +	kfree(data1);
> +}
> +
> +static void bht_update_cfg(struct mmc_host *mmc_host,
> +	struct mmc_card *card, u32 *cfg_data, int data_len)
> +{
> +	int ret = 0;

This is unused.

> +	struct sdhci_host *host;
> +
> +	host = mmc_priv(mmc_host);
> +	mmc_set_bus_width(mmc_host, MMC_BUS_WIDTH_4);
> +	if (host->ops->reset)
> +		host->ops->reset(host, SDHCI_RESET_CMD|SDHCI_RESET_DATA);
> +
> +	driver_send_command7(host);
> +	driver_send_command24(host, cfg_data, data_len);
> +	driver_send_command7(host);
> +
> +	mmc_set_bus_width(mmc_host, MMC_BUS_WIDTH_1);
> +}
> +
> +
[..]
> +static void bht_load_hw_inject(struct mmc_host *mmc_host,
> +	struct mmc_card *card, u32 *cfg_data, int data_len,
> +	u32 sel200, u32 sel100)
> +{
> +	struct sdhci_host *host = mmc_priv(mmc_host);
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct sdhci_msm_bayhub_host *vendor_host =
> +						sdhci_pltfm_priv(pltfm_host);
> +	u32 gg_hw_inj[16] = GGC_CFG_DATA;
> +
> +	gg_hw_inj[1] = 0x7364032;
> +
> +	if (vendor_host->ggc.bh201_sdr104_selb_hw_inject)
> +		gg_hw_inj[11] = vendor_host->ggc.bh201_sdr104_selb_hw_inject;
> +	else
> +		gg_hw_inj[11] = 0x57336200;

Make this the default value of bh201_sdr50_selb_hw_inject and you don't
need to make these conditionals throughout the code.

> +
> +	if (vendor_host->ggc.bh201_sdr50_selb_hw_inject)
> +		gg_hw_inj[12] = vendor_host->ggc.bh201_sdr50_selb_hw_inject;
> +	else
> +		gg_hw_inj[12] = 0x00725777;
> +
> +	if (vendor_host->ggc.bh201_drive_strength)
> +		gg_hw_inj[15] = vendor_host->ggc.bh201_drive_strength;
> +
> +	bht_update_cfg(mmc_host, card, gg_hw_inj, data_len);
> +}
> +
[..]
> +static void sdhci_msm_bayhub_writeb(struct sdhci_host *host, u8 val, int reg)
> +{
> +	u32 req_type = 0;

Don't initialize variables when the next operation on that variable is
an assignment.

> +
> +	req_type = __sdhci_msm_bayhub_check_write(host, val, reg);
> +
> +	writeb_relaxed(val, host->ioaddr + reg);
> +
> +	if (req_type)
> +		sdhci_msm_bayhub_check_power_status(host, req_type);
> +}
[..]
> +static int sdhci_msm_bayhub_probe(struct platform_device *pdev)
> +{
[..]
> +	if (of_find_property(node, "use-bayhub-bh201", NULL)) {

Why would someone use the bayhub compatible, but not have the bh201?

Isn't this a leftover from a previous version where this was a patch
ontop of sdhci-msm?

> +		sdhci_bh201_parse(msm_bayhub_host->mmc);
> +		INIT_DELAYED_WORK(&host->mmc->detect, mmc_rescan_bayhub);
> +	} else
> +		msm_bayhub_host->ggc.bh201_used = 0;
> +

Regards,
Bjorn



[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux