brintos

brintos / linux-shallow public Read only

0
0
Text · 6.6 KiB · 12b5b04 Raw
272 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * JZ4780 BCH controller driver4 *5 * Copyright (c) 2015 Imagination Technologies6 * Author: Alex Smith <alex.smith@imgtec.com>7 */8 9#include <linux/bitops.h>10#include <linux/clk.h>11#include <linux/device.h>12#include <linux/io.h>13#include <linux/iopoll.h>14#include <linux/module.h>15#include <linux/mutex.h>16#include <linux/of_platform.h>17#include <linux/platform_device.h>18 19#include "ingenic_ecc.h"20 21#define BCH_BHCR			0x022#define BCH_BHCCR			0x823#define BCH_BHCNT			0xc24#define BCH_BHDR			0x1025#define BCH_BHPAR0			0x1426#define BCH_BHERR0			0x8427#define BCH_BHINT			0x18428#define BCH_BHINTES			0x18829#define BCH_BHINTEC			0x18c30#define BCH_BHINTE			0x19031 32#define BCH_BHCR_BSEL_SHIFT		433#define BCH_BHCR_BSEL_MASK		(0x7f << BCH_BHCR_BSEL_SHIFT)34#define BCH_BHCR_ENCE			BIT(2)35#define BCH_BHCR_INIT			BIT(1)36#define BCH_BHCR_BCHE			BIT(0)37 38#define BCH_BHCNT_PARITYSIZE_SHIFT	1639#define BCH_BHCNT_PARITYSIZE_MASK	(0x7f << BCH_BHCNT_PARITYSIZE_SHIFT)40#define BCH_BHCNT_BLOCKSIZE_SHIFT	041#define BCH_BHCNT_BLOCKSIZE_MASK	(0x7ff << BCH_BHCNT_BLOCKSIZE_SHIFT)42 43#define BCH_BHERR_MASK_SHIFT		1644#define BCH_BHERR_MASK_MASK		(0xffff << BCH_BHERR_MASK_SHIFT)45#define BCH_BHERR_INDEX_SHIFT		046#define BCH_BHERR_INDEX_MASK		(0x7ff << BCH_BHERR_INDEX_SHIFT)47 48#define BCH_BHINT_ERRC_SHIFT		2449#define BCH_BHINT_ERRC_MASK		(0x7f << BCH_BHINT_ERRC_SHIFT)50#define BCH_BHINT_TERRC_SHIFT		1651#define BCH_BHINT_TERRC_MASK		(0x7f << BCH_BHINT_TERRC_SHIFT)52#define BCH_BHINT_DECF			BIT(3)53#define BCH_BHINT_ENCF			BIT(2)54#define BCH_BHINT_UNCOR			BIT(1)55#define BCH_BHINT_ERR			BIT(0)56 57#define BCH_CLK_RATE			(200 * 1000 * 1000)58 59/* Timeout for BCH calculation/correction. */60#define BCH_TIMEOUT_US			10000061 62static void jz4780_bch_reset(struct ingenic_ecc *bch,63			     struct ingenic_ecc_params *params, bool encode)64{65	u32 reg;66 67	/* Clear interrupt status. */68	writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT);69 70	/* Set up BCH count register. */71	reg = params->size << BCH_BHCNT_BLOCKSIZE_SHIFT;72	reg |= params->bytes << BCH_BHCNT_PARITYSIZE_SHIFT;73	writel(reg, bch->base + BCH_BHCNT);74 75	/* Initialise and enable BCH. */76	reg = BCH_BHCR_BCHE | BCH_BHCR_INIT;77	reg |= params->strength << BCH_BHCR_BSEL_SHIFT;78	if (encode)79		reg |= BCH_BHCR_ENCE;80	writel(reg, bch->base + BCH_BHCR);81}82 83static void jz4780_bch_disable(struct ingenic_ecc *bch)84{85	writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT);86	writel(BCH_BHCR_BCHE, bch->base + BCH_BHCCR);87}88 89static void jz4780_bch_write_data(struct ingenic_ecc *bch, const void *buf,90				  size_t size)91{92	size_t size32 = size / sizeof(u32);93	size_t size8 = size % sizeof(u32);94	const u32 *src32;95	const u8 *src8;96 97	src32 = (const u32 *)buf;98	while (size32--)99		writel(*src32++, bch->base + BCH_BHDR);100 101	src8 = (const u8 *)src32;102	while (size8--)103		writeb(*src8++, bch->base + BCH_BHDR);104}105 106static void jz4780_bch_read_parity(struct ingenic_ecc *bch, void *buf,107				   size_t size)108{109	size_t size32 = size / sizeof(u32);110	size_t size8 = size % sizeof(u32);111	u32 *dest32;112	u8 *dest8;113	u32 val, offset = 0;114 115	dest32 = (u32 *)buf;116	while (size32--) {117		*dest32++ = readl(bch->base + BCH_BHPAR0 + offset);118		offset += sizeof(u32);119	}120 121	dest8 = (u8 *)dest32;122	val = readl(bch->base + BCH_BHPAR0 + offset);123	switch (size8) {124	case 3:125		dest8[2] = (val >> 16) & 0xff;126		fallthrough;127	case 2:128		dest8[1] = (val >> 8) & 0xff;129		fallthrough;130	case 1:131		dest8[0] = val & 0xff;132		break;133	}134}135 136static bool jz4780_bch_wait_complete(struct ingenic_ecc *bch, unsigned int irq,137				     u32 *status)138{139	u32 reg;140	int ret;141 142	/*143	 * While we could use interrupts here and sleep until the operation144	 * completes, the controller works fairly quickly (usually a few145	 * microseconds) and so the overhead of sleeping until we get an146	 * interrupt quite noticeably decreases performance.147	 */148	ret = readl_poll_timeout(bch->base + BCH_BHINT, reg,149				 (reg & irq) == irq, 0, BCH_TIMEOUT_US);150	if (ret)151		return false;152 153	if (status)154		*status = reg;155 156	writel(reg, bch->base + BCH_BHINT);157	return true;158}159 160static int jz4780_calculate(struct ingenic_ecc *bch,161			    struct ingenic_ecc_params *params,162			    const u8 *buf, u8 *ecc_code)163{164	int ret = 0;165 166	mutex_lock(&bch->lock);167 168	jz4780_bch_reset(bch, params, true);169	jz4780_bch_write_data(bch, buf, params->size);170 171	if (jz4780_bch_wait_complete(bch, BCH_BHINT_ENCF, NULL)) {172		jz4780_bch_read_parity(bch, ecc_code, params->bytes);173	} else {174		dev_err(bch->dev, "timed out while calculating ECC\n");175		ret = -ETIMEDOUT;176	}177 178	jz4780_bch_disable(bch);179	mutex_unlock(&bch->lock);180	return ret;181}182 183static int jz4780_correct(struct ingenic_ecc *bch,184			  struct ingenic_ecc_params *params,185			  u8 *buf, u8 *ecc_code)186{187	u32 reg, mask, index;188	int i, ret, count;189 190	mutex_lock(&bch->lock);191 192	jz4780_bch_reset(bch, params, false);193	jz4780_bch_write_data(bch, buf, params->size);194	jz4780_bch_write_data(bch, ecc_code, params->bytes);195 196	if (!jz4780_bch_wait_complete(bch, BCH_BHINT_DECF, &reg)) {197		dev_err(bch->dev, "timed out while correcting data\n");198		ret = -ETIMEDOUT;199		goto out;200	}201 202	if (reg & BCH_BHINT_UNCOR) {203		dev_warn(bch->dev, "uncorrectable ECC error\n");204		ret = -EBADMSG;205		goto out;206	}207 208	/* Correct any detected errors. */209	if (reg & BCH_BHINT_ERR) {210		count = (reg & BCH_BHINT_ERRC_MASK) >> BCH_BHINT_ERRC_SHIFT;211		ret = (reg & BCH_BHINT_TERRC_MASK) >> BCH_BHINT_TERRC_SHIFT;212 213		for (i = 0; i < count; i++) {214			reg = readl(bch->base + BCH_BHERR0 + (i * 4));215			mask = (reg & BCH_BHERR_MASK_MASK) >>216						BCH_BHERR_MASK_SHIFT;217			index = (reg & BCH_BHERR_INDEX_MASK) >>218						BCH_BHERR_INDEX_SHIFT;219			buf[(index * 2) + 0] ^= mask;220			buf[(index * 2) + 1] ^= mask >> 8;221		}222	} else {223		ret = 0;224	}225 226out:227	jz4780_bch_disable(bch);228	mutex_unlock(&bch->lock);229	return ret;230}231 232static int jz4780_bch_probe(struct platform_device *pdev)233{234	struct ingenic_ecc *bch;235	int ret;236 237	ret = ingenic_ecc_probe(pdev);238	if (ret)239		return ret;240 241	bch = platform_get_drvdata(pdev);242	clk_set_rate(bch->clk, BCH_CLK_RATE);243 244	return 0;245}246 247static const struct ingenic_ecc_ops jz4780_bch_ops = {248	.disable = jz4780_bch_disable,249	.calculate = jz4780_calculate,250	.correct = jz4780_correct,251};252 253static const struct of_device_id jz4780_bch_dt_match[] = {254	{ .compatible = "ingenic,jz4780-bch", .data = &jz4780_bch_ops },255	{},256};257MODULE_DEVICE_TABLE(of, jz4780_bch_dt_match);258 259static struct platform_driver jz4780_bch_driver = {260	.probe		= jz4780_bch_probe,261	.driver	= {262		.name	= "jz4780-bch",263		.of_match_table = jz4780_bch_dt_match,264	},265};266module_platform_driver(jz4780_bch_driver);267 268MODULE_AUTHOR("Alex Smith <alex@alex-smith.me.uk>");269MODULE_AUTHOR("Harvey Hunt <harveyhuntnexus@gmail.com>");270MODULE_DESCRIPTION("Ingenic JZ4780 BCH error correction driver");271MODULE_LICENSE("GPL v2");272