| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Power management interface for GCIP devices. |
| * |
| * Copyright (C) 2023 Google LLC |
| */ |
| |
| #include <linux/device.h> |
| #include <linux/mutex.h> |
| #include <linux/slab.h> |
| #include <linux/workqueue.h> |
| |
| #include <gcip/gcip-pm.h> |
| |
| #define GCIP_ASYNC_POWER_DOWN_RETRY_DELAY 200 /* ms */ |
| |
| /* Caller must hold @pm->lock. */ |
| static void gcip_pm_try_power_down(struct gcip_pm *pm) |
| { |
| int ret; |
| |
| gcip_pm_lockdep_assert_held(pm); |
| |
| ret = pm->power_down(pm->data); |
| |
| if (ret == -EAGAIN) { |
| dev_warn(pm->dev, "Power down request denied, retrying in %d ms\n", |
| GCIP_ASYNC_POWER_DOWN_RETRY_DELAY); |
| pm->power_down_pending = true; |
| schedule_delayed_work(&pm->power_down_work, |
| msecs_to_jiffies(GCIP_ASYNC_POWER_DOWN_RETRY_DELAY)); |
| } else { |
| if (ret) |
| dev_err(pm->dev, "Power down request failed (%d)\n", ret); |
| pm->power_down_pending = false; |
| } |
| } |
| |
| /* Worker for async power down. */ |
| static void gcip_pm_async_power_down_work(struct work_struct *work) |
| { |
| struct delayed_work *dwork = container_of(work, struct delayed_work, work); |
| struct gcip_pm *pm = container_of(dwork, struct gcip_pm, power_down_work); |
| |
| mutex_lock(&pm->lock); |
| |
| if (pm->power_down_pending) |
| gcip_pm_try_power_down(pm); |
| else |
| dev_info(pm->dev, "Delayed power down cancelled\n"); |
| |
| mutex_unlock(&pm->lock); |
| } |
| |
| /* Worker for async gcip_pm_put(). */ |
| static void gcip_pm_async_put_work(struct work_struct *work) |
| { |
| struct gcip_pm *pm = container_of(work, struct gcip_pm, put_async_work); |
| |
| gcip_pm_put(pm); |
| } |
| |
| struct gcip_pm *gcip_pm_create(const struct gcip_pm_args *args) |
| { |
| struct gcip_pm *pm; |
| int ret; |
| |
| if (!args->dev || !args->power_up || !args->power_down) |
| return ERR_PTR(-EINVAL); |
| |
| pm = devm_kzalloc(args->dev, sizeof(*pm), GFP_KERNEL); |
| if (!pm) |
| return ERR_PTR(-ENOMEM); |
| |
| pm->dev = args->dev; |
| pm->data = args->data; |
| pm->after_create = args->after_create; |
| pm->before_destroy = args->before_destroy; |
| pm->power_up = args->power_up; |
| pm->power_down = args->power_down; |
| |
| mutex_init(&pm->lock); |
| INIT_DELAYED_WORK(&pm->power_down_work, gcip_pm_async_power_down_work); |
| INIT_WORK(&pm->put_async_work, gcip_pm_async_put_work); |
| |
| if (pm->after_create) { |
| ret = pm->after_create(pm->data); |
| if (ret) { |
| devm_kfree(args->dev, pm); |
| return ERR_PTR(ret); |
| } |
| } |
| |
| return pm; |
| } |
| |
| void gcip_pm_destroy(struct gcip_pm *pm) |
| { |
| if (!pm) |
| return; |
| |
| pm->power_down_pending = false; |
| cancel_delayed_work_sync(&pm->power_down_work); |
| |
| if (pm->before_destroy) |
| pm->before_destroy(pm->data); |
| |
| devm_kfree(pm->dev, pm); |
| } |
| |
| /* |
| * Increases the counter and calls the power_up callback. |
| * |
| * Returns zero on success. |
| * |
| * Caller holds pm->lock. |
| */ |
| static int gcip_pm_get_locked(struct gcip_pm *pm) |
| { |
| int ret = 0; |
| |
| gcip_pm_lockdep_assert_held(pm); |
| |
| if (!pm->count) { |
| if (pm->power_down_pending) |
| pm->power_down_pending = false; |
| else |
| ret = pm->power_up(pm->data); |
| } |
| |
| if (!ret) |
| pm->count++; |
| |
| dev_dbg(pm->dev, "%s: %d\n", __func__, pm->count); |
| |
| return ret; |
| } |
| |
| int gcip_pm_get_if_powered(struct gcip_pm *pm, bool blocking) |
| { |
| int ret = -EAGAIN; |
| |
| if (!pm) |
| return 0; |
| |
| /* Fast fails without holding the lock. */ |
| if (!pm->count) |
| return ret; |
| |
| if (blocking) |
| mutex_lock(&pm->lock); |
| else if (!mutex_trylock(&pm->lock)) |
| return ret; |
| |
| if (pm->count) |
| ret = gcip_pm_get_locked(pm); |
| |
| mutex_unlock(&pm->lock); |
| |
| return ret; |
| } |
| |
| int gcip_pm_get(struct gcip_pm *pm) |
| { |
| int ret; |
| |
| if (!pm) |
| return 0; |
| |
| mutex_lock(&pm->lock); |
| ret = gcip_pm_get_locked(pm); |
| mutex_unlock(&pm->lock); |
| |
| return ret; |
| } |
| |
| void gcip_pm_put(struct gcip_pm *pm) |
| { |
| if (!pm) |
| return; |
| |
| mutex_lock(&pm->lock); |
| |
| if (WARN_ON(!pm->count)) |
| goto unlock; |
| |
| if (!--pm->count) { |
| pm->power_down_pending = true; |
| gcip_pm_try_power_down(pm); |
| } |
| |
| dev_dbg(pm->dev, "%s: %d\n", __func__, pm->count); |
| |
| unlock: |
| mutex_unlock(&pm->lock); |
| } |
| |
| void gcip_pm_put_async(struct gcip_pm *pm) |
| { |
| schedule_work(&pm->put_async_work); |
| } |
| |
| void gcip_pm_flush_put_work(struct gcip_pm *pm) |
| { |
| flush_work(&pm->put_async_work); |
| } |
| |
| int gcip_pm_get_count(struct gcip_pm *pm) |
| { |
| if (!pm) |
| return 0; |
| |
| return pm->count; |
| } |
| |
| bool gcip_pm_is_powered(struct gcip_pm *pm) |
| { |
| /* Assumes powered-on in case of no power interface. */ |
| return pm ? gcip_pm_get_count(pm) > 0 : true; |
| } |
| |
| void gcip_pm_shutdown(struct gcip_pm *pm, bool force) |
| { |
| if (!pm) |
| return; |
| |
| mutex_lock(&pm->lock); |
| |
| if (pm->count) { |
| if (!force) |
| goto unlock; |
| dev_warn(pm->dev, "Force shutdown with power up count: %d", pm->count); |
| } |
| |
| gcip_pm_try_power_down(pm); |
| |
| unlock: |
| mutex_unlock(&pm->lock); |
| } |