You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
856 lines
21 KiB
856 lines
21 KiB
/* Copyright (c) 2015-2017, 2019 The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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/debugfs.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/pmic-voter.h>
|
|
|
|
#define NUM_MAX_CLIENTS 32
|
|
#define DEBUG_FORCE_CLIENT "DEBUG_FORCE_CLIENT"
|
|
|
|
static DEFINE_SPINLOCK(votable_list_slock);
|
|
static LIST_HEAD(votable_list);
|
|
|
|
static struct dentry *debug_root;
|
|
|
|
struct client_vote {
|
|
bool enabled;
|
|
int value;
|
|
};
|
|
|
|
struct votable {
|
|
const char *name;
|
|
const char *override_client;
|
|
struct list_head list;
|
|
struct client_vote votes[NUM_MAX_CLIENTS];
|
|
int num_clients;
|
|
int type;
|
|
int effective_client_id;
|
|
int effective_result;
|
|
int override_result;
|
|
struct mutex vote_lock;
|
|
void *data;
|
|
int (*callback)(struct votable *votable,
|
|
void *data,
|
|
int effective_result,
|
|
const char *effective_client);
|
|
char *client_strs[NUM_MAX_CLIENTS];
|
|
bool voted_on;
|
|
struct dentry *root;
|
|
struct dentry *status_ent;
|
|
u32 force_val;
|
|
struct dentry *force_val_ent;
|
|
bool force_active;
|
|
struct dentry *force_active_ent;
|
|
};
|
|
|
|
/**
|
|
* vote_set_any()
|
|
* @votable: votable object
|
|
* @client_id: client number of the latest voter
|
|
* @eff_res: sets 0 or 1 based on the voting
|
|
* @eff_id: Always returns the client_id argument
|
|
*
|
|
* Note that for SET_ANY voter, the value is always same as enabled. There is
|
|
* no idea of a voter abstaining from the election. Hence there is never a
|
|
* situation when the effective_id will be invalid, during election.
|
|
*
|
|
* Context:
|
|
* Must be called with the votable->lock held
|
|
*/
|
|
static void vote_set_any(struct votable *votable, int client_id,
|
|
int *eff_res, int *eff_id)
|
|
{
|
|
int i;
|
|
|
|
*eff_res = 0;
|
|
|
|
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++)
|
|
*eff_res |= votable->votes[i].enabled;
|
|
|
|
*eff_id = client_id;
|
|
}
|
|
|
|
/**
|
|
* vote_min() -
|
|
* @votable: votable object
|
|
* @client_id: client number of the latest voter
|
|
* @eff_res: sets this to the min. of all the values amongst enabled voters.
|
|
* If there is no enabled client, this is set to INT_MAX
|
|
* @eff_id: sets this to the client id that has the min value amongst all
|
|
* the enabled clients. If there is no enabled client, sets this
|
|
* to -EINVAL
|
|
*
|
|
* Context:
|
|
* Must be called with the votable->lock held
|
|
*/
|
|
static void vote_min(struct votable *votable, int client_id,
|
|
int *eff_res, int *eff_id)
|
|
{
|
|
int i;
|
|
|
|
*eff_res = INT_MAX;
|
|
*eff_id = -EINVAL;
|
|
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) {
|
|
if (votable->votes[i].enabled
|
|
&& *eff_res > votable->votes[i].value) {
|
|
*eff_res = votable->votes[i].value;
|
|
*eff_id = i;
|
|
}
|
|
}
|
|
if (*eff_id == -EINVAL)
|
|
*eff_res = -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* vote_max() -
|
|
* @votable: votable object
|
|
* @client_id: client number of the latest voter
|
|
* @eff_res: sets this to the max. of all the values amongst enabled voters.
|
|
* If there is no enabled client, this is set to -EINVAL
|
|
* @eff_id: sets this to the client id that has the max value amongst all
|
|
* the enabled clients. If there is no enabled client, sets this to
|
|
* -EINVAL
|
|
*
|
|
* Context:
|
|
* Must be called with the votable->lock held
|
|
*/
|
|
static void vote_max(struct votable *votable, int client_id,
|
|
int *eff_res, int *eff_id)
|
|
{
|
|
int i;
|
|
|
|
*eff_res = INT_MIN;
|
|
*eff_id = -EINVAL;
|
|
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) {
|
|
if (votable->votes[i].enabled &&
|
|
*eff_res < votable->votes[i].value) {
|
|
*eff_res = votable->votes[i].value;
|
|
*eff_id = i;
|
|
}
|
|
}
|
|
if (*eff_id == -EINVAL)
|
|
*eff_res = -EINVAL;
|
|
}
|
|
|
|
static int get_client_id(struct votable *votable, const char *client_str)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < votable->num_clients; i++) {
|
|
if (votable->client_strs[i]
|
|
&& (strcmp(votable->client_strs[i], client_str) == 0))
|
|
return i;
|
|
}
|
|
|
|
/* new client */
|
|
for (i = 0; i < votable->num_clients; i++) {
|
|
if (!votable->client_strs[i]) {
|
|
votable->client_strs[i]
|
|
= kstrdup(client_str, GFP_KERNEL);
|
|
if (!votable->client_strs[i])
|
|
return -ENOMEM;
|
|
return i;
|
|
}
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static char *get_client_str(struct votable *votable, int client_id)
|
|
{
|
|
if (!votable || (client_id == -EINVAL))
|
|
return NULL;
|
|
|
|
return votable->client_strs[client_id];
|
|
}
|
|
|
|
void lock_votable(struct votable *votable)
|
|
{
|
|
mutex_lock(&votable->vote_lock);
|
|
}
|
|
|
|
void unlock_votable(struct votable *votable)
|
|
{
|
|
mutex_unlock(&votable->vote_lock);
|
|
}
|
|
|
|
/**
|
|
* is_override_vote_enabled() -
|
|
* is_override_vote_enabled_locked() -
|
|
* The unlocked and locked variants of getting whether override
|
|
vote is enabled.
|
|
* @votable: the votable object
|
|
*
|
|
* Returns:
|
|
* True if the client's vote is enabled; false otherwise.
|
|
*/
|
|
bool is_override_vote_enabled_locked(struct votable *votable)
|
|
{
|
|
if (!votable)
|
|
return false;
|
|
|
|
return votable->override_result != -EINVAL;
|
|
}
|
|
|
|
bool is_override_vote_enabled(struct votable *votable)
|
|
{
|
|
bool enable;
|
|
|
|
if (!votable)
|
|
return false;
|
|
|
|
lock_votable(votable);
|
|
enable = is_override_vote_enabled_locked(votable);
|
|
unlock_votable(votable);
|
|
|
|
return enable;
|
|
}
|
|
|
|
/**
|
|
* is_client_vote_enabled() -
|
|
* is_client_vote_enabled_locked() -
|
|
* The unlocked and locked variants of getting whether a client's
|
|
vote is enabled.
|
|
* @votable: the votable object
|
|
* @client_str: client of interest
|
|
*
|
|
* Returns:
|
|
* True if the client's vote is enabled; false otherwise.
|
|
*/
|
|
bool is_client_vote_enabled_locked(struct votable *votable,
|
|
const char *client_str)
|
|
{
|
|
|
|
int client_id;
|
|
|
|
if (!votable || !client_str)
|
|
return false;
|
|
|
|
client_id = get_client_id(votable, client_str);
|
|
if (client_id < 0)
|
|
return false;
|
|
|
|
return votable->votes[client_id].enabled;
|
|
}
|
|
|
|
bool is_client_vote_enabled(struct votable *votable, const char *client_str)
|
|
{
|
|
bool enabled;
|
|
|
|
if (!votable || !client_str)
|
|
return false;
|
|
|
|
lock_votable(votable);
|
|
enabled = is_client_vote_enabled_locked(votable, client_str);
|
|
unlock_votable(votable);
|
|
return enabled;
|
|
}
|
|
|
|
/**
|
|
* get_client_vote() -
|
|
* get_client_vote_locked() -
|
|
* The unlocked and locked variants of getting a client's voted
|
|
* value.
|
|
* @votable: the votable object
|
|
* @client_str: client of interest
|
|
*
|
|
* Returns:
|
|
* The value the client voted for. -EINVAL is returned if the client
|
|
* is not enabled or the client is not found.
|
|
*/
|
|
int get_client_vote_locked(struct votable *votable, const char *client_str)
|
|
{
|
|
int client_id;
|
|
|
|
if (!votable || !client_str)
|
|
return -EINVAL;
|
|
|
|
client_id = get_client_id(votable, client_str);
|
|
if (client_id < 0)
|
|
return -EINVAL;
|
|
|
|
if ((votable->type != VOTE_SET_ANY)
|
|
&& !votable->votes[client_id].enabled)
|
|
return -EINVAL;
|
|
|
|
return votable->votes[client_id].value;
|
|
}
|
|
|
|
int get_client_vote(struct votable *votable, const char *client_str)
|
|
{
|
|
int value;
|
|
|
|
if (!votable || !client_str)
|
|
return -EINVAL;
|
|
|
|
lock_votable(votable);
|
|
value = get_client_vote_locked(votable, client_str);
|
|
unlock_votable(votable);
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* get_effective_result() -
|
|
* get_effective_result_locked() -
|
|
* The unlocked and locked variants of getting the effective value
|
|
* amongst all the enabled voters.
|
|
*
|
|
* @votable: the votable object
|
|
*
|
|
* Returns:
|
|
* The effective result.
|
|
* For MIN and MAX votable, returns -EINVAL when the votable
|
|
* object has been created but no clients have casted their votes or
|
|
* the last enabled client disables its vote.
|
|
* For SET_ANY votable it returns 0 when no clients have casted their votes
|
|
* because for SET_ANY there is no concept of abstaining from election. The
|
|
* votes for all the clients of SET_ANY votable is defaulted to false.
|
|
*/
|
|
int get_effective_result_locked(struct votable *votable)
|
|
{
|
|
if (!votable)
|
|
return -EINVAL;
|
|
|
|
if (votable->force_active)
|
|
return votable->force_val;
|
|
|
|
if (votable->override_result != -EINVAL)
|
|
return votable->override_result;
|
|
|
|
return votable->effective_result;
|
|
}
|
|
|
|
int get_effective_result(struct votable *votable)
|
|
{
|
|
int value;
|
|
|
|
if (!votable)
|
|
return -EINVAL;
|
|
|
|
lock_votable(votable);
|
|
value = get_effective_result_locked(votable);
|
|
unlock_votable(votable);
|
|
return value;
|
|
}
|
|
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
void get_setanytype_effective_client(struct votable *votable)
|
|
{
|
|
int i = 0;
|
|
|
|
lock_votable(votable);
|
|
for (i = 0; i < votable->num_clients; i++) {
|
|
if (votable->client_strs[i] && votable->votes[i].enabled) {
|
|
pr_info("sec_bat_monitor_work: %s: %s\n", votable->name, votable->client_strs[i]);
|
|
unlock_votable(votable);
|
|
return;
|
|
}
|
|
}
|
|
unlock_votable(votable);
|
|
return;
|
|
}
|
|
#endif
|
|
/**
|
|
* get_effective_client() -
|
|
* get_effective_client_locked() -
|
|
* The unlocked and locked variants of getting the effective client
|
|
* amongst all the enabled voters.
|
|
*
|
|
* @votable: the votable object
|
|
*
|
|
* Returns:
|
|
* The effective client.
|
|
* For MIN and MAX votable, returns NULL when the votable
|
|
* object has been created but no clients have casted their votes or
|
|
* the last enabled client disables its vote.
|
|
* For SET_ANY votable it returns NULL too when no clients have casted
|
|
* their votes. But for SET_ANY since there is no concept of abstaining
|
|
* from election, the only client that casts a vote or the client that
|
|
* caused the result to change is returned.
|
|
*/
|
|
const char *get_effective_client_locked(struct votable *votable)
|
|
{
|
|
if (!votable)
|
|
return NULL;
|
|
|
|
if (votable->force_active)
|
|
return DEBUG_FORCE_CLIENT;
|
|
|
|
if (votable->override_result != -EINVAL)
|
|
return votable->override_client;
|
|
|
|
return get_client_str(votable, votable->effective_client_id);
|
|
}
|
|
|
|
const char *get_effective_client(struct votable *votable)
|
|
{
|
|
const char *client_str;
|
|
|
|
if (!votable)
|
|
return NULL;
|
|
|
|
lock_votable(votable);
|
|
client_str = get_effective_client_locked(votable);
|
|
unlock_votable(votable);
|
|
return client_str;
|
|
}
|
|
|
|
/**
|
|
* vote() -
|
|
*
|
|
* @votable: the votable object
|
|
* @client_str: the voting client
|
|
* @enabled: This provides a means for the client to exclude himself from
|
|
* election. This clients val (the next argument) will be
|
|
* considered only when he has enabled his participation.
|
|
* Note that this takes a differnt meaning for SET_ANY type, as
|
|
* there is no concept of abstaining from participation.
|
|
* Enabled is treated as the boolean value the client is voting.
|
|
* @val: The vote value. This is ignored for SET_ANY votable types.
|
|
* For MIN, MAX votable types this value is used as the
|
|
* clients vote value when the enabled is true, this value is
|
|
* ignored if enabled is false.
|
|
*
|
|
* The callback is called only when there is a change in the election results or
|
|
* if it is the first time someone is voting.
|
|
*
|
|
* Returns:
|
|
* The return from the callback when present and needs to be called
|
|
* or zero.
|
|
*/
|
|
int vote(struct votable *votable, const char *client_str, bool enabled, int val)
|
|
{
|
|
int effective_id = -EINVAL;
|
|
int effective_result;
|
|
int client_id;
|
|
int rc = 0;
|
|
bool similar_vote = false;
|
|
|
|
if (!votable || !client_str)
|
|
return -EINVAL;
|
|
|
|
lock_votable(votable);
|
|
|
|
client_id = get_client_id(votable, client_str);
|
|
if (client_id < 0) {
|
|
rc = client_id;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* for SET_ANY the val is to be ignored, set it
|
|
* to enabled so that the election still works based on
|
|
* value regardless of the type
|
|
*/
|
|
if (votable->type == VOTE_SET_ANY)
|
|
val = enabled;
|
|
|
|
if ((votable->votes[client_id].enabled == enabled) &&
|
|
(votable->votes[client_id].value == val)) {
|
|
pr_info("%s: %s,%d same vote %s of val=%d\n",
|
|
votable->name,
|
|
client_str, client_id,
|
|
enabled ? "on" : "off",
|
|
val);
|
|
similar_vote = true;
|
|
}
|
|
|
|
votable->votes[client_id].enabled = enabled;
|
|
votable->votes[client_id].value = val;
|
|
|
|
if (similar_vote && votable->voted_on) {
|
|
pr_info("%s: %s,%d Ignoring similar vote %s of val=%d\n",
|
|
votable->name,
|
|
client_str, client_id, enabled ? "on" : "off", val);
|
|
goto out;
|
|
}
|
|
|
|
pr_info("%s: %s,%d voting %s of val=%d\n",
|
|
votable->name,
|
|
client_str, client_id, enabled ? "on" : "off", val);
|
|
switch (votable->type) {
|
|
case VOTE_MIN:
|
|
vote_min(votable, client_id, &effective_result, &effective_id);
|
|
break;
|
|
case VOTE_MAX:
|
|
vote_max(votable, client_id, &effective_result, &effective_id);
|
|
break;
|
|
case VOTE_SET_ANY:
|
|
vote_set_any(votable, client_id,
|
|
&effective_result, &effective_id);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Note that the callback is called with a NULL string and -EINVAL
|
|
* result when there are no enabled votes
|
|
*/
|
|
if (!votable->voted_on
|
|
|| (effective_result != votable->effective_result)) {
|
|
votable->effective_client_id = effective_id;
|
|
votable->effective_result = effective_result;
|
|
pr_info("%s: effective vote is now %d voted by %s,%d\n",
|
|
votable->name, effective_result,
|
|
get_client_str(votable, effective_id),
|
|
effective_id);
|
|
if (votable->callback && !votable->force_active
|
|
&& (votable->override_result == -EINVAL))
|
|
rc = votable->callback(votable, votable->data,
|
|
effective_result,
|
|
get_client_str(votable, effective_id));
|
|
}
|
|
|
|
votable->voted_on = true;
|
|
out:
|
|
unlock_votable(votable);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* vote_override() -
|
|
*
|
|
* @votable: The votable object
|
|
* @override_client: The voting client that will override other client's
|
|
* votes, that are already present. When force_active
|
|
* and override votes are set on a votable, force_active's
|
|
* client will have the higher priority and it's vote will
|
|
* be the effective one.
|
|
* @enabled: This provides a means for the override client to exclude
|
|
* itself from election. This client's vote
|
|
* (the next argument) will be considered only when
|
|
* it has enabled its participation. When this is
|
|
* set true, this will force a value on a MIN/MAX votable
|
|
* irrespective of its current value.
|
|
* @val: The vote value. This will be effective only if enabled
|
|
* is set true.
|
|
* Returns:
|
|
* The result of vote. 0 is returned if the vote
|
|
* is successfully set by the overriding client, when enabled is set.
|
|
*/
|
|
int vote_override(struct votable *votable, const char *override_client,
|
|
bool enabled, int val)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!votable || !override_client)
|
|
return -EINVAL;
|
|
|
|
lock_votable(votable);
|
|
if (votable->force_active) {
|
|
votable->override_result = enabled ? val : -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (enabled) {
|
|
rc = votable->callback(votable, votable->data,
|
|
val, override_client);
|
|
if (!rc) {
|
|
votable->override_client = override_client;
|
|
votable->override_result = val;
|
|
}
|
|
} else {
|
|
rc = votable->callback(votable, votable->data,
|
|
votable->effective_result,
|
|
get_client_str(votable, votable->effective_client_id));
|
|
votable->override_result = -EINVAL;
|
|
}
|
|
|
|
out:
|
|
unlock_votable(votable);
|
|
return rc;
|
|
}
|
|
|
|
int rerun_election(struct votable *votable)
|
|
{
|
|
int rc = 0;
|
|
int effective_result;
|
|
|
|
if (!votable)
|
|
return -EINVAL;
|
|
|
|
lock_votable(votable);
|
|
effective_result = get_effective_result_locked(votable);
|
|
if (votable->callback)
|
|
rc = votable->callback(votable,
|
|
votable->data,
|
|
effective_result,
|
|
get_client_str(votable, votable->effective_client_id));
|
|
unlock_votable(votable);
|
|
return rc;
|
|
}
|
|
|
|
struct votable *find_votable(const char *name)
|
|
{
|
|
unsigned long flags;
|
|
struct votable *v;
|
|
bool found = false;
|
|
|
|
if (!name)
|
|
return NULL;
|
|
|
|
spin_lock_irqsave(&votable_list_slock, flags);
|
|
if (list_empty(&votable_list))
|
|
goto out;
|
|
|
|
list_for_each_entry(v, &votable_list, list) {
|
|
if (strcmp(v->name, name) == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
out:
|
|
spin_unlock_irqrestore(&votable_list_slock, flags);
|
|
|
|
if (found)
|
|
return v;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
static int force_active_get(void *data, u64 *val)
|
|
{
|
|
struct votable *votable = data;
|
|
|
|
*val = votable->force_active;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int force_active_set(void *data, u64 val)
|
|
{
|
|
struct votable *votable = data;
|
|
int rc = 0;
|
|
int effective_result;
|
|
const char *client;
|
|
|
|
lock_votable(votable);
|
|
votable->force_active = !!val;
|
|
|
|
if (!votable->callback)
|
|
goto out;
|
|
|
|
if (votable->force_active) {
|
|
rc = votable->callback(votable, votable->data,
|
|
votable->force_val,
|
|
DEBUG_FORCE_CLIENT);
|
|
} else {
|
|
if (votable->override_result != -EINVAL) {
|
|
effective_result = votable->override_result;
|
|
client = votable->override_client;
|
|
} else {
|
|
effective_result = votable->effective_result;
|
|
client = get_client_str(votable,
|
|
votable->effective_client_id);
|
|
}
|
|
rc = votable->callback(votable, votable->data, effective_result,
|
|
client);
|
|
}
|
|
out:
|
|
unlock_votable(votable);
|
|
return rc;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(votable_force_ops, force_active_get, force_active_set,
|
|
"%lld\n");
|
|
|
|
static int show_votable_clients(struct seq_file *m, void *data)
|
|
{
|
|
struct votable *votable = m->private;
|
|
int i;
|
|
char *type_str = "Unkonwn";
|
|
const char *effective_client_str;
|
|
|
|
lock_votable(votable);
|
|
|
|
for (i = 0; i < votable->num_clients; i++) {
|
|
if (votable->client_strs[i]) {
|
|
seq_printf(m, "%s: %s:\t\t\ten=%d v=%d\n",
|
|
votable->name,
|
|
votable->client_strs[i],
|
|
votable->votes[i].enabled,
|
|
votable->votes[i].value);
|
|
}
|
|
}
|
|
|
|
switch (votable->type) {
|
|
case VOTE_MIN:
|
|
type_str = "Min";
|
|
break;
|
|
case VOTE_MAX:
|
|
type_str = "Max";
|
|
break;
|
|
case VOTE_SET_ANY:
|
|
type_str = "Set_any";
|
|
break;
|
|
}
|
|
|
|
effective_client_str = get_effective_client_locked(votable);
|
|
seq_printf(m, "%s: effective=%s type=%s v=%d\n",
|
|
votable->name,
|
|
effective_client_str ? effective_client_str : "none",
|
|
type_str,
|
|
get_effective_result_locked(votable));
|
|
unlock_votable(votable);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int votable_status_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct votable *votable = inode->i_private;
|
|
|
|
return single_open(file, show_votable_clients, votable);
|
|
}
|
|
|
|
static const struct file_operations votable_status_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = votable_status_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
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 *data)
|
|
{
|
|
struct votable *votable;
|
|
unsigned long flags;
|
|
|
|
if (!name)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
votable = find_votable(name);
|
|
if (votable)
|
|
return ERR_PTR(-EEXIST);
|
|
|
|
if (debug_root == NULL) {
|
|
debug_root = debugfs_create_dir("pmic-votable", NULL);
|
|
if (!debug_root) {
|
|
pr_err("Couldn't create debug dir\n");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
}
|
|
|
|
if (votable_type >= NUM_VOTABLE_TYPES) {
|
|
pr_err("Invalid votable_type specified for voter\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
votable = kzalloc(sizeof(struct votable), GFP_KERNEL);
|
|
if (!votable)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
votable->name = kstrdup(name, GFP_KERNEL);
|
|
if (!votable->name) {
|
|
kfree(votable);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
votable->num_clients = NUM_MAX_CLIENTS;
|
|
votable->callback = callback;
|
|
votable->type = votable_type;
|
|
votable->data = data;
|
|
votable->override_result = -EINVAL;
|
|
mutex_init(&votable->vote_lock);
|
|
|
|
/*
|
|
* Because effective_result and client states are invalid
|
|
* before the first vote, initialize them to -EINVAL
|
|
*/
|
|
votable->effective_result = -EINVAL;
|
|
if (votable->type == VOTE_SET_ANY)
|
|
votable->effective_result = 0;
|
|
votable->effective_client_id = -EINVAL;
|
|
|
|
spin_lock_irqsave(&votable_list_slock, flags);
|
|
list_add(&votable->list, &votable_list);
|
|
spin_unlock_irqrestore(&votable_list_slock, flags);
|
|
|
|
votable->root = debugfs_create_dir(name, debug_root);
|
|
if (!votable->root) {
|
|
pr_err("Couldn't create debug dir %s\n", name);
|
|
kfree(votable->name);
|
|
kfree(votable);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
votable->status_ent = debugfs_create_file("status", S_IFREG | 0444,
|
|
votable->root, votable,
|
|
&votable_status_ops);
|
|
if (!votable->status_ent) {
|
|
pr_err("Couldn't create status dbg file for %s\n", name);
|
|
debugfs_remove_recursive(votable->root);
|
|
kfree(votable->name);
|
|
kfree(votable);
|
|
return ERR_PTR(-EEXIST);
|
|
}
|
|
|
|
votable->force_val_ent = debugfs_create_u32("force_val",
|
|
S_IFREG | 0644,
|
|
votable->root,
|
|
&(votable->force_val));
|
|
|
|
if (!votable->force_val_ent) {
|
|
pr_err("Couldn't create force_val dbg file for %s\n", name);
|
|
debugfs_remove_recursive(votable->root);
|
|
kfree(votable->name);
|
|
kfree(votable);
|
|
return ERR_PTR(-EEXIST);
|
|
}
|
|
|
|
votable->force_active_ent = debugfs_create_file("force_active",
|
|
S_IFREG | 0444,
|
|
votable->root, votable,
|
|
&votable_force_ops);
|
|
if (!votable->force_active_ent) {
|
|
pr_err("Couldn't create force_active dbg file for %s\n", name);
|
|
debugfs_remove_recursive(votable->root);
|
|
kfree(votable->name);
|
|
kfree(votable);
|
|
return ERR_PTR(-EEXIST);
|
|
}
|
|
|
|
return votable;
|
|
}
|
|
|
|
void destroy_votable(struct votable *votable)
|
|
{
|
|
unsigned long flags;
|
|
int i;
|
|
|
|
if (!votable)
|
|
return;
|
|
|
|
spin_lock_irqsave(&votable_list_slock, flags);
|
|
list_del(&votable->list);
|
|
spin_unlock_irqrestore(&votable_list_slock, flags);
|
|
|
|
debugfs_remove_recursive(votable->root);
|
|
|
|
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++)
|
|
kfree(votable->client_strs[i]);
|
|
|
|
kfree(votable->name);
|
|
kfree(votable);
|
|
}
|
|
|