Microchip Azurite ZL3073x represents chip family providing DPLL and optionally PHC (PTP) functionality. The chips can be connected be connected over I2C or SPI bus. They have the following characteristics: * up to 5 separate DPLL units (channels) * 5 synthesizers * 10 input pins (references) * 10 outputs * 20 output pins (output pin pair shares one output) * Each reference and output can operate in either differential or single-ended mode (differential mode uses 2 pins) * Each output is connected to one of the synthesizers * Each synthesizer is driven by one of the DPLL unit The device uses 7-bit addresses and 8-bits values. It exposes 8-, 16-, 32- and 48-bits registers in address range <0x000,0x77F>. Due to 7bit addressing, the range is organized into pages of 128 bytes, with each page containing a page selector register at address 0x7F. For reading/writing multi-byte registers, the device supports bulk transfers. Add basic functionality to access device registers and probe functionality for both I2C and SPI cases. Signed-off-by: Ivan Vecera --- v12: * Using 'return dev_err_probe()' * Separate zl3073x_chip_info structures instead of array --- MAINTAINERS | 8 + drivers/Kconfig | 4 +- drivers/dpll/Kconfig | 6 + drivers/dpll/Makefile | 2 + drivers/dpll/zl3073x/Kconfig | 34 +++ drivers/dpll/zl3073x/Makefile | 10 + drivers/dpll/zl3073x/core.c | 458 ++++++++++++++++++++++++++++++++++ drivers/dpll/zl3073x/core.h | 52 ++++ drivers/dpll/zl3073x/i2c.c | 76 ++++++ drivers/dpll/zl3073x/regs.h | 75 ++++++ drivers/dpll/zl3073x/spi.c | 76 ++++++ 11 files changed, 799 insertions(+), 2 deletions(-) create mode 100644 drivers/dpll/zl3073x/Kconfig create mode 100644 drivers/dpll/zl3073x/Makefile create mode 100644 drivers/dpll/zl3073x/core.c create mode 100644 drivers/dpll/zl3073x/core.h create mode 100644 drivers/dpll/zl3073x/i2c.c create mode 100644 drivers/dpll/zl3073x/regs.h create mode 100644 drivers/dpll/zl3073x/spi.c diff --git a/MAINTAINERS b/MAINTAINERS index 5488a6fd31f59..b841cdb568306 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16479,6 +16479,14 @@ L: linux-wireless@vger.kernel.org S: Supported F: drivers/net/wireless/microchip/ +MICROCHIP ZL3073X DRIVER +M: Ivan Vecera +M: Prathosh Satish +L: netdev@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/dpll/microchip,zl30731.yaml +F: drivers/dpll/zl3073x/ + MICROSEMI MIPS SOCS M: Alexandre Belloni M: UNGLinuxDriver@microchip.com diff --git a/drivers/Kconfig b/drivers/Kconfig index 7c556c5ac4fdd..3d8c902c25610 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -77,6 +77,8 @@ source "drivers/pps/Kconfig" source "drivers/ptp/Kconfig" +source "drivers/dpll/Kconfig" + source "drivers/pinctrl/Kconfig" source "drivers/gpio/Kconfig" @@ -245,6 +247,4 @@ source "drivers/hte/Kconfig" source "drivers/cdx/Kconfig" -source "drivers/dpll/Kconfig" - endmenu diff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig index 20607ed542435..ade872c915ac6 100644 --- a/drivers/dpll/Kconfig +++ b/drivers/dpll/Kconfig @@ -3,5 +3,11 @@ # Generic DPLL drivers configuration # +menu "DPLL device support" + config DPLL bool + +source "drivers/dpll/zl3073x/Kconfig" + +endmenu diff --git a/drivers/dpll/Makefile b/drivers/dpll/Makefile index 2e5b278501105..9e7a3a3e592e8 100644 --- a/drivers/dpll/Makefile +++ b/drivers/dpll/Makefile @@ -7,3 +7,5 @@ obj-$(CONFIG_DPLL) += dpll.o dpll-y += dpll_core.o dpll-y += dpll_netlink.o dpll-y += dpll_nl.o + +obj-$(CONFIG_ZL3073X) += zl3073x/ diff --git a/drivers/dpll/zl3073x/Kconfig b/drivers/dpll/zl3073x/Kconfig new file mode 100644 index 0000000000000..217160df0f49a --- /dev/null +++ b/drivers/dpll/zl3073x/Kconfig @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config ZL3073X + tristate "Microchip Azurite DPLL/PTP/SyncE devices" + select DPLL + help + This driver supports Microchip Azurite DPLL/PTP/SyncE devices. + + To compile this driver as a module, choose M here. The module + will be called zl3073x. + +config ZL3073X_I2C + tristate "I2C bus implementation for Microchip Azurite devices" + depends on I2C && ZL3073X + select REGMAP_I2C + default m + help + This is I2C bus implementation for Microchip Azurite DPLL/PTP/SyncE + devices. + + To compile this driver as a module, choose M here: the module will + be called zl3073x_i2c. + +config ZL3073X_SPI + tristate "SPI bus implementation for Microchip Azurite devices" + depends on SPI && ZL3073X + select REGMAP_SPI + default m + help + This is SPI bus implementation for Microchip Azurite DPLL/PTP/SyncE + devices. + + To compile this driver as a module, choose M here: the module will + be called zl3073x_spi. diff --git a/drivers/dpll/zl3073x/Makefile b/drivers/dpll/zl3073x/Makefile new file mode 100644 index 0000000000000..8760f358f5447 --- /dev/null +++ b/drivers/dpll/zl3073x/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_ZL3073X) += zl3073x.o +zl3073x-objs := core.o + +obj-$(CONFIG_ZL3073X_I2C) += zl3073x_i2c.o +zl3073x_i2c-objs := i2c.o + +obj-$(CONFIG_ZL3073X_SPI) += zl3073x_spi.o +zl3073x_spi-objs := spi.o diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c new file mode 100644 index 0000000000000..e26b17ccbaaf2 --- /dev/null +++ b/drivers/dpll/zl3073x/core.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.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 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 + * 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_devm_alloc - allocates zl3073x device structure + * @dev: pointer to device structure + * + * Allocates zl3073x device structure as device resource. + * + * Return: pointer to zl3073x device on success, error pointer on error + */ +struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev) +{ + struct zl3073x_dev *zldev; + + zldev = devm_kzalloc(dev, sizeof(*zldev), GFP_KERNEL); + if (!zldev) + return ERR_PTR(-ENOMEM); + + zldev->dev = dev; + dev_set_drvdata(zldev->dev, zldev); + + return zldev; +} +EXPORT_SYMBOL_NS_GPL(zl3073x_devm_alloc, "ZL3073X"); + +/** + * 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)); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(zl3073x_dev_probe, "ZL3073X"); + +MODULE_AUTHOR("Ivan Vecera "); +MODULE_DESCRIPTION("Microchip ZL3073x core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h new file mode 100644 index 0000000000000..9093d13d7fd52 --- /dev/null +++ b/drivers/dpll/zl3073x/core.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_H +#define _ZL3073X_H + +#include + +struct device; +struct regmap; + +/** + * struct zl3073x_dev - zl3073x device + * @dev: pointer to device + * @regmap: regmap to access device registers + */ +struct zl3073x_dev { + struct device *dev; + struct regmap *regmap; +}; + +struct zl3073x_chip_info { + const u16 *ids; + size_t num_ids; + int num_channels; +}; + +extern const struct zl3073x_chip_info zl30731_chip_info; +extern const struct zl3073x_chip_info zl30732_chip_info; +extern const struct zl3073x_chip_info zl30733_chip_info; +extern const struct zl3073x_chip_info zl30734_chip_info; +extern const struct zl3073x_chip_info zl30735_chip_info; +extern const struct regmap_config zl3073x_regmap_config; + +struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev); +int zl3073x_dev_probe(struct zl3073x_dev *zldev, + const struct zl3073x_chip_info *chip_info); + +/********************** + * Registers operations + **********************/ + +int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask); +int zl3073x_read_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 *val); +int zl3073x_read_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 *val); +int zl3073x_read_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 *val); +int zl3073x_read_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 *val); +int zl3073x_write_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 val); +int zl3073x_write_u16(struct zl3073x_dev *zldev, unsigned int reg, u16 val); +int zl3073x_write_u32(struct zl3073x_dev *zldev, unsigned int reg, u32 val); +int zl3073x_write_u48(struct zl3073x_dev *zldev, unsigned int reg, u64 val); + +#endif /* _ZL3073X_H */ diff --git a/drivers/dpll/zl3073x/i2c.c b/drivers/dpll/zl3073x/i2c.c new file mode 100644 index 0000000000000..7bbfdd4ed8671 --- /dev/null +++ b/drivers/dpll/zl3073x/i2c.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "core.h" + +static int zl3073x_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct zl3073x_dev *zldev; + + zldev = zl3073x_devm_alloc(dev); + if (IS_ERR(zldev)) + return PTR_ERR(zldev); + + zldev->regmap = devm_regmap_init_i2c(client, &zl3073x_regmap_config); + if (IS_ERR(zldev->regmap)) + return dev_err_probe(dev, PTR_ERR(zldev->regmap), + "Failed to initialize regmap\n"); + + return zl3073x_dev_probe(zldev, i2c_get_match_data(client)); +} + +static const struct i2c_device_id zl3073x_i2c_id[] = { + { + .name = "zl30731", + .driver_data = (kernel_ulong_t)&zl30731_chip_info, + }, + { + .name = "zl30732", + .driver_data = (kernel_ulong_t)&zl30732_chip_info, + }, + { + .name = "zl30733", + .driver_data = (kernel_ulong_t)&zl30733_chip_info, + }, + { + .name = "zl30734", + .driver_data = (kernel_ulong_t)&zl30734_chip_info, + }, + { + .name = "zl30735", + .driver_data = (kernel_ulong_t)&zl30735_chip_info, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, zl3073x_i2c_id); + +static const struct of_device_id zl3073x_i2c_of_match[] = { + { .compatible = "microchip,zl30731", .data = &zl30731_chip_info }, + { .compatible = "microchip,zl30732", .data = &zl30732_chip_info }, + { .compatible = "microchip,zl30733", .data = &zl30733_chip_info }, + { .compatible = "microchip,zl30734", .data = &zl30734_chip_info }, + { .compatible = "microchip,zl30735", .data = &zl30735_chip_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, zl3073x_i2c_of_match); + +static struct i2c_driver zl3073x_i2c_driver = { + .driver = { + .name = "zl3073x-i2c", + .of_match_table = zl3073x_i2c_of_match, + }, + .probe = zl3073x_i2c_probe, + .id_table = zl3073x_i2c_id, +}; +module_i2c_driver(zl3073x_i2c_driver); + +MODULE_AUTHOR("Ivan Vecera "); +MODULE_DESCRIPTION("Microchip ZL3073x I2C driver"); +MODULE_IMPORT_NS("ZL3073X"); +MODULE_LICENSE("GPL"); diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h new file mode 100644 index 0000000000000..08bf595935ea1 --- /dev/null +++ b/drivers/dpll/zl3073x/regs.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_REGS_H +#define _ZL3073X_REGS_H + +#include +#include + +/* + * Register address structure: + * =========================== + * 25 19 18 16 15 7 6 0 + * +------------------------------------------+ + * | max_offset | size | page | page_offset | + * +------------------------------------------+ + * + * page_offset ... <0x00..0x7F> + * page .......... HW page number + * size .......... register byte size (1, 2, 4 or 6) + * max_offset .... maximal offset for indexed registers + * (for non-indexed regs max_offset == page_offset) + */ + +#define ZL_REG_OFFSET_MASK GENMASK(6, 0) +#define ZL_REG_PAGE_MASK GENMASK(15, 7) +#define ZL_REG_SIZE_MASK GENMASK(18, 16) +#define ZL_REG_MAX_OFFSET_MASK GENMASK(25, 19) +#define ZL_REG_ADDR_MASK GENMASK(15, 0) + +#define ZL_REG_OFFSET(_reg) FIELD_GET(ZL_REG_OFFSET_MASK, _reg) +#define ZL_REG_PAGE(_reg) FIELD_GET(ZL_REG_PAGE_MASK, _reg) +#define ZL_REG_MAX_OFFSET(_reg) FIELD_GET(ZL_REG_MAX_OFFSET_MASK, _reg) +#define ZL_REG_SIZE(_reg) FIELD_GET(ZL_REG_SIZE_MASK, _reg) +#define ZL_REG_ADDR(_reg) FIELD_GET(ZL_REG_ADDR_MASK, _reg) + +/** + * ZL_REG_IDX - define indexed register + * @_idx: index of register to access + * @_page: register page + * @_offset: register offset in page + * @_size: register byte size (1, 2, 4 or 6) + * @_items: number of register indices + * @_stride: stride between items in bytes + * + * All parameters except @_idx should be constant. + */ +#define ZL_REG_IDX(_idx, _page, _offset, _size, _items, _stride) \ + (FIELD_PREP(ZL_REG_OFFSET_MASK, \ + (_offset) + (_idx) * (_stride)) | \ + FIELD_PREP_CONST(ZL_REG_PAGE_MASK, _page) | \ + FIELD_PREP_CONST(ZL_REG_SIZE_MASK, _size) | \ + FIELD_PREP_CONST(ZL_REG_MAX_OFFSET_MASK, \ + (_offset) + ((_items) - 1) * (_stride))) + +/** + * ZL_REG - define simple (non-indexed) register + * @_page: register page + * @_offset: register offset in page + * @_size: register byte size (1, 2, 4 or 6) + * + * All parameters should be constant. + */ +#define ZL_REG(_page, _offset, _size) \ + ZL_REG_IDX(0, _page, _offset, _size, 1, 0) + +/************************** + * Register Page 0, General + **************************/ + +#define ZL_REG_ID ZL_REG(0, 0x01, 2) +#define ZL_REG_REVISION ZL_REG(0, 0x03, 2) +#define ZL_REG_FW_VER ZL_REG(0, 0x05, 2) +#define ZL_REG_CUSTOM_CONFIG_VER ZL_REG(0, 0x07, 4) + +#endif /* _ZL3073X_REGS_H */ diff --git a/drivers/dpll/zl3073x/spi.c b/drivers/dpll/zl3073x/spi.c new file mode 100644 index 0000000000000..af901b4d6dda0 --- /dev/null +++ b/drivers/dpll/zl3073x/spi.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "core.h" + +static int zl3073x_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct zl3073x_dev *zldev; + + zldev = zl3073x_devm_alloc(dev); + if (IS_ERR(zldev)) + return PTR_ERR(zldev); + + zldev->regmap = devm_regmap_init_spi(spi, &zl3073x_regmap_config); + if (IS_ERR(zldev->regmap)) + return dev_err_probe(dev, PTR_ERR(zldev->regmap), + "Failed to initialize regmap\n"); + + return zl3073x_dev_probe(zldev, spi_get_device_match_data(spi)); +} + +static const struct spi_device_id zl3073x_spi_id[] = { + { + .name = "zl30731", + .driver_data = (kernel_ulong_t)&zl30731_chip_info + }, + { + .name = "zl30732", + .driver_data = (kernel_ulong_t)&zl30732_chip_info, + }, + { + .name = "zl30733", + .driver_data = (kernel_ulong_t)&zl30733_chip_info, + }, + { + .name = "zl30734", + .driver_data = (kernel_ulong_t)&zl30734_chip_info, + }, + { + .name = "zl30735", + .driver_data = (kernel_ulong_t)&zl30735_chip_info, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, zl3073x_spi_id); + +static const struct of_device_id zl3073x_spi_of_match[] = { + { .compatible = "microchip,zl30731", .data = &zl30731_chip_info }, + { .compatible = "microchip,zl30732", .data = &zl30732_chip_info }, + { .compatible = "microchip,zl30733", .data = &zl30733_chip_info }, + { .compatible = "microchip,zl30734", .data = &zl30734_chip_info }, + { .compatible = "microchip,zl30735", .data = &zl30735_chip_info }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, zl3073x_spi_of_match); + +static struct spi_driver zl3073x_spi_driver = { + .driver = { + .name = "zl3073x-spi", + .of_match_table = zl3073x_spi_of_match, + }, + .probe = zl3073x_spi_probe, + .id_table = zl3073x_spi_id, +}; +module_spi_driver(zl3073x_spi_driver); + +MODULE_AUTHOR("Ivan Vecera "); +MODULE_DESCRIPTION("Microchip ZL3073x SPI driver"); +MODULE_IMPORT_NS("ZL3073X"); +MODULE_LICENSE("GPL"); -- 2.49.0