237 lines
5.0 KiB
C
237 lines
5.0 KiB
C
/*
|
|
* (c) 2010 STMicroelectronics Limited
|
|
*
|
|
* Author: Stuart Menefy <stuart.menefy@st.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/bug.h>
|
|
#include <linux/stm/device.h>
|
|
|
|
struct stm_device_state {
|
|
struct device *dev;
|
|
struct stm_device_config *config;
|
|
enum stm_device_power_state power_state;
|
|
struct stm_pad_state *pad_state;
|
|
struct sysconf_field *sysconf_fields[0]; /* To be expanded */
|
|
};
|
|
|
|
static int __stm_device_init(struct stm_device_state *state,
|
|
struct stm_device_config *config, struct device *dev)
|
|
{
|
|
int i;
|
|
|
|
state->dev = dev;
|
|
state->config = config;
|
|
state->power_state = stm_device_power_off;
|
|
|
|
for (i = 0; i < config->sysconfs_num; i++) {
|
|
struct stm_device_sysconf *sysconf = &config->sysconfs[i];
|
|
|
|
state->sysconf_fields[i] = sysconf_claim(sysconf->regtype,
|
|
sysconf->regnum, sysconf->lsb, sysconf->msb,
|
|
dev_name(dev));
|
|
if (!state->sysconf_fields[i])
|
|
goto sysconf_error;
|
|
}
|
|
|
|
if (config->init && config->init(state))
|
|
goto sysconf_error;
|
|
|
|
if (config->pad_config &&
|
|
(state->pad_state = stm_pad_claim(config->pad_config,
|
|
dev_name(dev))) == NULL)
|
|
goto pad_error;
|
|
|
|
stm_device_power(state, stm_device_power_on);
|
|
|
|
return 0;
|
|
|
|
pad_error:
|
|
if (config->exit)
|
|
config->exit(state);
|
|
|
|
sysconf_error:
|
|
for (i--; i>=0; i--)
|
|
sysconf_release(state->sysconf_fields[i]);
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
static void __stm_device_exit(struct stm_device_state *state)
|
|
{
|
|
struct stm_device_config *config = state->config;
|
|
int i;
|
|
|
|
stm_device_power(state, stm_device_power_off);
|
|
|
|
if (config->pad_config)
|
|
stm_pad_release(state->pad_state);
|
|
|
|
if (config->exit)
|
|
config->exit(state);
|
|
|
|
for (i = 0; i < config->sysconfs_num; i++)
|
|
sysconf_release(state->sysconf_fields[i]);
|
|
}
|
|
|
|
struct stm_device_state *stm_device_init(struct stm_device_config *config,
|
|
struct device *dev)
|
|
{
|
|
struct stm_device_state *state = kzalloc(sizeof(*state) +
|
|
sizeof(*state->sysconf_fields) * config->sysconfs_num,
|
|
GFP_KERNEL);
|
|
|
|
BUG_ON(!config);
|
|
BUG_ON(!dev);
|
|
|
|
if (state && __stm_device_init(state, config, dev) != 0)
|
|
state = NULL;
|
|
|
|
return state;
|
|
}
|
|
EXPORT_SYMBOL(stm_device_init);
|
|
|
|
void stm_device_exit(struct stm_device_state *state)
|
|
{
|
|
BUG_ON(!state);
|
|
|
|
__stm_device_exit(state);
|
|
|
|
kfree(state);
|
|
}
|
|
EXPORT_SYMBOL(stm_device_exit);
|
|
|
|
|
|
|
|
static void stm_device_devres_exit(struct device *dev, void *res)
|
|
{
|
|
struct stm_device_state *state = res;
|
|
|
|
BUG_ON(!state);
|
|
|
|
__stm_device_exit(state);
|
|
}
|
|
|
|
static int stm_device_devres_match(struct device *dev, void *res, void *data)
|
|
{
|
|
struct stm_device_state *state = res, *match = data;
|
|
|
|
return state == match;
|
|
}
|
|
|
|
struct stm_device_state *devm_stm_device_init(struct device *dev,
|
|
struct stm_device_config *config)
|
|
{
|
|
struct stm_device_state *state = devres_alloc(stm_device_devres_exit,
|
|
sizeof(*state) + sizeof(*state->sysconf_fields) *
|
|
config->sysconfs_num, GFP_KERNEL);
|
|
|
|
BUG_ON(!dev);
|
|
BUG_ON(!config);
|
|
|
|
if (state) {
|
|
if (__stm_device_init(state, config, dev) == 0) {
|
|
devres_add(dev, state);
|
|
} else {
|
|
devres_free(state);
|
|
state = NULL;
|
|
}
|
|
}
|
|
|
|
return state;
|
|
}
|
|
EXPORT_SYMBOL(devm_stm_device_init);
|
|
|
|
void devm_stm_device_exit(struct device *dev, struct stm_device_state *state)
|
|
{
|
|
int err;
|
|
|
|
__stm_device_exit(state);
|
|
|
|
err = devres_destroy(dev, stm_device_devres_exit,
|
|
stm_device_devres_match, state);
|
|
WARN_ON(err);
|
|
}
|
|
EXPORT_SYMBOL(devm_stm_device_exit);
|
|
|
|
|
|
|
|
static int stm_device_find_sysconf(struct stm_device_config *config,
|
|
const char *name)
|
|
{
|
|
int result = -1;
|
|
int i;
|
|
|
|
BUG_ON(!name);
|
|
|
|
for (i = 0; i < config->sysconfs_num; i++) {
|
|
struct stm_device_sysconf *pad_sysconf = &config->sysconfs[i];
|
|
|
|
if (strcmp(name, pad_sysconf->name) == 0) {
|
|
result = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void stm_device_sysconf_write(struct stm_device_state *state,
|
|
const char* name, unsigned long long value)
|
|
{
|
|
int i;
|
|
|
|
i = stm_device_find_sysconf(state->config, name);
|
|
WARN_ON(i < 0);
|
|
if (i >= 0)
|
|
sysconf_write(state->sysconf_fields[i], value);
|
|
}
|
|
EXPORT_SYMBOL(stm_device_sysconf_write);
|
|
|
|
unsigned long long stm_device_sysconf_read(struct stm_device_state *state,
|
|
const char* name)
|
|
{
|
|
int i;
|
|
unsigned long long result = -1;
|
|
|
|
i = stm_device_find_sysconf(state->config, name);
|
|
WARN_ON(i < 0);
|
|
if (i >= 0)
|
|
result = sysconf_read(state->sysconf_fields[i]);
|
|
|
|
return result;
|
|
}
|
|
EXPORT_SYMBOL(stm_device_sysconf_read);
|
|
|
|
|
|
|
|
void stm_device_power(struct stm_device_state *device_state,
|
|
enum stm_device_power_state power_state)
|
|
{
|
|
if (device_state->power_state == power_state)
|
|
return;
|
|
|
|
if (device_state->config->power)
|
|
device_state->config->power(device_state, power_state);
|
|
|
|
device_state->power_state = power_state;
|
|
}
|
|
EXPORT_SYMBOL(stm_device_power);
|
|
|
|
struct stm_pad_state* stm_device_get_pad_state(struct stm_device_state *state)
|
|
{
|
|
return state->pad_state;
|
|
}
|
|
EXPORT_SYMBOL(stm_device_get_pad_state);
|
|
|
|
struct stm_device_config* stm_device_get_config(struct stm_device_state *state)
|
|
{
|
|
return state->config;
|
|
}
|
|
EXPORT_SYMBOL(stm_device_get_config);
|