blob: 4062a4127a543ae86ae8be6d5eb975fb4bc5d54b [file] [log] [blame]
/* 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 <jwylder@google.com>");
MODULE_AUTHOR("AleX Pelosi <apelosi@google.com>");
MODULE_DESCRIPTION("QC PMIC Votable compatibility");
MODULE_LICENSE("GPL");