diff options
Diffstat (limited to 'drivers/dpll')
-rw-r--r-- | drivers/dpll/dpll_netlink.c | 70 | ||||
-rw-r--r-- | drivers/dpll/dpll_nl.c | 5 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/Makefile | 2 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/core.c | 392 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/core.h | 48 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/devlink.c | 155 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/devlink.h | 3 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/dpll.c | 58 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/dpll.h | 2 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/flash.c | 666 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/flash.h | 29 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/fw.c | 419 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/fw.h | 52 | ||||
-rw-r--r-- | drivers/dpll/zl3073x/regs.h | 51 |
14 files changed, 1848 insertions, 104 deletions
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 036f21cac0a9..74c1f0ca95f2 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -165,6 +165,27 @@ dpll_msg_add_phase_offset_monitor(struct sk_buff *msg, struct dpll_device *dpll, } static int +dpll_msg_add_phase_offset_avg_factor(struct sk_buff *msg, + struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + const struct dpll_device_ops *ops = dpll_device_ops(dpll); + u32 factor; + int ret; + + if (ops->phase_offset_avg_factor_get) { + ret = ops->phase_offset_avg_factor_get(dpll, dpll_priv(dpll), + &factor, extack); + if (ret) + return ret; + if (nla_put_u32(msg, DPLL_A_PHASE_OFFSET_AVG_FACTOR, factor)) + return -EMSGSIZE; + } + + return 0; +} + +static int dpll_msg_add_lock_status(struct sk_buff *msg, struct dpll_device *dpll, struct netlink_ext_ack *extack) { @@ -211,8 +232,8 @@ static int dpll_msg_add_clock_quality_level(struct sk_buff *msg, struct dpll_device *dpll, struct netlink_ext_ack *extack) { + DECLARE_BITMAP(qls, DPLL_CLOCK_QUALITY_LEVEL_MAX + 1) = { 0 }; const struct dpll_device_ops *ops = dpll_device_ops(dpll); - DECLARE_BITMAP(qls, DPLL_CLOCK_QUALITY_LEVEL_MAX) = { 0 }; enum dpll_clock_quality_level ql; int ret; @@ -221,7 +242,7 @@ dpll_msg_add_clock_quality_level(struct sk_buff *msg, struct dpll_device *dpll, ret = ops->clock_quality_level_get(dpll, dpll_priv(dpll), qls, extack); if (ret) return ret; - for_each_set_bit(ql, qls, DPLL_CLOCK_QUALITY_LEVEL_MAX) + for_each_set_bit(ql, qls, DPLL_CLOCK_QUALITY_LEVEL_MAX + 1) if (nla_put_u32(msg, DPLL_A_CLOCK_QUALITY_LEVEL, ql)) return -EMSGSIZE; @@ -677,6 +698,9 @@ dpll_device_get_one(struct dpll_device *dpll, struct sk_buff *msg, ret = dpll_msg_add_phase_offset_monitor(msg, dpll, extack); if (ret) return ret; + ret = dpll_msg_add_phase_offset_avg_factor(msg, dpll, extack); + if (ret) + return ret; return 0; } @@ -840,6 +864,23 @@ dpll_phase_offset_monitor_set(struct dpll_device *dpll, struct nlattr *a, } static int +dpll_phase_offset_avg_factor_set(struct dpll_device *dpll, struct nlattr *a, + struct netlink_ext_ack *extack) +{ + const struct dpll_device_ops *ops = dpll_device_ops(dpll); + u32 factor = nla_get_u32(a); + + if (!ops->phase_offset_avg_factor_set) { + NL_SET_ERR_MSG_ATTR(extack, a, + "device not capable of changing phase offset average factor"); + return -EOPNOTSUPP; + } + + return ops->phase_offset_avg_factor_set(dpll, dpll_priv(dpll), factor, + extack); +} + +static int dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, struct netlink_ext_ack *extack) { @@ -1736,14 +1777,25 @@ int dpll_nl_device_get_doit(struct sk_buff *skb, struct genl_info *info) static int dpll_set_from_nlattr(struct dpll_device *dpll, struct genl_info *info) { - int ret; - - if (info->attrs[DPLL_A_PHASE_OFFSET_MONITOR]) { - struct nlattr *a = info->attrs[DPLL_A_PHASE_OFFSET_MONITOR]; + struct nlattr *a; + int rem, ret; - ret = dpll_phase_offset_monitor_set(dpll, a, info->extack); - if (ret) - return ret; + nla_for_each_attr(a, genlmsg_data(info->genlhdr), + genlmsg_len(info->genlhdr), rem) { + switch (nla_type(a)) { + case DPLL_A_PHASE_OFFSET_MONITOR: + ret = dpll_phase_offset_monitor_set(dpll, a, + info->extack); + if (ret) + return ret; + break; + case DPLL_A_PHASE_OFFSET_AVG_FACTOR: + ret = dpll_phase_offset_avg_factor_set(dpll, a, + info->extack); + if (ret) + return ret; + break; + } } return 0; diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index 9f2efaf25268..3c6d570babf8 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -42,9 +42,10 @@ static const struct nla_policy dpll_device_get_nl_policy[DPLL_A_ID + 1] = { }; /* DPLL_CMD_DEVICE_SET - do */ -static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_PHASE_OFFSET_MONITOR + 1] = { +static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_PHASE_OFFSET_AVG_FACTOR + 1] = { [DPLL_A_ID] = { .type = NLA_U32, }, [DPLL_A_PHASE_OFFSET_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1), + [DPLL_A_PHASE_OFFSET_AVG_FACTOR] = { .type = NLA_U32, }, }; /* DPLL_CMD_PIN_ID_GET - do */ @@ -112,7 +113,7 @@ static const struct genl_split_ops dpll_nl_ops[] = { .doit = dpll_nl_device_set_doit, .post_doit = dpll_post_doit, .policy = dpll_device_set_nl_policy, - .maxattr = DPLL_A_PHASE_OFFSET_MONITOR, + .maxattr = DPLL_A_PHASE_OFFSET_AVG_FACTOR, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/drivers/dpll/zl3073x/Makefile b/drivers/dpll/zl3073x/Makefile index c3e2f02f319d..84e22aae57e5 100644 --- a/drivers/dpll/zl3073x/Makefile +++ b/drivers/dpll/zl3073x/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_ZL3073X) += zl3073x.o -zl3073x-objs := core.o devlink.o dpll.o prop.o +zl3073x-objs := core.o devlink.o dpll.o flash.o fw.o prop.o obj-$(CONFIG_ZL3073X_I2C) += zl3073x_i2c.o zl3073x_i2c-objs := i2c.o diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 7ebcfc5ec1f0..092e7027948a 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -95,9 +95,9 @@ 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_NUM_PAGES 256 #define ZL_PAGE_SEL 0x7F -#define ZL_PAGE_SEL_MASK GENMASK(3, 0) +#define ZL_PAGE_SEL_MASK GENMASK(7, 0) #define ZL_NUM_REGS (ZL_NUM_PAGES * ZL_PAGE_SIZE) /* Regmap range configuration */ @@ -174,9 +174,10 @@ 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. + * from page 10 and above except the page 255 that does not + * need this protection. */ - if (ZL_REG_PAGE(reg) >= 10) + if (ZL_REG_PAGE(reg) >= 10 && ZL_REG_PAGE(reg) < 255) lockdep_assert_held(&zldev->multiop_lock); /* Check the index is in valid range for indexed register */ @@ -447,6 +448,152 @@ int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, } /** + * zl3073x_do_hwreg_op - Perform HW register read/write operation + * @zldev: zl3073x device pointer + * @op: operation to perform + * + * Performs requested operation and waits for its completion. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_do_hwreg_op(struct zl3073x_dev *zldev, u8 op) +{ + int rc; + + /* Set requested operation and set pending bit */ + rc = zl3073x_write_u8(zldev, ZL_REG_HWREG_OP, op | ZL_HWREG_OP_PENDING); + if (rc) + return rc; + + /* Poll for completion - pending bit cleared */ + return zl3073x_poll_zero_u8(zldev, ZL_REG_HWREG_OP, + ZL_HWREG_OP_PENDING); +} + +/** + * zl3073x_read_hwreg - Read HW register + * @zldev: zl3073x device pointer + * @addr: HW register address + * @value: Value of the HW register + * + * Reads HW register value and stores it into @value. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_read_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 *value) +{ + int rc; + + /* Set address to read data from */ + rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_ADDR, addr); + if (rc) + return rc; + + /* Perform the read operation */ + rc = zl3073x_do_hwreg_op(zldev, ZL_HWREG_OP_READ); + if (rc) + return rc; + + /* Read the received data */ + return zl3073x_read_u32(zldev, ZL_REG_HWREG_READ_DATA, value); +} + +/** + * zl3073x_write_hwreg - Write value to HW register + * @zldev: zl3073x device pointer + * @addr: HW registers address + * @value: Value to be written to HW register + * + * Stores the requested value into HW register. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_write_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value) +{ + int rc; + + /* Set address to write data to */ + rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_ADDR, addr); + if (rc) + return rc; + + /* Set data to be written */ + rc = zl3073x_write_u32(zldev, ZL_REG_HWREG_WRITE_DATA, value); + if (rc) + return rc; + + /* Perform the write operation */ + return zl3073x_do_hwreg_op(zldev, ZL_HWREG_OP_WRITE); +} + +/** + * zl3073x_update_hwreg - Update certain bits in HW register + * @zldev: zl3073x device pointer + * @addr: HW register address + * @value: Value to be written into HW register + * @mask: Bitmask indicating bits to be updated + * + * Reads given HW register, updates requested bits specified by value and + * mask and writes result back to HW register. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_update_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value, + u32 mask) +{ + u32 tmp; + int rc; + + rc = zl3073x_read_hwreg(zldev, addr, &tmp); + if (rc) + return rc; + + tmp &= ~mask; + tmp |= value & mask; + + return zl3073x_write_hwreg(zldev, addr, tmp); +} + +/** + * zl3073x_write_hwreg_seq - Write HW registers sequence + * @zldev: pointer to device structure + * @seq: pointer to first sequence item + * @num_items: number of items in sequence + * + * Writes given HW registers sequence. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev, + const struct zl3073x_hwreg_seq_item *seq, + size_t num_items) +{ + int i, rc = 0; + + for (i = 0; i < num_items; i++) { + dev_dbg(zldev->dev, "Write 0x%0x [0x%0x] to 0x%0x", + seq[i].value, seq[i].mask, seq[i].addr); + + if (seq[i].mask == U32_MAX) + /* Write value directly */ + rc = zl3073x_write_hwreg(zldev, seq[i].addr, + seq[i].value); + else + /* Update only bits specified by the mask */ + rc = zl3073x_update_hwreg(zldev, seq[i].addr, + seq[i].value, seq[i].mask); + if (rc) + return rc; + + if (seq->wait) + msleep(seq->wait); + } + + return rc; +} + +/** * zl3073x_ref_state_fetch - get input reference state * @zldev: pointer to zl3073x_dev structure * @index: input reference index to fetch state for @@ -809,21 +956,169 @@ zl3073x_dev_periodic_work(struct kthread_work *work) msecs_to_jiffies(500)); } +int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor) +{ + u8 dpll_meas_ctrl, value; + int rc; + + /* Read DPLL phase measurement control register */ + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl); + if (rc) + return rc; + + /* Convert requested factor to register value */ + value = (factor + 1) & 0x0f; + + /* Update phase measurement control register */ + dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR; + dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, value); + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl); + if (rc) + return rc; + + /* Save the new factor */ + zldev->phase_avg_factor = factor; + + return 0; +} + +/** + * zl3073x_dev_phase_meas_setup - setup phase offset measurement + * @zldev: pointer to zl3073x_dev structure + * + * Enable phase offset measurement block, set measurement averaging factor + * and enable DPLL-to-its-ref phase measurement for all DPLLs. + * + * Returns: 0 on success, <0 on error + */ +static int +zl3073x_dev_phase_meas_setup(struct zl3073x_dev *zldev) +{ + struct zl3073x_dpll *zldpll; + u8 dpll_meas_ctrl, mask = 0; + int rc; + + /* Setup phase measurement averaging factor */ + rc = zl3073x_dev_phase_avg_factor_set(zldev, zldev->phase_avg_factor); + if (rc) + return rc; + + /* Read DPLL phase measurement control register */ + rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl); + if (rc) + return rc; + + /* Enable DPLL measurement block */ + dpll_meas_ctrl |= ZL_DPLL_MEAS_CTRL_EN; + + /* Update phase measurement control register */ + rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl); + if (rc) + return rc; + + /* Enable DPLL-to-connected-ref measurement for each channel */ + list_for_each_entry(zldpll, &zldev->dplls, list) + mask |= BIT(zldpll->id); + + return zl3073x_write_u8(zldev, ZL_REG_DPLL_PHASE_ERR_READ_MASK, mask); +} + +/** + * zl3073x_dev_start - Start normal operation + * @zldev: zl3073x device pointer + * @full: perform full initialization + * + * The function starts normal operation, which means registering all DPLLs and + * their pins, and starting monitoring. If full initialization is requested, + * the function additionally initializes the phase offset measurement block and + * fetches hardware-invariant parameters. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full) +{ + struct zl3073x_dpll *zldpll; + int rc; + + if (full) { + /* Fetch device state */ + rc = zl3073x_dev_state_fetch(zldev); + if (rc) + return rc; + + /* Setup phase offset measurement block */ + rc = zl3073x_dev_phase_meas_setup(zldev); + if (rc) { + dev_err(zldev->dev, + "Failed to setup phase measurement\n"); + return rc; + } + } + + /* Register all DPLLs */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + rc = zl3073x_dpll_register(zldpll); + if (rc) { + dev_err_probe(zldev->dev, rc, + "Failed to register DPLL%u\n", + zldpll->id); + return rc; + } + } + + /* Perform initial firmware fine phase correction */ + rc = zl3073x_dpll_init_fine_phase_adjust(zldev); + if (rc) { + dev_err_probe(zldev->dev, rc, + "Failed to init fine phase correction\n"); + return rc; + } + + /* Start monitoring */ + kthread_queue_delayed_work(zldev->kworker, &zldev->work, 0); + + return 0; +} + +/** + * zl3073x_dev_stop - Stop normal operation + * @zldev: zl3073x device pointer + * + * The function stops the normal operation that mean deregistration of all + * DPLLs and their pins and stop monitoring. + * + * Return: 0 on success, <0 on error + */ +void zl3073x_dev_stop(struct zl3073x_dev *zldev) +{ + struct zl3073x_dpll *zldpll; + + /* Stop monitoring */ + kthread_cancel_delayed_work_sync(&zldev->work); + + /* Unregister all DPLLs */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + if (zldpll->dpll_dev) + zl3073x_dpll_unregister(zldpll); + } +} + static void zl3073x_dev_dpll_fini(void *ptr) { struct zl3073x_dpll *zldpll, *next; struct zl3073x_dev *zldev = ptr; - /* Stop monitoring thread */ + /* Stop monitoring and unregister DPLLs */ + zl3073x_dev_stop(zldev); + + /* Destroy monitoring thread */ if (zldev->kworker) { - kthread_cancel_delayed_work_sync(&zldev->work); kthread_destroy_worker(zldev->kworker); zldev->kworker = NULL; } - /* Release DPLLs */ + /* Free all DPLLs */ list_for_each_entry_safe(zldpll, next, &zldev->dplls, list) { - zl3073x_dpll_unregister(zldpll); list_del(&zldpll->list); zl3073x_dpll_free(zldpll); } @@ -839,7 +1134,7 @@ zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls) INIT_LIST_HEAD(&zldev->dplls); - /* Initialize all DPLLs */ + /* Allocate all DPLLs */ for (i = 0; i < num_dplls; i++) { zldpll = zl3073x_dpll_alloc(zldev, i); if (IS_ERR(zldpll)) { @@ -849,25 +1144,9 @@ zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls) goto error; } - rc = zl3073x_dpll_register(zldpll); - if (rc) { - dev_err_probe(zldev->dev, rc, - "Failed to register DPLL%u\n", i); - zl3073x_dpll_free(zldpll); - goto error; - } - list_add_tail(&zldpll->list, &zldev->dplls); } - /* Perform initial firmware fine phase correction */ - rc = zl3073x_dpll_init_fine_phase_adjust(zldev); - if (rc) { - dev_err_probe(zldev->dev, rc, - "Failed to init fine phase correction\n"); - goto error; - } - /* Initialize monitoring thread */ kthread_init_delayed_work(&zldev->work, zl3073x_dev_periodic_work); kworker = kthread_run_worker(0, "zl3073x-%s", dev_name(zldev->dev)); @@ -875,9 +1154,14 @@ zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls) rc = PTR_ERR(kworker); goto error; } - zldev->kworker = kworker; - kthread_queue_delayed_work(zldev->kworker, &zldev->work, 0); + + /* Start normal operation */ + rc = zl3073x_dev_start(zldev, true); + if (rc) { + dev_err_probe(zldev->dev, rc, "Failed to start device\n"); + goto error; + } /* Add devres action to release DPLL related resources */ rc = devm_add_action_or_reset(zldev->dev, zl3073x_dev_dpll_fini, zldev); @@ -893,46 +1177,6 @@ error: } /** - * zl3073x_dev_phase_meas_setup - setup phase offset measurement - * @zldev: pointer to zl3073x_dev structure - * @num_channels: number of DPLL channels - * - * Enable phase offset measurement block, set measurement averaging factor - * and enable DPLL-to-its-ref phase measurement for all DPLLs. - * - * Returns: 0 on success, <0 on error - */ -static int -zl3073x_dev_phase_meas_setup(struct zl3073x_dev *zldev, int num_channels) -{ - u8 dpll_meas_ctrl, mask; - int i, rc; - - /* Read DPLL phase measurement control register */ - rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, &dpll_meas_ctrl); - if (rc) - return rc; - - /* Setup phase measurement averaging factor */ - dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR; - dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, 3); - - /* Enable DPLL measurement block */ - dpll_meas_ctrl |= ZL_DPLL_MEAS_CTRL_EN; - - /* Update phase measurement control register */ - rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl); - if (rc) - return rc; - - /* Enable DPLL-to-connected-ref measurement for each channel */ - for (i = 0, mask = 0; i < num_channels; i++) - mask |= BIT(i); - - return zl3073x_write_u8(zldev, ZL_REG_DPLL_PHASE_ERR_READ_MASK, mask); -} - -/** * zl3073x_dev_probe - initialize zl3073x device * @zldev: pointer to zl3073x device * @chip_info: chip info based on compatible @@ -991,6 +1235,9 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev, */ zldev->clock_id = get_random_u64(); + /* Default phase offset averaging factor */ + zldev->phase_avg_factor = 2; + /* Initialize mutex for operations where multiple reads, writes * and/or polls are required to be done atomically. */ @@ -999,17 +1246,6 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev, return dev_err_probe(zldev->dev, rc, "Failed to initialize mutex\n"); - /* Fetch device state */ - rc = zl3073x_dev_state_fetch(zldev); - if (rc) - return rc; - - /* Setup phase offset measurement block */ - rc = zl3073x_dev_phase_meas_setup(zldev, chip_info->num_channels); - if (rc) - return dev_err_probe(zldev->dev, rc, - "Failed to setup phase measurement\n"); - /* Register DPLL channels */ rc = zl3073x_devm_dpll_init(zldev, chip_info->num_channels); if (rc) diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h index 71af2c800110..1dca4ddcf235 100644 --- a/drivers/dpll/zl3073x/core.h +++ b/drivers/dpll/zl3073x/core.h @@ -3,6 +3,7 @@ #ifndef _ZL3073X_CORE_H #define _ZL3073X_CORE_H +#include <linux/bitfield.h> #include <linux/kthread.h> #include <linux/list.h> #include <linux/mutex.h> @@ -67,19 +68,19 @@ struct zl3073x_synth { * @dev: pointer to device * @regmap: regmap to access device registers * @multiop_lock: to serialize multiple register operations - * @clock_id: clock id of the device * @ref: array of input references' invariants * @out: array of outs' invariants * @synth: array of synths' invariants * @dplls: list of DPLLs * @kworker: thread for periodic work * @work: periodic work + * @clock_id: clock id of the device + * @phase_avg_factor: phase offset measurement averaging factor */ struct zl3073x_dev { struct device *dev; struct regmap *regmap; struct mutex multiop_lock; - u64 clock_id; /* Invariants */ struct zl3073x_ref ref[ZL3073X_NUM_REFS]; @@ -92,6 +93,10 @@ struct zl3073x_dev { /* Monitor */ struct kthread_worker *kworker; struct kthread_delayed_work work; + + /* Devlink parameters */ + u64 clock_id; + u8 phase_avg_factor; }; struct zl3073x_chip_info { @@ -111,10 +116,42 @@ struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev); int zl3073x_dev_probe(struct zl3073x_dev *zldev, const struct zl3073x_chip_info *chip_info); +int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full); +void zl3073x_dev_stop(struct zl3073x_dev *zldev); + +static inline u8 zl3073x_dev_phase_avg_factor_get(struct zl3073x_dev *zldev) +{ + return zldev->phase_avg_factor; +} + +int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor); + /********************** * Registers operations **********************/ +/** + * struct zl3073x_hwreg_seq_item - HW register write sequence item + * @addr: HW register to be written + * @value: value to be written to HW register + * @mask: bitmask indicating bits to be updated + * @wait: number of ms to wait after register write + */ +struct zl3073x_hwreg_seq_item { + u32 addr; + u32 value; + u32 mask; + u32 wait; +}; + +#define HWREG_SEQ_ITEM(_addr, _value, _mask, _wait) \ +{ \ + .addr = _addr, \ + .value = FIELD_PREP_CONST(_mask, _value), \ + .mask = _mask, \ + .wait = _wait, \ +} + int zl3073x_mb_op(struct zl3073x_dev *zldev, unsigned int op_reg, u8 op_val, unsigned int mask_reg, u16 mask_val); int zl3073x_poll_zero_u8(struct zl3073x_dev *zldev, unsigned int reg, u8 mask); @@ -126,6 +163,13 @@ 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); +int zl3073x_read_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 *value); +int zl3073x_write_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value); +int zl3073x_update_hwreg(struct zl3073x_dev *zldev, u32 addr, u32 value, + u32 mask); +int zl3073x_write_hwreg_seq(struct zl3073x_dev *zldev, + const struct zl3073x_hwreg_seq_item *seq, + size_t num_items); /***************** * Misc operations diff --git a/drivers/dpll/zl3073x/devlink.c b/drivers/dpll/zl3073x/devlink.c index 7e7fe726ee37..ccc22332b346 100644 --- a/drivers/dpll/zl3073x/devlink.c +++ b/drivers/dpll/zl3073x/devlink.c @@ -9,6 +9,8 @@ #include "core.h" #include "devlink.h" #include "dpll.h" +#include "flash.h" +#include "fw.h" #include "regs.h" /** @@ -86,14 +88,12 @@ zl3073x_devlink_reload_down(struct devlink *devlink, bool netns_change, struct netlink_ext_ack *extack) { struct zl3073x_dev *zldev = devlink_priv(devlink); - struct zl3073x_dpll *zldpll; if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT) return -EOPNOTSUPP; - /* Unregister all DPLLs */ - list_for_each_entry(zldpll, &zldev->dplls, list) - zl3073x_dpll_unregister(zldpll); + /* Stop normal operation */ + zl3073x_dev_stop(zldev); return 0; } @@ -107,7 +107,6 @@ zl3073x_devlink_reload_up(struct devlink *devlink, { struct zl3073x_dev *zldev = devlink_priv(devlink); union devlink_param_value val; - struct zl3073x_dpll *zldpll; int rc; if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT) @@ -125,24 +124,156 @@ zl3073x_devlink_reload_up(struct devlink *devlink, zldev->clock_id = val.vu64; } - /* Re-register all DPLLs */ - list_for_each_entry(zldpll, &zldev->dplls, list) { - rc = zl3073x_dpll_register(zldpll); - if (rc) - dev_warn(zldev->dev, - "Failed to re-register DPLL%u\n", zldpll->id); - } + /* Restart normal operation */ + rc = zl3073x_dev_start(zldev, false); + if (rc) + dev_warn(zldev->dev, "Failed to re-start normal operation\n"); *actions_performed = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT); return 0; } +void zl3073x_devlink_flash_notify(struct zl3073x_dev *zldev, const char *msg, + const char *component, u32 done, u32 total) +{ + struct devlink *devlink = priv_to_devlink(zldev); + + devlink_flash_update_status_notify(devlink, msg, component, done, + total); +} + +/** + * zl3073x_devlink_flash_prepare - Prepare and enter flash mode + * @zldev: zl3073x device pointer + * @zlfw: pointer to loaded firmware + * @extack: netlink extack pointer to report errors + * + * The function stops normal operation and switches the device to flash mode. + * If an error occurs the normal operation is resumed. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_flash_prepare(struct zl3073x_dev *zldev, + struct zl3073x_fw *zlfw, + struct netlink_ext_ack *extack) +{ + struct zl3073x_fw_component *util; + int rc; + + util = zlfw->component[ZL_FW_COMPONENT_UTIL]; + if (!util) { + zl3073x_devlink_flash_notify(zldev, + "Utility is missing in firmware", + NULL, 0, 0); + return -ENOEXEC; + } + + /* Stop normal operation prior entering flash mode */ + zl3073x_dev_stop(zldev); + + rc = zl3073x_flash_mode_enter(zldev, util->data, util->size, extack); + if (rc) { + zl3073x_devlink_flash_notify(zldev, + "Failed to enter flash mode", + NULL, 0, 0); + + /* Resume normal operation */ + zl3073x_dev_start(zldev, true); + + return rc; + } + + return 0; +} + +/** + * zl3073x_devlink_flash_finish - Leave flash mode and resume normal operation + * @zldev: zl3073x device pointer + * @extack: netlink extack pointer to report errors + * + * The function switches the device back to standard mode and resumes normal + * operation. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_flash_finish(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack) +{ + int rc; + + /* Reset device CPU to normal mode */ + zl3073x_flash_mode_leave(zldev, extack); + + /* Resume normal operation */ + rc = zl3073x_dev_start(zldev, true); + if (rc) + zl3073x_devlink_flash_notify(zldev, + "Failed to start normal operation", + NULL, 0, 0); + + return rc; +} + +/** + * zl3073x_devlink_flash_update - Devlink flash update callback + * @devlink: devlink structure pointer + * @params: flashing parameters pointer + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_flash_update(struct devlink *devlink, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + struct zl3073x_fw *zlfw; + int rc = 0; + + zlfw = zl3073x_fw_load(zldev, params->fw->data, params->fw->size, + extack); + if (IS_ERR(zlfw)) { + zl3073x_devlink_flash_notify(zldev, "Failed to load firmware", + NULL, 0, 0); + rc = PTR_ERR(zlfw); + goto finish; + } + + /* Stop normal operation and enter flash mode */ + rc = zl3073x_devlink_flash_prepare(zldev, zlfw, extack); + if (rc) + goto finish; + + rc = zl3073x_fw_flash(zldev, zlfw, extack); + if (rc) { + zl3073x_devlink_flash_finish(zldev, extack); + goto finish; + } + + /* Resume normal mode */ + rc = zl3073x_devlink_flash_finish(zldev, extack); + +finish: + if (!IS_ERR(zlfw)) + zl3073x_fw_free(zlfw); + + zl3073x_devlink_flash_notify(zldev, + rc ? "Flashing failed" : "Flashing done", + NULL, 0, 0); + + return rc; +} + static const struct devlink_ops zl3073x_devlink_ops = { .info_get = zl3073x_devlink_info_get, .reload_actions = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT), .reload_down = zl3073x_devlink_reload_down, .reload_up = zl3073x_devlink_reload_up, + .flash_update = zl3073x_devlink_flash_update, }; static void diff --git a/drivers/dpll/zl3073x/devlink.h b/drivers/dpll/zl3073x/devlink.h index 037720db204f..63dfd6fa1cd6 100644 --- a/drivers/dpll/zl3073x/devlink.h +++ b/drivers/dpll/zl3073x/devlink.h @@ -9,4 +9,7 @@ struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev); int zl3073x_devlink_register(struct zl3073x_dev *zldev); +void zl3073x_devlink_flash_notify(struct zl3073x_dev *zldev, const char *msg, + const char *component, u32 done, u32 total); + #endif /* _ZL3073X_DEVLINK_H */ diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 3e42e9e7fd27..93dc93eec79e 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -1577,6 +1577,59 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv, } static int +zl3073x_dpll_phase_offset_avg_factor_get(const struct dpll_device *dpll, + void *dpll_priv, u32 *factor, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *zldpll = dpll_priv; + + *factor = zl3073x_dev_phase_avg_factor_get(zldpll->dev); + + return 0; +} + +static void +zl3073x_dpll_change_work(struct work_struct *work) +{ + struct zl3073x_dpll *zldpll; + + zldpll = container_of(work, struct zl3073x_dpll, change_work); + dpll_device_change_ntf(zldpll->dpll_dev); +} + +static int +zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll, + void *dpll_priv, u32 factor, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dpll *item, *zldpll = dpll_priv; + int rc; + + if (factor > 15) { + NL_SET_ERR_MSG_FMT(extack, + "Phase offset average factor has to be from range <0,15>"); + return -EINVAL; + } + + rc = zl3073x_dev_phase_avg_factor_set(zldpll->dev, factor); + if (rc) { + NL_SET_ERR_MSG_FMT(extack, + "Failed to set phase offset averaging factor"); + return rc; + } + + /* The averaging factor is common for all DPLL channels so after change + * we have to send a notification for other DPLL devices. + */ + list_for_each_entry(item, &zldpll->dev->dplls, list) { + if (item != zldpll) + schedule_work(&item->change_work); + } + + return 0; +} + +static int zl3073x_dpll_phase_offset_monitor_get(const struct dpll_device *dpll, void *dpll_priv, enum dpll_feature_state *state, @@ -1635,6 +1688,8 @@ static const struct dpll_pin_ops zl3073x_dpll_output_pin_ops = { static const struct dpll_device_ops zl3073x_dpll_device_ops = { .lock_status_get = zl3073x_dpll_lock_status_get, .mode_get = zl3073x_dpll_mode_get, + .phase_offset_avg_factor_get = zl3073x_dpll_phase_offset_avg_factor_get, + .phase_offset_avg_factor_set = zl3073x_dpll_phase_offset_avg_factor_set, .phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get, .phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set, }; @@ -1983,6 +2038,8 @@ zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll) { WARN(!zldpll->dpll_dev, "DPLL device is not registered\n"); + cancel_work_sync(&zldpll->change_work); + dpll_device_unregister(zldpll->dpll_dev, &zl3073x_dpll_device_ops, zldpll); dpll_device_put(zldpll->dpll_dev); @@ -2258,6 +2315,7 @@ zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch) zldpll->dev = zldev; zldpll->id = ch; INIT_LIST_HEAD(&zldpll->pins); + INIT_WORK(&zldpll->change_work, zl3073x_dpll_change_work); return zldpll; } diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h index 304910ffc9c0..e8c39b44b356 100644 --- a/drivers/dpll/zl3073x/dpll.h +++ b/drivers/dpll/zl3073x/dpll.h @@ -20,6 +20,7 @@ * @dpll_dev: pointer to registered DPLL device * @lock_status: last saved DPLL lock status * @pins: list of pins + * @change_work: device change notification work */ struct zl3073x_dpll { struct list_head list; @@ -32,6 +33,7 @@ struct zl3073x_dpll { struct dpll_device *dpll_dev; enum dpll_lock_status lock_status; struct list_head pins; + struct work_struct change_work; }; struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch); diff --git a/drivers/dpll/zl3073x/flash.c b/drivers/dpll/zl3073x/flash.c new file mode 100644 index 000000000000..83452a77e3e9 --- /dev/null +++ b/drivers/dpll/zl3073x/flash.c @@ -0,0 +1,666 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/errno.h> +#include <linux/jiffies.h> +#include <linux/minmax.h> +#include <linux/netlink.h> +#include <linux/sched/signal.h> +#include <linux/sizes.h> +#include <linux/sprintf.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/unaligned.h> +#include <net/devlink.h> + +#include "core.h" +#include "devlink.h" +#include "flash.h" + +#define ZL_FLASH_ERR_PFX "FW update failed: " +#define ZL_FLASH_ERR_MSG(_extack, _msg, ...) \ + NL_SET_ERR_MSG_FMT_MOD((_extack), ZL_FLASH_ERR_PFX _msg, \ + ## __VA_ARGS__) + +/** + * zl3073x_flash_download - Download image block to device memory + * @zldev: zl3073x device structure + * @component: name of the component to be downloaded + * @addr: device memory target address + * @data: pointer to data to download + * @size: size of data to download + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_download(struct zl3073x_dev *zldev, const char *component, + u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack) +{ +#define ZL_CHECK_DELAY 5000 /* Check for interrupt each 5 seconds */ + unsigned long check_time; + const void *ptr, *end; + int rc = 0; + + dev_dbg(zldev->dev, "Downloading %zu bytes to device memory at 0x%0x\n", + size, addr); + + check_time = jiffies + msecs_to_jiffies(ZL_CHECK_DELAY); + + for (ptr = data, end = data + size; ptr < end; ptr += 4, addr += 4) { + /* Write current word to HW memory */ + rc = zl3073x_write_hwreg(zldev, addr, + get_unaligned((u32 *)ptr)); + if (rc) { + ZL_FLASH_ERR_MSG(extack, + "failed to write to memory at 0x%0x", + addr); + return rc; + } + + if (time_is_before_jiffies(check_time)) { + if (signal_pending(current)) { + ZL_FLASH_ERR_MSG(extack, + "Flashing interrupted"); + return -EINTR; + } + + check_time = jiffies + msecs_to_jiffies(ZL_CHECK_DELAY); + } + + /* Report status each 1 kB block */ + if ((ptr - data) % 1024 == 0) + zl3073x_devlink_flash_notify(zldev, "Downloading image", + component, ptr - data, + size); + } + + zl3073x_devlink_flash_notify(zldev, "Downloading image", component, + ptr - data, size); + + dev_dbg(zldev->dev, "%zu bytes downloaded to device memory\n", size); + + return rc; +} + +/** + * zl3073x_flash_error_check - Check for flash utility errors + * @zldev: zl3073x device structure + * @extack: netlink extack pointer to report errors + * + * The function checks for errors detected by the flash utility and + * reports them if any were found. + * + * Return: 0 on success, -EIO when errors are detected + */ +static int +zl3073x_flash_error_check(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack) +{ + u32 count, cause; + int rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_ERROR_COUNT, &count); + if (rc) + return rc; + else if (!count) + return 0; /* No error */ + + rc = zl3073x_read_u32(zldev, ZL_REG_ERROR_CAUSE, &cause); + if (rc) + return rc; + + /* Report errors */ + ZL_FLASH_ERR_MSG(extack, + "utility error occurred: count=%u cause=0x%x", count, + cause); + + return -EIO; +} + +/** + * zl3073x_flash_wait_ready - Check or wait for utility to be ready to flash + * @zldev: zl3073x device structure + * @timeout_ms: timeout for the waiting + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_wait_ready(struct zl3073x_dev *zldev, unsigned int timeout_ms) +{ +#define ZL_FLASH_POLL_DELAY_MS 100 + unsigned long timeout; + int rc, i; + + dev_dbg(zldev->dev, "Waiting for flashing to be ready\n"); + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + + for (i = 0; time_is_after_jiffies(timeout); i++) { + u8 value; + + /* Check for interrupt each 1s */ + if (i > 9) { + if (signal_pending(current)) + return -EINTR; + i = 0; + } + + rc = zl3073x_read_u8(zldev, ZL_REG_WRITE_FLASH, &value); + if (rc) + return rc; + + value = FIELD_GET(ZL_WRITE_FLASH_OP, value); + + if (value == ZL_WRITE_FLASH_OP_DONE) + return 0; /* Successfully done */ + + msleep(ZL_FLASH_POLL_DELAY_MS); + } + + return -ETIMEDOUT; +} + +/** + * zl3073x_flash_cmd_wait - Perform flash operation and wait for finish + * @zldev: zl3073x device structure + * @operation: operation to perform + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_cmd_wait(struct zl3073x_dev *zldev, u32 operation, + struct netlink_ext_ack *extack) +{ +#define ZL_FLASH_PHASE1_TIMEOUT_MS 60000 /* up to 1 minute */ +#define ZL_FLASH_PHASE2_TIMEOUT_MS 120000 /* up to 2 minutes */ + u8 value; + int rc; + + dev_dbg(zldev->dev, "Sending flash command: 0x%x\n", operation); + + rc = zl3073x_flash_wait_ready(zldev, ZL_FLASH_PHASE1_TIMEOUT_MS); + if (rc) + return rc; + + /* Issue the requested operation */ + rc = zl3073x_read_u8(zldev, ZL_REG_WRITE_FLASH, &value); + if (rc) + return rc; + + value &= ~ZL_WRITE_FLASH_OP; + value |= FIELD_PREP(ZL_WRITE_FLASH_OP, operation); + + rc = zl3073x_write_u8(zldev, ZL_REG_WRITE_FLASH, value); + if (rc) + return rc; + + /* Wait for command completion */ + rc = zl3073x_flash_wait_ready(zldev, ZL_FLASH_PHASE2_TIMEOUT_MS); + if (rc) + return rc; + + return zl3073x_flash_error_check(zldev, extack); +} + +/** + * zl3073x_flash_get_sector_size - Get flash sector size + * @zldev: zl3073x device structure + * @sector_size: sector size returned by the function + * + * The function reads the flash sector size detected by flash utility and + * stores it into @sector_size. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_get_sector_size(struct zl3073x_dev *zldev, size_t *sector_size) +{ + u8 flash_info; + int rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_INFO, &flash_info); + if (rc) + return rc; + + switch (FIELD_GET(ZL_FLASH_INFO_SECTOR_SIZE, flash_info)) { + case ZL_FLASH_INFO_SECTOR_4K: + *sector_size = SZ_4K; + break; + case ZL_FLASH_INFO_SECTOR_64K: + *sector_size = SZ_64K; + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +/** + * zl3073x_flash_block - Download and flash memory block + * @zldev: zl3073x device structure + * @component: component name + * @operation: flash operation to perform + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function downloads the memory block given by the @data pointer and + * the size @size and flashes it into internal memory on flash page @page. + * The internal flash operation performed by the firmware is specified by + * the @operation parameter. + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_flash_block(struct zl3073x_dev *zldev, const char *component, + u32 operation, u32 page, u32 addr, const void *data, + size_t size, struct netlink_ext_ack *extack) +{ + int rc; + + /* Download block to device memory */ + rc = zl3073x_flash_download(zldev, component, addr, data, size, extack); + if (rc) + return rc; + + /* Set address to flash from */ + rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_START_ADDR, addr); + if (rc) + return rc; + + /* Set size of block to flash */ + rc = zl3073x_write_u32(zldev, ZL_REG_IMAGE_SIZE, size); + if (rc) + return rc; + + /* Set destination page to flash */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, page); + if (rc) + return rc; + + /* Set filling pattern */ + rc = zl3073x_write_u32(zldev, ZL_REG_FILL_PATTERN, U32_MAX); + if (rc) + return rc; + + zl3073x_devlink_flash_notify(zldev, "Flashing image", component, 0, + size); + + dev_dbg(zldev->dev, "Flashing %zu bytes to page %u\n", size, page); + + /* Execute sectors flash operation */ + rc = zl3073x_flash_cmd_wait(zldev, operation, extack); + if (rc) + return rc; + + zl3073x_devlink_flash_notify(zldev, "Flashing image", component, size, + size); + + return 0; +} + +/** + * zl3073x_flash_sectors - Flash sectors + * @zldev: zl3073x device structure + * @component: component name + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function flashes given @data with size of @size to the internal flash + * memory block starting from page @page. The function uses sector flash + * method and has to take into account the flash sector size reported by + * flashing utility. Input data are spliced into blocks according this + * sector size and each block is flashed separately. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_sectors(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack) +{ +#define ZL_FLASH_MAX_BLOCK_SIZE 0x0001E000 +#define ZL_FLASH_PAGE_SIZE 256 + size_t max_block_size, block_size, sector_size; + const void *ptr, *end; + int rc; + + /* Get flash sector size */ + rc = zl3073x_flash_get_sector_size(zldev, §or_size); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "Failed to get flash sector size"); + return rc; + } + + /* Determine max block size depending on sector size */ + max_block_size = ALIGN_DOWN(ZL_FLASH_MAX_BLOCK_SIZE, sector_size); + + for (ptr = data, end = data + size; ptr < end; ptr += block_size) { + char comp_str[32]; + + block_size = min_t(size_t, max_block_size, end - ptr); + + /* Add suffix '-partN' if the requested component size is + * greater than max_block_size. + */ + if (max_block_size < size) + snprintf(comp_str, sizeof(comp_str), "%s-part%zu", + component, (ptr - data) / max_block_size + 1); + else + strscpy(comp_str, component); + + /* Flash the memory block */ + rc = zl3073x_flash_block(zldev, comp_str, + ZL_WRITE_FLASH_OP_SECTORS, page, addr, + ptr, block_size, extack); + if (rc) + goto finish; + + /* Move to next page */ + page += block_size / ZL_FLASH_PAGE_SIZE; + } + +finish: + zl3073x_devlink_flash_notify(zldev, + rc ? "Flashing failed" : "Flashing done", + component, 0, 0); + + return rc; +} + +/** + * zl3073x_flash_page - Flash page + * @zldev: zl3073x device structure + * @component: component name + * @page: destination flash page + * @addr: device memory address to load data + * @data: pointer to data to be flashed + * @size: size of data + * @extack: netlink extack pointer to report errors + * + * The function flashes given @data with size of @size to the internal flash + * memory block starting with page @page. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_page(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack) +{ + int rc; + + /* Flash the memory block */ + rc = zl3073x_flash_block(zldev, component, ZL_WRITE_FLASH_OP_PAGE, page, + addr, data, size, extack); + + zl3073x_devlink_flash_notify(zldev, + rc ? "Flashing failed" : "Flashing done", + component, 0, 0); + + return rc; +} + +/** + * zl3073x_flash_page_copy - Copy flash page + * @zldev: zl3073x device structure + * @component: component name + * @src_page: source page to copy + * @dst_page: destination page + * @extack: netlink extack pointer to report errors + * + * The function copies one flash page specified by @src_page into the flash + * page specified by @dst_page. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_page_copy(struct zl3073x_dev *zldev, const char *component, + u32 src_page, u32 dst_page, + struct netlink_ext_ack *extack) +{ + int rc; + + /* Set source page to be copied */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_READ, src_page); + if (rc) + return rc; + + /* Set destination page for the copy */ + rc = zl3073x_write_u32(zldev, ZL_REG_FLASH_INDEX_WRITE, dst_page); + if (rc) + return rc; + + /* Perform copy operation */ + rc = zl3073x_flash_cmd_wait(zldev, ZL_WRITE_FLASH_OP_COPY_PAGE, extack); + if (rc) + ZL_FLASH_ERR_MSG(extack, "Failed to copy page %u to page %u", + src_page, dst_page); + + return rc; +} + +/** + * zl3073x_flash_mode_verify - Check flash utility + * @zldev: zl3073x device structure + * + * Return: 0 if the flash utility is ready, <0 on error + */ +static int +zl3073x_flash_mode_verify(struct zl3073x_dev *zldev) +{ + u8 family, release; + u32 hash; + int rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_FLASH_HASH, &hash); + if (rc) + return rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_FAMILY, &family); + if (rc) + return rc; + + rc = zl3073x_read_u8(zldev, ZL_REG_FLASH_RELEASE, &release); + if (rc) + return rc; + + dev_dbg(zldev->dev, + "Flash utility check: hash 0x%08x, fam 0x%02x, rel 0x%02x\n", + hash, family, release); + + /* Return success for correct family */ + return (family == 0x21) ? 0 : -ENODEV; +} + +static int +zl3073x_flash_host_ctrl_enable(struct zl3073x_dev *zldev) +{ + u8 host_ctrl; + int rc; + + /* Enable host control */ + rc = zl3073x_read_u8(zldev, ZL_REG_HOST_CONTROL, &host_ctrl); + if (rc) + return rc; + + host_ctrl |= ZL_HOST_CONTROL_ENABLE; + + return zl3073x_write_u8(zldev, ZL_REG_HOST_CONTROL, host_ctrl); +} + +/** + * zl3073x_flash_mode_enter - Switch the device to flash mode + * @zldev: zl3073x device structure + * @util_ptr: buffer with flash utility + * @util_size: size of buffer with flash utility + * @extack: netlink extack pointer to report errors + * + * The function prepares and switches the device into flash mode. + * + * The procedure: + * 1) Stop device CPU by specific HW register sequence + * 2) Download flash utility to device memory + * 3) Resume device CPU by specific HW register sequence + * 4) Check communication with flash utility + * 5) Enable host control necessary to access flash API + * 6) Check for potential error detected by the utility + * + * The API provided by normal firmware is not available in flash mode + * so the caller has to ensure that this API is not used in this mode. + * + * After performing flash operation the caller should call + * @zl3073x_flash_mode_leave to return back to normal operation. + * + * Return: 0 on success, <0 on error. + */ +int zl3073x_flash_mode_enter(struct zl3073x_dev *zldev, const void *util_ptr, + size_t util_size, struct netlink_ext_ack *extack) +{ + /* Sequence to be written prior utility download */ + static const struct zl3073x_hwreg_seq_item pre_seq[] = { + HWREG_SEQ_ITEM(0x80000400, 1, BIT(0), 0), + HWREG_SEQ_ITEM(0x80206340, 1, BIT(4), 0), + HWREG_SEQ_ITEM(0x10000000, 1, BIT(2), 0), + HWREG_SEQ_ITEM(0x10000024, 0x00000001, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000020, 0x00000001, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000000, 1, BIT(10), 1000), + }; + /* Sequence to be written after utility download */ + static const struct zl3073x_hwreg_seq_item post_seq[] = { + HWREG_SEQ_ITEM(0x10400004, 0x000000C0, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10400008, 0x00000000, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10400010, 0x20000000, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10400014, 0x20000004, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000000, 1, GENMASK(10, 9), 0), + HWREG_SEQ_ITEM(0x10000020, 0x00000000, U32_MAX, 0), + HWREG_SEQ_ITEM(0x10000000, 0, BIT(0), 1000), + }; + int rc; + + zl3073x_devlink_flash_notify(zldev, "Prepare flash mode", "utility", + 0, 0); + + /* Execure pre-load sequence */ + rc = zl3073x_write_hwreg_seq(zldev, pre_seq, ARRAY_SIZE(pre_seq)); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot execute pre-load sequence"); + goto error; + } + + /* Download utility image to device memory */ + rc = zl3073x_flash_download(zldev, "utility", 0x20000000, util_ptr, + util_size, extack); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot download flash utility"); + goto error; + } + + /* Execute post-load sequence */ + rc = zl3073x_write_hwreg_seq(zldev, post_seq, ARRAY_SIZE(post_seq)); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot execute post-load sequence"); + goto error; + } + + /* Check that utility identifies itself correctly */ + rc = zl3073x_flash_mode_verify(zldev); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "flash utility check failed"); + goto error; + } + + /* Enable host control */ + rc = zl3073x_flash_host_ctrl_enable(zldev); + if (rc) { + ZL_FLASH_ERR_MSG(extack, "cannot enable host control"); + goto error; + } + + zl3073x_devlink_flash_notify(zldev, "Flash mode enabled", "utility", + 0, 0); + + return 0; + +error: + zl3073x_flash_mode_leave(zldev, extack); + + return rc; +} + +/** + * zl3073x_flash_mode_leave - Leave flash mode + * @zldev: zl3073x device structure + * @extack: netlink extack pointer to report errors + * + * The function instructs the device to leave the flash mode and + * to return back to normal operation. + * + * The procedure: + * 1) Set reset flag + * 2) Reset the device CPU by specific HW register sequence + * 3) Wait for the device to be ready + * 4) Check the reset flag was cleared + * + * Return: 0 on success, <0 on error + */ +int zl3073x_flash_mode_leave(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack) +{ + /* Sequence to be written after flash */ + static const struct zl3073x_hwreg_seq_item fw_reset_seq[] = { + HWREG_SEQ_ITEM(0x80000404, 1, BIT(0), 0), + HWREG_SEQ_ITEM(0x80000410, 1, BIT(0), 0), + }; + u8 reset_status; + int rc; + + zl3073x_devlink_flash_notify(zldev, "Leaving flash mode", "utility", + 0, 0); + + /* Read reset status register */ + rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status); + if (rc) + return rc; + + /* Set reset bit */ + reset_status |= ZL_REG_RESET_STATUS_RESET; + + /* Update reset status register */ + rc = zl3073x_write_u8(zldev, ZL_REG_RESET_STATUS, reset_status); + if (rc) + return rc; + + /* We do not check the return value here as the sequence resets + * the device CPU and the last write always return an error. + */ + zl3073x_write_hwreg_seq(zldev, fw_reset_seq, ARRAY_SIZE(fw_reset_seq)); + + /* Wait for the device to be ready */ + msleep(500); + + /* Read again the reset status register */ + rc = zl3073x_read_u8(zldev, ZL_REG_RESET_STATUS, &reset_status); + if (rc) + return rc; + + /* Check the reset bit was cleared */ + if (reset_status & ZL_REG_RESET_STATUS_RESET) { + dev_err(zldev->dev, + "Reset not confirmed after switch to normal mode\n"); + return -EINVAL; + } + + return 0; +} diff --git a/drivers/dpll/zl3073x/flash.h b/drivers/dpll/zl3073x/flash.h new file mode 100644 index 000000000000..effe1b16b359 --- /dev/null +++ b/drivers/dpll/zl3073x/flash.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef __ZL3073X_FLASH_H +#define __ZL3073X_FLASH_H + +#include <linux/types.h> + +struct netlink_ext_ack; +struct zl3073x_dev; + +int zl3073x_flash_mode_enter(struct zl3073x_dev *zldev, const void *util_ptr, + size_t util_size, struct netlink_ext_ack *extack); + +int zl3073x_flash_mode_leave(struct zl3073x_dev *zldev, + struct netlink_ext_ack *extack); + +int zl3073x_flash_page(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack); + +int zl3073x_flash_page_copy(struct zl3073x_dev *zldev, const char *component, + u32 src_page, u32 dst_page, + struct netlink_ext_ack *extack); + +int zl3073x_flash_sectors(struct zl3073x_dev *zldev, const char *component, + u32 page, u32 addr, const void *data, size_t size, + struct netlink_ext_ack *extack); + +#endif /* __ZL3073X_FLASH_H */ diff --git a/drivers/dpll/zl3073x/fw.c b/drivers/dpll/zl3073x/fw.c new file mode 100644 index 000000000000..d5418ff74886 --- /dev/null +++ b/drivers/dpll/zl3073x/fw.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/array_size.h> +#include <linux/build_bug.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/netlink.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include "core.h" +#include "flash.h" +#include "fw.h" + +#define ZL3073X_FW_ERR_PFX "FW load failed: " +#define ZL3073X_FW_ERR_MSG(_extack, _msg, ...) \ + NL_SET_ERR_MSG_FMT_MOD((_extack), ZL3073X_FW_ERR_PFX _msg, \ + ## __VA_ARGS__) + +enum zl3073x_flash_type { + ZL3073X_FLASH_TYPE_NONE = 0, + ZL3073X_FLASH_TYPE_SECTORS, + ZL3073X_FLASH_TYPE_PAGE, + ZL3073X_FLASH_TYPE_PAGE_AND_COPY, +}; + +struct zl3073x_fw_component_info { + const char *name; + size_t max_size; + enum zl3073x_flash_type flash_type; + u32 load_addr; + u32 dest_page; + u32 copy_page; +}; + +static const struct zl3073x_fw_component_info component_info[] = { + [ZL_FW_COMPONENT_UTIL] = { + .name = "utility", + .max_size = 0x2300, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_NONE, + }, + [ZL_FW_COMPONENT_FW1] = { + .name = "firmware1", + .max_size = 0x35000, + .load_addr = 0x20002000, + .flash_type = ZL3073X_FLASH_TYPE_SECTORS, + .dest_page = 0x020, + }, + [ZL_FW_COMPONENT_FW2] = { + .name = "firmware2", + .max_size = 0x0040, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE_AND_COPY, + .dest_page = 0x3e0, + .copy_page = 0x000, + }, + [ZL_FW_COMPONENT_FW3] = { + .name = "firmware3", + .max_size = 0x0248, + .load_addr = 0x20000400, + .flash_type = ZL3073X_FLASH_TYPE_PAGE_AND_COPY, + .dest_page = 0x3e4, + .copy_page = 0x004, + }, + [ZL_FW_COMPONENT_CFG0] = { + .name = "config0", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3d0, + }, + [ZL_FW_COMPONENT_CFG1] = { + .name = "config1", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3c0, + }, + [ZL_FW_COMPONENT_CFG2] = { + .name = "config2", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3b0, + }, + [ZL_FW_COMPONENT_CFG3] = { + .name = "config3", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x3a0, + }, + [ZL_FW_COMPONENT_CFG4] = { + .name = "config4", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x390, + }, + [ZL_FW_COMPONENT_CFG5] = { + .name = "config5", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x380, + }, + [ZL_FW_COMPONENT_CFG6] = { + .name = "config6", + .max_size = 0x1000, + .load_addr = 0x20000000, + .flash_type = ZL3073X_FLASH_TYPE_PAGE, + .dest_page = 0x370, + }, +}; + +/* Sanity check */ +static_assert(ARRAY_SIZE(component_info) == ZL_FW_NUM_COMPONENTS); + +/** + * zl3073x_fw_component_alloc - Alloc structure to hold firmware component + * @size: size of buffer to store data + * + * Return: pointer to allocated component structure or NULL on error. + */ +static struct zl3073x_fw_component * +zl3073x_fw_component_alloc(size_t size) +{ + struct zl3073x_fw_component *comp; + + comp = kzalloc(sizeof(*comp), GFP_KERNEL); + if (!comp) + return NULL; + + comp->size = size; + comp->data = kzalloc(size, GFP_KERNEL); + if (!comp->data) { + kfree(comp); + return NULL; + } + + return comp; +} + +/** + * zl3073x_fw_component_free - Free allocated component structure + * @comp: pointer to allocated component + */ +static void +zl3073x_fw_component_free(struct zl3073x_fw_component *comp) +{ + if (comp) + kfree(comp->data); + + kfree(comp); +} + +/** + * zl3073x_fw_component_id_get - Get ID for firmware component name + * @name: input firmware component name + * + * Return: + * - ZL3073X_FW_COMPONENT_* ID for known component name + * - ZL3073X_FW_COMPONENT_INVALID if the given name is unknown + */ +static enum zl3073x_fw_component_id +zl3073x_fw_component_id_get(const char *name) +{ + enum zl3073x_fw_component_id id; + + for (id = 0; id < ZL_FW_NUM_COMPONENTS; id++) + if (!strcasecmp(name, component_info[id].name)) + return id; + + return ZL_FW_COMPONENT_INVALID; +} + +/** + * zl3073x_fw_component_load - Load component from firmware source + * @zldev: zl3073x device structure + * @pcomp: pointer to loaded component + * @psrc: data pointer to load component from + * @psize: remaining bytes in buffer + * @extack: netlink extack pointer to report errors + * + * The function allocates single firmware component and loads the data from + * the buffer specified by @psrc and @psize. Pointer to allocated component + * is stored in output @pcomp. Source data pointer @psrc and remaining bytes + * @psize are updated accordingly. + * + * Return: + * * 1 when component was allocated and loaded + * * 0 when there is no component to load + * * <0 on error + */ +static ssize_t +zl3073x_fw_component_load(struct zl3073x_dev *zldev, + struct zl3073x_fw_component **pcomp, + const char **psrc, size_t *psize, + struct netlink_ext_ack *extack) +{ + const struct zl3073x_fw_component_info *info; + struct zl3073x_fw_component *comp = NULL; + struct device *dev = zldev->dev; + enum zl3073x_fw_component_id id; + char buf[32], name[16]; + u32 count, size, *dest; + int pos, rc; + + /* Fetch image name and size from input */ + strscpy(buf, *psrc, min(sizeof(buf), *psize)); + rc = sscanf(buf, "%15s %u %n", name, &count, &pos); + if (!rc) { + /* No more data */ + return 0; + } else if (rc == 1 || count > U32_MAX / sizeof(u32)) { + ZL3073X_FW_ERR_MSG(extack, "invalid component size"); + return -EINVAL; + } + *psrc += pos; + *psize -= pos; + + dev_dbg(dev, "Firmware component '%s' found\n", name); + + id = zl3073x_fw_component_id_get(name); + if (id == ZL_FW_COMPONENT_INVALID) { + ZL3073X_FW_ERR_MSG(extack, "unknown component type '%s'", name); + return -EINVAL; + } + + info = &component_info[id]; + size = count * sizeof(u32); /* get size in bytes */ + + /* Check image size validity */ + if (size > component_info[id].max_size) { + ZL3073X_FW_ERR_MSG(extack, + "[%s] component is too big (%u bytes)\n", + info->name, size); + return -EINVAL; + } + + dev_dbg(dev, "Indicated component image size: %u bytes\n", size); + + /* Alloc component */ + comp = zl3073x_fw_component_alloc(size); + if (!comp) { + ZL3073X_FW_ERR_MSG(extack, "failed to alloc memory"); + return -ENOMEM; + } + comp->id = id; + + /* Load component data from firmware source */ + for (dest = comp->data; count; count--, dest++) { + strscpy(buf, *psrc, min(sizeof(buf), *psize)); + rc = sscanf(buf, "%x %n", dest, &pos); + if (!rc) + goto err_data; + + *psrc += pos; + *psize -= pos; + } + + *pcomp = comp; + + return 1; + +err_data: + ZL3073X_FW_ERR_MSG(extack, "[%s] invalid or missing data", info->name); + + zl3073x_fw_component_free(comp); + + return -ENODATA; +} + +/** + * zl3073x_fw_free - Free allocated firmware + * @fw: firmware pointer + * + * The function frees existing firmware allocated by @zl3073x_fw_load. + */ +void zl3073x_fw_free(struct zl3073x_fw *fw) +{ + size_t i; + + if (!fw) + return; + + for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++) + zl3073x_fw_component_free(fw->component[i]); + + kfree(fw); +} + +/** + * zl3073x_fw_load - Load all components from source + * @zldev: zl3073x device structure + * @data: source buffer pointer + * @size: size of source buffer + * @extack: netlink extack pointer to report errors + * + * The functions allocate firmware structure and loads all components from + * the given buffer specified by @data and @size. + * + * Return: pointer to firmware on success, error pointer on error + */ +struct zl3073x_fw *zl3073x_fw_load(struct zl3073x_dev *zldev, const char *data, + size_t size, struct netlink_ext_ack *extack) +{ + struct zl3073x_fw_component *comp; + enum zl3073x_fw_component_id id; + struct zl3073x_fw *fw; + ssize_t rc; + + /* Allocate firmware structure */ + fw = kzalloc(sizeof(*fw), GFP_KERNEL); + if (!fw) + return ERR_PTR(-ENOMEM); + + do { + /* Load single component */ + rc = zl3073x_fw_component_load(zldev, &comp, &data, &size, + extack); + if (rc <= 0) + /* Everything was read or error occurred */ + break; + + id = comp->id; + + /* Report error if the given component is present twice + * or more. + */ + if (fw->component[id]) { + ZL3073X_FW_ERR_MSG(extack, + "duplicate component '%s' detected", + component_info[id].name); + zl3073x_fw_component_free(comp); + rc = -EINVAL; + break; + } + + fw->component[id] = comp; + } while (true); + + if (rc) { + /* Free allocated firmware in case of error */ + zl3073x_fw_free(fw); + return ERR_PTR(rc); + } + + return fw; +} + +/** + * zl3073x_flash_bundle_flash - Flash all components + * @zldev: zl3073x device structure + * @components: pointer to components array + * @extack: netlink extack pointer to report errors + * + * Returns 0 in case of success or negative number otherwise. + */ +static int +zl3073x_fw_component_flash(struct zl3073x_dev *zldev, + struct zl3073x_fw_component *comp, + struct netlink_ext_ack *extack) +{ + const struct zl3073x_fw_component_info *info; + int rc; + + info = &component_info[comp->id]; + + switch (info->flash_type) { + case ZL3073X_FLASH_TYPE_NONE: + /* Non-flashable component - used for utility */ + return 0; + case ZL3073X_FLASH_TYPE_SECTORS: + rc = zl3073x_flash_sectors(zldev, info->name, info->dest_page, + info->load_addr, comp->data, + comp->size, extack); + break; + case ZL3073X_FLASH_TYPE_PAGE: + rc = zl3073x_flash_page(zldev, info->name, info->dest_page, + info->load_addr, comp->data, comp->size, + extack); + break; + case ZL3073X_FLASH_TYPE_PAGE_AND_COPY: + rc = zl3073x_flash_page(zldev, info->name, info->dest_page, + info->load_addr, comp->data, comp->size, + extack); + if (!rc) + rc = zl3073x_flash_page_copy(zldev, info->name, + info->dest_page, + info->copy_page, extack); + break; + } + if (rc) + ZL3073X_FW_ERR_MSG(extack, "Failed to flash component '%s'", + info->name); + + return rc; +} + +int zl3073x_fw_flash(struct zl3073x_dev *zldev, struct zl3073x_fw *zlfw, + struct netlink_ext_ack *extack) +{ + int i, rc = 0; + + for (i = 0; i < ZL_FW_NUM_COMPONENTS; i++) { + if (!zlfw->component[i]) + continue; /* Component is not present */ + + rc = zl3073x_fw_component_flash(zldev, zlfw->component[i], + extack); + if (rc) + break; + } + + return rc; +} diff --git a/drivers/dpll/zl3073x/fw.h b/drivers/dpll/zl3073x/fw.h new file mode 100644 index 000000000000..fcaa89ab075e --- /dev/null +++ b/drivers/dpll/zl3073x/fw.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _ZL3073X_FW_H +#define _ZL3073X_FW_H + +/* + * enum zl3073x_fw_component_id - Identifiers for possible flash components + */ +enum zl3073x_fw_component_id { + ZL_FW_COMPONENT_INVALID = -1, + ZL_FW_COMPONENT_UTIL = 0, + ZL_FW_COMPONENT_FW1, + ZL_FW_COMPONENT_FW2, + ZL_FW_COMPONENT_FW3, + ZL_FW_COMPONENT_CFG0, + ZL_FW_COMPONENT_CFG1, + ZL_FW_COMPONENT_CFG2, + ZL_FW_COMPONENT_CFG3, + ZL_FW_COMPONENT_CFG4, + ZL_FW_COMPONENT_CFG5, + ZL_FW_COMPONENT_CFG6, + ZL_FW_NUM_COMPONENTS +}; + +/** + * struct zl3073x_fw_component - Firmware component + * @id: Flash component ID + * @size: Size of the buffer + * @data: Pointer to buffer with component data + */ +struct zl3073x_fw_component { + enum zl3073x_fw_component_id id; + size_t size; + void *data; +}; + +/** + * struct zl3073x_fw - Firmware bundle + * @component: firmware components array + */ +struct zl3073x_fw { + struct zl3073x_fw_component *component[ZL_FW_NUM_COMPONENTS]; +}; + +struct zl3073x_fw *zl3073x_fw_load(struct zl3073x_dev *zldev, const char *data, + size_t size, struct netlink_ext_ack *extack); +void zl3073x_fw_free(struct zl3073x_fw *fw); + +int zl3073x_fw_flash(struct zl3073x_dev *zldev, struct zl3073x_fw *zlfw, + struct netlink_ext_ack *extack); + +#endif /* _ZL3073X_FW_H */ diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h index 614e33128a5c..19a25325bd9c 100644 --- a/drivers/dpll/zl3073x/regs.h +++ b/drivers/dpll/zl3073x/regs.h @@ -72,6 +72,9 @@ #define ZL_REG_FW_VER ZL_REG(0, 0x05, 2) #define ZL_REG_CUSTOM_CONFIG_VER ZL_REG(0, 0x07, 4) +#define ZL_REG_RESET_STATUS ZL_REG(0, 0x18, 1) +#define ZL_REG_RESET_STATUS_RESET BIT(0) + /************************* * Register Page 2, Status *************************/ @@ -260,4 +263,52 @@ #define ZL_REG_OUTPUT_ESYNC_WIDTH ZL_REG(14, 0x18, 4) #define ZL_REG_OUTPUT_PHASE_COMP ZL_REG(14, 0x20, 4) +/* + * Register Page 255 - HW registers access + */ +#define ZL_REG_HWREG_OP ZL_REG(0xff, 0x00, 1) +#define ZL_HWREG_OP_WRITE 0x28 +#define ZL_HWREG_OP_READ 0x29 +#define ZL_HWREG_OP_PENDING BIT(1) + +#define ZL_REG_HWREG_ADDR ZL_REG(0xff, 0x04, 4) +#define ZL_REG_HWREG_WRITE_DATA ZL_REG(0xff, 0x08, 4) +#define ZL_REG_HWREG_READ_DATA ZL_REG(0xff, 0x0c, 4) + +/* + * Registers available in flash mode + */ +#define ZL_REG_FLASH_HASH ZL_REG(0, 0x78, 4) +#define ZL_REG_FLASH_FAMILY ZL_REG(0, 0x7c, 1) +#define ZL_REG_FLASH_RELEASE ZL_REG(0, 0x7d, 1) + +#define ZL_REG_HOST_CONTROL ZL_REG(1, 0x02, 1) +#define ZL_HOST_CONTROL_ENABLE BIT(0) + +#define ZL_REG_IMAGE_START_ADDR ZL_REG(1, 0x04, 4) +#define ZL_REG_IMAGE_SIZE ZL_REG(1, 0x08, 4) +#define ZL_REG_FLASH_INDEX_READ ZL_REG(1, 0x0c, 4) +#define ZL_REG_FLASH_INDEX_WRITE ZL_REG(1, 0x10, 4) +#define ZL_REG_FILL_PATTERN ZL_REG(1, 0x14, 4) + +#define ZL_REG_WRITE_FLASH ZL_REG(1, 0x18, 1) +#define ZL_WRITE_FLASH_OP GENMASK(2, 0) +#define ZL_WRITE_FLASH_OP_DONE 0x0 +#define ZL_WRITE_FLASH_OP_SECTORS 0x2 +#define ZL_WRITE_FLASH_OP_PAGE 0x3 +#define ZL_WRITE_FLASH_OP_COPY_PAGE 0x4 + +#define ZL_REG_FLASH_INFO ZL_REG(2, 0x00, 1) +#define ZL_FLASH_INFO_SECTOR_SIZE GENMASK(3, 0) +#define ZL_FLASH_INFO_SECTOR_4K 0 +#define ZL_FLASH_INFO_SECTOR_64K 1 + +#define ZL_REG_ERROR_COUNT ZL_REG(2, 0x04, 4) +#define ZL_REG_ERROR_CAUSE ZL_REG(2, 0x08, 4) + +#define ZL_REG_OP_STATE ZL_REG(2, 0x14, 1) +#define ZL_OP_STATE_NO_COMMAND 0 +#define ZL_OP_STATE_PENDING 1 +#define ZL_OP_STATE_DONE 2 + #endif /* _ZL3073X_REGS_H */ |