diff options
Diffstat (limited to 'drivers/misc/xilinx_sdfec.c')
| -rw-r--r-- | drivers/misc/xilinx_sdfec.c | 1214 | 
1 files changed, 1189 insertions, 25 deletions
diff --git a/drivers/misc/xilinx_sdfec.c b/drivers/misc/xilinx_sdfec.c index f257d3812110..11835969e982 100644 --- a/drivers/misc/xilinx_sdfec.c +++ b/drivers/misc/xilinx_sdfec.c @@ -19,11 +19,150 @@  #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 struct idr dev_idr; -static struct mutex dev_idr_lock; +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 @@ -49,31 +188,1043 @@ struct xsdfec_clks {  /**   * struct xsdfec_dev - Driver data for SDFEC - * @regs: device physical base address - * @dev: pointer to device struct   * @miscdev: Misc device handle - * @error_data_lock: Error counter and states spinlock   * @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; -	struct miscdevice miscdev; +	enum xsdfec_state state;  	/* Spinlock to protect state_updated and stats_updated */  	spinlock_t error_data_lock; -	struct xsdfec_clks clks; -	char dev_name[DEV_NAME_LEN];  	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; +	u32 res; +	u32 n, i; +	u32 *addr = NULL; +	struct page *page[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; + +	res = get_user_pages_fast((unsigned long)src_ptr, n, 0, page); +	if (res < n) { +		for (i = 0; i < res; i++) +			put_page(page[i]); +		return -EINVAL; +	} + +	for (i = 0; i < n; i++) { +		addr = kmap(page[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)); +		put_page(page[i]); +	} +	return reg; +} + +static int xsdfec_add_ldpc(struct xsdfec_dev *xsdfec, void __user *arg) +{ +	struct xsdfec_ldpc_params *ldpc; +	int ret, n; + +	ldpc = kzalloc(sizeof(*ldpc), GFP_KERNEL); +	if (!ldpc) +		return -ENOMEM; + +	if (copy_from_user(ldpc, arg, sizeof(*ldpc))) { +		ret = -EFAULT; +		goto err_out; +	} + +	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); +	if (ret > 0) +		ret = 0; +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 *)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_dev_open(struct inode *iptr, struct file *fptr) +{ +	return 0; +} + +static int xsdfec_dev_release(struct inode *iptr, struct file *fptr) +{ +	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 = NULL; +	int rval = -EINVAL; + +	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; +	} + +	if (_IOC_TYPE(cmd) != XSDFEC_MAGIC) +		return -ENOTTY; + +	/* check if ioctl argument is present and valid */ +	if (_IOC_DIR(cmd) != _IOC_NONE) { +		arg = (void __user *)data; +		if (!arg) +			return rval; +	} + +	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: +		/* Should not get here */ +		break; +	} +	return rval; +} + +#ifdef CONFIG_COMPAT +static long xsdfec_dev_compat_ioctl(struct file *file, unsigned int cmd, +				    unsigned long data) +{ +	return xsdfec_dev_ioctl(file, cmd, (unsigned long)compat_ptr(data)); +} +#endif + +static unsigned int xsdfec_poll(struct file *file, poll_table *wait) +{ +	unsigned int mask = 0; +	struct xsdfec_dev *xsdfec; + +	xsdfec = container_of(file->private_data, struct xsdfec_dev, miscdev); + +	if (!xsdfec) +		return POLLNVAL | POLLHUP; + +	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 |= POLLIN | POLLPRI; + +	if (xsdfec->stats_updated) +		mask |= POLLIN | POLLRDNORM; +	spin_unlock_irqrestore(&xsdfec->error_data_lock, xsdfec->flags); + +	return mask; +} +  static const struct file_operations xsdfec_fops = {  	.owner = THIS_MODULE, +	.open = xsdfec_dev_open, +	.release = xsdfec_dev_release, +	.unlocked_ioctl = xsdfec_dev_ioctl, +	.poll = xsdfec_poll, +#ifdef CONFIG_COMPAT +	.compat_ioctl = xsdfec_dev_compat_ioctl, +#endif  }; +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)  { @@ -227,19 +1378,13 @@ static void xsdfec_disable_all_clks(struct xsdfec_clks *clks)  	clk_disable_unprepare(clks->axi_clk);  } -static void xsdfec_idr_remove(struct xsdfec_dev *xsdfec) -{ -	mutex_lock(&dev_idr_lock); -	idr_remove(&dev_idr, xsdfec->dev_id); -	mutex_unlock(&dev_idr_lock); -} -  static int xsdfec_probe(struct platform_device *pdev)  {  	struct xsdfec_dev *xsdfec;  	struct device *dev;  	struct resource *res;  	int err; +	bool irq_enabled = true;  	xsdfec = devm_kzalloc(&pdev->dev, sizeof(*xsdfec), GFP_KERNEL);  	if (!xsdfec) @@ -260,12 +1405,34 @@ static int xsdfec_probe(struct platform_device *pdev)  		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); -	mutex_lock(&dev_idr_lock); -	err = idr_alloc(&dev_idr, xsdfec->dev_name, 0, 0, GFP_KERNEL); -	mutex_unlock(&dev_idr_lock); +	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; @@ -278,12 +1445,12 @@ static int xsdfec_probe(struct platform_device *pdev)  	err = misc_register(&xsdfec->miscdev);  	if (err) {  		dev_err(dev, "error:%d. Unable to register device", err); -		goto err_xsdfec_idr; +		goto err_xsdfec_ida;  	}  	return 0; -err_xsdfec_idr: -	xsdfec_idr_remove(xsdfec); +err_xsdfec_ida: +	ida_free(&dev_nrs, xsdfec->dev_id);  err_xsdfec_dev:  	xsdfec_disable_all_clks(&xsdfec->clks);  	return err; @@ -295,7 +1462,7 @@ static int xsdfec_remove(struct platform_device *pdev)  	xsdfec = platform_get_drvdata(pdev);  	misc_deregister(&xsdfec->miscdev); -	xsdfec_idr_remove(xsdfec); +	ida_free(&dev_nrs, xsdfec->dev_id);  	xsdfec_disable_all_clks(&xsdfec->clks);  	return 0;  } @@ -321,8 +1488,6 @@ static int __init xsdfec_init(void)  {  	int err; -	mutex_init(&dev_idr_lock); -	idr_init(&dev_idr);  	err = platform_driver_register(&xsdfec_driver);  	if (err < 0) {  		pr_err("%s Unabled to register SDFEC driver", __func__); @@ -334,7 +1499,6 @@ static int __init xsdfec_init(void)  static void __exit xsdfec_exit(void)  {  	platform_driver_unregister(&xsdfec_driver); -	idr_destroy(&dev_idr);  }  module_init(xsdfec_init);  |