| /** | |
| * 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"); |