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