| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Tegra host1x Syncpoints |
| * |
| * Copyright (c) 2010-2013, NVIDIA Corporation. |
| */ |
| |
| #include <linux/io.h> |
| |
| #include "../dev.h" |
| #include "../syncpt.h" |
| |
| /* |
| * Write the current syncpoint value back to hw. |
| */ |
| static void syncpt_restore(struct host1x_syncpt *sp) |
| { |
| u32 min = host1x_syncpt_read_min(sp); |
| struct host1x *host = sp->host; |
| |
| host1x_sync_writel(host, min, HOST1X_SYNC_SYNCPT(sp->id)); |
| } |
| |
| /* |
| * Write the current waitbase value back to hw. |
| */ |
| static void syncpt_restore_wait_base(struct host1x_syncpt *sp) |
| { |
| #if HOST1X_HW < 7 |
| struct host1x *host = sp->host; |
| |
| host1x_sync_writel(host, sp->base_val, |
| HOST1X_SYNC_SYNCPT_BASE(sp->id)); |
| #endif |
| } |
| |
| /* |
| * Read waitbase value from hw. |
| */ |
| static void syncpt_read_wait_base(struct host1x_syncpt *sp) |
| { |
| #if HOST1X_HW < 7 |
| struct host1x *host = sp->host; |
| |
| sp->base_val = |
| host1x_sync_readl(host, HOST1X_SYNC_SYNCPT_BASE(sp->id)); |
| #endif |
| } |
| |
| /* |
| * Updates the last value read from hardware. |
| */ |
| static u32 syncpt_load(struct host1x_syncpt *sp) |
| { |
| struct host1x *host = sp->host; |
| u32 old, live; |
| |
| /* Loop in case there's a race writing to min_val */ |
| do { |
| old = host1x_syncpt_read_min(sp); |
| live = host1x_sync_readl(host, HOST1X_SYNC_SYNCPT(sp->id)); |
| } while ((u32)atomic_cmpxchg(&sp->min_val, old, live) != old); |
| |
| if (!host1x_syncpt_check_max(sp, live)) |
| dev_err(host->dev, "%s failed: id=%u, min=%d, max=%d\n", |
| __func__, sp->id, host1x_syncpt_read_min(sp), |
| host1x_syncpt_read_max(sp)); |
| |
| return live; |
| } |
| |
| /* |
| * Write a cpu syncpoint increment to the hardware, without touching |
| * the cache. |
| */ |
| static int syncpt_cpu_incr(struct host1x_syncpt *sp) |
| { |
| struct host1x *host = sp->host; |
| u32 reg_offset = sp->id / 32; |
| |
| if (!host1x_syncpt_client_managed(sp) && |
| host1x_syncpt_idle(sp)) |
| return -EINVAL; |
| |
| host1x_sync_writel(host, BIT(sp->id % 32), |
| HOST1X_SYNC_SYNCPT_CPU_INCR(reg_offset)); |
| wmb(); |
| |
| return 0; |
| } |
| |
| /** |
| * syncpt_assign_to_channel() - Assign syncpoint to channel |
| * @sp: syncpoint |
| * @ch: channel |
| * |
| * On chips with the syncpoint protection feature (Tegra186+), assign @sp to |
| * @ch, preventing other channels from incrementing the syncpoints. If @ch is |
| * NULL, unassigns the syncpoint. |
| * |
| * On older chips, do nothing. |
| */ |
| static void syncpt_assign_to_channel(struct host1x_syncpt *sp, |
| struct host1x_channel *ch) |
| { |
| #if HOST1X_HW >= 6 |
| struct host1x *host = sp->host; |
| |
| if (!host->hv_regs) |
| return; |
| |
| host1x_sync_writel(host, |
| HOST1X_SYNC_SYNCPT_CH_APP_CH(ch ? ch->id : 0xff), |
| HOST1X_SYNC_SYNCPT_CH_APP(sp->id)); |
| #endif |
| } |
| |
| /** |
| * syncpt_enable_protection() - Enable syncpoint protection |
| * @host: host1x instance |
| * |
| * On chips with the syncpoint protection feature (Tegra186+), enable this |
| * feature. On older chips, do nothing. |
| */ |
| static void syncpt_enable_protection(struct host1x *host) |
| { |
| #if HOST1X_HW >= 6 |
| if (!host->hv_regs) |
| return; |
| |
| host1x_hypervisor_writel(host, HOST1X_HV_SYNCPT_PROT_EN_CH_EN, |
| HOST1X_HV_SYNCPT_PROT_EN); |
| #endif |
| } |
| |
| static const struct host1x_syncpt_ops host1x_syncpt_ops = { |
| .restore = syncpt_restore, |
| .restore_wait_base = syncpt_restore_wait_base, |
| .load_wait_base = syncpt_read_wait_base, |
| .load = syncpt_load, |
| .cpu_incr = syncpt_cpu_incr, |
| .assign_to_channel = syncpt_assign_to_channel, |
| .enable_protection = syncpt_enable_protection, |
| }; |