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, ®)) {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