Search Linux Wireless

[RFC v2 22/96] cl8k: add dsp.c

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

 



From: Viktor Barna <viktor.barna@xxxxxxxxxx>

(Part of the split. Please, take a look at the cover letter for more
details).

Signed-off-by: Viktor Barna <viktor.barna@xxxxxxxxxx>
---
 drivers/net/wireless/celeno/cl8k/dsp.c | 627 +++++++++++++++++++++++++
 1 file changed, 627 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/dsp.c

diff --git a/drivers/net/wireless/celeno/cl8k/dsp.c b/drivers/net/wireless/celeno/cl8k/dsp.c
new file mode 100644
index 000000000000..fbc8b29b9257
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/dsp.c
@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include <linux/firmware.h>
+
+#include "reg/reg_access.h"
+#include "reg/reg_defs.h"
+#include "dsp.h"
+#include "hw.h"
+
+#define BUSY_WAIT_LIMIT 10000
+
+static int cl_dsp_busy_wait(struct cl_hw *cl_hw, u32 control_reg)
+{
+	int i;
+
+	for (i = 0; i < BUSY_WAIT_LIMIT; i++) {
+		/* Poll Bit29 to verify DMA transfer has ended. */
+		if ((cl_reg_read(cl_hw, control_reg) & 0x20000000) == 0)
+			return 0;
+
+		cpu_relax();
+	}
+
+	return -EIO;
+}
+
+static void cl_dsp_boot(struct cl_hw *cl_hw)
+{
+	struct cl_chip *chip = cl_hw->chip;
+
+	if (cl_hw_is_tcv0(cl_hw)) {
+		/* Disable ceva_free_clk */
+		cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 0);
+		/* Assert Ceva reset */
+		cmu_phy_0_rst_ceva_0_global_rst_n_setf(chip, 0);
+	} else {
+		/* Disable ceva_free_clk */
+		cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 0);
+		/* Assert Ceva reset */
+		cmu_phy_1_rst_ceva_1_global_rst_n_setf(chip, 0);
+	}
+
+	/* Set Ceva boot=1 */
+	modem_gcu_ceva_ctrl_boot_setf(cl_hw, 1);
+	/* Set Ceva vector */
+	modem_gcu_ceva_vec_set(cl_hw, 0);
+
+	if (cl_hw_is_tcv0(cl_hw)) {
+		/* Enable ceva_clk */
+		cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 1);
+		/* Disabel ceva_clk */
+		cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 0);
+		/* De-Assert Ceva reset - Reset Release */
+		cmu_phy_0_rst_ceva_0_global_rst_n_setf(chip, 1);
+		/* Enable ceva_clk */
+		cmu_phy_0_clk_en_ceva_0_clk_en_setf(chip, 1);
+	} else {
+		/* Enable ceva_clk */
+		cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 1);
+		/* Disabel ceva_clk */
+		cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 0);
+		/* De-Assert Ceva reset - Reset Release */
+		cmu_phy_1_rst_ceva_1_global_rst_n_setf(chip, 1);
+		/* Enable ceva_clk */
+		cmu_phy_1_clk_en_ceva_1_clk_en_setf(chip, 1);
+	}
+
+	/* Release Ceva external_wait */
+	modem_gcu_ceva_ctrl_external_wait_setf(cl_hw, 0);
+	/* Set Ceva boot=0 */
+	modem_gcu_ceva_ctrl_boot_setf(cl_hw, 0);
+}
+
+static void cl_dsp_config_dma_for_code_copy(struct cl_hw *cl_hw, u32 page, u32 page_size)
+{
+	/* Configure Program DMA to copy FW code from Shared PMEM to internal PMEM. */
+
+	/* External address to read from. */
+	cl_reg_write(cl_hw, CEVA_CPM_PDEA_REG, CEVA_SHARED_PMEM_BASE_ADDR_INTERNAL);
+	/* Internal address to write to. */
+	cl_reg_write(cl_hw, CEVA_CPM_PDIA_REG, CEVA_SHARED_PMEM_SIZE * page);
+	/* Page size */
+	cl_reg_write(cl_hw, CEVA_CPM_PDTC_REG, page_size);
+}
+
+static void cl_dsp_config_dma_for_external_data_copy(struct cl_hw *cl_hw)
+{
+	/* Configure Program DMA to copy FW code from Shared XMEM to internal XMEM. */
+
+	/* External address to read from. */
+	cl_reg_write(cl_hw, CEVA_CPM_DDEA_REG, CEVA_SHARED_XMEM_BASE_ADDR_INTERNAL);
+	/* Internal address to write to. */
+	cl_reg_write(cl_hw, CEVA_CPM_DDIA_REG, 0);
+	/* Page size + DMA direction is write */
+	cl_reg_write(cl_hw, CEVA_CPM_DDTC_REG,
+		     CEVA_SHARED_XMEM_SIZE | CEVA_CPM_DDTC_WRITE_COMMAND);
+}
+
+static int cl_dsp_hex_load(struct cl_hw *cl_hw, const u8 *buf,
+			   off_t offset, size_t size, size_t buf_size)
+{
+	u8 single_buf[4] = {0};
+	u32 bin_data = 0;
+	u8 next_byte;
+	u8 byte_num = 0;
+	int ret = 0;
+	ssize_t oft = 0;
+	size_t real_size = min(size * 3, buf_size);
+	/*
+	 * CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST is global and we don't
+	 * want to add TCV reg offset.
+	 */
+	bool chip_reg = (offset == CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST);
+
+	if (buf_size % 3) {
+		cl_dbg_err(cl_hw, "DSP size %zu must be divided by 3 !!!\n",
+			   buf_size);
+		return -EINVAL;
+	}
+
+	while (oft < real_size) {
+		memcpy(single_buf, buf + oft, 3);
+		/* Each line contains 2 hex digits + a line feed, i.e. 3 bytes */
+		ret = kstrtou8(single_buf, 16, &next_byte);
+		if (ret < 0) {
+			cl_dbg_err(cl_hw,
+				   "ret = %d, oft = %zu,"
+				   "single_buf = 0x%x 0x%x 0x%x 0x%x\n",
+				   ret, oft, single_buf[0], single_buf[1],
+				   single_buf[2], single_buf[3]);
+			return ret;
+		}
+
+		/* Little-endian order. */
+		bin_data += next_byte << (8 * byte_num);
+		byte_num = (byte_num + 1) % 4;
+
+		/* Read 4 lines from the file, and then write. */
+		if (byte_num == 0) {
+			if (chip_reg)
+				cl_reg_write_chip(cl_hw->chip, offset, bin_data);
+			else
+				cl_reg_write_direct(cl_hw, offset, bin_data);
+			offset += 4;
+			bin_data = 0;
+		}
+
+		memset(&single_buf, 0, sizeof(single_buf));
+		oft += 3;
+	}
+
+	return 0;
+}
+
+static int cl_dsp_load_code(struct cl_hw *cl_hw)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	u32 real_size;
+	u32 page;
+	u32 page_size = CEVA_SHARED_PMEM_SIZE;
+	const struct firmware *fw;
+	size_t size = 0;
+	u8 *buf = NULL;
+	char path_name[CL_PATH_MAX] = {0};
+	int ret;
+
+	snprintf(path_name, sizeof(path_name), "cl8k/%s",
+		 cl_hw->conf->ce_dsp_code);
+
+	cl_dbg_verbose(cl_hw, "from %s\n", cl_hw->conf->ce_dsp_code);
+
+	ret = request_firmware(&fw, path_name, chip->dev);
+
+	if (ret) {
+		cl_dbg_err(cl_hw, "Failed to get %s, with error: %x!\n",
+			   path_name, ret);
+		goto out;
+	}
+
+	size = fw->size;
+	buf = (u8 *)fw->data;
+
+	/* Load all pages + 1 extra cache page */
+	for (page = 0; page < CEVA_MAX_PAGES + 1; page++) {
+		if (page >= CEVA_MAX_PAGES)
+			page_size = CEVA_SHARED_PMEM_CACHE_SIZE;
+
+		real_size = min_t(u32, page_size * 3, size);
+
+		if (!real_size)
+			break;
+
+		/* Copy DSP code (one page each time) */
+		ret =  cl_dsp_hex_load(cl_hw, buf,
+				       CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST,
+				       page_size, size);
+		if (ret != 0) {
+			cl_dbg_err(cl_hw, "Failed to load pmem page 0x%x!\n", page);
+			break;
+		}
+
+		cl_dsp_config_dma_for_code_copy(cl_hw, page, page_size);
+		ret = cl_dsp_busy_wait(cl_hw, CEVA_CPM_PDTC_REG);
+
+		if (ret) {
+			cl_dbg_err(cl_hw, "cl_dsp_busy_wait failed!\n");
+			goto out;
+		}
+
+		buf += real_size;
+		size -= real_size;
+	}
+
+out:
+	release_firmware(fw);
+
+	return ret;
+}
+
+static int cl_dsp_load_data(struct cl_hw *cl_hw)
+{
+	struct cl_chip *chip = cl_hw->chip;
+	const struct firmware *fw;
+	size_t size = 0;
+	char path_name[CL_PATH_MAX] = {0};
+	int ret;
+
+	snprintf(path_name, sizeof(path_name), "cl8k/%s",
+		 cl_hw->conf->ce_dsp_data);
+
+	cl_dbg_verbose(cl_hw, "from %s\n", cl_hw->conf->ce_dsp_data);
+
+	ret = request_firmware(&fw, path_name, chip->dev);
+	if (ret) {
+		cl_dbg_err(cl_hw, "Failed to get %s, with error: %x!\n",
+			   path_name, ret);
+		goto out;
+	}
+
+	size = fw->size;
+
+	ret = cl_dsp_hex_load(cl_hw, fw->data, REG_MACDSP_API_BASE_ADDR,
+			      CEVA_DSP_DATA_SIZE, size);
+	if (ret) {
+		cl_dbg_err(cl_hw, "Failed to load HEX file\n");
+		goto out;
+	}
+out:
+	release_firmware(fw);
+
+	return ret;
+}
+
+static int cl_dsp_load_external_data(struct cl_hw *cl_hw)
+{
+	/*
+	 * Shared XMEM is not accessible by host.
+	 * Copy the XMEM section to DRAM first and then use CEVA internal DMA to copy to
+	 * SHARED XMEM.
+	 */
+	struct cl_chip *chip = cl_hw->chip;
+	const struct firmware *fw;
+	size_t size = 0;
+	char path_name[CL_PATH_MAX] = {0};
+	int ret;
+
+	snprintf(path_name, sizeof(path_name), "cl8k/%s",
+		 cl_hw->conf->ce_dsp_external_data);
+
+	cl_dbg_verbose(cl_hw, "from %s\n", cl_hw->conf->ce_dsp_external_data);
+
+	ret = request_firmware(&fw, path_name, chip->dev);
+	if (ret) {
+		cl_dbg_err(cl_hw, "Failed to get %s, with error: %x!\n",
+			   path_name, ret);
+		goto out;
+	}
+
+	size = fw->size;
+
+	ret = cl_dsp_hex_load(cl_hw, fw->data, REG_MACDSP_API_BASE_ADDR,
+			      CEVA_DSP_EXT_DATA_SIZE, size);
+
+	if (ret) {
+		cl_dbg_err(cl_hw, "Failed to load HEX file\n");
+		goto out;
+	}
+
+	cl_dsp_config_dma_for_external_data_copy(cl_hw);
+	ret = cl_dsp_busy_wait(cl_hw, CEVA_CPM_DDTC_REG);
+
+	if (ret) {
+		cl_dbg_err(cl_hw, "cl_dsp_busy_wait failed!\n");
+		goto out;
+	}
+
+out:
+	release_firmware(fw);
+
+	return ret;
+}
+
+static bool cl_dsp_is_universal_file(struct cl_chip *chip)
+{
+	return (cl_chip_is_tcv0_enabled(chip) &&
+		cl_chip_is_tcv1_enabled(chip) &&
+		!strcmp(chip->cl_hw_tcv0->conf->ce_dsp_code,
+			chip->cl_hw_tcv1->conf->ce_dsp_code));
+}
+
+static int cl_dsp_load_code_dual(struct cl_chip *chip, const char *filename)
+{
+	struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+	struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+	u32 real_size;
+	u32 page;
+	u32 page_size = CEVA_SHARED_PMEM_SIZE;
+	const struct firmware *fw;
+	size_t size = 0;
+	u8 *buf = NULL;
+	char path_name[CL_PATH_MAX] = {0};
+	int ret;
+
+	snprintf(path_name, sizeof(path_name), "cl8k/%s", filename);
+	cl_dbg_chip_verbose(chip, "from %s\n", filename);
+	ret = request_firmware(&fw, path_name, chip->dev);
+
+	if (ret) {
+		cl_dbg_chip_err(chip, "Failed to get %s, with error: %x!\n",
+				path_name, ret);
+		goto out;
+	}
+
+	size = fw->size;
+	buf = (u8 *)fw->data;
+
+	/* Load all pages + 1 extra cache page */
+	for (page = 0; page < CEVA_MAX_PAGES + 1; page++) {
+		if (page >= CEVA_MAX_PAGES)
+			page_size = CEVA_SHARED_PMEM_CACHE_SIZE;
+
+		real_size = min_t(u32, page_size * 3, size);
+
+		if (!real_size)
+			break;
+
+		/* Copy DSP code (one page each time) */
+		ret = cl_dsp_hex_load(chip->cl_hw_tcv0, buf,
+				      CEVA_SHARED_PMEM_BASE_ADDR_FROM_HOST,
+				      page_size, size);
+		if (ret) {
+			cl_dbg_chip_err(chip, "Failed to load pmem page 0x%x!\n", page);
+			break;
+		}
+
+		cl_dsp_config_dma_for_code_copy(cl_hw_tcv0, page, page_size);
+		ret = cl_dsp_busy_wait(cl_hw_tcv0, CEVA_CPM_PDTC_REG);
+
+		if (ret) {
+			cl_dbg_err(cl_hw_tcv0, "cl_dsp_busy_wait failed\n");
+			goto out;
+		}
+
+		cl_dsp_config_dma_for_code_copy(cl_hw_tcv1, page, page_size);
+		ret = cl_dsp_busy_wait(cl_hw_tcv1, CEVA_CPM_PDTC_REG);
+
+		if (ret) {
+			cl_dbg_err(cl_hw_tcv1, "cl_dsp_busy_wait failed\n");
+			goto out;
+		}
+
+		buf += real_size;
+		size -= real_size;
+	}
+
+out:
+	release_firmware(fw);
+
+	return ret;
+}
+
+static int cl_dsp_load_external_data_dual(struct cl_chip *chip, const char *filename)
+{
+	/*
+	 * Shared XMEM is not accessible by host.
+	 * Copy the XMEM section to DRAM first and then use CEVA internal DMA to copy to
+	 * SHARED XMEM.
+	 */
+	struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+	struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+	const struct firmware *fw;
+	size_t size = 0;
+	char path_name[CL_PATH_MAX] = {0};
+	int ret;
+
+	snprintf(path_name, sizeof(path_name), "cl8k/%s", filename);
+	cl_dbg_chip_verbose(chip, "from %s\n", filename);
+	ret = request_firmware(&fw, path_name, chip->dev);
+
+	if (ret) {
+		cl_dbg_chip_err(chip, "Failed to get %s, with error: %x!\n",
+				path_name, ret);
+		goto out;
+	}
+
+	size = fw->size;
+
+	/* TCV0 */
+	ret = cl_dsp_hex_load(cl_hw_tcv0, fw->data, REG_MACDSP_API_BASE_ADDR,
+			      CEVA_DSP_EXT_DATA_SIZE, size);
+
+	if (ret) {
+		cl_dbg_err(cl_hw_tcv0, "Failed to load HEX file\n");
+		goto out;
+	}
+
+	cl_dsp_config_dma_for_external_data_copy(cl_hw_tcv0);
+	ret = cl_dsp_busy_wait(cl_hw_tcv0, CEVA_CPM_DDTC_REG);
+
+	if (ret) {
+		cl_dbg_err(cl_hw_tcv0, "dsp_busy_wait failed!\n");
+		goto out;
+	}
+
+	/* TCV1 */
+	ret = cl_dsp_hex_load(cl_hw_tcv1, fw->data, REG_MACDSP_API_BASE_ADDR,
+			      CEVA_DSP_EXT_DATA_SIZE, size);
+
+	if (ret) {
+		cl_dbg_err(cl_hw_tcv1, "Failed to load HEX file\n");
+		goto out;
+	}
+
+	cl_dsp_config_dma_for_external_data_copy(cl_hw_tcv1);
+	ret = cl_dsp_busy_wait(cl_hw_tcv1, CEVA_CPM_DDTC_REG);
+
+	if (ret) {
+		cl_dbg_err(cl_hw_tcv1, "cl_dsp_busy_wait failed!\n");
+		goto out;
+	}
+
+out:
+	release_firmware(fw);
+
+	return ret;
+}
+
+static int cl_dsp_load_data_dual(struct cl_chip *chip, const char *filename)
+{
+	struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+	struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+	const struct firmware *fw;
+	size_t size = 0;
+	char path_name[CL_PATH_MAX] = {0};
+	int ret;
+
+	snprintf(path_name, sizeof(path_name), "cl8k/%s", filename);
+	cl_dbg_chip_verbose(chip, "from %s\n", filename);
+	ret = request_firmware(&fw, path_name, chip->dev);
+
+	if (ret) {
+		cl_dbg_chip_err(chip, "Failed to get %s, with error: %x!\n",
+				path_name, ret);
+		goto out;
+	}
+
+	size = fw->size;
+
+	ret = cl_dsp_hex_load(cl_hw_tcv0, fw->data,
+			      REG_MACDSP_API_BASE_ADDR,
+			      CEVA_DSP_DATA_SIZE, size);
+
+	if (ret != 0) {
+		cl_dbg_err(cl_hw_tcv0, "Failed to load HEX file\n");
+		goto out;
+	}
+
+	ret = cl_dsp_hex_load(cl_hw_tcv1, fw->data,
+			      REG_MACDSP_API_BASE_ADDR,
+			      CEVA_DSP_DATA_SIZE, size);
+
+	if (ret != 0) {
+		cl_dbg_err(cl_hw_tcv1, "Failed to load HEX file\n");
+		goto out;
+	}
+
+out:
+	release_firmware(fw);
+
+	return ret;
+}
+
+static void cl_dsp_print_ceva_core_info(struct cl_hw *cl_hw)
+{
+	cl_dbg_trace(cl_hw, "CEVA_CORE_VERSION_ADDR=0x%X.\n",
+		     cl_reg_read(cl_hw, CEVA_CORE_VERSION_ADDR));
+	cl_dbg_trace(cl_hw, "CEVA_CORE_ID_ADDR=0x%X.\n",
+		     cl_reg_read(cl_hw, CEVA_CORE_ID_ADDR));
+}
+
+static int cl_dsp_load_dual(struct cl_chip *chip)
+{
+	int ret = 0;
+	struct cl_hw *cl_hw_tcv0 = chip->cl_hw_tcv0;
+	struct cl_hw *cl_hw_tcv1 = chip->cl_hw_tcv1;
+	struct cl_tcv_conf *tcv0_conf = cl_hw_tcv0->conf;
+
+	modem_gcu_ceva_ctrl_external_wait_setf(cl_hw_tcv0, 0x1);
+	modem_gcu_ceva_ctrl_external_wait_setf(cl_hw_tcv1, 0x1);
+
+	cl_dsp_print_ceva_core_info(cl_hw_tcv0);
+	cl_dsp_print_ceva_core_info(cl_hw_tcv1);
+
+	ret = cl_dsp_load_code_dual(chip, tcv0_conf->ce_dsp_code);
+	if (ret != 0) {
+		cl_dbg_chip_err(chip,
+				"Failed to load DSP code. Error code %d.\n",
+				ret);
+		return ret;
+	}
+
+	ret = cl_dsp_load_external_data_dual(chip, tcv0_conf->ce_dsp_external_data);
+	if (ret != 0) {
+		cl_dbg_chip_err(chip,
+				"Failed to load DSP external data. Error code %d.\n",
+				ret);
+		return ret;
+	}
+
+	ret = cl_dsp_load_data_dual(chip, tcv0_conf->ce_dsp_data);
+	if (ret != 0) {
+		cl_dbg_chip_err(chip,
+				"Failed to load DSP data. Error code %d.\n",
+				ret);
+		return ret;
+	}
+
+	macdsp_api_config_space_set(cl_hw_tcv0, 0);
+	/* Release DSP wait. */
+	cl_dsp_boot(cl_hw_tcv0);
+
+	macdsp_api_config_space_set(cl_hw_tcv1, 0);
+	/* Release DSP wait. */
+	cl_dsp_boot(cl_hw_tcv1);
+
+	return ret;
+}
+
+static int _cl_dsp_load(struct cl_hw *cl_hw)
+{
+	int ret = 0;
+
+	modem_gcu_ceva_ctrl_external_wait_setf(cl_hw, 0x1);
+	cl_dsp_print_ceva_core_info(cl_hw);
+
+	ret = cl_dsp_load_code(cl_hw);
+	if (ret) {
+		cl_dbg_err(cl_hw, "Failed to load DSP code %d\n", ret);
+		return ret;
+	}
+
+	ret = cl_dsp_load_external_data(cl_hw);
+	if (ret) {
+		cl_dbg_err(cl_hw, "Failed to load DSP external data %d\n", ret);
+		return ret;
+	}
+
+	ret = cl_dsp_load_data(cl_hw);
+	if (ret) {
+		cl_dbg_err(cl_hw, "Failed to load DSP data %d\n", ret);
+		return ret;
+	}
+
+	macdsp_api_config_space_set(cl_hw, 0);
+	/* Release DSP wait */
+	cl_dsp_boot(cl_hw);
+
+	return ret;
+}
+
+int cl_dsp_load_regular(struct cl_chip *chip)
+{
+	int ret = 0;
+
+	if (cl_dsp_is_universal_file(chip))
+		return cl_dsp_load_dual(chip);
+
+	if (cl_chip_is_tcv0_enabled(chip)) {
+		ret = _cl_dsp_load(chip->cl_hw_tcv0);
+		if (ret)
+			return ret;
+	}
+
+	if (cl_chip_is_tcv1_enabled(chip)) {
+		ret = _cl_dsp_load(chip->cl_hw_tcv1);
+		if (ret)
+			return ret;
+	}
+
+	return ret;
+}
+
+int cl_dsp_load_recovery(struct cl_hw *cl_hw)
+{
+	int ret = 0;
+
+	modem_gcu_ceva_ctrl_external_wait_setf(cl_hw, 0x1);
+
+	ret = cl_dsp_load_external_data(cl_hw);
+	if (ret) {
+		cl_dbg_err(cl_hw, "Failed to load DSP external data %d\n", ret);
+		return ret;
+	}
+
+	ret = cl_dsp_load_data(cl_hw);
+	if (ret) {
+		cl_dbg_err(cl_hw, "Failed to load DSP data %d\n", ret);
+		return ret;
+	}
+
+	macdsp_api_config_space_set(cl_hw, 0);
+	/* Release DSP wait. */
+	cl_dsp_boot(cl_hw);
+
+	return ret;
+}
-- 
2.36.1




[Index of Archives]     [Linux Host AP]     [ATH6KL]     [Linux Wireless Personal Area Network]     [Linux Bluetooth]     [Wireless Regulations]     [Linux Netdev]     [Kernel Newbies]     [Linux Kernel]     [IDE]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite Hiking]     [MIPS Linux]     [ARM Linux]     [Linux RAID]

  Powered by Linux