// 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 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, 
 | 
}; 
 | 
  
 | 
int 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); 
 | 
    if (IS_ERR(ctx->regmap)) 
 | 
        return PTR_ERR(ctx->regmap); 
 | 
    ctx->phymap = devm_regmap_init(dev, &phymap_encx24j600, ctx, &phycfg); 
 | 
    if (IS_ERR(ctx->phymap)) 
 | 
        return PTR_ERR(ctx->phymap); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
EXPORT_SYMBOL_GPL(devm_regmap_init_encx24j600); 
 | 
  
 | 
MODULE_LICENSE("GPL"); 
 |