| /* SPDX-License-Identifier: GPL-2.0 */ |
| /* |
| * Copyright 2019 Google, LLC |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/list.h> |
| #include "linux/slab.h" |
| #include <misc/gvotable.h> |
| #include "pmic-voter.h" |
| |
| #define V2EL(x) ((struct gvotable_election *)(v)) |
| |
| struct votable_data { |
| const char *name; |
| void *callback_data; /* data passed to create_votable */ |
| int (*callback)(struct votable *votable, |
| void *data, |
| int effective_result, |
| const char *effective_client); |
| }; |
| |
| |
| bool is_client_vote_enabled_locked(struct votable *v, const char *client_str) |
| { |
| int ret; |
| bool enabled = false; |
| |
| ret = gvotable_is_enabled(V2EL(v), client_str, &enabled); |
| if (ret) |
| pr_err("Error gvotable_is_enabled returned %d\n", ret); |
| |
| return enabled; |
| } |
| |
| bool is_client_vote_enabled(struct votable *votable, const char *client_str) |
| { |
| return is_client_vote_enabled_locked(votable, client_str); |
| } |
| |
| int get_client_vote_locked(struct votable *v, const char *client_str) |
| { |
| void *ptr; |
| int ret; |
| |
| ret = gvotable_get_vote(V2EL(votable), client_str, &ptr); |
| return ret ? : (uintptr_t)ptr; |
| } |
| |
| int get_client_vote(struct votable *votable, const char *client_str) |
| { |
| return get_client_vote_locked(votable, client_str); |
| } |
| EXPORT_SYMBOL_GPL(get_client_vote); |
| |
| int get_effective_result_locked(struct votable *v) |
| { |
| const void *ptr; |
| int ret; |
| |
| ret = gvotable_get_current_vote(V2EL(v), &ptr); |
| return ret ? : (uintptr_t)ptr; |
| } |
| EXPORT_SYMBOL_GPL(get_effective_result_locked); |
| |
| int get_effective_result(struct votable *votable) |
| { |
| return get_effective_result_locked(votable); |
| } |
| EXPORT_SYMBOL_GPL(get_effective_result); |
| |
| int vote(struct votable *v, const char *client_str, bool state, int val) |
| { |
| return gvotable_cast_vote(V2EL(v), client_str, (void *)(long)val, |
| state); |
| } |
| EXPORT_SYMBOL_GPL(vote); |
| |
| /* It needs to work for new and instances so we can mix the code |
| */ |
| struct votable *find_votable(const char *name) |
| { |
| return (struct votable *)gvotable_election_get_handle(name); |
| } |
| EXPORT_SYMBOL_GPL(find_votable); |
| |
| static void pmic_voter_compat_cb(struct gvotable_election *el, |
| const char *cb_reason, void *cb_result) |
| { |
| struct votable_data *vd = (struct votable_data *)gvotable_get_data(el); |
| char reason[GVOTABLE_MAX_REASON_LEN] = { 0 }; |
| char *effective_reason = NULL; |
| int effective_result = -EINVAL; |
| const void *ptr; |
| int ret; |
| |
| if (!vd->callback) |
| return; |
| |
| ret = gvotable_get_current_vote(el, &ptr); |
| if (ret == 0) |
| effective_result = (uintptr_t)ptr; |
| |
| ret = gvotable_get_current_reason(el, reason, sizeof(reason)); |
| if (ret > 0) |
| effective_reason = reason; |
| |
| /* for SET_ANY voter, the value is always same as enabled. */ |
| pr_debug("%s: name=%s result=%d reason=%s\n", __func__, vd->name, |
| effective_result, effective_reason ? effective_reason : "<>"); |
| |
| /* call with NULL reason and -EINVAL if votes no enabled */ |
| vd->callback((struct votable *)el, vd->callback_data, |
| effective_result, effective_reason); |
| } |
| |
| /* Allow redefining the allocator: required for testing */ |
| #ifndef kzalloc_votable |
| #define kzalloc_votable(p, f) (typeof(p))kzalloc(sizeof(*(p)), f) |
| #endif |
| |
| struct votable *create_votable(const char *name, |
| int votable_type, |
| int (*callback)(struct votable *votable, |
| void *data, |
| int effective_result, |
| const char *effective_client), |
| void *callback_data) |
| { |
| int (*comp_fn)(void * , void *) = NULL; |
| struct gvotable_election *el; |
| struct votable_data *vd; |
| int ret; |
| |
| if (!name) |
| return ERR_PTR(-EINVAL); |
| |
| /* create extra votable data */ |
| vd = kzalloc_votable(vd, GFP_KERNEL); |
| if (!vd) |
| return ERR_PTR(-ENOMEM); |
| vd->callback_data = callback_data; |
| vd->callback = callback; |
| vd->name = name; |
| |
| switch (votable_type) { |
| case VOTE_MIN: |
| comp_fn = gvotable_comparator_int_min; |
| break; |
| case VOTE_MAX: |
| comp_fn = gvotable_comparator_int_max; |
| break; |
| case VOTE_SET_ANY: |
| break; |
| default: |
| kfree(vd); |
| return ERR_PTR(-EINVAL); |
| break; |
| } |
| |
| if (votable_type == VOTE_SET_ANY) { |
| el = gvotable_create_bool_election(NULL, pmic_voter_compat_cb, |
| vd); |
| } else { |
| el = gvotable_create_int_election(NULL, comp_fn, |
| pmic_voter_compat_cb, |
| vd); |
| } |
| |
| if (IS_ERR_OR_NULL(el)) { |
| kfree(vd); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| gvotable_set_vote2str(el, gvotable_v2s_int); |
| /* votables of type ANY have a default but they don't use it */ |
| if (votable_type == VOTE_SET_ANY) { |
| gvotable_set_default(el, 0); |
| gvotable_use_default(el, false); |
| } |
| |
| ret = gvotable_election_set_name(el, vd->name); |
| if (ret < 0) { |
| gvotable_destroy_election(el); |
| kfree(vd); |
| return ERR_PTR(-EEXIST); |
| } |
| |
| |
| return (struct votable *)el; |
| } |
| EXPORT_SYMBOL_GPL(create_votable); |
| |
| void destroy_votable(struct votable *v) |
| { |
| if (!v) |
| return; |
| |
| kfree(gvotable_get_data(V2EL(v))); |
| gvotable_destroy_election(V2EL(v)); |
| } |
| EXPORT_SYMBOL_GPL(destroy_votable); |
| |
| MODULE_AUTHOR("Jim Wylder <[email protected]>"); |
| MODULE_AUTHOR("AleX Pelosi <[email protected]>"); |
| MODULE_DESCRIPTION("QC PMIC Votable compatibility"); |
| MODULE_LICENSE("GPL"); |