Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213// SPDX-License-Identifier: GPL-2.0/* * dpll_core.c - DPLL subsystem kernel-space interface implementation. * * Copyright (c) 2023 Meta Platforms, Inc. and affiliates * Copyright (c) 2023 Intel Corporation. */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>#include <linux/err.h>#include <linux/idr.h>#include <linux/module.h>#include <linux/property.h>#include <linux/slab.h>#include <linux/string.h>
#include "dpll_core.h"#include "dpll_netlink.h"
/* Mutex lock to protect DPLL subsystem devices and pins */DEFINE_MUTEX(dpll_lock);
DEFINE_XARRAY_FLAGS(dpll_device_xa, XA_FLAGS_ALLOC);DEFINE_XARRAY_FLAGS(dpll_pin_xa, XA_FLAGS_ALLOC);
static RAW_NOTIFIER_HEAD(dpll_notifier_chain);static DEFINE_IDA(dpll_pin_idx_ida);
static u32 dpll_device_xa_id;static u32 dpll_pin_xa_id;
#define ASSERT_DPLL_REGISTERED(d) \ WARN_ON_ONCE(!xa_get_mark(&dpll_device_xa, (d)->id, DPLL_REGISTERED))#define ASSERT_DPLL_NOT_REGISTERED(d) \ WARN_ON_ONCE(xa_get_mark(&dpll_device_xa, (d)->id, DPLL_REGISTERED))#define ASSERT_DPLL_PIN_REGISTERED(p) \ WARN_ON_ONCE(!xa_get_mark(&dpll_pin_xa, (p)->id, DPLL_REGISTERED))
struct dpll_device_registration { struct list_head list; const struct dpll_device_ops *ops; void *priv; dpll_tracker tracker;};
struct dpll_pin_registration { struct list_head list; const struct dpll_pin_ops *ops; void *priv; void *cookie; dpll_tracker tracker;};
static int call_dpll_notifiers(unsigned long action, void *info){ lockdep_assert_held(&dpll_lock); return raw_notifier_call_chain(&dpll_notifier_chain, action, info);}
void dpll_device_notify(struct dpll_device *dpll, unsigned long action){ struct dpll_device_notifier_info info = { .dpll = dpll, .id = dpll->id, .idx = dpll->device_idx, .clock_id = dpll->clock_id, .type = dpll->type, };
call_dpll_notifiers(action, &info);}
void dpll_pin_notify(struct dpll_pin *pin, u64 src_clock_id, unsigned long action){ struct dpll_pin_notifier_info info = { .pin = pin, .id = pin->id, .idx = pin->pin_idx, .clock_id = pin->clock_id, .fwnode = pin->fwnode, .prop = &pin->prop, .src_clock_id = src_clock_id, };
call_dpll_notifiers(action, &info);}
static void dpll_device_tracker_alloc(struct dpll_device *dpll, dpll_tracker *tracker){#ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_alloc(&dpll->refcnt_tracker, tracker, GFP_KERNEL);#endif}
static void dpll_device_tracker_free(struct dpll_device *dpll, dpll_tracker *tracker){#ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_free(&dpll->refcnt_tracker, tracker);#endif}
static void __dpll_device_hold(struct dpll_device *dpll, dpll_tracker *tracker){ dpll_device_tracker_alloc(dpll, tracker); refcount_inc(&dpll->refcount);}
static void __dpll_device_put(struct dpll_device *dpll, dpll_tracker *tracker){ dpll_device_tracker_free(dpll, tracker); if (refcount_dec_and_test(&dpll->refcount)) { ASSERT_DPLL_NOT_REGISTERED(dpll); WARN_ON_ONCE(!xa_empty(&dpll->pin_refs)); xa_destroy(&dpll->pin_refs); xa_erase(&dpll_device_xa, dpll->id); WARN_ON(!list_empty(&dpll->registration_list)); ref_tracker_dir_exit(&dpll->refcnt_tracker); kfree(dpll); }}
static void dpll_pin_tracker_alloc(struct dpll_pin *pin, dpll_tracker *tracker){#ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_alloc(&pin->refcnt_tracker, tracker, GFP_KERNEL);#endif}
static void dpll_pin_tracker_free(struct dpll_pin *pin, dpll_tracker *tracker){#ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_free(&pin->refcnt_tracker, tracker);#endif}
static void __dpll_pin_hold(struct dpll_pin *pin, dpll_tracker *tracker){ dpll_pin_tracker_alloc(pin, tracker); refcount_inc(&pin->refcount);}
static void dpll_pin_idx_free(u32 pin_idx);static void dpll_pin_prop_free(struct dpll_pin_properties *prop);
static void __dpll_pin_put(struct dpll_pin *pin, dpll_tracker *tracker){ dpll_pin_tracker_free(pin, tracker); if (refcount_dec_and_test(&pin->refcount)) { xa_erase(&dpll_pin_xa, pin->id); xa_destroy(&pin->dpll_refs); xa_destroy(&pin->parent_refs); xa_destroy(&pin->ref_sync_pins); dpll_pin_prop_free(&pin->prop); fwnode_handle_put(pin->fwnode); dpll_pin_idx_free(pin->pin_idx); ref_tracker_dir_exit(&pin->refcnt_tracker); kfree_rcu(pin, rcu); }}
struct dpll_device *dpll_device_get_by_id(int id){ if (xa_get_mark(&dpll_device_xa, id, DPLL_REGISTERED)) return xa_load(&dpll_device_xa, id);
return NULL;}
static struct dpll_pin_registration *dpll_pin_registration_find(struct dpll_pin_ref *ref, const struct dpll_pin_ops *ops, void *priv, void *cookie){ struct dpll_pin_registration *reg;
list_for_each_entry(reg, &ref->registration_list, list) { if (reg->ops == ops && reg->priv == priv && reg->cookie == cookie) return reg; } return NULL;}
static intdpll_xa_ref_pin_add(struct xarray *xa_pins, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv, void *cookie){ struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; bool ref_exists = false; unsigned long i; int ret;
xa_for_each(xa_pins, i, ref) { if (ref->pin != pin) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (reg) return -EEXIST; ref_exists = true; break; }
if (!ref_exists) { ref = kzalloc_obj(*ref); if (!ref) return -ENOMEM; ref->pin = pin; INIT_LIST_HEAD(&ref->registration_list); ret = xa_insert(xa_pins, pin->pin_idx, ref, GFP_KERNEL); if (ret) { kfree(ref); return ret; } refcount_set(&ref->refcount, 1); }
reg = kzalloc_obj(*reg); if (!reg) { if (!ref_exists) { xa_erase(xa_pins, pin->pin_idx); kfree(ref); } return -ENOMEM; } reg->ops = ops; reg->priv = priv; reg->cookie = cookie; __dpll_pin_hold(pin, ®->tracker); if (ref_exists) refcount_inc(&ref->refcount); list_add_tail(®->list, &ref->registration_list);
return 0;}
static int dpll_xa_ref_pin_del(struct xarray *xa_pins, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv, void *cookie){ struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; unsigned long i;
xa_for_each(xa_pins, i, ref) { if (ref->pin != pin) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (WARN_ON(!reg)) return -EINVAL; list_del(®->list); __dpll_pin_put(pin, ®->tracker); kfree(reg); if (refcount_dec_and_test(&ref->refcount)) { xa_erase(xa_pins, i); WARN_ON(!list_empty(&ref->registration_list)); kfree(ref); } return 0; }
return -EINVAL;}
static intdpll_xa_ref_dpll_add(struct xarray *xa_dplls, struct dpll_device *dpll, const struct dpll_pin_ops *ops, void *priv, void *cookie){ struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; bool ref_exists = false; unsigned long i; int ret;
xa_for_each(xa_dplls, i, ref) { if (ref->dpll != dpll) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (reg) return -EEXIST; ref_exists = true; break; }
if (!ref_exists) { ref = kzalloc_obj(*ref); if (!ref) return -ENOMEM; ref->dpll = dpll; INIT_LIST_HEAD(&ref->registration_list); ret = xa_insert(xa_dplls, dpll->id, ref, GFP_KERNEL); if (ret) { kfree(ref); return ret; } refcount_set(&ref->refcount, 1); }
reg = kzalloc_obj(*reg); if (!reg) { if (!ref_exists) { xa_erase(xa_dplls, dpll->id); kfree(ref); } return -ENOMEM; } reg->ops = ops; reg->priv = priv; reg->cookie = cookie; __dpll_device_hold(dpll, ®->tracker); if (ref_exists) refcount_inc(&ref->refcount); list_add_tail(®->list, &ref->registration_list);
return 0;}
static voiddpll_xa_ref_dpll_del(struct xarray *xa_dplls, struct dpll_device *dpll, const struct dpll_pin_ops *ops, void *priv, void *cookie){ struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; unsigned long i;
xa_for_each(xa_dplls, i, ref) { if (ref->dpll != dpll) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (WARN_ON(!reg)) return; list_del(®->list); __dpll_device_put(dpll, ®->tracker); kfree(reg); if (refcount_dec_and_test(&ref->refcount)) { xa_erase(xa_dplls, i); WARN_ON(!list_empty(&ref->registration_list)); kfree(ref); } return; }}
struct dpll_pin_ref *dpll_xa_ref_dpll_first(struct xarray *xa_refs){ struct dpll_pin_ref *ref; unsigned long i = 0;
ref = xa_find(xa_refs, &i, ULONG_MAX, XA_PRESENT); WARN_ON(!ref); return ref;}
static struct dpll_device *dpll_device_alloc(const u64 clock_id, u32 device_idx, struct module *module){ struct dpll_device *dpll; int ret;
dpll = kzalloc_obj(*dpll); if (!dpll) return ERR_PTR(-ENOMEM); refcount_set(&dpll->refcount, 1); INIT_LIST_HEAD(&dpll->registration_list); dpll->device_idx = device_idx; dpll->clock_id = clock_id; dpll->module = module; ret = xa_alloc_cyclic(&dpll_device_xa, &dpll->id, dpll, xa_limit_32b, &dpll_device_xa_id, GFP_KERNEL); if (ret < 0) { kfree(dpll); return ERR_PTR(ret); } xa_init_flags(&dpll->pin_refs, XA_FLAGS_ALLOC); ref_tracker_dir_init(&dpll->refcnt_tracker, 128, "dpll_device");
return dpll;}
/** * dpll_device_get - find existing or create new dpll device * @clock_id: clock_id of creator * @device_idx: idx given by device driver * @module: reference to registering module * @tracker: tracking object for the acquired reference * * Get existing object of a dpll device, unique for given arguments. * Create new if doesn't exist yet. * * Context: Acquires a lock (dpll_lock) * Return: * * valid dpll_device struct pointer if succeeded * * ERR_PTR(X) - error */struct dpll_device *dpll_device_get(u64 clock_id, u32 device_idx, struct module *module, dpll_tracker *tracker){ struct dpll_device *dpll, *ret = NULL; unsigned long index;
mutex_lock(&dpll_lock); xa_for_each(&dpll_device_xa, index, dpll) { if (dpll->clock_id == clock_id && dpll->device_idx == device_idx && dpll->module == module) { __dpll_device_hold(dpll, tracker); ret = dpll; break; } } if (!ret) { ret = dpll_device_alloc(clock_id, device_idx, module); if (!IS_ERR(ret)) dpll_device_tracker_alloc(ret, tracker); }
mutex_unlock(&dpll_lock);
return ret;}EXPORT_SYMBOL_GPL(dpll_device_get);
/** * dpll_device_put - decrease the refcount and free memory if possible * @dpll: dpll_device struct pointer * @tracker: tracking object for the acquired reference * * Context: Acquires a lock (dpll_lock) * Drop reference for a dpll device, if all references are gone, delete * dpll device object. */void dpll_device_put(struct dpll_device *dpll, dpll_tracker *tracker){ mutex_lock(&dpll_lock); __dpll_device_put(dpll, tracker); mutex_unlock(&dpll_lock);}EXPORT_SYMBOL_GPL(dpll_device_put);
static struct dpll_device_registration *dpll_device_registration_find(struct dpll_device *dpll, const struct dpll_device_ops *ops, void *priv){ struct dpll_device_registration *reg;
list_for_each_entry(reg, &dpll->registration_list, list) { if (reg->ops == ops && reg->priv == priv) return reg; } return NULL;}
/** * dpll_device_register - register the dpll device in the subsystem * @dpll: pointer to a dpll * @type: type of a dpll * @ops: ops for a dpll device * @priv: pointer to private information of owner * * Make dpll device available for user space. * * Context: Acquires a lock (dpll_lock) * Return: * * 0 on success * * negative - error value */int dpll_device_register(struct dpll_device *dpll, enum dpll_type type, const struct dpll_device_ops *ops, void *priv){ struct dpll_device_registration *reg; bool first_registration = false;
if (WARN_ON(!ops)) return -EINVAL; if (WARN_ON(!ops->mode_get)) return -EINVAL; if (WARN_ON(!ops->lock_status_get)) return -EINVAL; if (WARN_ON(type < DPLL_TYPE_PPS || type > DPLL_TYPE_MAX)) return -EINVAL;
mutex_lock(&dpll_lock); reg = dpll_device_registration_find(dpll, ops, priv); if (reg) { mutex_unlock(&dpll_lock); return -EEXIST; }
reg = kzalloc_obj(*reg); if (!reg) { mutex_unlock(&dpll_lock); return -ENOMEM; } reg->ops = ops; reg->priv = priv; dpll->type = type; __dpll_device_hold(dpll, ®->tracker); first_registration = list_empty(&dpll->registration_list); list_add_tail(®->list, &dpll->registration_list); if (!first_registration) { mutex_unlock(&dpll_lock); return 0; }
xa_set_mark(&dpll_device_xa, dpll->id, DPLL_REGISTERED); dpll_device_create_ntf(dpll); mutex_unlock(&dpll_lock);
return 0;}EXPORT_SYMBOL_GPL(dpll_device_register);
/** * dpll_device_unregister - unregister dpll device * @dpll: registered dpll pointer * @ops: ops for a dpll device * @priv: pointer to private information of owner * * Unregister device, make it unavailable for userspace. * Note: It does not free the memory * Context: Acquires a lock (dpll_lock) */void dpll_device_unregister(struct dpll_device *dpll, const struct dpll_device_ops *ops, void *priv){ struct dpll_device_registration *reg;
mutex_lock(&dpll_lock); ASSERT_DPLL_REGISTERED(dpll); dpll_device_delete_ntf(dpll); reg = dpll_device_registration_find(dpll, ops, priv); if (WARN_ON(!reg)) { mutex_unlock(&dpll_lock); return; } list_del(®->list); __dpll_device_put(dpll, ®->tracker); kfree(reg);
if (!list_empty(&dpll->registration_list)) { mutex_unlock(&dpll_lock); return; } xa_clear_mark(&dpll_device_xa, dpll->id, DPLL_REGISTERED); mutex_unlock(&dpll_lock);}EXPORT_SYMBOL_GPL(dpll_device_unregister);
static int dpll_pin_idx_alloc(u32 *pin_idx){ int ret;
if (!pin_idx) return -EINVAL;
/* Alloc unique number from IDA. Number belongs to <0, INT_MAX> range */ ret = ida_alloc(&dpll_pin_idx_ida, GFP_KERNEL); if (ret < 0) return ret;
/* Map the value to dynamic pin index range <INT_MAX+1, U32_MAX> */ *pin_idx = (u32)ret + INT_MAX + 1;
return 0;}
static void dpll_pin_idx_free(u32 pin_idx){ if (pin_idx <= INT_MAX) return; /* Not a dynamic pin index */
/* Map the index value from dynamic pin index range to IDA range and * free it. */ pin_idx -= (u32)INT_MAX + 1; ida_free(&dpll_pin_idx_ida, pin_idx);}
static void dpll_pin_prop_free(struct dpll_pin_properties *prop){ kfree(prop->package_label); kfree(prop->panel_label); kfree(prop->board_label); kfree(prop->freq_supported);}
static int dpll_pin_prop_dup(const struct dpll_pin_properties *src, struct dpll_pin_properties *dst){ if (WARN_ON(src->freq_supported && !src->freq_supported_num)) return -EINVAL;
memcpy(dst, src, sizeof(*dst)); if (src->freq_supported) { size_t freq_size = src->freq_supported_num * sizeof(*src->freq_supported); dst->freq_supported = kmemdup(src->freq_supported, freq_size, GFP_KERNEL); if (!dst->freq_supported) return -ENOMEM; } if (src->board_label) { dst->board_label = kstrdup(src->board_label, GFP_KERNEL); if (!dst->board_label) goto err_board_label; } if (src->panel_label) { dst->panel_label = kstrdup(src->panel_label, GFP_KERNEL); if (!dst->panel_label) goto err_panel_label; } if (src->package_label) { dst->package_label = kstrdup(src->package_label, GFP_KERNEL); if (!dst->package_label) goto err_package_label; }
return 0;
err_package_label: kfree(dst->panel_label);err_panel_label: kfree(dst->board_label);err_board_label: kfree(dst->freq_supported); return -ENOMEM;}
static struct dpll_pin *dpll_pin_alloc(u64 clock_id, u32 pin_idx, struct module *module, const struct dpll_pin_properties *prop){ struct dpll_pin *pin; int ret;
if (pin_idx == DPLL_PIN_IDX_UNSPEC) { ret = dpll_pin_idx_alloc(&pin_idx); if (ret) return ERR_PTR(ret); } else if (pin_idx > INT_MAX) { return ERR_PTR(-EINVAL); } pin = kzalloc_obj(*pin); if (!pin) { ret = -ENOMEM; goto err_pin_alloc; } pin->pin_idx = pin_idx; pin->clock_id = clock_id; pin->module = module; strscpy(pin->module_name, module_name(module)); if (WARN_ON(prop->type < DPLL_PIN_TYPE_MUX || prop->type > DPLL_PIN_TYPE_MAX)) { ret = -EINVAL; goto err_pin_prop; } ret = dpll_pin_prop_dup(prop, &pin->prop); if (ret) goto err_pin_prop; refcount_set(&pin->refcount, 1); xa_init_flags(&pin->dpll_refs, XA_FLAGS_ALLOC); xa_init_flags(&pin->parent_refs, XA_FLAGS_ALLOC); xa_init_flags(&pin->ref_sync_pins, XA_FLAGS_ALLOC); ret = xa_alloc_cyclic(&dpll_pin_xa, &pin->id, pin, xa_limit_32b, &dpll_pin_xa_id, GFP_KERNEL); if (ret < 0) goto err_xa_alloc; ref_tracker_dir_init(&pin->refcnt_tracker, 128, "dpll_pin"); return pin;err_xa_alloc: xa_destroy(&pin->dpll_refs); xa_destroy(&pin->parent_refs); xa_destroy(&pin->ref_sync_pins); dpll_pin_prop_free(&pin->prop);err_pin_prop: kfree(pin);err_pin_alloc: dpll_pin_idx_free(pin_idx); return ERR_PTR(ret);}
static void dpll_netdev_pin_assign(struct net_device *dev, struct dpll_pin *dpll_pin){ rtnl_lock(); rcu_assign_pointer(dev->dpll_pin, dpll_pin); rtnl_unlock();}
void dpll_netdev_pin_set(struct net_device *dev, struct dpll_pin *dpll_pin){ WARN_ON(!dpll_pin); dpll_netdev_pin_assign(dev, dpll_pin);}EXPORT_SYMBOL(dpll_netdev_pin_set);
void dpll_netdev_pin_clear(struct net_device *dev){ dpll_netdev_pin_assign(dev, NULL);}EXPORT_SYMBOL(dpll_netdev_pin_clear);
int register_dpll_notifier(struct notifier_block *nb){ int ret;
mutex_lock(&dpll_lock); ret = raw_notifier_chain_register(&dpll_notifier_chain, nb); mutex_unlock(&dpll_lock); return ret;}EXPORT_SYMBOL_GPL(register_dpll_notifier);
int unregister_dpll_notifier(struct notifier_block *nb){ int ret;
mutex_lock(&dpll_lock); ret = raw_notifier_chain_unregister(&dpll_notifier_chain, nb); mutex_unlock(&dpll_lock); return ret;}EXPORT_SYMBOL_GPL(unregister_dpll_notifier);
/** * dpll_pin_get - find existing or create new dpll pin * @clock_id: clock_id of creator * @pin_idx: idx given by dev driver * @module: reference to registering module * @prop: dpll pin properties * @tracker: tracking object for the acquired reference * * Get existing object of a pin (unique for given arguments) or create new * if doesn't exist yet. * * Context: Acquires a lock (dpll_lock) * Return: * * valid allocated dpll_pin struct pointer if succeeded * * ERR_PTR(X) - error */struct dpll_pin *dpll_pin_get(u64 clock_id, u32 pin_idx, struct module *module, const struct dpll_pin_properties *prop, dpll_tracker *tracker){ struct dpll_pin *pos, *ret = NULL; unsigned long i;
mutex_lock(&dpll_lock); xa_for_each(&dpll_pin_xa, i, pos) { if (pos->clock_id == clock_id && pos->pin_idx == pin_idx && pos->module == module) { __dpll_pin_hold(pos, tracker); ret = pos; break; } } if (!ret) { ret = dpll_pin_alloc(clock_id, pin_idx, module, prop); if (!IS_ERR(ret)) dpll_pin_tracker_alloc(ret, tracker); } mutex_unlock(&dpll_lock);
return ret;}EXPORT_SYMBOL_GPL(dpll_pin_get);
/** * dpll_pin_put - decrease the refcount and free memory if possible * @pin: pointer to a pin to be put * @tracker: tracking object for the acquired reference * * Drop reference for a pin, if all references are gone, delete pin object. * * Context: Acquires a lock (dpll_lock) */void dpll_pin_put(struct dpll_pin *pin, dpll_tracker *tracker){ mutex_lock(&dpll_lock); __dpll_pin_put(pin, tracker); mutex_unlock(&dpll_lock);}EXPORT_SYMBOL_GPL(dpll_pin_put);
/** * dpll_pin_fwnode_set - set dpll pin firmware node reference * @pin: pointer to a dpll pin * @fwnode: firmware node handle * * Set firmware node handle for the given dpll pin. */void dpll_pin_fwnode_set(struct dpll_pin *pin, struct fwnode_handle *fwnode){ mutex_lock(&dpll_lock); fwnode_handle_put(pin->fwnode); /* Drop fwnode previously set */ pin->fwnode = fwnode_handle_get(fwnode); mutex_unlock(&dpll_lock);}EXPORT_SYMBOL_GPL(dpll_pin_fwnode_set);
/** * fwnode_dpll_pin_find - find dpll pin by firmware node reference * @fwnode: reference to firmware node * @tracker: tracking object for the acquired reference * * Get existing object of a pin that is associated with given firmware node * reference. * * Context: Acquires a lock (dpll_lock) * Return: * * valid dpll_pin pointer on success * * NULL when no such pin exists */struct dpll_pin *fwnode_dpll_pin_find(struct fwnode_handle *fwnode, dpll_tracker *tracker){ struct dpll_pin *pin, *ret = NULL; unsigned long index;
mutex_lock(&dpll_lock); xa_for_each(&dpll_pin_xa, index, pin) { if (pin->fwnode == fwnode) { __dpll_pin_hold(pin, tracker); ret = pin; break; } } mutex_unlock(&dpll_lock);
return ret;}EXPORT_SYMBOL_GPL(fwnode_dpll_pin_find);
static int__dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv, void *cookie){ int ret;
ret = dpll_xa_ref_pin_add(&dpll->pin_refs, pin, ops, priv, cookie); if (ret) return ret; ret = dpll_xa_ref_dpll_add(&pin->dpll_refs, dpll, ops, priv, cookie); if (ret) goto ref_pin_del; xa_set_mark(&dpll_pin_xa, pin->id, DPLL_REGISTERED); dpll_pin_create_ntf(pin, dpll->clock_id);
return ret;
ref_pin_del: dpll_xa_ref_pin_del(&dpll->pin_refs, pin, ops, priv, cookie); return ret;}
/** * dpll_pin_register - register the dpll pin in the subsystem * @dpll: pointer to a dpll * @pin: pointer to a dpll pin * @ops: ops for a dpll pin ops * @priv: pointer to private information of owner * * Context: Acquires a lock (dpll_lock) * Return: * * 0 on success * * negative - error value */intdpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv){ const struct dpll_device_ops *dev_ops; int ret;
if (WARN_ON(!ops) || WARN_ON(!ops->state_on_dpll_get) || WARN_ON(!ops->direction_get) || WARN_ON(ops->supported_ffo && !ops->ffo_get) || WARN_ON((pin->prop.capabilities & DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE) && !(pin->prop.capabilities & DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE))) return -EINVAL;
mutex_lock(&dpll_lock);
dev_ops = dpll_device_ops(dpll); if (WARN_ON(ops->measured_freq_get && (!dev_ops || !dev_ops->freq_monitor_get || !dev_ops->freq_monitor_set))) { ret = -EINVAL; goto out_unlock; }
/* * For pins identified via firmware (pin->fwnode), allow registration * even if the pin's (module, clock_id) differs from the target DPLL. * For non-fwnode pins, require a strict (module, clock_id) match. */ if (!pin->fwnode && WARN_ON_ONCE(dpll->module != pin->module || dpll->clock_id != pin->clock_id)) { ret = -EINVAL; goto out_unlock; }
ret = __dpll_pin_register(dpll, pin, ops, priv, NULL);out_unlock: mutex_unlock(&dpll_lock);
return ret;}EXPORT_SYMBOL_GPL(dpll_pin_register);
static void dpll_pin_ref_sync_pair_del(u32 ref_sync_pin_id){ struct dpll_pin *pin, *ref_sync_pin; unsigned long i;
xa_for_each(&dpll_pin_xa, i, pin) { ref_sync_pin = xa_load(&pin->ref_sync_pins, ref_sync_pin_id); if (ref_sync_pin) { xa_erase(&pin->ref_sync_pins, ref_sync_pin_id); __dpll_pin_change_ntf(pin); } }}
static void__dpll_pin_unregister(struct dpll_device *dpll, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv, void *cookie){ ASSERT_DPLL_PIN_REGISTERED(pin); dpll_pin_delete_ntf(pin, dpll->clock_id); dpll_xa_ref_pin_del(&dpll->pin_refs, pin, ops, priv, cookie); dpll_xa_ref_dpll_del(&pin->dpll_refs, dpll, ops, priv, cookie); if (xa_empty(&pin->dpll_refs)) { dpll_pin_ref_sync_pair_del(pin->id); xa_clear_mark(&dpll_pin_xa, pin->id, DPLL_REGISTERED); }}
/** * dpll_pin_unregister - unregister dpll pin from dpll device * @dpll: registered dpll pointer * @pin: pointer to a pin * @ops: ops for a dpll pin * @priv: pointer to private information of owner * * Note: It does not free the memory * Context: Acquires a lock (dpll_lock) */void dpll_pin_unregister(struct dpll_device *dpll, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv){ if (WARN_ON(xa_empty(&dpll->pin_refs))) return; if (WARN_ON(!xa_empty(&pin->parent_refs))) return;
mutex_lock(&dpll_lock); __dpll_pin_unregister(dpll, pin, ops, priv, NULL); mutex_unlock(&dpll_lock);}EXPORT_SYMBOL_GPL(dpll_pin_unregister);
/** * dpll_pin_on_pin_register - register a pin with a parent pin * @parent: pointer to a parent pin * @pin: pointer to a pin * @ops: ops for a dpll pin * @priv: pointer to private information of owner * * Register a pin with a parent pin, create references between them and * between newly registered pin and dplls connected with a parent pin. * * Context: Acquires a lock (dpll_lock) * Return: * * 0 on success * * negative - error value */int dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv){ struct dpll_pin_ref *ref; unsigned long i, stop; int ret;
if (WARN_ON(parent->prop.type != DPLL_PIN_TYPE_MUX)) return -EINVAL;
if (WARN_ON(!ops) || WARN_ON(!ops->state_on_pin_get) || WARN_ON(!ops->direction_get)) return -EINVAL;
mutex_lock(&dpll_lock); ret = dpll_xa_ref_pin_add(&pin->parent_refs, parent, ops, priv, pin); if (ret) goto unlock; xa_for_each(&parent->dpll_refs, i, ref) { ret = __dpll_pin_register(ref->dpll, pin, ops, priv, parent); if (ret) { stop = i; goto dpll_unregister; } dpll_pin_create_ntf(pin, parent->clock_id); } mutex_unlock(&dpll_lock);
return ret;
dpll_unregister: xa_for_each(&parent->dpll_refs, i, ref) if (i < stop) { dpll_pin_delete_ntf(pin, parent->clock_id); __dpll_pin_unregister(ref->dpll, pin, ops, priv, parent); } dpll_xa_ref_pin_del(&pin->parent_refs, parent, ops, priv, pin);unlock: mutex_unlock(&dpll_lock); return ret;}EXPORT_SYMBOL_GPL(dpll_pin_on_pin_register);
/** * dpll_pin_on_pin_unregister - unregister dpll pin from a parent pin * @parent: pointer to a parent pin * @pin: pointer to a pin * @ops: ops for a dpll pin * @priv: pointer to private information of owner * * Context: Acquires a lock (dpll_lock) * Note: It does not free the memory */void dpll_pin_on_pin_unregister(struct dpll_pin *parent, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv){ struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; unsigned long i;
mutex_lock(&dpll_lock); xa_for_each(&pin->dpll_refs, i, ref) { reg = dpll_pin_registration_find(ref, ops, priv, parent); if (!reg) continue; dpll_pin_delete_ntf(pin, parent->clock_id); __dpll_pin_unregister(ref->dpll, pin, ops, priv, parent); } dpll_xa_ref_pin_del(&pin->parent_refs, parent, ops, priv, pin); mutex_unlock(&dpll_lock);}EXPORT_SYMBOL_GPL(dpll_pin_on_pin_unregister);
/** * dpll_pin_ref_sync_pair_add - create a reference sync signal pin pair * @pin: pin which produces the base frequency * @ref_sync_pin: pin which produces the sync signal * * Once pins are paired, the user-space configuration of reference sync pair * is possible. * Context: Acquires a lock (dpll_lock) * Return: * * 0 on success * * negative - error value */int dpll_pin_ref_sync_pair_add(struct dpll_pin *pin, struct dpll_pin *ref_sync_pin){ int ret;
mutex_lock(&dpll_lock); ret = xa_insert(&pin->ref_sync_pins, ref_sync_pin->id, ref_sync_pin, GFP_KERNEL); __dpll_pin_change_ntf(pin); mutex_unlock(&dpll_lock);
return ret;}EXPORT_SYMBOL_GPL(dpll_pin_ref_sync_pair_add);
static struct dpll_device_registration *dpll_device_registration_first(struct dpll_device *dpll){ return list_first_entry_or_null((struct list_head *)&dpll->registration_list, struct dpll_device_registration, list);}
void *dpll_priv(struct dpll_device *dpll){ struct dpll_device_registration *reg;
reg = dpll_device_registration_first(dpll); if (!reg) return NULL; return reg->priv;}
const struct dpll_device_ops *dpll_device_ops(struct dpll_device *dpll){ struct dpll_device_registration *reg;
reg = dpll_device_registration_first(dpll); if (!reg) return NULL; return reg->ops;}
static struct dpll_pin_registration *dpll_pin_registration_first(struct dpll_pin_ref *ref){ struct dpll_pin_registration *reg;
reg = list_first_entry_or_null(&ref->registration_list, struct dpll_pin_registration, list); WARN_ON(!reg); return reg;}
void *dpll_pin_on_dpll_priv(struct dpll_device *dpll, struct dpll_pin *pin){ struct dpll_pin_registration *reg; struct dpll_pin_ref *ref;
ref = xa_load(&dpll->pin_refs, pin->pin_idx); if (!ref) return NULL; reg = dpll_pin_registration_first(ref); return reg->priv;}
void *dpll_pin_on_pin_priv(struct dpll_pin *parent, struct dpll_pin *pin){ struct dpll_pin_registration *reg; struct dpll_pin_ref *ref;
ref = xa_load(&pin->parent_refs, parent->pin_idx); if (!ref) return NULL; reg = dpll_pin_registration_first(ref); return reg->priv;}
/** * dpll_pin_own_dpll_ref_first - find the first owner dpll ref of a pin * @pin: pointer to a dpll pin * * Search pin's dpll_refs for a ref whose dpll matches the pin's * (module, clock_id) tuple, i.e. the dpll registered by the driver * that created the pin. This ensures pin-level attributes are * reported and modified using the owner's ops even when the pin is * also registered with dplls from other drivers. * * Return: pointer to the owner's dpll_pin_ref, or NULL if no * owner ref is found. */struct dpll_pin_ref *dpll_pin_own_dpll_ref_first(struct dpll_pin *pin){ struct dpll_pin_ref *ref; unsigned long i;
xa_for_each(&pin->dpll_refs, i, ref) { if (ref->dpll->module == pin->module && ref->dpll->clock_id == pin->clock_id) return ref; }
return NULL;}
const struct dpll_pin_ops *dpll_pin_ops(struct dpll_pin_ref *ref){ struct dpll_pin_registration *reg;
reg = dpll_pin_registration_first(ref); return reg->ops;}
static int __init dpll_init(void){ int ret;
ret = genl_register_family(&dpll_nl_family); if (ret) goto error;
return 0;
error: mutex_destroy(&dpll_lock); return ret;}
static void __exit dpll_exit(void){ genl_unregister_family(&dpll_nl_family); mutex_destroy(&dpll_lock);}
subsys_initcall(dpll_init);module_exit(dpll_exit);