2743e96f88
The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Acked-by: Arnd Bergmann <arnd@arndb.de> Link: https://lore.kernel.org/r/705b89c3cd7c0a42ce3f482f202204f5e3377aa2.1708508896.git.u.kleine-koenig@pengutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1455 lines
38 KiB
C
1455 lines
38 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Xilinx SDFEC
|
|
*
|
|
* Copyright (C) 2019 Xilinx, Inc.
|
|
*
|
|
* Description:
|
|
* This driver is developed for SDFEC16 (Soft Decision FEC 16nm)
|
|
* IP. It exposes a char device which supports file operations
|
|
* like open(), close() and ioctl().
|
|
*/
|
|
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/io.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/compat.h>
|
|
#include <linux/highmem.h>
|
|
|
|
#include <uapi/misc/xilinx_sdfec.h>
|
|
|
|
#define DEV_NAME_LEN 12
|
|
|
|
static DEFINE_IDA(dev_nrs);
|
|
|
|
/* Xilinx SDFEC Register Map */
|
|
/* CODE_WRI_PROTECT Register */
|
|
#define XSDFEC_CODE_WR_PROTECT_ADDR (0x4)
|
|
|
|
/* ACTIVE Register */
|
|
#define XSDFEC_ACTIVE_ADDR (0x8)
|
|
#define XSDFEC_IS_ACTIVITY_SET (0x1)
|
|
|
|
/* AXIS_WIDTH Register */
|
|
#define XSDFEC_AXIS_WIDTH_ADDR (0xC)
|
|
#define XSDFEC_AXIS_DOUT_WORDS_LSB (5)
|
|
#define XSDFEC_AXIS_DOUT_WIDTH_LSB (3)
|
|
#define XSDFEC_AXIS_DIN_WORDS_LSB (2)
|
|
#define XSDFEC_AXIS_DIN_WIDTH_LSB (0)
|
|
|
|
/* AXIS_ENABLE Register */
|
|
#define XSDFEC_AXIS_ENABLE_ADDR (0x10)
|
|
#define XSDFEC_AXIS_OUT_ENABLE_MASK (0x38)
|
|
#define XSDFEC_AXIS_IN_ENABLE_MASK (0x7)
|
|
#define XSDFEC_AXIS_ENABLE_MASK \
|
|
(XSDFEC_AXIS_OUT_ENABLE_MASK | XSDFEC_AXIS_IN_ENABLE_MASK)
|
|
|
|
/* FEC_CODE Register */
|
|
#define XSDFEC_FEC_CODE_ADDR (0x14)
|
|
|
|
/* ORDER Register Map */
|
|
#define XSDFEC_ORDER_ADDR (0x18)
|
|
|
|
/* Interrupt Status Register */
|
|
#define XSDFEC_ISR_ADDR (0x1C)
|
|
/* Interrupt Status Register Bit Mask */
|
|
#define XSDFEC_ISR_MASK (0x3F)
|
|
|
|
/* Write Only - Interrupt Enable Register */
|
|
#define XSDFEC_IER_ADDR (0x20)
|
|
/* Write Only - Interrupt Disable Register */
|
|
#define XSDFEC_IDR_ADDR (0x24)
|
|
/* Read Only - Interrupt Mask Register */
|
|
#define XSDFEC_IMR_ADDR (0x28)
|
|
|
|
/* ECC Interrupt Status Register */
|
|
#define XSDFEC_ECC_ISR_ADDR (0x2C)
|
|
/* Single Bit Errors */
|
|
#define XSDFEC_ECC_ISR_SBE_MASK (0x7FF)
|
|
/* PL Initialize Single Bit Errors */
|
|
#define XSDFEC_PL_INIT_ECC_ISR_SBE_MASK (0x3C00000)
|
|
/* Multi Bit Errors */
|
|
#define XSDFEC_ECC_ISR_MBE_MASK (0x3FF800)
|
|
/* PL Initialize Multi Bit Errors */
|
|
#define XSDFEC_PL_INIT_ECC_ISR_MBE_MASK (0x3C000000)
|
|
/* Multi Bit Error to Event Shift */
|
|
#define XSDFEC_ECC_ISR_MBE_TO_EVENT_SHIFT (11)
|
|
/* PL Initialize Multi Bit Error to Event Shift */
|
|
#define XSDFEC_PL_INIT_ECC_ISR_MBE_TO_EVENT_SHIFT (4)
|
|
/* ECC Interrupt Status Bit Mask */
|
|
#define XSDFEC_ECC_ISR_MASK (XSDFEC_ECC_ISR_SBE_MASK | XSDFEC_ECC_ISR_MBE_MASK)
|
|
/* ECC Interrupt Status PL Initialize Bit Mask */
|
|
#define XSDFEC_PL_INIT_ECC_ISR_MASK \
|
|
(XSDFEC_PL_INIT_ECC_ISR_SBE_MASK | XSDFEC_PL_INIT_ECC_ISR_MBE_MASK)
|
|
/* ECC Interrupt Status All Bit Mask */
|
|
#define XSDFEC_ALL_ECC_ISR_MASK \
|
|
(XSDFEC_ECC_ISR_MASK | XSDFEC_PL_INIT_ECC_ISR_MASK)
|
|
/* ECC Interrupt Status Single Bit Errors Mask */
|
|
#define XSDFEC_ALL_ECC_ISR_SBE_MASK \
|
|
(XSDFEC_ECC_ISR_SBE_MASK | XSDFEC_PL_INIT_ECC_ISR_SBE_MASK)
|
|
/* ECC Interrupt Status Multi Bit Errors Mask */
|
|
#define XSDFEC_ALL_ECC_ISR_MBE_MASK \
|
|
(XSDFEC_ECC_ISR_MBE_MASK | XSDFEC_PL_INIT_ECC_ISR_MBE_MASK)
|
|
|
|
/* Write Only - ECC Interrupt Enable Register */
|
|
#define XSDFEC_ECC_IER_ADDR (0x30)
|
|
/* Write Only - ECC Interrupt Disable Register */
|
|
#define XSDFEC_ECC_IDR_ADDR (0x34)
|
|
/* Read Only - ECC Interrupt Mask Register */
|
|
#define XSDFEC_ECC_IMR_ADDR (0x38)
|
|
|
|
/* BYPASS Register */
|
|
#define XSDFEC_BYPASS_ADDR (0x3C)
|
|
|
|
/* Turbo Code Register */
|
|
#define XSDFEC_TURBO_ADDR (0x100)
|
|
#define XSDFEC_TURBO_SCALE_MASK (0xFFF)
|
|
#define XSDFEC_TURBO_SCALE_BIT_POS (8)
|
|
#define XSDFEC_TURBO_SCALE_MAX (15)
|
|
|
|
/* REG0 Register */
|
|
#define XSDFEC_LDPC_CODE_REG0_ADDR_BASE (0x2000)
|
|
#define XSDFEC_LDPC_CODE_REG0_ADDR_HIGH (0x27F0)
|
|
#define XSDFEC_REG0_N_MIN (4)
|
|
#define XSDFEC_REG0_N_MAX (32768)
|
|
#define XSDFEC_REG0_N_MUL_P (256)
|
|
#define XSDFEC_REG0_N_LSB (0)
|
|
#define XSDFEC_REG0_K_MIN (2)
|
|
#define XSDFEC_REG0_K_MAX (32766)
|
|
#define XSDFEC_REG0_K_MUL_P (256)
|
|
#define XSDFEC_REG0_K_LSB (16)
|
|
|
|
/* REG1 Register */
|
|
#define XSDFEC_LDPC_CODE_REG1_ADDR_BASE (0x2004)
|
|
#define XSDFEC_LDPC_CODE_REG1_ADDR_HIGH (0x27f4)
|
|
#define XSDFEC_REG1_PSIZE_MIN (2)
|
|
#define XSDFEC_REG1_PSIZE_MAX (512)
|
|
#define XSDFEC_REG1_NO_PACKING_MASK (0x400)
|
|
#define XSDFEC_REG1_NO_PACKING_LSB (10)
|
|
#define XSDFEC_REG1_NM_MASK (0xFF800)
|
|
#define XSDFEC_REG1_NM_LSB (11)
|
|
#define XSDFEC_REG1_BYPASS_MASK (0x100000)
|
|
|
|
/* REG2 Register */
|
|
#define XSDFEC_LDPC_CODE_REG2_ADDR_BASE (0x2008)
|
|
#define XSDFEC_LDPC_CODE_REG2_ADDR_HIGH (0x27f8)
|
|
#define XSDFEC_REG2_NLAYERS_MIN (1)
|
|
#define XSDFEC_REG2_NLAYERS_MAX (256)
|
|
#define XSDFEC_REG2_NNMQC_MASK (0xFFE00)
|
|
#define XSDFEC_REG2_NMQC_LSB (9)
|
|
#define XSDFEC_REG2_NORM_TYPE_MASK (0x100000)
|
|
#define XSDFEC_REG2_NORM_TYPE_LSB (20)
|
|
#define XSDFEC_REG2_SPECIAL_QC_MASK (0x200000)
|
|
#define XSDFEC_REG2_SPEICAL_QC_LSB (21)
|
|
#define XSDFEC_REG2_NO_FINAL_PARITY_MASK (0x400000)
|
|
#define XSDFEC_REG2_NO_FINAL_PARITY_LSB (22)
|
|
#define XSDFEC_REG2_MAX_SCHEDULE_MASK (0x1800000)
|
|
#define XSDFEC_REG2_MAX_SCHEDULE_LSB (23)
|
|
|
|
/* REG3 Register */
|
|
#define XSDFEC_LDPC_CODE_REG3_ADDR_BASE (0x200C)
|
|
#define XSDFEC_LDPC_CODE_REG3_ADDR_HIGH (0x27FC)
|
|
#define XSDFEC_REG3_LA_OFF_LSB (8)
|
|
#define XSDFEC_REG3_QC_OFF_LSB (16)
|
|
|
|
#define XSDFEC_LDPC_REG_JUMP (0x10)
|
|
#define XSDFEC_REG_WIDTH_JUMP (4)
|
|
|
|
/* The maximum number of pinned pages */
|
|
#define MAX_NUM_PAGES ((XSDFEC_QC_TABLE_DEPTH / PAGE_SIZE) + 1)
|
|
|
|
/**
|
|
* struct xsdfec_clks - For managing SD-FEC clocks
|
|
* @core_clk: Main processing clock for core
|
|
* @axi_clk: AXI4-Lite memory-mapped clock
|
|
* @din_words_clk: DIN Words AXI4-Stream Slave clock
|
|
* @din_clk: DIN AXI4-Stream Slave clock
|
|
* @dout_clk: DOUT Words AXI4-Stream Slave clock
|
|
* @dout_words_clk: DOUT AXI4-Stream Slave clock
|
|
* @ctrl_clk: Control AXI4-Stream Slave clock
|
|
* @status_clk: Status AXI4-Stream Slave clock
|
|
*/
|
|
struct xsdfec_clks {
|
|
struct clk *core_clk;
|
|
struct clk *axi_clk;
|
|
struct clk *din_words_clk;
|
|
struct clk *din_clk;
|
|
struct clk *dout_clk;
|
|
struct clk *dout_words_clk;
|
|
struct clk *ctrl_clk;
|
|
struct clk *status_clk;
|
|
};
|
|
|
|
/**
|
|
* struct xsdfec_dev - Driver data for SDFEC
|
|
* @miscdev: Misc device handle
|
|
* @clks: Clocks managed by the SDFEC driver
|
|
* @waitq: Driver wait queue
|
|
* @config: Configuration of the SDFEC device
|
|
* @dev_name: Device name
|
|
* @flags: spinlock flags
|
|
* @regs: device physical base address
|
|
* @dev: pointer to device struct
|
|
* @state: State of the SDFEC device
|
|
* @error_data_lock: Error counter and states spinlock
|
|
* @dev_id: Device ID
|
|
* @isr_err_count: Count of ISR errors
|
|
* @cecc_count: Count of Correctable ECC errors (SBE)
|
|
* @uecc_count: Count of Uncorrectable ECC errors (MBE)
|
|
* @irq: IRQ number
|
|
* @state_updated: indicates State updated by interrupt handler
|
|
* @stats_updated: indicates Stats updated by interrupt handler
|
|
* @intr_enabled: indicates IRQ enabled
|
|
*
|
|
* This structure contains necessary state for SDFEC driver to operate
|
|
*/
|
|
struct xsdfec_dev {
|
|
struct miscdevice miscdev;
|
|
struct xsdfec_clks clks;
|
|
wait_queue_head_t waitq;
|
|
struct xsdfec_config config;
|
|
char dev_name[DEV_NAME_LEN];
|
|
unsigned long flags;
|
|
void __iomem *regs;
|
|
struct device *dev;
|
|
enum xsdfec_state state;
|
|
/* Spinlock to protect state_updated and stats_updated */
|
|
spinlock_t error_data_lock;
|
|
int dev_id;
|
|
u32 isr_err_count;
|
|
u32 cecc_count;
|
|
u32 uecc_count;
|
|
int irq;
|
|
bool state_updated;
|
|
bool stats_updated;
|
|
bool intr_enabled;
|
|
};
|
|
|
|
static inline void xsdfec_regwrite(struct xsdfec_dev *xsdfec, u32 addr,
|
|
u32 value)
|
|
{
|
|
dev_dbg(xsdfec->dev, "Writing 0x%x to offset 0x%x", value, addr);
|
|
iowrite32(value, xsdfec->regs + addr);
|
|
}
|
|
|
|
static inline u32 xsdfec_regread(struct xsdfec_dev *xsdfec, u32 addr)
|
|
{
|
|
u32 rval;
|
|
|
|
rval = ioread32(xsdfec->regs + addr);
|
|
dev_dbg(xsdfec->dev, "Read value = 0x%x from offset 0x%x", rval, addr);
|
|
return rval;
|
|
}
|
|
|
|
static void update_bool_config_from_reg(struct xsdfec_dev *xsdfec,
|
|
u32 reg_offset, u32 bit_num,
|
|
char *config_value)
|
|
{
|
|
u32 reg_val;
|
|
u32 bit_mask = 1 << bit_num;
|
|
|
|
reg_val = xsdfec_regread(xsdfec, reg_offset);
|
|
*config_value = (reg_val & bit_mask) > 0;
|
|
}
|
|
|
|
static void update_config_from_hw(struct xsdfec_dev *xsdfec)
|
|
{
|
|
u32 reg_value;
|
|
bool sdfec_started;
|
|
|
|
/* Update the Order */
|
|
reg_value = xsdfec_regread(xsdfec, XSDFEC_ORDER_ADDR);
|
|
xsdfec->config.order = reg_value;
|
|
|
|
update_bool_config_from_reg(xsdfec, XSDFEC_BYPASS_ADDR,
|
|
0, /* Bit Number, maybe change to mask */
|
|
&xsdfec->config.bypass);
|
|
|
|
update_bool_config_from_reg(xsdfec, XSDFEC_CODE_WR_PROTECT_ADDR,
|
|
0, /* Bit Number */
|
|
&xsdfec->config.code_wr_protect);
|
|
|
|
reg_value = xsdfec_regread(xsdfec, XSDFEC_IMR_ADDR);
|
|
xsdfec->config.irq.enable_isr = (reg_value & XSDFEC_ISR_MASK) > 0;
|
|
|
|
reg_value = xsdfec_regread(xsdfec, XSDFEC_ECC_IMR_ADDR);
|
|
xsdfec->config.irq.enable_ecc_isr =
|
|
(reg_value & XSDFEC_ECC_ISR_MASK) > 0;
|
|
|
|
reg_value = xsdfec_regread(xsdfec, XSDFEC_AXIS_ENABLE_ADDR);
|
|
sdfec_started = (reg_value & XSDFEC_AXIS_IN_ENABLE_MASK) > 0;
|
|
if (sdfec_started)
|
|
xsdfec->state = XSDFEC_STARTED;
|
|
else
|
|
xsdfec->state = XSDFEC_STOPPED;
|
|
}
|
|
|
|
static int xsdfec_get_status(struct xsdfec_dev *xsdfec, void __user *arg)
|
|
{
|
|
struct xsdfec_status status;
|
|
int err;
|
|
|
|
memset(&status, 0, sizeof(status));
|
|
spin_lock_irqsave(&xsdfec->error_data_lock, xsdfec->flags);
|
|
status.state = xsdfec->state;
|
|
xsdfec->state_updated = false;
|
|
spin_unlock_irqrestore(&xsdfec->error_data_lock, xsdfec->flags);
|
|
status.activity = (xsdfec_regread(xsdfec, XSDFEC_ACTIVE_ADDR) &
|
|
XSDFEC_IS_ACTIVITY_SET);
|
|
|
|
err = copy_to_user(arg, &status, sizeof(status));
|
|
if (err)
|
|
err = -EFAULT;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int xsdfec_get_config(struct xsdfec_dev *xsdfec, void __user *arg)
|
|
{
|
|
int err;
|
|
|
|
err = copy_to_user(arg, &xsdfec->config, sizeof(xsdfec->config));
|
|
if (err)
|
|
err = -EFAULT;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int xsdfec_isr_enable(struct xsdfec_dev *xsdfec, bool enable)
|
|
{
|
|
u32 mask_read;
|
|
|
|
if (enable) {
|
|
/* Enable */
|
|
xsdfec_regwrite(xsdfec, XSDFEC_IER_ADDR, XSDFEC_ISR_MASK);
|
|
mask_read = xsdfec_regread(xsdfec, XSDFEC_IMR_ADDR);
|
|
if (mask_read & XSDFEC_ISR_MASK) {
|
|
dev_dbg(xsdfec->dev,
|
|
"SDFEC enabling irq with IER failed");
|
|
return -EIO;
|
|
}
|
|
} else {
|
|
/* Disable */
|
|
xsdfec_regwrite(xsdfec, XSDFEC_IDR_ADDR, XSDFEC_ISR_MASK);
|
|
mask_read = xsdfec_regread(xsdfec, XSDFEC_IMR_ADDR);
|
|
if ((mask_read & XSDFEC_ISR_MASK) != XSDFEC_ISR_MASK) {
|
|
dev_dbg(xsdfec->dev,
|
|
"SDFEC disabling irq with IDR failed");
|
|
return -EIO;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_ecc_isr_enable(struct xsdfec_dev *xsdfec, bool enable)
|
|
{
|
|
u32 mask_read;
|
|
|
|
if (enable) {
|
|
/* Enable */
|
|
xsdfec_regwrite(xsdfec, XSDFEC_ECC_IER_ADDR,
|
|
XSDFEC_ALL_ECC_ISR_MASK);
|
|
mask_read = xsdfec_regread(xsdfec, XSDFEC_ECC_IMR_ADDR);
|
|
if (mask_read & XSDFEC_ALL_ECC_ISR_MASK) {
|
|
dev_dbg(xsdfec->dev,
|
|
"SDFEC enabling ECC irq with ECC IER failed");
|
|
return -EIO;
|
|
}
|
|
} else {
|
|
/* Disable */
|
|
xsdfec_regwrite(xsdfec, XSDFEC_ECC_IDR_ADDR,
|
|
XSDFEC_ALL_ECC_ISR_MASK);
|
|
mask_read = xsdfec_regread(xsdfec, XSDFEC_ECC_IMR_ADDR);
|
|
if (!(((mask_read & XSDFEC_ALL_ECC_ISR_MASK) ==
|
|
XSDFEC_ECC_ISR_MASK) ||
|
|
((mask_read & XSDFEC_ALL_ECC_ISR_MASK) ==
|
|
XSDFEC_PL_INIT_ECC_ISR_MASK))) {
|
|
dev_dbg(xsdfec->dev,
|
|
"SDFEC disable ECC irq with ECC IDR failed");
|
|
return -EIO;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_set_irq(struct xsdfec_dev *xsdfec, void __user *arg)
|
|
{
|
|
struct xsdfec_irq irq;
|
|
int err;
|
|
int isr_err;
|
|
int ecc_err;
|
|
|
|
err = copy_from_user(&irq, arg, sizeof(irq));
|
|
if (err)
|
|
return -EFAULT;
|
|
|
|
/* Setup tlast related IRQ */
|
|
isr_err = xsdfec_isr_enable(xsdfec, irq.enable_isr);
|
|
if (!isr_err)
|
|
xsdfec->config.irq.enable_isr = irq.enable_isr;
|
|
|
|
/* Setup ECC related IRQ */
|
|
ecc_err = xsdfec_ecc_isr_enable(xsdfec, irq.enable_ecc_isr);
|
|
if (!ecc_err)
|
|
xsdfec->config.irq.enable_ecc_isr = irq.enable_ecc_isr;
|
|
|
|
if (isr_err < 0 || ecc_err < 0)
|
|
err = -EIO;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int xsdfec_set_turbo(struct xsdfec_dev *xsdfec, void __user *arg)
|
|
{
|
|
struct xsdfec_turbo turbo;
|
|
int err;
|
|
u32 turbo_write;
|
|
|
|
err = copy_from_user(&turbo, arg, sizeof(turbo));
|
|
if (err)
|
|
return -EFAULT;
|
|
|
|
if (turbo.alg >= XSDFEC_TURBO_ALG_MAX)
|
|
return -EINVAL;
|
|
|
|
if (turbo.scale > XSDFEC_TURBO_SCALE_MAX)
|
|
return -EINVAL;
|
|
|
|
/* Check to see what device tree says about the FEC codes */
|
|
if (xsdfec->config.code == XSDFEC_LDPC_CODE)
|
|
return -EIO;
|
|
|
|
turbo_write = ((turbo.scale & XSDFEC_TURBO_SCALE_MASK)
|
|
<< XSDFEC_TURBO_SCALE_BIT_POS) |
|
|
turbo.alg;
|
|
xsdfec_regwrite(xsdfec, XSDFEC_TURBO_ADDR, turbo_write);
|
|
return err;
|
|
}
|
|
|
|
static int xsdfec_get_turbo(struct xsdfec_dev *xsdfec, void __user *arg)
|
|
{
|
|
u32 reg_value;
|
|
struct xsdfec_turbo turbo_params;
|
|
int err;
|
|
|
|
if (xsdfec->config.code == XSDFEC_LDPC_CODE)
|
|
return -EIO;
|
|
|
|
memset(&turbo_params, 0, sizeof(turbo_params));
|
|
reg_value = xsdfec_regread(xsdfec, XSDFEC_TURBO_ADDR);
|
|
|
|
turbo_params.scale = (reg_value & XSDFEC_TURBO_SCALE_MASK) >>
|
|
XSDFEC_TURBO_SCALE_BIT_POS;
|
|
turbo_params.alg = reg_value & 0x1;
|
|
|
|
err = copy_to_user(arg, &turbo_params, sizeof(turbo_params));
|
|
if (err)
|
|
err = -EFAULT;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int xsdfec_reg0_write(struct xsdfec_dev *xsdfec, u32 n, u32 k, u32 psize,
|
|
u32 offset)
|
|
{
|
|
u32 wdata;
|
|
|
|
if (n < XSDFEC_REG0_N_MIN || n > XSDFEC_REG0_N_MAX || psize == 0 ||
|
|
(n > XSDFEC_REG0_N_MUL_P * psize) || n <= k || ((n % psize) != 0)) {
|
|
dev_dbg(xsdfec->dev, "N value is not in range");
|
|
return -EINVAL;
|
|
}
|
|
n <<= XSDFEC_REG0_N_LSB;
|
|
|
|
if (k < XSDFEC_REG0_K_MIN || k > XSDFEC_REG0_K_MAX ||
|
|
(k > XSDFEC_REG0_K_MUL_P * psize) || ((k % psize) != 0)) {
|
|
dev_dbg(xsdfec->dev, "K value is not in range");
|
|
return -EINVAL;
|
|
}
|
|
k = k << XSDFEC_REG0_K_LSB;
|
|
wdata = k | n;
|
|
|
|
if (XSDFEC_LDPC_CODE_REG0_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) >
|
|
XSDFEC_LDPC_CODE_REG0_ADDR_HIGH) {
|
|
dev_dbg(xsdfec->dev, "Writing outside of LDPC reg0 space 0x%x",
|
|
XSDFEC_LDPC_CODE_REG0_ADDR_BASE +
|
|
(offset * XSDFEC_LDPC_REG_JUMP));
|
|
return -EINVAL;
|
|
}
|
|
xsdfec_regwrite(xsdfec,
|
|
XSDFEC_LDPC_CODE_REG0_ADDR_BASE +
|
|
(offset * XSDFEC_LDPC_REG_JUMP),
|
|
wdata);
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_reg1_write(struct xsdfec_dev *xsdfec, u32 psize,
|
|
u32 no_packing, u32 nm, u32 offset)
|
|
{
|
|
u32 wdata;
|
|
|
|
if (psize < XSDFEC_REG1_PSIZE_MIN || psize > XSDFEC_REG1_PSIZE_MAX) {
|
|
dev_dbg(xsdfec->dev, "Psize is not in range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (no_packing != 0 && no_packing != 1)
|
|
dev_dbg(xsdfec->dev, "No-packing bit register invalid");
|
|
no_packing = ((no_packing << XSDFEC_REG1_NO_PACKING_LSB) &
|
|
XSDFEC_REG1_NO_PACKING_MASK);
|
|
|
|
if (nm & ~(XSDFEC_REG1_NM_MASK >> XSDFEC_REG1_NM_LSB))
|
|
dev_dbg(xsdfec->dev, "NM is beyond 10 bits");
|
|
nm = (nm << XSDFEC_REG1_NM_LSB) & XSDFEC_REG1_NM_MASK;
|
|
|
|
wdata = nm | no_packing | psize;
|
|
if (XSDFEC_LDPC_CODE_REG1_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) >
|
|
XSDFEC_LDPC_CODE_REG1_ADDR_HIGH) {
|
|
dev_dbg(xsdfec->dev, "Writing outside of LDPC reg1 space 0x%x",
|
|
XSDFEC_LDPC_CODE_REG1_ADDR_BASE +
|
|
(offset * XSDFEC_LDPC_REG_JUMP));
|
|
return -EINVAL;
|
|
}
|
|
xsdfec_regwrite(xsdfec,
|
|
XSDFEC_LDPC_CODE_REG1_ADDR_BASE +
|
|
(offset * XSDFEC_LDPC_REG_JUMP),
|
|
wdata);
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_reg2_write(struct xsdfec_dev *xsdfec, u32 nlayers, u32 nmqc,
|
|
u32 norm_type, u32 special_qc, u32 no_final_parity,
|
|
u32 max_schedule, u32 offset)
|
|
{
|
|
u32 wdata;
|
|
|
|
if (nlayers < XSDFEC_REG2_NLAYERS_MIN ||
|
|
nlayers > XSDFEC_REG2_NLAYERS_MAX) {
|
|
dev_dbg(xsdfec->dev, "Nlayers is not in range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (nmqc & ~(XSDFEC_REG2_NNMQC_MASK >> XSDFEC_REG2_NMQC_LSB))
|
|
dev_dbg(xsdfec->dev, "NMQC exceeds 11 bits");
|
|
nmqc = (nmqc << XSDFEC_REG2_NMQC_LSB) & XSDFEC_REG2_NNMQC_MASK;
|
|
|
|
if (norm_type > 1)
|
|
dev_dbg(xsdfec->dev, "Norm type is invalid");
|
|
norm_type = ((norm_type << XSDFEC_REG2_NORM_TYPE_LSB) &
|
|
XSDFEC_REG2_NORM_TYPE_MASK);
|
|
if (special_qc > 1)
|
|
dev_dbg(xsdfec->dev, "Special QC in invalid");
|
|
special_qc = ((special_qc << XSDFEC_REG2_SPEICAL_QC_LSB) &
|
|
XSDFEC_REG2_SPECIAL_QC_MASK);
|
|
|
|
if (no_final_parity > 1)
|
|
dev_dbg(xsdfec->dev, "No final parity check invalid");
|
|
no_final_parity =
|
|
((no_final_parity << XSDFEC_REG2_NO_FINAL_PARITY_LSB) &
|
|
XSDFEC_REG2_NO_FINAL_PARITY_MASK);
|
|
if (max_schedule &
|
|
~(XSDFEC_REG2_MAX_SCHEDULE_MASK >> XSDFEC_REG2_MAX_SCHEDULE_LSB))
|
|
dev_dbg(xsdfec->dev, "Max Schedule exceeds 2 bits");
|
|
max_schedule = ((max_schedule << XSDFEC_REG2_MAX_SCHEDULE_LSB) &
|
|
XSDFEC_REG2_MAX_SCHEDULE_MASK);
|
|
|
|
wdata = (max_schedule | no_final_parity | special_qc | norm_type |
|
|
nmqc | nlayers);
|
|
|
|
if (XSDFEC_LDPC_CODE_REG2_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) >
|
|
XSDFEC_LDPC_CODE_REG2_ADDR_HIGH) {
|
|
dev_dbg(xsdfec->dev, "Writing outside of LDPC reg2 space 0x%x",
|
|
XSDFEC_LDPC_CODE_REG2_ADDR_BASE +
|
|
(offset * XSDFEC_LDPC_REG_JUMP));
|
|
return -EINVAL;
|
|
}
|
|
xsdfec_regwrite(xsdfec,
|
|
XSDFEC_LDPC_CODE_REG2_ADDR_BASE +
|
|
(offset * XSDFEC_LDPC_REG_JUMP),
|
|
wdata);
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_reg3_write(struct xsdfec_dev *xsdfec, u8 sc_off, u8 la_off,
|
|
u16 qc_off, u32 offset)
|
|
{
|
|
u32 wdata;
|
|
|
|
wdata = ((qc_off << XSDFEC_REG3_QC_OFF_LSB) |
|
|
(la_off << XSDFEC_REG3_LA_OFF_LSB) | sc_off);
|
|
if (XSDFEC_LDPC_CODE_REG3_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) >
|
|
XSDFEC_LDPC_CODE_REG3_ADDR_HIGH) {
|
|
dev_dbg(xsdfec->dev, "Writing outside of LDPC reg3 space 0x%x",
|
|
XSDFEC_LDPC_CODE_REG3_ADDR_BASE +
|
|
(offset * XSDFEC_LDPC_REG_JUMP));
|
|
return -EINVAL;
|
|
}
|
|
xsdfec_regwrite(xsdfec,
|
|
XSDFEC_LDPC_CODE_REG3_ADDR_BASE +
|
|
(offset * XSDFEC_LDPC_REG_JUMP),
|
|
wdata);
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_table_write(struct xsdfec_dev *xsdfec, u32 offset,
|
|
u32 *src_ptr, u32 len, const u32 base_addr,
|
|
const u32 depth)
|
|
{
|
|
u32 reg = 0;
|
|
int res, i, nr_pages;
|
|
u32 n;
|
|
u32 *addr = NULL;
|
|
struct page *pages[MAX_NUM_PAGES];
|
|
|
|
/*
|
|
* Writes that go beyond the length of
|
|
* Shared Scale(SC) table should fail
|
|
*/
|
|
if (offset > depth / XSDFEC_REG_WIDTH_JUMP ||
|
|
len > depth / XSDFEC_REG_WIDTH_JUMP ||
|
|
offset + len > depth / XSDFEC_REG_WIDTH_JUMP) {
|
|
dev_dbg(xsdfec->dev, "Write exceeds SC table length");
|
|
return -EINVAL;
|
|
}
|
|
|
|
n = (len * XSDFEC_REG_WIDTH_JUMP) / PAGE_SIZE;
|
|
if ((len * XSDFEC_REG_WIDTH_JUMP) % PAGE_SIZE)
|
|
n += 1;
|
|
|
|
if (WARN_ON_ONCE(n > INT_MAX))
|
|
return -EINVAL;
|
|
|
|
nr_pages = n;
|
|
|
|
res = pin_user_pages_fast((unsigned long)src_ptr, nr_pages, 0, pages);
|
|
if (res < nr_pages) {
|
|
if (res > 0)
|
|
unpin_user_pages(pages, res);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < nr_pages; i++) {
|
|
addr = kmap_local_page(pages[i]);
|
|
do {
|
|
xsdfec_regwrite(xsdfec,
|
|
base_addr + ((offset + reg) *
|
|
XSDFEC_REG_WIDTH_JUMP),
|
|
addr[reg]);
|
|
reg++;
|
|
} while ((reg < len) &&
|
|
((reg * XSDFEC_REG_WIDTH_JUMP) % PAGE_SIZE));
|
|
kunmap_local(addr);
|
|
unpin_user_page(pages[i]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_add_ldpc(struct xsdfec_dev *xsdfec, void __user *arg)
|
|
{
|
|
struct xsdfec_ldpc_params *ldpc;
|
|
int ret, n;
|
|
|
|
ldpc = memdup_user(arg, sizeof(*ldpc));
|
|
if (IS_ERR(ldpc))
|
|
return PTR_ERR(ldpc);
|
|
|
|
if (xsdfec->config.code == XSDFEC_TURBO_CODE) {
|
|
ret = -EIO;
|
|
goto err_out;
|
|
}
|
|
|
|
/* Verify Device has not started */
|
|
if (xsdfec->state == XSDFEC_STARTED) {
|
|
ret = -EIO;
|
|
goto err_out;
|
|
}
|
|
|
|
if (xsdfec->config.code_wr_protect) {
|
|
ret = -EIO;
|
|
goto err_out;
|
|
}
|
|
|
|
/* Write Reg 0 */
|
|
ret = xsdfec_reg0_write(xsdfec, ldpc->n, ldpc->k, ldpc->psize,
|
|
ldpc->code_id);
|
|
if (ret)
|
|
goto err_out;
|
|
|
|
/* Write Reg 1 */
|
|
ret = xsdfec_reg1_write(xsdfec, ldpc->psize, ldpc->no_packing, ldpc->nm,
|
|
ldpc->code_id);
|
|
if (ret)
|
|
goto err_out;
|
|
|
|
/* Write Reg 2 */
|
|
ret = xsdfec_reg2_write(xsdfec, ldpc->nlayers, ldpc->nmqc,
|
|
ldpc->norm_type, ldpc->special_qc,
|
|
ldpc->no_final_parity, ldpc->max_schedule,
|
|
ldpc->code_id);
|
|
if (ret)
|
|
goto err_out;
|
|
|
|
/* Write Reg 3 */
|
|
ret = xsdfec_reg3_write(xsdfec, ldpc->sc_off, ldpc->la_off,
|
|
ldpc->qc_off, ldpc->code_id);
|
|
if (ret)
|
|
goto err_out;
|
|
|
|
/* Write Shared Codes */
|
|
n = ldpc->nlayers / 4;
|
|
if (ldpc->nlayers % 4)
|
|
n++;
|
|
|
|
ret = xsdfec_table_write(xsdfec, ldpc->sc_off, ldpc->sc_table, n,
|
|
XSDFEC_LDPC_SC_TABLE_ADDR_BASE,
|
|
XSDFEC_SC_TABLE_DEPTH);
|
|
if (ret < 0)
|
|
goto err_out;
|
|
|
|
ret = xsdfec_table_write(xsdfec, 4 * ldpc->la_off, ldpc->la_table,
|
|
ldpc->nlayers, XSDFEC_LDPC_LA_TABLE_ADDR_BASE,
|
|
XSDFEC_LA_TABLE_DEPTH);
|
|
if (ret < 0)
|
|
goto err_out;
|
|
|
|
ret = xsdfec_table_write(xsdfec, 4 * ldpc->qc_off, ldpc->qc_table,
|
|
ldpc->nqc, XSDFEC_LDPC_QC_TABLE_ADDR_BASE,
|
|
XSDFEC_QC_TABLE_DEPTH);
|
|
err_out:
|
|
kfree(ldpc);
|
|
return ret;
|
|
}
|
|
|
|
static int xsdfec_set_order(struct xsdfec_dev *xsdfec, void __user *arg)
|
|
{
|
|
bool order_invalid;
|
|
enum xsdfec_order order;
|
|
int err;
|
|
|
|
err = get_user(order, (enum xsdfec_order __user *)arg);
|
|
if (err)
|
|
return -EFAULT;
|
|
|
|
order_invalid = (order != XSDFEC_MAINTAIN_ORDER) &&
|
|
(order != XSDFEC_OUT_OF_ORDER);
|
|
if (order_invalid)
|
|
return -EINVAL;
|
|
|
|
/* Verify Device has not started */
|
|
if (xsdfec->state == XSDFEC_STARTED)
|
|
return -EIO;
|
|
|
|
xsdfec_regwrite(xsdfec, XSDFEC_ORDER_ADDR, order);
|
|
|
|
xsdfec->config.order = order;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_set_bypass(struct xsdfec_dev *xsdfec, bool __user *arg)
|
|
{
|
|
bool bypass;
|
|
int err;
|
|
|
|
err = get_user(bypass, arg);
|
|
if (err)
|
|
return -EFAULT;
|
|
|
|
/* Verify Device has not started */
|
|
if (xsdfec->state == XSDFEC_STARTED)
|
|
return -EIO;
|
|
|
|
if (bypass)
|
|
xsdfec_regwrite(xsdfec, XSDFEC_BYPASS_ADDR, 1);
|
|
else
|
|
xsdfec_regwrite(xsdfec, XSDFEC_BYPASS_ADDR, 0);
|
|
|
|
xsdfec->config.bypass = bypass;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_is_active(struct xsdfec_dev *xsdfec, bool __user *arg)
|
|
{
|
|
u32 reg_value;
|
|
bool is_active;
|
|
int err;
|
|
|
|
reg_value = xsdfec_regread(xsdfec, XSDFEC_ACTIVE_ADDR);
|
|
/* using a double ! operator instead of casting */
|
|
is_active = !!(reg_value & XSDFEC_IS_ACTIVITY_SET);
|
|
err = put_user(is_active, arg);
|
|
if (err)
|
|
return -EFAULT;
|
|
|
|
return err;
|
|
}
|
|
|
|
static u32
|
|
xsdfec_translate_axis_width_cfg_val(enum xsdfec_axis_width axis_width_cfg)
|
|
{
|
|
u32 axis_width_field = 0;
|
|
|
|
switch (axis_width_cfg) {
|
|
case XSDFEC_1x128b:
|
|
axis_width_field = 0;
|
|
break;
|
|
case XSDFEC_2x128b:
|
|
axis_width_field = 1;
|
|
break;
|
|
case XSDFEC_4x128b:
|
|
axis_width_field = 2;
|
|
break;
|
|
}
|
|
|
|
return axis_width_field;
|
|
}
|
|
|
|
static u32 xsdfec_translate_axis_words_cfg_val(enum xsdfec_axis_word_include
|
|
axis_word_inc_cfg)
|
|
{
|
|
u32 axis_words_field = 0;
|
|
|
|
if (axis_word_inc_cfg == XSDFEC_FIXED_VALUE ||
|
|
axis_word_inc_cfg == XSDFEC_IN_BLOCK)
|
|
axis_words_field = 0;
|
|
else if (axis_word_inc_cfg == XSDFEC_PER_AXI_TRANSACTION)
|
|
axis_words_field = 1;
|
|
|
|
return axis_words_field;
|
|
}
|
|
|
|
static int xsdfec_cfg_axi_streams(struct xsdfec_dev *xsdfec)
|
|
{
|
|
u32 reg_value;
|
|
u32 dout_words_field;
|
|
u32 dout_width_field;
|
|
u32 din_words_field;
|
|
u32 din_width_field;
|
|
struct xsdfec_config *config = &xsdfec->config;
|
|
|
|
/* translate config info to register values */
|
|
dout_words_field =
|
|
xsdfec_translate_axis_words_cfg_val(config->dout_word_include);
|
|
dout_width_field =
|
|
xsdfec_translate_axis_width_cfg_val(config->dout_width);
|
|
din_words_field =
|
|
xsdfec_translate_axis_words_cfg_val(config->din_word_include);
|
|
din_width_field =
|
|
xsdfec_translate_axis_width_cfg_val(config->din_width);
|
|
|
|
reg_value = dout_words_field << XSDFEC_AXIS_DOUT_WORDS_LSB;
|
|
reg_value |= dout_width_field << XSDFEC_AXIS_DOUT_WIDTH_LSB;
|
|
reg_value |= din_words_field << XSDFEC_AXIS_DIN_WORDS_LSB;
|
|
reg_value |= din_width_field << XSDFEC_AXIS_DIN_WIDTH_LSB;
|
|
|
|
xsdfec_regwrite(xsdfec, XSDFEC_AXIS_WIDTH_ADDR, reg_value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_start(struct xsdfec_dev *xsdfec)
|
|
{
|
|
u32 regread;
|
|
|
|
regread = xsdfec_regread(xsdfec, XSDFEC_FEC_CODE_ADDR);
|
|
regread &= 0x1;
|
|
if (regread != xsdfec->config.code) {
|
|
dev_dbg(xsdfec->dev,
|
|
"%s SDFEC HW code does not match driver code, reg %d, code %d",
|
|
__func__, regread, xsdfec->config.code);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Set AXIS enable */
|
|
xsdfec_regwrite(xsdfec, XSDFEC_AXIS_ENABLE_ADDR,
|
|
XSDFEC_AXIS_ENABLE_MASK);
|
|
/* Done */
|
|
xsdfec->state = XSDFEC_STARTED;
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_stop(struct xsdfec_dev *xsdfec)
|
|
{
|
|
u32 regread;
|
|
|
|
if (xsdfec->state != XSDFEC_STARTED)
|
|
dev_dbg(xsdfec->dev, "Device not started correctly");
|
|
/* Disable AXIS_ENABLE Input interfaces only */
|
|
regread = xsdfec_regread(xsdfec, XSDFEC_AXIS_ENABLE_ADDR);
|
|
regread &= (~XSDFEC_AXIS_IN_ENABLE_MASK);
|
|
xsdfec_regwrite(xsdfec, XSDFEC_AXIS_ENABLE_ADDR, regread);
|
|
/* Stop */
|
|
xsdfec->state = XSDFEC_STOPPED;
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_clear_stats(struct xsdfec_dev *xsdfec)
|
|
{
|
|
spin_lock_irqsave(&xsdfec->error_data_lock, xsdfec->flags);
|
|
xsdfec->isr_err_count = 0;
|
|
xsdfec->uecc_count = 0;
|
|
xsdfec->cecc_count = 0;
|
|
spin_unlock_irqrestore(&xsdfec->error_data_lock, xsdfec->flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xsdfec_get_stats(struct xsdfec_dev *xsdfec, void __user *arg)
|
|
{
|
|
int err;
|
|
struct xsdfec_stats user_stats;
|
|
|
|
spin_lock_irqsave(&xsdfec->error_data_lock, xsdfec->flags);
|
|
user_stats.isr_err_count = xsdfec->isr_err_count;
|
|
user_stats.cecc_count = xsdfec->cecc_count;
|
|
user_stats.uecc_count = xsdfec->uecc_count;
|
|
xsdfec->stats_updated = false;
|
|
spin_unlock_irqrestore(&xsdfec->error_data_lock, xsdfec->flags);
|
|
|
|
err = copy_to_user(arg, &user_stats, sizeof(user_stats));
|
|
if (err)
|
|
err = -EFAULT;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int xsdfec_set_default_config(struct xsdfec_dev *xsdfec)
|
|
{
|
|
/* Ensure registers are aligned with core configuration */
|
|
xsdfec_regwrite(xsdfec, XSDFEC_FEC_CODE_ADDR, xsdfec->config.code);
|
|
xsdfec_cfg_axi_streams(xsdfec);
|
|
update_config_from_hw(xsdfec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long xsdfec_dev_ioctl(struct file *fptr, unsigned int cmd,
|
|
unsigned long data)
|
|
{
|
|
struct xsdfec_dev *xsdfec;
|
|
void __user *arg = (void __user *)data;
|
|
int rval;
|
|
|
|
xsdfec = container_of(fptr->private_data, struct xsdfec_dev, miscdev);
|
|
|
|
/* In failed state allow only reset and get status IOCTLs */
|
|
if (xsdfec->state == XSDFEC_NEEDS_RESET &&
|
|
(cmd != XSDFEC_SET_DEFAULT_CONFIG && cmd != XSDFEC_GET_STATUS &&
|
|
cmd != XSDFEC_GET_STATS && cmd != XSDFEC_CLEAR_STATS)) {
|
|
return -EPERM;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case XSDFEC_START_DEV:
|
|
rval = xsdfec_start(xsdfec);
|
|
break;
|
|
case XSDFEC_STOP_DEV:
|
|
rval = xsdfec_stop(xsdfec);
|
|
break;
|
|
case XSDFEC_CLEAR_STATS:
|
|
rval = xsdfec_clear_stats(xsdfec);
|
|
break;
|
|
case XSDFEC_GET_STATS:
|
|
rval = xsdfec_get_stats(xsdfec, arg);
|
|
break;
|
|
case XSDFEC_GET_STATUS:
|
|
rval = xsdfec_get_status(xsdfec, arg);
|
|
break;
|
|
case XSDFEC_GET_CONFIG:
|
|
rval = xsdfec_get_config(xsdfec, arg);
|
|
break;
|
|
case XSDFEC_SET_DEFAULT_CONFIG:
|
|
rval = xsdfec_set_default_config(xsdfec);
|
|
break;
|
|
case XSDFEC_SET_IRQ:
|
|
rval = xsdfec_set_irq(xsdfec, arg);
|
|
break;
|
|
case XSDFEC_SET_TURBO:
|
|
rval = xsdfec_set_turbo(xsdfec, arg);
|
|
break;
|
|
case XSDFEC_GET_TURBO:
|
|
rval = xsdfec_get_turbo(xsdfec, arg);
|
|
break;
|
|
case XSDFEC_ADD_LDPC_CODE_PARAMS:
|
|
rval = xsdfec_add_ldpc(xsdfec, arg);
|
|
break;
|
|
case XSDFEC_SET_ORDER:
|
|
rval = xsdfec_set_order(xsdfec, arg);
|
|
break;
|
|
case XSDFEC_SET_BYPASS:
|
|
rval = xsdfec_set_bypass(xsdfec, arg);
|
|
break;
|
|
case XSDFEC_IS_ACTIVE:
|
|
rval = xsdfec_is_active(xsdfec, (bool __user *)arg);
|
|
break;
|
|
default:
|
|
rval = -ENOTTY;
|
|
break;
|
|
}
|
|
return rval;
|
|
}
|
|
|
|
static __poll_t xsdfec_poll(struct file *file, poll_table *wait)
|
|
{
|
|
__poll_t mask = 0;
|
|
struct xsdfec_dev *xsdfec;
|
|
|
|
xsdfec = container_of(file->private_data, struct xsdfec_dev, miscdev);
|
|
|
|
poll_wait(file, &xsdfec->waitq, wait);
|
|
|
|
/* XSDFEC ISR detected an error */
|
|
spin_lock_irqsave(&xsdfec->error_data_lock, xsdfec->flags);
|
|
if (xsdfec->state_updated)
|
|
mask |= EPOLLIN | EPOLLPRI;
|
|
|
|
if (xsdfec->stats_updated)
|
|
mask |= EPOLLIN | EPOLLRDNORM;
|
|
spin_unlock_irqrestore(&xsdfec->error_data_lock, xsdfec->flags);
|
|
|
|
return mask;
|
|
}
|
|
|
|
static const struct file_operations xsdfec_fops = {
|
|
.owner = THIS_MODULE,
|
|
.unlocked_ioctl = xsdfec_dev_ioctl,
|
|
.poll = xsdfec_poll,
|
|
.compat_ioctl = compat_ptr_ioctl,
|
|
};
|
|
|
|
static int xsdfec_parse_of(struct xsdfec_dev *xsdfec)
|
|
{
|
|
struct device *dev = xsdfec->dev;
|
|
struct device_node *node = dev->of_node;
|
|
int rval;
|
|
const char *fec_code;
|
|
u32 din_width;
|
|
u32 din_word_include;
|
|
u32 dout_width;
|
|
u32 dout_word_include;
|
|
|
|
rval = of_property_read_string(node, "xlnx,sdfec-code", &fec_code);
|
|
if (rval < 0)
|
|
return rval;
|
|
|
|
if (!strcasecmp(fec_code, "ldpc"))
|
|
xsdfec->config.code = XSDFEC_LDPC_CODE;
|
|
else if (!strcasecmp(fec_code, "turbo"))
|
|
xsdfec->config.code = XSDFEC_TURBO_CODE;
|
|
else
|
|
return -EINVAL;
|
|
|
|
rval = of_property_read_u32(node, "xlnx,sdfec-din-words",
|
|
&din_word_include);
|
|
if (rval < 0)
|
|
return rval;
|
|
|
|
if (din_word_include < XSDFEC_AXIS_WORDS_INCLUDE_MAX)
|
|
xsdfec->config.din_word_include = din_word_include;
|
|
else
|
|
return -EINVAL;
|
|
|
|
rval = of_property_read_u32(node, "xlnx,sdfec-din-width", &din_width);
|
|
if (rval < 0)
|
|
return rval;
|
|
|
|
switch (din_width) {
|
|
/* Fall through and set for valid values */
|
|
case XSDFEC_1x128b:
|
|
case XSDFEC_2x128b:
|
|
case XSDFEC_4x128b:
|
|
xsdfec->config.din_width = din_width;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
rval = of_property_read_u32(node, "xlnx,sdfec-dout-words",
|
|
&dout_word_include);
|
|
if (rval < 0)
|
|
return rval;
|
|
|
|
if (dout_word_include < XSDFEC_AXIS_WORDS_INCLUDE_MAX)
|
|
xsdfec->config.dout_word_include = dout_word_include;
|
|
else
|
|
return -EINVAL;
|
|
|
|
rval = of_property_read_u32(node, "xlnx,sdfec-dout-width", &dout_width);
|
|
if (rval < 0)
|
|
return rval;
|
|
|
|
switch (dout_width) {
|
|
/* Fall through and set for valid values */
|
|
case XSDFEC_1x128b:
|
|
case XSDFEC_2x128b:
|
|
case XSDFEC_4x128b:
|
|
xsdfec->config.dout_width = dout_width;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Write LDPC to CODE Register */
|
|
xsdfec_regwrite(xsdfec, XSDFEC_FEC_CODE_ADDR, xsdfec->config.code);
|
|
|
|
xsdfec_cfg_axi_streams(xsdfec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t xsdfec_irq_thread(int irq, void *dev_id)
|
|
{
|
|
struct xsdfec_dev *xsdfec = dev_id;
|
|
irqreturn_t ret = IRQ_HANDLED;
|
|
u32 ecc_err;
|
|
u32 isr_err;
|
|
u32 uecc_count;
|
|
u32 cecc_count;
|
|
u32 isr_err_count;
|
|
u32 aecc_count;
|
|
u32 tmp;
|
|
|
|
WARN_ON(xsdfec->irq != irq);
|
|
|
|
/* Mask Interrupts */
|
|
xsdfec_isr_enable(xsdfec, false);
|
|
xsdfec_ecc_isr_enable(xsdfec, false);
|
|
/* Read ISR */
|
|
ecc_err = xsdfec_regread(xsdfec, XSDFEC_ECC_ISR_ADDR);
|
|
isr_err = xsdfec_regread(xsdfec, XSDFEC_ISR_ADDR);
|
|
/* Clear the interrupts */
|
|
xsdfec_regwrite(xsdfec, XSDFEC_ECC_ISR_ADDR, ecc_err);
|
|
xsdfec_regwrite(xsdfec, XSDFEC_ISR_ADDR, isr_err);
|
|
|
|
tmp = ecc_err & XSDFEC_ALL_ECC_ISR_MBE_MASK;
|
|
/* Count uncorrectable 2-bit errors */
|
|
uecc_count = hweight32(tmp);
|
|
/* Count all ECC errors */
|
|
aecc_count = hweight32(ecc_err);
|
|
/* Number of correctable 1-bit ECC error */
|
|
cecc_count = aecc_count - 2 * uecc_count;
|
|
/* Count ISR errors */
|
|
isr_err_count = hweight32(isr_err);
|
|
dev_dbg(xsdfec->dev, "tmp=%x, uecc=%x, aecc=%x, cecc=%x, isr=%x", tmp,
|
|
uecc_count, aecc_count, cecc_count, isr_err_count);
|
|
dev_dbg(xsdfec->dev, "uecc=%x, cecc=%x, isr=%x", xsdfec->uecc_count,
|
|
xsdfec->cecc_count, xsdfec->isr_err_count);
|
|
|
|
spin_lock_irqsave(&xsdfec->error_data_lock, xsdfec->flags);
|
|
/* Add new errors to a 2-bits counter */
|
|
if (uecc_count)
|
|
xsdfec->uecc_count += uecc_count;
|
|
/* Add new errors to a 1-bits counter */
|
|
if (cecc_count)
|
|
xsdfec->cecc_count += cecc_count;
|
|
/* Add new errors to a ISR counter */
|
|
if (isr_err_count)
|
|
xsdfec->isr_err_count += isr_err_count;
|
|
|
|
/* Update state/stats flag */
|
|
if (uecc_count) {
|
|
if (ecc_err & XSDFEC_ECC_ISR_MBE_MASK)
|
|
xsdfec->state = XSDFEC_NEEDS_RESET;
|
|
else if (ecc_err & XSDFEC_PL_INIT_ECC_ISR_MBE_MASK)
|
|
xsdfec->state = XSDFEC_PL_RECONFIGURE;
|
|
xsdfec->stats_updated = true;
|
|
xsdfec->state_updated = true;
|
|
}
|
|
|
|
if (cecc_count)
|
|
xsdfec->stats_updated = true;
|
|
|
|
if (isr_err_count) {
|
|
xsdfec->state = XSDFEC_NEEDS_RESET;
|
|
xsdfec->stats_updated = true;
|
|
xsdfec->state_updated = true;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&xsdfec->error_data_lock, xsdfec->flags);
|
|
dev_dbg(xsdfec->dev, "state=%x, stats=%x", xsdfec->state_updated,
|
|
xsdfec->stats_updated);
|
|
|
|
/* Enable another polling */
|
|
if (xsdfec->state_updated || xsdfec->stats_updated)
|
|
wake_up_interruptible(&xsdfec->waitq);
|
|
else
|
|
ret = IRQ_NONE;
|
|
|
|
/* Unmask Interrupts */
|
|
xsdfec_isr_enable(xsdfec, true);
|
|
xsdfec_ecc_isr_enable(xsdfec, true);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int xsdfec_clk_init(struct platform_device *pdev,
|
|
struct xsdfec_clks *clks)
|
|
{
|
|
int err;
|
|
|
|
clks->core_clk = devm_clk_get(&pdev->dev, "core_clk");
|
|
if (IS_ERR(clks->core_clk)) {
|
|
dev_err(&pdev->dev, "failed to get core_clk");
|
|
return PTR_ERR(clks->core_clk);
|
|
}
|
|
|
|
clks->axi_clk = devm_clk_get(&pdev->dev, "s_axi_aclk");
|
|
if (IS_ERR(clks->axi_clk)) {
|
|
dev_err(&pdev->dev, "failed to get axi_clk");
|
|
return PTR_ERR(clks->axi_clk);
|
|
}
|
|
|
|
clks->din_words_clk = devm_clk_get(&pdev->dev, "s_axis_din_words_aclk");
|
|
if (IS_ERR(clks->din_words_clk)) {
|
|
if (PTR_ERR(clks->din_words_clk) != -ENOENT) {
|
|
err = PTR_ERR(clks->din_words_clk);
|
|
return err;
|
|
}
|
|
clks->din_words_clk = NULL;
|
|
}
|
|
|
|
clks->din_clk = devm_clk_get(&pdev->dev, "s_axis_din_aclk");
|
|
if (IS_ERR(clks->din_clk)) {
|
|
if (PTR_ERR(clks->din_clk) != -ENOENT) {
|
|
err = PTR_ERR(clks->din_clk);
|
|
return err;
|
|
}
|
|
clks->din_clk = NULL;
|
|
}
|
|
|
|
clks->dout_clk = devm_clk_get(&pdev->dev, "m_axis_dout_aclk");
|
|
if (IS_ERR(clks->dout_clk)) {
|
|
if (PTR_ERR(clks->dout_clk) != -ENOENT) {
|
|
err = PTR_ERR(clks->dout_clk);
|
|
return err;
|
|
}
|
|
clks->dout_clk = NULL;
|
|
}
|
|
|
|
clks->dout_words_clk =
|
|
devm_clk_get(&pdev->dev, "s_axis_dout_words_aclk");
|
|
if (IS_ERR(clks->dout_words_clk)) {
|
|
if (PTR_ERR(clks->dout_words_clk) != -ENOENT) {
|
|
err = PTR_ERR(clks->dout_words_clk);
|
|
return err;
|
|
}
|
|
clks->dout_words_clk = NULL;
|
|
}
|
|
|
|
clks->ctrl_clk = devm_clk_get(&pdev->dev, "s_axis_ctrl_aclk");
|
|
if (IS_ERR(clks->ctrl_clk)) {
|
|
if (PTR_ERR(clks->ctrl_clk) != -ENOENT) {
|
|
err = PTR_ERR(clks->ctrl_clk);
|
|
return err;
|
|
}
|
|
clks->ctrl_clk = NULL;
|
|
}
|
|
|
|
clks->status_clk = devm_clk_get(&pdev->dev, "m_axis_status_aclk");
|
|
if (IS_ERR(clks->status_clk)) {
|
|
if (PTR_ERR(clks->status_clk) != -ENOENT) {
|
|
err = PTR_ERR(clks->status_clk);
|
|
return err;
|
|
}
|
|
clks->status_clk = NULL;
|
|
}
|
|
|
|
err = clk_prepare_enable(clks->core_clk);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "failed to enable core_clk (%d)", err);
|
|
return err;
|
|
}
|
|
|
|
err = clk_prepare_enable(clks->axi_clk);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "failed to enable axi_clk (%d)", err);
|
|
goto err_disable_core_clk;
|
|
}
|
|
|
|
err = clk_prepare_enable(clks->din_clk);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "failed to enable din_clk (%d)", err);
|
|
goto err_disable_axi_clk;
|
|
}
|
|
|
|
err = clk_prepare_enable(clks->din_words_clk);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "failed to enable din_words_clk (%d)", err);
|
|
goto err_disable_din_clk;
|
|
}
|
|
|
|
err = clk_prepare_enable(clks->dout_clk);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "failed to enable dout_clk (%d)", err);
|
|
goto err_disable_din_words_clk;
|
|
}
|
|
|
|
err = clk_prepare_enable(clks->dout_words_clk);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "failed to enable dout_words_clk (%d)",
|
|
err);
|
|
goto err_disable_dout_clk;
|
|
}
|
|
|
|
err = clk_prepare_enable(clks->ctrl_clk);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "failed to enable ctrl_clk (%d)", err);
|
|
goto err_disable_dout_words_clk;
|
|
}
|
|
|
|
err = clk_prepare_enable(clks->status_clk);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "failed to enable status_clk (%d)\n", err);
|
|
goto err_disable_ctrl_clk;
|
|
}
|
|
|
|
return err;
|
|
|
|
err_disable_ctrl_clk:
|
|
clk_disable_unprepare(clks->ctrl_clk);
|
|
err_disable_dout_words_clk:
|
|
clk_disable_unprepare(clks->dout_words_clk);
|
|
err_disable_dout_clk:
|
|
clk_disable_unprepare(clks->dout_clk);
|
|
err_disable_din_words_clk:
|
|
clk_disable_unprepare(clks->din_words_clk);
|
|
err_disable_din_clk:
|
|
clk_disable_unprepare(clks->din_clk);
|
|
err_disable_axi_clk:
|
|
clk_disable_unprepare(clks->axi_clk);
|
|
err_disable_core_clk:
|
|
clk_disable_unprepare(clks->core_clk);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void xsdfec_disable_all_clks(struct xsdfec_clks *clks)
|
|
{
|
|
clk_disable_unprepare(clks->status_clk);
|
|
clk_disable_unprepare(clks->ctrl_clk);
|
|
clk_disable_unprepare(clks->dout_words_clk);
|
|
clk_disable_unprepare(clks->dout_clk);
|
|
clk_disable_unprepare(clks->din_words_clk);
|
|
clk_disable_unprepare(clks->din_clk);
|
|
clk_disable_unprepare(clks->core_clk);
|
|
clk_disable_unprepare(clks->axi_clk);
|
|
}
|
|
|
|
static int xsdfec_probe(struct platform_device *pdev)
|
|
{
|
|
struct xsdfec_dev *xsdfec;
|
|
struct device *dev;
|
|
int err;
|
|
bool irq_enabled = true;
|
|
|
|
xsdfec = devm_kzalloc(&pdev->dev, sizeof(*xsdfec), GFP_KERNEL);
|
|
if (!xsdfec)
|
|
return -ENOMEM;
|
|
|
|
xsdfec->dev = &pdev->dev;
|
|
spin_lock_init(&xsdfec->error_data_lock);
|
|
|
|
err = xsdfec_clk_init(pdev, &xsdfec->clks);
|
|
if (err)
|
|
return err;
|
|
|
|
dev = xsdfec->dev;
|
|
xsdfec->regs = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(xsdfec->regs)) {
|
|
err = PTR_ERR(xsdfec->regs);
|
|
goto err_xsdfec_dev;
|
|
}
|
|
|
|
xsdfec->irq = platform_get_irq(pdev, 0);
|
|
if (xsdfec->irq < 0) {
|
|
dev_dbg(dev, "platform_get_irq failed");
|
|
irq_enabled = false;
|
|
}
|
|
|
|
err = xsdfec_parse_of(xsdfec);
|
|
if (err < 0)
|
|
goto err_xsdfec_dev;
|
|
|
|
update_config_from_hw(xsdfec);
|
|
|
|
/* Save driver private data */
|
|
platform_set_drvdata(pdev, xsdfec);
|
|
|
|
if (irq_enabled) {
|
|
init_waitqueue_head(&xsdfec->waitq);
|
|
/* Register IRQ thread */
|
|
err = devm_request_threaded_irq(dev, xsdfec->irq, NULL,
|
|
xsdfec_irq_thread, IRQF_ONESHOT,
|
|
"xilinx-sdfec16", xsdfec);
|
|
if (err < 0) {
|
|
dev_err(dev, "unable to request IRQ%d", xsdfec->irq);
|
|
goto err_xsdfec_dev;
|
|
}
|
|
}
|
|
|
|
err = ida_alloc(&dev_nrs, GFP_KERNEL);
|
|
if (err < 0)
|
|
goto err_xsdfec_dev;
|
|
xsdfec->dev_id = err;
|
|
|
|
snprintf(xsdfec->dev_name, DEV_NAME_LEN, "xsdfec%d", xsdfec->dev_id);
|
|
xsdfec->miscdev.minor = MISC_DYNAMIC_MINOR;
|
|
xsdfec->miscdev.name = xsdfec->dev_name;
|
|
xsdfec->miscdev.fops = &xsdfec_fops;
|
|
xsdfec->miscdev.parent = dev;
|
|
err = misc_register(&xsdfec->miscdev);
|
|
if (err) {
|
|
dev_err(dev, "error:%d. Unable to register device", err);
|
|
goto err_xsdfec_ida;
|
|
}
|
|
return 0;
|
|
|
|
err_xsdfec_ida:
|
|
ida_free(&dev_nrs, xsdfec->dev_id);
|
|
err_xsdfec_dev:
|
|
xsdfec_disable_all_clks(&xsdfec->clks);
|
|
return err;
|
|
}
|
|
|
|
static void xsdfec_remove(struct platform_device *pdev)
|
|
{
|
|
struct xsdfec_dev *xsdfec;
|
|
|
|
xsdfec = platform_get_drvdata(pdev);
|
|
misc_deregister(&xsdfec->miscdev);
|
|
ida_free(&dev_nrs, xsdfec->dev_id);
|
|
xsdfec_disable_all_clks(&xsdfec->clks);
|
|
}
|
|
|
|
static const struct of_device_id xsdfec_of_match[] = {
|
|
{
|
|
.compatible = "xlnx,sd-fec-1.1",
|
|
},
|
|
{ /* end of table */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, xsdfec_of_match);
|
|
|
|
static struct platform_driver xsdfec_driver = {
|
|
.driver = {
|
|
.name = "xilinx-sdfec",
|
|
.of_match_table = xsdfec_of_match,
|
|
},
|
|
.probe = xsdfec_probe,
|
|
.remove_new = xsdfec_remove,
|
|
};
|
|
|
|
module_platform_driver(xsdfec_driver);
|
|
|
|
MODULE_AUTHOR("Xilinx, Inc");
|
|
MODULE_DESCRIPTION("Xilinx SD-FEC16 Driver");
|
|
MODULE_LICENSE("GPL");
|