/** * Register map access API - ENCX24J600 support * * Copyright 2015 Gridpoint * * Author: Jon Ringle <jringle@gridpoint.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include <linux/delay.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/module.h> #include <linux/netdevice.h> #include <linux/regmap.h> #include <linux/spi/spi.h> #include "encx24j600_hw.h" static inline bool is_bits_set(int value, int mask) { return (value & mask) == mask; } static int encx24j600_switch_bank(struct encx24j600_context *ctx, int bank) { int ret = 0; int bank_opcode = BANK_SELECT(bank); ret = spi_write(ctx->spi, &bank_opcode, 1); if (ret == 0) ctx->bank = bank; return ret; } static int encx24j600_cmdn(struct encx24j600_context *ctx, u8 opcode, const void *buf, size_t len) { struct spi_message m; struct spi_transfer t[2] = { { .tx_buf = &opcode, .len = 1, }, { .tx_buf = buf, .len = len }, }; spi_message_init(&m); spi_message_add_tail(&t[0], &m); spi_message_add_tail(&t[1], &m); return spi_sync(ctx->spi, &m); } static void regmap_lock_mutex(void *context) { struct encx24j600_context *ctx = context; mutex_lock(&ctx->mutex); } static void regmap_unlock_mutex(void *context) { struct encx24j600_context *ctx = context; mutex_unlock(&ctx->mutex); } static int regmap_encx24j600_sfr_read(void *context, u8 reg, u8 *val, size_t len) { struct encx24j600_context *ctx = context; u8 banked_reg = reg & ADDR_MASK; u8 bank = ((reg & BANK_MASK) >> BANK_SHIFT); u8 cmd = RCRU; int ret = 0; int i = 0; u8 tx_buf[2]; if (reg < 0x80) { cmd = RCRCODE | banked_reg; if ((banked_reg < 0x16) && (ctx->bank != bank)) ret = encx24j600_switch_bank(ctx, bank); if (unlikely(ret)) return ret; } else { /* Translate registers that are more effecient using * 3-byte SPI commands */ switch (reg) { case EGPRDPT: cmd = RGPRDPT; break; case EGPWRPT: cmd = RGPWRPT; break; case ERXRDPT: cmd = RRXRDPT; break; case ERXWRPT: cmd = RRXWRPT; break; case EUDARDPT: cmd = RUDARDPT; break; case EUDAWRPT: cmd = RUDAWRPT; break; case EGPDATA: case ERXDATA: case EUDADATA: default: return -EINVAL; } } tx_buf[i++] = cmd; if (cmd == RCRU) tx_buf[i++] = reg; ret = spi_write_then_read(ctx->spi, tx_buf, i, val, len); return ret; } static int regmap_encx24j600_sfr_update(struct encx24j600_context *ctx, u8 reg, u8 *val, size_t len, u8 unbanked_cmd, u8 banked_code) { u8 banked_reg = reg & ADDR_MASK; u8 bank = ((reg & BANK_MASK) >> BANK_SHIFT); u8 cmd = unbanked_cmd; struct spi_message m; struct spi_transfer t[3] = { { .tx_buf = &cmd, .len = sizeof(cmd), }, { .tx_buf = ®, .len = sizeof(reg), }, { .tx_buf = val, .len = len }, }; if (reg < 0x80) { int ret = 0; cmd = banked_code | banked_reg; if ((banked_reg < 0x16) && (ctx->bank != bank)) ret = encx24j600_switch_bank(ctx, bank); if (unlikely(ret)) return ret; } else { /* Translate registers that are more effecient using * 3-byte SPI commands */ switch (reg) { case EGPRDPT: cmd = WGPRDPT; break; case EGPWRPT: cmd = WGPWRPT; break; case ERXRDPT: cmd = WRXRDPT; break; case ERXWRPT: cmd = WRXWRPT; break; case EUDARDPT: cmd = WUDARDPT; break; case EUDAWRPT: cmd = WUDAWRPT; break; case EGPDATA: case ERXDATA: case EUDADATA: default: return -EINVAL; } } spi_message_init(&m); spi_message_add_tail(&t[0], &m); if (cmd == unbanked_cmd) { t[1].tx_buf = ® spi_message_add_tail(&t[1], &m); } spi_message_add_tail(&t[2], &m); return spi_sync(ctx->spi, &m); } static int regmap_encx24j600_sfr_write(void *context, u8 reg, u8 *val, size_t len) { struct encx24j600_context *ctx = context; return regmap_encx24j600_sfr_update(ctx, reg, val, len, WCRU, WCRCODE); } static int regmap_encx24j600_sfr_set_bits(struct encx24j600_context *ctx, u8 reg, u8 val) { return regmap_encx24j600_sfr_update(ctx, reg, &val, 1, BFSU, BFSCODE); } static int regmap_encx24j600_sfr_clr_bits(struct encx24j600_context *ctx, u8 reg, u8 val) { return regmap_encx24j600_sfr_update(ctx, reg, &val, 1, BFCU, BFCCODE); } static int regmap_encx24j600_reg_update_bits(void *context, unsigned int reg, unsigned int mask, unsigned int val) { struct encx24j600_context *ctx = context; int ret = 0; unsigned int set_mask = mask & val; unsigned int clr_mask = mask & ~val; if ((reg >= 0x40 && reg < 0x6c) || reg >= 0x80) return -EINVAL; if (set_mask & 0xff) ret = regmap_encx24j600_sfr_set_bits(ctx, reg, set_mask); set_mask = (set_mask & 0xff00) >> 8; if ((set_mask & 0xff) && (ret == 0)) ret = regmap_encx24j600_sfr_set_bits(ctx, reg + 1, set_mask); if ((clr_mask & 0xff) && (ret == 0)) ret = regmap_encx24j600_sfr_clr_bits(ctx, reg, clr_mask); clr_mask = (clr_mask & 0xff00) >> 8; if ((clr_mask & 0xff) && (ret == 0)) ret = regmap_encx24j600_sfr_clr_bits(ctx, reg + 1, clr_mask); return ret; } int regmap_encx24j600_spi_write(void *context, u8 reg, const u8 *data, size_t count) { struct encx24j600_context *ctx = context; if (reg < 0xc0) return encx24j600_cmdn(ctx, reg, data, count); else /* SPI 1-byte command. Ignore data */ return spi_write(ctx->spi, ®, 1); } EXPORT_SYMBOL_GPL(regmap_encx24j600_spi_write); int regmap_encx24j600_spi_read(void *context, u8 reg, u8 *data, size_t count) { struct encx24j600_context *ctx = context; if (reg == RBSEL && count > 1) count = 1; return spi_write_then_read(ctx->spi, ®, sizeof(reg), data, count); } EXPORT_SYMBOL_GPL(regmap_encx24j600_spi_read); static int regmap_encx24j600_write(void *context, const void *data, size_t len) { u8 *dout = (u8 *)data; u8 reg = dout[0]; ++dout; --len; if (reg > 0xa0) return regmap_encx24j600_spi_write(context, reg, dout, len); if (len > 2) return -EINVAL; return regmap_encx24j600_sfr_write(context, reg, dout, len); } static int regmap_encx24j600_read(void *context, const void *reg_buf, size_t reg_size, void *val, size_t val_size) { u8 reg = *(const u8 *)reg_buf; if (reg_size != 1) { pr_err("%s: reg=%02x reg_size=%zu\n", __func__, reg, reg_size); return -EINVAL; } if (reg > 0xa0) return regmap_encx24j600_spi_read(context, reg, val, val_size); if (val_size > 2) { pr_err("%s: reg=%02x val_size=%zu\n", __func__, reg, val_size); return -EINVAL; } return regmap_encx24j600_sfr_read(context, reg, val, val_size); } static bool encx24j600_regmap_readable(struct device *dev, unsigned int reg) { if ((reg < 0x36) || ((reg >= 0x40) && (reg < 0x4c)) || ((reg >= 0x52) && (reg < 0x56)) || ((reg >= 0x60) && (reg < 0x66)) || ((reg >= 0x68) && (reg < 0x80)) || ((reg >= 0x86) && (reg < 0x92)) || (reg == 0xc8)) return true; else return false; } static bool encx24j600_regmap_writeable(struct device *dev, unsigned int reg) { if ((reg < 0x12) || ((reg >= 0x14) && (reg < 0x1a)) || ((reg >= 0x1c) && (reg < 0x36)) || ((reg >= 0x40) && (reg < 0x4c)) || ((reg >= 0x52) && (reg < 0x56)) || ((reg >= 0x60) && (reg < 0x68)) || ((reg >= 0x6c) && (reg < 0x80)) || ((reg >= 0x86) && (reg < 0x92)) || ((reg >= 0xc0) && (reg < 0xc8)) || ((reg >= 0xca) && (reg < 0xf0))) return true; else return false; } static bool encx24j600_regmap_volatile(struct device *dev, unsigned int reg) { switch (reg) { case ERXHEAD: case EDMACS: case ETXSTAT: case ETXWIRE: case ECON1: /* Can be modified via single byte cmds */ case ECON2: /* Can be modified via single byte cmds */ case ESTAT: case EIR: /* Can be modified via single byte cmds */ case MIRD: case MISTAT: return true; default: break; } return false; } static bool encx24j600_regmap_precious(struct device *dev, unsigned int reg) { /* single byte cmds are precious */ if (((reg >= 0xc0) && (reg < 0xc8)) || ((reg >= 0xca) && (reg < 0xf0))) return true; else return false; } static int regmap_encx24j600_phy_reg_read(void *context, unsigned int reg, unsigned int *val) { struct encx24j600_context *ctx = context; int ret; unsigned int mistat; reg = MIREGADR_VAL | (reg & PHREG_MASK); ret = regmap_write(ctx->regmap, MIREGADR, reg); if (unlikely(ret)) goto err_out; ret = regmap_write(ctx->regmap, MICMD, MIIRD); if (unlikely(ret)) goto err_out; usleep_range(26, 100); while ((ret = regmap_read(ctx->regmap, MISTAT, &mistat) != 0) && (mistat & BUSY)) cpu_relax(); if (unlikely(ret)) goto err_out; ret = regmap_write(ctx->regmap, MICMD, 0); if (unlikely(ret)) goto err_out; ret = regmap_read(ctx->regmap, MIRD, val); err_out: if (ret) pr_err("%s: error %d reading reg %02x\n", __func__, ret, reg & PHREG_MASK); return ret; } static int regmap_encx24j600_phy_reg_write(void *context, unsigned int reg, unsigned int val) { struct encx24j600_context *ctx = context; int ret; unsigned int mistat; reg = MIREGADR_VAL | (reg & PHREG_MASK); ret = regmap_write(ctx->regmap, MIREGADR, reg); if (unlikely(ret)) goto err_out; ret = regmap_write(ctx->regmap, MIWR, val); if (unlikely(ret)) goto err_out; usleep_range(26, 100); while ((ret = regmap_read(ctx->regmap, MISTAT, &mistat) != 0) && (mistat & BUSY)) cpu_relax(); err_out: if (ret) pr_err("%s: error %d writing reg %02x=%04x\n", __func__, ret, reg & PHREG_MASK, val); return ret; } static bool encx24j600_phymap_readable(struct device *dev, unsigned int reg) { switch (reg) { case PHCON1: case PHSTAT1: case PHANA: case PHANLPA: case PHANE: case PHCON2: case PHSTAT2: case PHSTAT3: return true; default: return false; } } static bool encx24j600_phymap_writeable(struct device *dev, unsigned int reg) { switch (reg) { case PHCON1: case PHCON2: case PHANA: return true; case PHSTAT1: case PHSTAT2: case PHSTAT3: case PHANLPA: case PHANE: default: return false; } } static bool encx24j600_phymap_volatile(struct device *dev, unsigned int reg) { switch (reg) { case PHSTAT1: case PHSTAT2: case PHSTAT3: case PHANLPA: case PHANE: case PHCON2: return true; default: return false; } } static struct regmap_config regcfg = { .name = "reg", .reg_bits = 8, .val_bits = 16, .max_register = 0xee, .reg_stride = 2, .cache_type = REGCACHE_RBTREE, .val_format_endian = REGMAP_ENDIAN_LITTLE, .readable_reg = encx24j600_regmap_readable, .writeable_reg = encx24j600_regmap_writeable, .volatile_reg = encx24j600_regmap_volatile, .precious_reg = encx24j600_regmap_precious, .lock = regmap_lock_mutex, .unlock = regmap_unlock_mutex, }; static struct regmap_bus regmap_encx24j600 = { .write = regmap_encx24j600_write, .read = regmap_encx24j600_read, .reg_update_bits = regmap_encx24j600_reg_update_bits, }; static struct regmap_config phycfg = { .name = "phy", .reg_bits = 8, .val_bits = 16, .max_register = 0x1f, .cache_type = REGCACHE_RBTREE, .val_format_endian = REGMAP_ENDIAN_LITTLE, .readable_reg = encx24j600_phymap_readable, .writeable_reg = encx24j600_phymap_writeable, .volatile_reg = encx24j600_phymap_volatile, }; static struct regmap_bus phymap_encx24j600 = { .reg_write = regmap_encx24j600_phy_reg_write, .reg_read = regmap_encx24j600_phy_reg_read, }; void devm_regmap_init_encx24j600(struct device *dev, struct encx24j600_context *ctx) { mutex_init(&ctx->mutex); regcfg.lock_arg = ctx; ctx->regmap = devm_regmap_init(dev, ®map_encx24j600, ctx, ®cfg); ctx->phymap = devm_regmap_init(dev, &phymap_encx24j600, ctx, &phycfg); } EXPORT_SYMBOL_GPL(devm_regmap_init_encx24j600); MODULE_LICENSE("GPL");