| // SPDX-License-Identifier: GPL-2.0-only |
| /** |
| * Register map access API - ENCX24J600 support |
| * |
| * Copyright 2015 Gridpoint |
| * |
| * Author: Jon Ringle <jringle@gridpoint.com> |
| */ |
| |
| #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); |
| |
| /* 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"); |