mirror of https://github.com/torvalds/linux.git
466 lines
11 KiB
C
466 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include <linux/array_size.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/bits.h>
|
|
#include <linux/dev_printk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/export.h>
|
|
#include <linux/module.h>
|
|
#include <linux/netlink.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/sprintf.h>
|
|
#include <linux/unaligned.h>
|
|
#include <net/devlink.h>
|
|
|
|
#include "core.h"
|
|
#include "devlink.h"
|
|
#include "regs.h"
|
|
|
|
/* Chip IDs for zl30731 */
|
|
static const u16 zl30731_ids[] = {
|
|
0x0E93,
|
|
0x1E93,
|
|
0x2E93,
|
|
};
|
|
|
|
const struct zl3073x_chip_info zl30731_chip_info = {
|
|
.ids = zl30731_ids,
|
|
.num_ids = ARRAY_SIZE(zl30731_ids),
|
|
.num_channels = 1,
|
|
};
|
|
EXPORT_SYMBOL_NS_GPL(zl30731_chip_info, "ZL3073X");
|
|
|
|
/* Chip IDs for zl30732 */
|
|
static const u16 zl30732_ids[] = {
|
|
0x0E30,
|
|
0x0E94,
|
|
0x1E94,
|
|
0x1F60,
|
|
0x2E94,
|
|
0x3FC4,
|
|
};
|
|
|
|
const struct zl3073x_chip_info zl30732_chip_info = {
|
|
.ids = zl30732_ids,
|
|
.num_ids = ARRAY_SIZE(zl30732_ids),
|
|
.num_channels = 2,
|
|
};
|
|
EXPORT_SYMBOL_NS_GPL(zl30732_chip_info, "ZL3073X");
|
|
|
|
/* Chip IDs for zl30733 */
|
|
static const u16 zl30733_ids[] = {
|
|
0x0E95,
|
|
0x1E95,
|
|
0x2E95,
|
|
};
|
|
|
|
const struct zl3073x_chip_info zl30733_chip_info = {
|
|
.ids = zl30733_ids,
|
|
.num_ids = ARRAY_SIZE(zl30733_ids),
|
|
.num_channels = 3,
|
|
};
|
|
EXPORT_SYMBOL_NS_GPL(zl30733_chip_info, "ZL3073X");
|
|
|
|
/* Chip IDs for zl30734 */
|
|
static const u16 zl30734_ids[] = {
|
|
0x0E96,
|
|
0x1E96,
|
|
0x2E96,
|
|
};
|
|
|
|
const struct zl3073x_chip_info zl30734_chip_info = {
|
|
.ids = zl30734_ids,
|
|
.num_ids = ARRAY_SIZE(zl30734_ids),
|
|
.num_channels = 4,
|
|
};
|
|
EXPORT_SYMBOL_NS_GPL(zl30734_chip_info, "ZL3073X");
|
|
|
|
/* Chip IDs for zl30735 */
|
|
static const u16 zl30735_ids[] = {
|
|
0x0E97,
|
|
0x1E97,
|
|
0x2E97,
|
|
};
|
|
|
|
const struct zl3073x_chip_info zl30735_chip_info = {
|
|
.ids = zl30735_ids,
|
|
.num_ids = ARRAY_SIZE(zl30735_ids),
|
|
.num_channels = 5,
|
|
};
|
|
EXPORT_SYMBOL_NS_GPL(zl30735_chip_info, "ZL3073X");
|
|
|
|
#define ZL_RANGE_OFFSET 0x80
|
|
#define ZL_PAGE_SIZE 0x80
|
|
#define ZL_NUM_PAGES 15
|
|
#define ZL_PAGE_SEL 0x7F
|
|
#define ZL_PAGE_SEL_MASK GENMASK(3, 0)
|
|
#define ZL_NUM_REGS (ZL_NUM_PAGES * ZL_PAGE_SIZE)
|
|
|
|
/* Regmap range configuration */
|
|
static const struct regmap_range_cfg zl3073x_regmap_range = {
|
|
.range_min = ZL_RANGE_OFFSET,
|
|
.range_max = ZL_RANGE_OFFSET + ZL_NUM_REGS - 1,
|
|
.selector_reg = ZL_PAGE_SEL,
|
|
.selector_mask = ZL_PAGE_SEL_MASK,
|
|
.selector_shift = 0,
|
|
.window_start = 0,
|
|
.window_len = ZL_PAGE_SIZE,
|
|
};
|
|
|
|
static bool
|
|
zl3073x_is_volatile_reg(struct device *dev __maybe_unused, unsigned int reg)
|
|
{
|
|
/* Only page selector is non-volatile */
|
|
return reg != ZL_PAGE_SEL;
|
|
}
|
|
|
|
const struct regmap_config zl3073x_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
.max_register = ZL_RANGE_OFFSET + ZL_NUM_REGS - 1,
|
|
.ranges = &zl3073x_regmap_range,
|
|
.num_ranges = 1,
|
|
.cache_type = REGCACHE_MAPLE,
|
|
.volatile_reg = zl3073x_is_volatile_reg,
|
|
};
|
|
EXPORT_SYMBOL_NS_GPL(zl3073x_regmap_config, "ZL3073X");
|
|
|
|
static bool
|
|
zl3073x_check_reg(struct zl3073x_dev *zldev, unsigned int reg, size_t size)
|
|
{
|
|
/* Check that multiop lock is held when accessing registers
|
|
* from page 10 and above.
|
|
*/
|
|
if (ZL_REG_PAGE(reg) >= 10)
|
|
lockdep_assert_held(&zldev->multiop_lock);
|
|
|
|
/* Check the index is in valid range for indexed register */
|
|
if (ZL_REG_OFFSET(reg) > ZL_REG_MAX_OFFSET(reg)) {
|
|
dev_err(zldev->dev, "Index out of range for reg 0x%04lx\n",
|
|
ZL_REG_ADDR(reg));
|
|
return false;
|
|
}
|
|
/* Check the requested size corresponds to register size */
|
|
if (ZL_REG_SIZE(reg) != size) {
|
|
dev_err(zldev->dev, "Invalid size %zu for reg 0x%04lx\n",
|
|
size, ZL_REG_ADDR(reg));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
zl3073x_read_reg(struct zl3073x_dev *zldev, unsigned int reg, void *val,
|
|
size_t size)
|
|
{
|
|
int rc;
|
|
|
|
if (!zl3073x_check_reg(zldev, reg, size))
|
|
return -EINVAL;
|
|
|
|
/* Map the register address to virtual range */
|
|
reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
|
|
|
|
rc = regmap_bulk_read(zldev->regmap, reg, val, size);
|
|
if (rc) {
|
|
dev_err(zldev->dev, "Failed to read reg 0x%04x: %pe\n", reg,
|
|
ERR_PTR(rc));
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
zl3073x_write_reg(struct zl3073x_dev *zldev, unsigned int reg, const void *val,
|
|
size_t size)
|
|
{
|
|
int rc;
|
|
|
|
if (!zl3073x_check_reg(zldev, reg, size))
|
|
return -EINVAL;
|
|
|
|
/* Map the register address to virtual range */
|
|
reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
|
|
|
|
rc = regmap_bulk_write(zldev->regmap, reg, val, size);
|
|
if (rc) {
|
|
dev_err(zldev->dev, "Failed to write reg 0x%04x: %pe\n", reg,
|
|
ERR_PTR(rc));
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* zl3073x_read_u8 - read value from 8bit register
|
|
* @zldev: zl3073x device pointer
|
|
* @reg: register to write to
|
|
* @val: value to write
|
|
*
|
|
* Reads value from given 8bit register.
|
|
*
|
|
* Returns: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val)
|
|
{
|
|
return zl3073x_read_reg(zldev, reg, val, sizeof(*val));
|
|
}
|
|
|
|
/**
|
|
* zl3073x_write_u8 - write value to 16bit register
|
|
* @zldev: zl3073x device pointer
|
|
* @reg: register to write to
|
|
* @val: value to write
|
|
*
|
|
* Writes value into given 8bit register.
|
|
*
|
|
* Returns: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_write_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 val)
|
|
{
|
|
return zl3073x_write_reg(zldev, reg, &val, sizeof(val));
|
|
}
|
|
|
|
/**
|
|
* zl3073x_read_u16 - read value from 16bit register
|
|
* @zldev: zl3073x device pointer
|
|
* @reg: register to write to
|
|
* @val: value to write
|
|
*
|
|
* Reads value from given 16bit register.
|
|
*
|
|
* Returns: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val)
|
|
{
|
|
int rc;
|
|
|
|
rc = zl3073x_read_reg(zldev, reg, val, sizeof(*val));
|
|
if (!rc)
|
|
be16_to_cpus(val);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* zl3073x_write_u16 - write value to 16bit register
|
|
* @zldev: zl3073x device pointer
|
|
* @reg: register to write to
|
|
* @val: value to write
|
|
*
|
|
* Writes value into given 16bit register.
|
|
*
|
|
* Returns: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_write_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 val)
|
|
{
|
|
cpu_to_be16s(&val);
|
|
|
|
return zl3073x_write_reg(zldev, reg, &val, sizeof(val));
|
|
}
|
|
|
|
/**
|
|
* zl3073x_read_u32 - read value from 32bit register
|
|
* @zldev: zl3073x device pointer
|
|
* @reg: register to write to
|
|
* @val: value to write
|
|
*
|
|
* Reads value from given 32bit register.
|
|
*
|
|
* Returns: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val)
|
|
{
|
|
int rc;
|
|
|
|
rc = zl3073x_read_reg(zldev, reg, val, sizeof(*val));
|
|
if (!rc)
|
|
be32_to_cpus(val);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* zl3073x_write_u32 - write value to 32bit register
|
|
* @zldev: zl3073x device pointer
|
|
* @reg: register to write to
|
|
* @val: value to write
|
|
*
|
|
* Writes value into given 32bit register.
|
|
*
|
|
* Returns: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_write_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 val)
|
|
{
|
|
cpu_to_be32s(&val);
|
|
|
|
return zl3073x_write_reg(zldev, reg, &val, sizeof(val));
|
|
}
|
|
|
|
/**
|
|
* zl3073x_read_u48 - read value from 48bit register
|
|
* @zldev: zl3073x device pointer
|
|
* @reg: register to write to
|
|
* @val: value to write
|
|
*
|
|
* Reads value from given 48bit register.
|
|
*
|
|
* Returns: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_read_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 *val)
|
|
{
|
|
u8 buf[6];
|
|
int rc;
|
|
|
|
rc = zl3073x_read_reg(zldev, reg, buf, sizeof(buf));
|
|
if (!rc)
|
|
*val = get_unaligned_be48(buf);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* zl3073x_write_u48 - write value to 48bit register
|
|
* @zldev: zl3073x device pointer
|
|
* @reg: register to write to
|
|
* @val: value to write
|
|
*
|
|
* Writes value into given 48bit register.
|
|
* The value must be from the interval -S48_MIN to U48_MAX.
|
|
*
|
|
* Returns: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val)
|
|
{
|
|
u8 buf[6];
|
|
|
|
/* Check the value belongs to <S48_MIN, U48_MAX>
|
|
* Any value >= S48_MIN has bits 47..63 set.
|
|
*/
|
|
if (val > GENMASK_ULL(47, 0) && val < GENMASK_ULL(63, 47)) {
|
|
dev_err(zldev->dev, "Value 0x%0llx out of range\n", val);
|
|
return -EINVAL;
|
|
}
|
|
|
|
put_unaligned_be48(val, buf);
|
|
|
|
return zl3073x_write_reg(zldev, reg, buf, sizeof(buf));
|
|
}
|
|
|
|
/**
|
|
* zl3073x_poll_zero_u8 - wait for register to be cleared by device
|
|
* @zldev: zl3073x device pointer
|
|
* @reg: register to poll (has to be 8bit register)
|
|
* @mask: bit mask for polling
|
|
*
|
|
* Waits for bits specified by @mask in register @reg value to be cleared
|
|
* by the device.
|
|
*
|
|
* Returns: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask)
|
|
{
|
|
/* Register polling sleep & timeout */
|
|
#define ZL_POLL_SLEEP_US 10
|
|
#define ZL_POLL_TIMEOUT_US 2000000
|
|
unsigned int val;
|
|
|
|
/* Check the register is 8bit */
|
|
if (ZL_REG_SIZE(reg) != 1) {
|
|
dev_err(zldev->dev, "Invalid reg 0x%04lx size for polling\n",
|
|
ZL_REG_ADDR(reg));
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Map the register address to virtual range */
|
|
reg = ZL_REG_ADDR(reg) + ZL_RANGE_OFFSET;
|
|
|
|
return regmap_read_poll_timeout(zldev->regmap, reg, val, !(val & mask),
|
|
ZL_POLL_SLEEP_US, ZL_POLL_TIMEOUT_US);
|
|
}
|
|
|
|
/**
|
|
* zl3073x_dev_probe - initialize zl3073x device
|
|
* @zldev: pointer to zl3073x device
|
|
* @chip_info: chip info based on compatible
|
|
*
|
|
* Common initialization of zl3073x device structure.
|
|
*
|
|
* Returns: 0 on success, <0 on error
|
|
*/
|
|
int zl3073x_dev_probe(struct zl3073x_dev *zldev,
|
|
const struct zl3073x_chip_info *chip_info)
|
|
{
|
|
u16 id, revision, fw_ver;
|
|
unsigned int i;
|
|
u32 cfg_ver;
|
|
int rc;
|
|
|
|
/* Read chip ID */
|
|
rc = zl3073x_read_u16(zldev, ZL_REG_ID, &id);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Check it matches */
|
|
for (i = 0; i < chip_info->num_ids; i++) {
|
|
if (id == chip_info->ids[i])
|
|
break;
|
|
}
|
|
|
|
if (i == chip_info->num_ids) {
|
|
return dev_err_probe(zldev->dev, -ENODEV,
|
|
"Unknown or non-match chip ID: 0x%0x\n",
|
|
id);
|
|
}
|
|
|
|
/* Read revision, firmware version and custom config version */
|
|
rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision);
|
|
if (rc)
|
|
return rc;
|
|
rc = zl3073x_read_u16(zldev, ZL_REG_FW_VER, &fw_ver);
|
|
if (rc)
|
|
return rc;
|
|
rc = zl3073x_read_u32(zldev, ZL_REG_CUSTOM_CONFIG_VER, &cfg_ver);
|
|
if (rc)
|
|
return rc;
|
|
|
|
dev_dbg(zldev->dev, "ChipID(%X), ChipRev(%X), FwVer(%u)\n", id,
|
|
revision, fw_ver);
|
|
dev_dbg(zldev->dev, "Custom config version: %lu.%lu.%lu.%lu\n",
|
|
FIELD_GET(GENMASK(31, 24), cfg_ver),
|
|
FIELD_GET(GENMASK(23, 16), cfg_ver),
|
|
FIELD_GET(GENMASK(15, 8), cfg_ver),
|
|
FIELD_GET(GENMASK(7, 0), cfg_ver));
|
|
|
|
/* Generate random clock ID as the device has not such property that
|
|
* could be used for this purpose. A user can later change this value
|
|
* using devlink.
|
|
*/
|
|
zldev->clock_id = get_random_u64();
|
|
|
|
/* Initialize mutex for operations where multiple reads, writes
|
|
* and/or polls are required to be done atomically.
|
|
*/
|
|
rc = devm_mutex_init(zldev->dev, &zldev->multiop_lock);
|
|
if (rc)
|
|
return dev_err_probe(zldev->dev, rc,
|
|
"Failed to initialize mutex\n");
|
|
|
|
/* Register the devlink instance and parameters */
|
|
rc = zl3073x_devlink_register(zldev);
|
|
if (rc)
|
|
return dev_err_probe(zldev->dev, rc,
|
|
"Failed to register devlink instance\n");
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(zl3073x_dev_probe, "ZL3073X");
|
|
|
|
MODULE_AUTHOR("Ivan Vecera <ivecera@redhat.com>");
|
|
MODULE_DESCRIPTION("Microchip ZL3073x core driver");
|
|
MODULE_LICENSE("GPL");
|