[RFC PATCH 4/7] HW Filter Initialization code and register access APIs
Satha Koteswara Rao
satha.rao at caviumnetworks.com
Wed Dec 21 00:46:48 PST 2016
---
drivers/net/ethernet/cavium/thunder/pf_reg.c | 660 +++++++++++++++++++++++++++
1 file changed, 660 insertions(+)
create mode 100644 drivers/net/ethernet/cavium/thunder/pf_reg.c
diff --git a/drivers/net/ethernet/cavium/thunder/pf_reg.c b/drivers/net/ethernet/cavium/thunder/pf_reg.c
new file mode 100644
index 0000000..1f95c7f
--- /dev/null
+++ b/drivers/net/ethernet/cavium/thunder/pf_reg.c
@@ -0,0 +1,660 @@
+/*
+ * Copyright (C) 2015 Cavium, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/version.h>
+#include <linux/proc_fs.h>
+#include <linux/device.h>
+#include <linux/mman.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/cdev.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/firmware.h>
+#include "pf_globals.h"
+#include "pf_locals.h"
+#include "tbl_access.h"
+#include "linux/lz4.h"
+
+struct tns_table_s tbl_info[TNS_MAX_TABLE];
+
+#define TNS_TDMA_SST_ACC_CMD_ADDR 0x0000842000000270ull
+
+#define BAR0_START 0x842000000000
+#define BAR0_END 0x84200000FFFF
+#define BAR0_SIZE (64 * 1024)
+#define BAR2_START 0x842040000000
+#define BAR2_END 0x84207FFFFFFF
+#define BAR2_SIZE (1024 * 1024 * 1024)
+
+#define NODE1_BAR0_START 0x942000000000
+#define NODE1_BAR0_END 0x94200000FFFF
+#define NODE1_BAR0_SIZE (64 * 1024)
+#define NODE1_BAR2_START 0x942040000000
+#define NODE1_BAR2_END 0x94207FFFFFFF
+#define NODE1_BAR2_SIZE (1024 * 1024 * 1024)
+/* Allow a max of 4 chunks for the Indirect Read/Write */
+#define MAX_SIZE (64 * 4)
+#define CHUNK_SIZE (64)
+/* To protect register access */
+spinlock_t pf_reg_lock;
+
+u64 iomem0;
+u64 iomem2;
+u8 tns_enabled;
+u64 node1_iomem0;
+u64 node1_iomem2;
+u8 node1_tns;
+int n1_tns;
+
+int tns_write_register_indirect(int node_id, u64 address, u8 size,
+ u8 *kern_buffer)
+{
+ union tns_tdma_sst_acc_cmd acccmd;
+ union tns_tdma_sst_acc_stat_t accstat;
+ union tns_acc_data data;
+ int i, j, w = 0;
+ int cnt = 0;
+ u32 *dataw = NULL;
+ int temp = 0;
+ int k = 0;
+ int chunks = 0;
+ u64 acccmd_address;
+ u64 lmem2 = 0, lmem0 = 0;
+
+ if (size == 0 || !kern_buffer) {
+ filter_dbg(FERR, "%s data size cannot be zero\n", __func__);
+ return TNS_ERROR_INVALID_ARG;
+ }
+ if (size > MAX_SIZE) {
+ filter_dbg(FERR, "%s Max allowed size exceeded\n", __func__);
+ return TNS_ERROR_DATA_TOO_LARGE;
+ }
+ if (node_id) {
+ lmem0 = node1_iomem0;
+ lmem2 = node1_iomem2;
+ } else {
+ lmem0 = iomem0;
+ lmem2 = iomem2;
+ }
+
+ chunks = ((size + (CHUNK_SIZE - 1)) / CHUNK_SIZE);
+ acccmd_address = (address & 0x00000000ffffffff);
+ spin_lock_bh(&pf_reg_lock);
+
+ for (k = 0; k < chunks; k++) {
+ /* Should never happen */
+ if (size < 0) {
+ filter_dbg(FERR, "%s size mismatch [CHUNK %d]\n",
+ __func__, k);
+ break;
+ }
+ temp = (size > CHUNK_SIZE) ? CHUNK_SIZE : size;
+ dataw = (u32 *)(kern_buffer + (k * CHUNK_SIZE));
+ cnt = ((temp + 3) / 4);
+ data.u = 0ULL;
+ for (j = 0, i = 0; i < cnt; i++) {
+ /* Odd words go in the upper 32 bits of the data
+ * register
+ */
+ if (i & 1) {
+ data.s.upper32 = dataw[i];
+ writeq_relaxed(data.u, (void *)(lmem0 +
+ TNS_TDMA_SST_ACC_WDATX(j)));
+ data.u = 0ULL;
+ j++; /* Advance to the next data word */
+ w = 0;
+ } else {
+ /* Lower 32 bits contain words 0, 2, 4, etc. */
+ data.s.lower32 = dataw[i];
+ w = 1;
+ }
+ }
+
+ /* If the last word was a partial (< 64 bits) then
+ * see if we need to write it.
+ */
+ if (w)
+ writeq_relaxed(data.u, (void *)(lmem0 +
+ TNS_TDMA_SST_ACC_WDATX(j)));
+
+ acccmd.u = 0ULL;
+ acccmd.s.go = 1; /* Cleared once the request is serviced */
+ acccmd.s.size = cnt;
+ acccmd.s.addr = (acccmd_address >> 2);
+ writeq_relaxed(acccmd.u, (void *)(lmem0 +
+ TDMA_SST_ACC_CMD));
+ accstat.u = 0ULL;
+
+ while (!accstat.s.cmd_done && !accstat.s.error)
+ accstat.u = readq_relaxed((void *)(lmem0 +
+ TDMA_SST_ACC_STAT));
+
+ if (accstat.s.error) {
+ data.u = readq_relaxed((void *)(lmem2 +
+ TDMA_NB_INT_STAT));
+ filter_dbg(FERR, "%s Reading data from ", __func__);
+ filter_dbg(FERR, "0x%0lx chunk %d failed 0x%0lx",
+ (unsigned long)address, k,
+ (unsigned long)data.u);
+ spin_unlock_bh(&pf_reg_lock);
+ kfree(kern_buffer);
+ return TNS_ERROR_INDIRECT_WRITE;
+ }
+ /* Calculate the next offset to write */
+ acccmd_address = acccmd_address + CHUNK_SIZE;
+ size -= CHUNK_SIZE;
+ }
+ spin_unlock_bh(&pf_reg_lock);
+
+ return 0;
+}
+
+int tns_read_register_indirect(int node_id, u64 address, u8 size,
+ u8 *kern_buffer)
+{
+ union tns_tdma_sst_acc_cmd acccmd;
+ union tns_tdma_sst_acc_stat_t accstat;
+ union tns_acc_data data;
+ int i, j, dcnt;
+ int cnt = 0;
+ u32 *dataw = NULL;
+ int temp = 0;
+ int k = 0;
+ int chunks = 0;
+ u64 acccmd_address;
+ u64 lmem2 = 0, lmem0 = 0;
+
+ if (size == 0 || !kern_buffer) {
+ filter_dbg(FERR, "%s data size cannot be zero\n", __func__);
+ return TNS_ERROR_INVALID_ARG;
+ }
+ if (size > MAX_SIZE) {
+ filter_dbg(FERR, "%s Max allowed size exceeded\n", __func__);
+ return TNS_ERROR_DATA_TOO_LARGE;
+ }
+ if (node_id) {
+ lmem0 = node1_iomem0;
+ lmem2 = node1_iomem2;
+ } else {
+ lmem0 = iomem0;
+ lmem2 = iomem2;
+ }
+
+ chunks = ((size + (CHUNK_SIZE - 1)) / CHUNK_SIZE);
+ acccmd_address = (address & 0x00000000ffffffff);
+ spin_lock_bh(&pf_reg_lock);
+ for (k = 0; k < chunks; k++) {
+ /* This should never happen */
+ if (size < 0) {
+ filter_dbg(FERR, "%s size mismatch [CHUNK:%d]\n",
+ __func__, k);
+ break;
+ }
+ temp = (size > CHUNK_SIZE) ? CHUNK_SIZE : size;
+ dataw = (u32 *)(kern_buffer + (k * CHUNK_SIZE));
+ cnt = ((temp + 3) / 4);
+ acccmd.u = 0ULL;
+ acccmd.s.op = 1; /* Read operation */
+ acccmd.s.size = cnt;
+ acccmd.s.addr = (acccmd_address >> 2);
+ acccmd.s.go = 1; /* Execute */
+ writeq_relaxed(acccmd.u, (void *)(lmem0 +
+ TDMA_SST_ACC_CMD));
+ accstat.u = 0ULL;
+
+ while (!accstat.s.cmd_done && !accstat.s.error)
+ accstat.u = readq_relaxed((void *)(lmem0 +
+ TDMA_SST_ACC_STAT));
+
+ if (accstat.s.error) {
+ data.u = readq_relaxed((void *)(lmem2 +
+ TDMA_NB_INT_STAT));
+ filter_dbg(FERR, "%s Reading data from", __func__);
+ filter_dbg(FERR, "0x%0lx chunk %d failed 0x%0lx",
+ (unsigned long)address, k,
+ (unsigned long)data.u);
+ spin_unlock_bh(&pf_reg_lock);
+ kfree(kern_buffer);
+ return TNS_ERROR_INDIRECT_READ;
+ }
+
+ dcnt = cnt / 2;
+ if (cnt & 1)
+ dcnt++;
+ for (i = 0, j = 0; (j < dcnt) && (i < cnt); j++) {
+ data.u = readq_relaxed((void *)(lmem0 +
+ TNS_TDMA_SST_ACC_RDATX(j)));
+ dataw[i++] = data.s.lower32;
+ if (i < cnt)
+ dataw[i++] = data.s.upper32;
+ }
+ /* Calculate the next offset to read */
+ acccmd_address = acccmd_address + CHUNK_SIZE;
+ size -= CHUNK_SIZE;
+ }
+ spin_unlock_bh(&pf_reg_lock);
+ return 0;
+}
+
+u64 tns_read_register(u64 start, u64 offset)
+{
+ return readq_relaxed((void *)(start + offset));
+}
+
+void tns_write_register(u64 start, u64 offset, u64 data)
+{
+ writeq_relaxed(data, (void *)(start + offset));
+}
+
+/* Check if TNS is available. If yes return 0 else 1 */
+int is_tns_available(void)
+{
+ union tns_tdma_cap tdma_cap;
+
+ tdma_cap.u = tns_read_register(iomem0, TNS_TDMA_CAP_OFFSET);
+ tns_enabled = tdma_cap.s.switch_capable;
+ /* In multi-node systems, make sure TNS should be there in both nodes */
+ if (nr_node_ids > 1) {
+ tdma_cap.u = tns_read_register(node1_iomem0,
+ TNS_TDMA_CAP_OFFSET);
+ if (tdma_cap.s.switch_capable)
+ n1_tns = 1;
+ }
+ tns_enabled &= tdma_cap.s.switch_capable;
+ return (!tns_enabled);
+}
+
+int bist_error_check(void)
+{
+ int fail = 0, i;
+ u64 bist_stat = 0;
+
+ for (i = 0; i < 12; i++) {
+ bist_stat = tns_read_register(iomem0, (i * 16));
+ if (bist_stat) {
+ filter_dbg(FERR, "TNS BIST%d fail 0x%llx\n",
+ i, bist_stat);
+ fail = 1;
+ }
+ if (!n1_tns)
+ continue;
+ bist_stat = tns_read_register(node1_iomem0, (i * 16));
+ if (bist_stat) {
+ filter_dbg(FERR, "TNS(N1) BIST%d fail 0x%llx\n",
+ i, bist_stat);
+ fail = 1;
+ }
+ }
+
+ return fail;
+}
+
+int replay_indirect_trace(int node, u64 *buf_ptr, int idx)
+{
+ union _tns_sst_config cmd = (union _tns_sst_config)(buf_ptr[idx]);
+ int remaining = cmd.cmd.run;
+ u64 io_addr;
+ int word_cnt = cmd.cmd.word_cnt;
+ int size = (word_cnt + 1) / 2;
+ u64 stride = word_cnt;
+ u64 acc_cmd = cmd.copy.do_copy;
+ u64 lmem2 = 0, lmem0 = 0;
+ union tns_tdma_sst_acc_stat_t accstat;
+ union tns_acc_data data;
+
+ if (node) {
+ lmem0 = node1_iomem0;
+ lmem2 = node1_iomem2;
+ } else {
+ lmem0 = iomem0;
+ lmem2 = iomem2;
+ }
+
+ if (word_cnt == 0) {
+ word_cnt = 16;
+ stride = 16;
+ size = 8;
+ } else {
+ // make stride next power of 2
+ if (cmd.cmd.powerof2stride)
+ while ((stride & (stride - 1)) != 0)
+ stride++;
+ }
+ stride *= 4; //convert stride from 32-bit words to bytes
+
+ do {
+ int addr_p = 1;
+ /* extract (big endian) data from the config
+ * into the data array
+ */
+ while (size > 0) {
+ io_addr = lmem0 + TDMA_SST_ACC_CMD + addr_p * 16;
+ tns_write_register(io_addr, 0, buf_ptr[idx + size]);
+ addr_p += 1;
+ size--;
+ }
+ tns_write_register((lmem0 + TDMA_SST_ACC_CMD), 0, acc_cmd);
+ /* TNS Block access registers indirectly, ran memory barrier
+ * between two writes
+ */
+ wmb();
+ /* Check for completion */
+ accstat.u = 0ULL;
+ while (!accstat.s.cmd_done && !accstat.s.error)
+ accstat.u = readq_relaxed((void *)(lmem0 +
+ TDMA_SST_ACC_STAT));
+
+ /* Check for error, and report it */
+ if (accstat.s.error) {
+ filter_dbg(FERR, "%s data from 0x%0llx failed 0x%llx\n",
+ __func__, acc_cmd, accstat.u);
+ data.u = readq_relaxed((void *)(lmem2 +
+ TDMA_NB_INT_STAT));
+ filter_dbg(FERR, "Status 0x%llx\n", data.u);
+ }
+ /* update the address */
+ acc_cmd += stride;
+ size = (word_cnt + 1) / 2;
+ usleep_range(20, 30);
+ } while (remaining-- > 0);
+
+ return size;
+}
+
+void replay_tns_node(int node, u64 *buf_ptr, int reg_cnt)
+{
+ int counter = 0;
+ u64 offset = 0;
+ u64 io_address;
+ int datapathmode = 1;
+ u64 lmem2 = 0, lmem0 = 0;
+
+ if (node) {
+ lmem0 = node1_iomem0;
+ lmem2 = node1_iomem2;
+ } else {
+ lmem0 = iomem0;
+ lmem2 = iomem2;
+ }
+ for (counter = 0; counter < reg_cnt; counter++) {
+ if (buf_ptr[counter] == 0xDADADADADADADADAull) {
+ datapathmode = 1;
+ continue;
+ } else if (buf_ptr[counter] == 0xDEDEDEDEDEDEDEDEull) {
+ datapathmode = 0;
+ continue;
+ }
+ if (datapathmode == 1) {
+ if (buf_ptr[counter] >= BAR0_START &&
+ buf_ptr[counter] <= BAR0_END) {
+ offset = buf_ptr[counter] - BAR0_START;
+ io_address = lmem0 + offset;
+ } else if (buf_ptr[counter] >= BAR2_START &&
+ buf_ptr[counter] <= BAR2_END) {
+ offset = buf_ptr[counter] - BAR2_START;
+ io_address = lmem2 + offset;
+ } else {
+ filter_dbg(FERR, "%s Address 0x%llx invalid\n",
+ __func__, buf_ptr[counter]);
+ return;
+ }
+
+ tns_write_register(io_address, 0, buf_ptr[counter + 1]);
+ /* TNS Block access registers indirectly, ran memory
+ * barrier between two writes
+ */
+ wmb();
+ counter += 1;
+ usleep_range(20, 30);
+ } else if (datapathmode == 0) {
+ int sz = replay_indirect_trace(node, buf_ptr, counter);
+
+ counter += sz;
+ }
+ }
+}
+
+int alloc_table_info(int i, struct table_static_s tbl_sdata[])
+{
+ tbl_info[i].ddata[0].bitmap = kcalloc(BITS_TO_LONGS(tbl_sdata[i].depth),
+ sizeof(uintptr_t), GFP_KERNEL);
+ if (!tbl_info[i].ddata[0].bitmap)
+ return 1;
+
+ if (!n1_tns)
+ return 0;
+
+ tbl_info[i].ddata[1].bitmap = kcalloc(BITS_TO_LONGS(tbl_sdata[i].depth),
+ sizeof(uintptr_t), GFP_KERNEL);
+ if (!tbl_info[i].ddata[1].bitmap) {
+ kfree(tbl_info[i].ddata[0].bitmap);
+ return 1;
+ }
+
+ return 0;
+}
+
+void tns_replay_register_trace(const struct firmware *fw, struct device *dev)
+{
+ int i;
+ int node = 0;
+ u8 *buffer = NULL;
+ u64 *buf_ptr = NULL;
+ struct tns_global_st *fw_header = NULL;
+ struct table_static_s tbl_sdata[TNS_MAX_TABLE];
+ size_t src_len;
+ size_t dest_len = TNS_FW_MAX_SIZE;
+ int rc;
+ u8 *fw2_buf = NULL;
+ unsigned char *decomp_dest = NULL;
+
+ fw2_buf = (u8 *)fw->data;
+ src_len = fw->size - 8;
+
+ decomp_dest = kcalloc((dest_len * 2), sizeof(char), GFP_KERNEL);
+ if (!decomp_dest)
+ return;
+
+ memset(decomp_dest, 0, (dest_len * 2));
+ rc = lz4_decompress_unknownoutputsize(&fw2_buf[8], src_len, decomp_dest,
+ &dest_len);
+ if (rc) {
+ filter_dbg(FERR, "Decompress Error %d\n", rc);
+ pr_info("Uncompressed destination length %ld\n", dest_len);
+ kfree(decomp_dest);
+ return;
+ }
+ fw_header = (struct tns_global_st *)decomp_dest;
+ buffer = (u8 *)decomp_dest;
+
+ filter_dbg(FINFO, "TNS Firmware version: %s Loading...\n",
+ fw_header->version);
+
+ memset(tbl_info, 0x0, sizeof(tbl_info));
+ buf_ptr = (u64 *)(buffer + sizeof(struct tns_global_st));
+ memcpy(tbl_sdata, fw_header->tbl_info, sizeof(fw_header->tbl_info));
+
+ for (i = 0; i < TNS_MAX_TABLE; i++) {
+ if (!tbl_sdata[i].valid)
+ continue;
+ memcpy(&tbl_info[i].sdata, &tbl_sdata[i],
+ sizeof(struct table_static_s));
+ if (alloc_table_info(i, tbl_sdata)) {
+ kfree(decomp_dest);
+ return;
+ }
+ }
+
+ for (node = 0; node < nr_node_ids; node++)
+ replay_tns_node(node, buf_ptr, fw_header->reg_cnt);
+
+ kfree(decomp_dest);
+ release_firmware(fw);
+}
+
+int tns_init(const struct firmware *fw, struct device *dev)
+{
+ int result = 0;
+ int i = 0;
+ int temp;
+ union tns_tdma_config tdma_config;
+ union tns_tdma_lmacx_config tdma_lmac_cfg;
+ u64 reg_init_val;
+
+ spin_lock_init(&pf_reg_lock);
+
+ /* use two regions insted of a single big mapping to save
+ * the kernel virtual space
+ */
+ iomem0 = (u64)ioremap(BAR0_START, BAR0_SIZE);
+ if (iomem0 == 0ULL) {
+ filter_dbg(FERR, "Node0 ioremap failed for BAR0\n");
+ result = -EAGAIN;
+ goto error;
+ } else {
+ filter_dbg(FINFO, "ioremap success for BAR0\n");
+ }
+
+ if (nr_node_ids > 1) {
+ node1_iomem0 = (u64)ioremap(NODE1_BAR0_START, NODE1_BAR0_SIZE);
+ if (node1_iomem0 == 0ULL) {
+ filter_dbg(FERR, "Node1 ioremap failed for BAR0\n");
+ result = -EAGAIN;
+ goto error;
+ } else {
+ filter_dbg(FINFO, "ioremap success for BAR0\n");
+ }
+ }
+
+ if (is_tns_available()) {
+ filter_dbg(FERR, "TNS NOT AVAILABLE\n");
+ goto error;
+ }
+
+ if (bist_error_check()) {
+ filter_dbg(FERR, "BIST ERROR CHECK FAILED");
+ goto error;
+ }
+
+ /* NIC0-BGX0 is TNS, NIC1-BGX1 is TNS, DISABLE BACKPRESSURE */
+ reg_init_val = 0ULL;
+ pr_info("NIC Block configured in TNS/TNS mode");
+ tns_write_register(iomem0, TNS_RDMA_CONFIG_OFFSET, reg_init_val);
+ usleep_range(10, 20);
+ if (n1_tns) {
+ tns_write_register(node1_iomem0, TNS_RDMA_CONFIG_OFFSET,
+ reg_init_val);
+ usleep_range(10, 20);
+ }
+
+ // Configure each LMAC with 512 credits in BYPASS mode
+ for (i = TNS_MIN_LMAC; i < (TNS_MIN_LMAC + TNS_MAX_LMAC); i++) {
+ tdma_lmac_cfg.u = 0ULL;
+ tdma_lmac_cfg.s.fifo_cdts = 0x200;
+ tns_write_register(iomem0, TNS_TDMA_LMACX_CONFIG_OFFSET(i),
+ tdma_lmac_cfg.u);
+ usleep_range(10, 20);
+ if (n1_tns) {
+ tns_write_register(node1_iomem0,
+ TNS_TDMA_LMACX_CONFIG_OFFSET(i),
+ tdma_lmac_cfg.u);
+ usleep_range(10, 20);
+ }
+ }
+
+ //ENABLE TNS CLOCK AND CSR READS
+ temp = tns_read_register(iomem0, TNS_TDMA_CONFIG_OFFSET);
+ tdma_config.u = temp;
+ tdma_config.s.clk_2x_ena = 1;
+ tdma_config.s.clk_ena = 1;
+ tns_write_register(iomem0, TNS_TDMA_CONFIG_OFFSET, tdma_config.u);
+ if (n1_tns)
+ tns_write_register(node1_iomem0, TNS_TDMA_CONFIG_OFFSET,
+ tdma_config.u);
+
+ temp = tns_read_register(iomem0, TNS_TDMA_CONFIG_OFFSET);
+ tdma_config.u = temp;
+ tdma_config.s.csr_access_ena = 1;
+ tns_write_register(iomem0, TNS_TDMA_CONFIG_OFFSET, tdma_config.u);
+ if (n1_tns)
+ tns_write_register(node1_iomem0, TNS_TDMA_CONFIG_OFFSET,
+ tdma_config.u);
+
+ reg_init_val = 0ULL;
+ tns_write_register(iomem0, TNS_TDMA_RESET_CTL_OFFSET, reg_init_val);
+ if (n1_tns)
+ tns_write_register(node1_iomem0, TNS_TDMA_RESET_CTL_OFFSET,
+ reg_init_val);
+
+ iomem2 = (u64)ioremap(BAR2_START, BAR2_SIZE);
+ if (iomem2 == 0ULL) {
+ filter_dbg(FERR, "ioremap failed for BAR2\n");
+ result = -EAGAIN;
+ goto error;
+ } else {
+ filter_dbg(FINFO, "ioremap success for BAR2\n");
+ }
+
+ if (n1_tns) {
+ node1_iomem2 = (u64)ioremap(NODE1_BAR2_START, NODE1_BAR2_SIZE);
+ if (node1_iomem2 == 0ULL) {
+ filter_dbg(FERR, "Node1 ioremap failed for BAR2\n");
+ result = -EAGAIN;
+ goto error;
+ } else {
+ filter_dbg(FINFO, "Node1 ioremap success for BAR2\n");
+ }
+ }
+ msleep(1000);
+ //We will replay register trace to initialize TNS block
+ tns_replay_register_trace(fw, dev);
+
+ return 0;
+error:
+ if (iomem0 != 0)
+ iounmap((void *)iomem0);
+ if (iomem2 != 0)
+ iounmap((void *)iomem2);
+
+ if (node1_iomem0 != 0)
+ iounmap((void *)node1_iomem0);
+ if (node1_iomem2 != 0)
+ iounmap((void *)node1_iomem2);
+
+ return result;
+}
+
+void tns_exit(void)
+{
+ int i;
+
+ if (iomem0 != 0)
+ iounmap((void *)iomem0);
+ if (iomem2 != 0)
+ iounmap((void *)iomem2);
+
+ if (node1_iomem0 != 0)
+ iounmap((void *)node1_iomem0);
+ if (node1_iomem2 != 0)
+ iounmap((void *)node1_iomem2);
+
+ for (i = 0; i < TNS_MAX_TABLE; i++) {
+ if (!tbl_info[i].sdata.valid)
+ continue;
+ kfree(tbl_info[i].ddata[0].bitmap);
+ kfree(tbl_info[i].ddata[n1_tns].bitmap);
+ }
+}
--
1.8.3.1
More information about the linux-arm-kernel
mailing list