842 lines
18 KiB
C
842 lines
18 KiB
C
|
/*
|
||
|
* (c) 2010 STMicroelectronics Limited
|
||
|
*
|
||
|
* Author: Pawel Moll <pawel.moll@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/bootmem.h>
|
||
|
#include <linux/bug.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/gpio.h>
|
||
|
#include <linux/list.h>
|
||
|
#include <linux/mutex.h>
|
||
|
#include <linux/seq_file.h>
|
||
|
#include <linux/stm/pad.h>
|
||
|
#include <linux/stm/sysconf.h>
|
||
|
#include <linux/module.h>
|
||
|
|
||
|
|
||
|
|
||
|
/* Internal memory allocation (may be used very early, when
|
||
|
* no kmalloc() is possible yet...) */
|
||
|
|
||
|
#define STM_PAD_STATIC_BUFFER_SIZE 1024
|
||
|
|
||
|
static unsigned char stm_pad_static_buffer[STM_PAD_STATIC_BUFFER_SIZE];
|
||
|
static unsigned char *stm_pad_static_buffer_pointer = stm_pad_static_buffer;
|
||
|
static int stm_pad_static_buffer_avail = sizeof(stm_pad_static_buffer);
|
||
|
|
||
|
static void *stm_pad_alloc(int size)
|
||
|
{
|
||
|
void *result = NULL;
|
||
|
|
||
|
size = ALIGN(size, 4);
|
||
|
|
||
|
if (stm_pad_static_buffer_avail >= size) {
|
||
|
result = stm_pad_static_buffer_pointer;
|
||
|
stm_pad_static_buffer_avail -= size;
|
||
|
stm_pad_static_buffer_pointer += size;
|
||
|
} else {
|
||
|
static int notified;
|
||
|
|
||
|
if (!notified) {
|
||
|
pr_debug("stm_pad: Out of static buffer!\n");
|
||
|
notified = 1;
|
||
|
}
|
||
|
|
||
|
result = kzalloc(size, GFP_KERNEL);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static void stm_pad_free(void *addr)
|
||
|
{
|
||
|
if (addr >= (void *)stm_pad_static_buffer &&
|
||
|
addr < (void *)(stm_pad_static_buffer +
|
||
|
STM_PAD_STATIC_BUFFER_SIZE))
|
||
|
return;
|
||
|
|
||
|
kfree(addr);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* Pads interface implementation */
|
||
|
|
||
|
static int stm_pad_initialized;
|
||
|
|
||
|
struct stm_pad_state {
|
||
|
struct list_head list;
|
||
|
const char *owner;
|
||
|
struct stm_pad_config *config;
|
||
|
struct sysconf_field *sysconf_fields[0]; /* To be expanded */
|
||
|
};
|
||
|
static LIST_HEAD(stm_pad_list);
|
||
|
|
||
|
static enum {
|
||
|
stm_pad_gpio_unused = 0,
|
||
|
stm_pad_gpio_normal_gpio,
|
||
|
stm_pad_gpio_claimed,
|
||
|
stm_pad_gpio_claimed_to_be_requested,
|
||
|
stm_pad_gpio_claimed_requested,
|
||
|
} *stm_pad_gpios;
|
||
|
static int stm_pad_gpios_num;
|
||
|
|
||
|
static DEFINE_MUTEX(stm_pad_mutex);
|
||
|
|
||
|
static int stm_pad_gpio_function_in;
|
||
|
static int stm_pad_gpio_function_out;
|
||
|
|
||
|
static int (*stm_pad_gpio_config)(unsigned gpio,
|
||
|
enum stm_pad_gpio_direction direction,
|
||
|
int function, void *priv);
|
||
|
|
||
|
|
||
|
|
||
|
int __init stm_pad_init(int gpios_num,
|
||
|
int gpio_function_in, int gpio_function_out,
|
||
|
int (*gpio_config)(unsigned gpio,
|
||
|
enum stm_pad_gpio_direction direction,
|
||
|
int function, void *priv))
|
||
|
{
|
||
|
BUG_ON(!gpio_config);
|
||
|
|
||
|
stm_pad_gpios = alloc_bootmem(sizeof(*stm_pad_gpios) * gpios_num);
|
||
|
stm_pad_gpios_num = gpios_num;
|
||
|
stm_pad_gpio_function_in = gpio_function_in;
|
||
|
stm_pad_gpio_function_out = gpio_function_out;
|
||
|
stm_pad_gpio_config = gpio_config;
|
||
|
|
||
|
stm_pad_initialized = 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static int __stm_pad_claim(struct stm_pad_config *config,
|
||
|
struct stm_pad_state *state, const char *owner)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
BUG_ON(!stm_pad_initialized);
|
||
|
WARN_ON(!owner);
|
||
|
|
||
|
mutex_lock(&stm_pad_mutex);
|
||
|
|
||
|
state->owner = owner;
|
||
|
state->config = config;
|
||
|
|
||
|
for (i = 0; i < config->gpios_num; i++) {
|
||
|
struct stm_pad_gpio *pad_gpio = &config->gpios[i];
|
||
|
unsigned gpio = pad_gpio->gpio;
|
||
|
|
||
|
if (pad_gpio->direction == stm_pad_gpio_direction_ignored)
|
||
|
continue;
|
||
|
|
||
|
BUG_ON(pad_gpio->direction != stm_pad_gpio_direction_in &&
|
||
|
pad_gpio->direction !=
|
||
|
stm_pad_gpio_direction_out &&
|
||
|
pad_gpio->direction !=
|
||
|
stm_pad_gpio_direction_bidir &&
|
||
|
pad_gpio->direction !=
|
||
|
stm_pad_gpio_direction_custom);
|
||
|
|
||
|
if (stm_pad_gpios[gpio] != stm_pad_gpio_unused)
|
||
|
goto error_gpios;
|
||
|
|
||
|
stm_pad_gpios[gpio] = stm_pad_gpio_claimed;
|
||
|
|
||
|
if (stm_pad_gpio_config(gpio, pad_gpio->direction,
|
||
|
pad_gpio->function, pad_gpio->priv) != 0) {
|
||
|
i++; /* Current GPIO must be released as well... */
|
||
|
goto error_gpios;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < config->sysconfs_num; i++) {
|
||
|
struct stm_pad_sysconf *sysconf = &config->sysconfs[i];
|
||
|
|
||
|
state->sysconf_fields[i] = sysconf_claim(sysconf->regtype,
|
||
|
sysconf->regnum, sysconf->lsb, sysconf->msb,
|
||
|
owner);
|
||
|
if (!state->sysconf_fields[i])
|
||
|
goto error_sysconfs;
|
||
|
BUG_ON(sysconf->value < 0);
|
||
|
sysconf_write(state->sysconf_fields[i], sysconf->value);
|
||
|
}
|
||
|
|
||
|
list_add(&state->list, &stm_pad_list);
|
||
|
|
||
|
mutex_unlock(&stm_pad_mutex);
|
||
|
|
||
|
if (config->custom_claim &&
|
||
|
config->custom_claim(state, config->custom_priv) != 0)
|
||
|
goto error_custom;
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
error_custom:
|
||
|
mutex_lock(&stm_pad_mutex);
|
||
|
|
||
|
list_del(&state->list);
|
||
|
|
||
|
error_sysconfs:
|
||
|
for (i = 0; i < config->sysconfs_num; i++)
|
||
|
if (state->sysconf_fields[i])
|
||
|
sysconf_release(state->sysconf_fields[i]);
|
||
|
else
|
||
|
break;
|
||
|
i = config->gpios_num;
|
||
|
error_gpios:
|
||
|
while (i--)
|
||
|
stm_pad_gpios[config->gpios[i].gpio] = stm_pad_gpio_unused;
|
||
|
|
||
|
mutex_unlock(&stm_pad_mutex);
|
||
|
|
||
|
return -EBUSY;
|
||
|
}
|
||
|
|
||
|
static int __stm_pad_release(struct stm_pad_state *state)
|
||
|
{
|
||
|
struct stm_pad_config *config = state->config;
|
||
|
int i;
|
||
|
|
||
|
if (config->custom_release)
|
||
|
config->custom_release(state, config->custom_priv);
|
||
|
|
||
|
mutex_lock(&stm_pad_mutex);
|
||
|
|
||
|
list_del(&state->list);
|
||
|
|
||
|
for (i = 0; i < config->sysconfs_num; i++)
|
||
|
sysconf_release(state->sysconf_fields[i]);
|
||
|
|
||
|
for (i = 0; i < config->gpios_num; i++) {
|
||
|
unsigned gpio = config->gpios[i].gpio;
|
||
|
|
||
|
BUG_ON(stm_pad_gpios[gpio] != stm_pad_gpio_claimed);
|
||
|
stm_pad_gpios[gpio] = stm_pad_gpio_unused;
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&stm_pad_mutex);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
struct stm_pad_state *stm_pad_claim(struct stm_pad_config *config,
|
||
|
const char *owner)
|
||
|
{
|
||
|
struct stm_pad_state *state = kzalloc(sizeof(*state) +
|
||
|
sizeof(*state->sysconf_fields) * config->sysconfs_num,
|
||
|
GFP_KERNEL);
|
||
|
|
||
|
BUG_ON(!config);
|
||
|
BUG_ON(!owner);
|
||
|
|
||
|
if (state && __stm_pad_claim(config, state, owner) != 0)
|
||
|
state = NULL;
|
||
|
|
||
|
return state;
|
||
|
}
|
||
|
EXPORT_SYMBOL(stm_pad_claim);
|
||
|
|
||
|
void stm_pad_release(struct stm_pad_state *state)
|
||
|
{
|
||
|
BUG_ON(!state);
|
||
|
|
||
|
__stm_pad_release(state);
|
||
|
|
||
|
stm_pad_free(state);
|
||
|
}
|
||
|
EXPORT_SYMBOL(stm_pad_release);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
static void stm_pad_devres_release(struct device *dev, void *res)
|
||
|
{
|
||
|
struct stm_pad_state *state = res;
|
||
|
|
||
|
__stm_pad_release(state);
|
||
|
}
|
||
|
|
||
|
static int stm_pad_devres_match(struct device *dev, void *res, void *data)
|
||
|
{
|
||
|
struct stm_pad_state *state = res, *match = data;
|
||
|
|
||
|
return state == match;
|
||
|
}
|
||
|
|
||
|
struct stm_pad_state *devm_stm_pad_claim(struct device *dev,
|
||
|
struct stm_pad_config *config, const char *owner)
|
||
|
{
|
||
|
struct stm_pad_state *state = devres_alloc(stm_pad_devres_release,
|
||
|
sizeof(*state) + sizeof(*state->sysconf_fields) *
|
||
|
config->sysconfs_num, GFP_KERNEL);
|
||
|
|
||
|
BUG_ON(!config);
|
||
|
BUG_ON(!owner);
|
||
|
|
||
|
if (state) {
|
||
|
if (__stm_pad_claim(config, state, owner) == 0) {
|
||
|
devres_add(dev, state);
|
||
|
} else {
|
||
|
devres_free(state);
|
||
|
state = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return state;
|
||
|
}
|
||
|
EXPORT_SYMBOL(devm_stm_pad_claim);
|
||
|
|
||
|
void devm_stm_pad_release(struct device *dev, struct stm_pad_state *state)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
__stm_pad_release(state);
|
||
|
|
||
|
err = devres_destroy(dev, stm_pad_devres_release,
|
||
|
stm_pad_devres_match, state);
|
||
|
WARN_ON(err);
|
||
|
}
|
||
|
EXPORT_SYMBOL(devm_stm_pad_release);
|
||
|
|
||
|
|
||
|
|
||
|
/* Private interface for GPIO driver */
|
||
|
|
||
|
int stm_pad_claim_gpio(unsigned gpio)
|
||
|
{
|
||
|
int result = 0;
|
||
|
|
||
|
BUG_ON(!stm_pad_initialized);
|
||
|
BUG_ON(gpio > stm_pad_gpios_num);
|
||
|
|
||
|
mutex_lock(&stm_pad_mutex);
|
||
|
|
||
|
switch (stm_pad_gpios[gpio]) {
|
||
|
case stm_pad_gpio_unused:
|
||
|
stm_pad_gpios[gpio] = stm_pad_gpio_normal_gpio;
|
||
|
break;
|
||
|
case stm_pad_gpio_claimed_to_be_requested:
|
||
|
stm_pad_gpios[gpio] = stm_pad_gpio_claimed_requested;
|
||
|
break;
|
||
|
case stm_pad_gpio_normal_gpio:
|
||
|
case stm_pad_gpio_claimed:
|
||
|
case stm_pad_gpio_claimed_requested:
|
||
|
result = -EBUSY;
|
||
|
break;
|
||
|
default:
|
||
|
result = -EINVAL;
|
||
|
BUG();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&stm_pad_mutex);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
void stm_pad_configure_gpio(unsigned gpio, unsigned direction)
|
||
|
{
|
||
|
switch (direction) {
|
||
|
case STM_GPIO_DIRECTION_IN:
|
||
|
stm_pad_gpio_config(gpio, stm_pad_gpio_direction_in,
|
||
|
stm_pad_gpio_function_in, NULL);
|
||
|
break;
|
||
|
case STM_GPIO_DIRECTION_OUT:
|
||
|
stm_pad_gpio_config(gpio, stm_pad_gpio_direction_out,
|
||
|
stm_pad_gpio_function_out, NULL);
|
||
|
break;
|
||
|
default:
|
||
|
BUG();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void stm_pad_release_gpio(unsigned gpio)
|
||
|
{
|
||
|
BUG_ON(gpio > stm_pad_gpios_num);
|
||
|
|
||
|
mutex_lock(&stm_pad_mutex);
|
||
|
|
||
|
switch (stm_pad_gpios[gpio]) {
|
||
|
case stm_pad_gpio_normal_gpio:
|
||
|
stm_pad_gpios[gpio] = stm_pad_gpio_unused;
|
||
|
break;
|
||
|
case stm_pad_gpio_claimed_requested:
|
||
|
stm_pad_gpios[gpio] = stm_pad_gpio_claimed;
|
||
|
break;
|
||
|
default:
|
||
|
BUG();
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&stm_pad_mutex);
|
||
|
}
|
||
|
|
||
|
const char *stm_pad_get_gpio_owner(unsigned gpio)
|
||
|
{
|
||
|
const char *result = NULL;
|
||
|
struct stm_pad_state *state;
|
||
|
|
||
|
BUG_ON(gpio > stm_pad_gpios_num);
|
||
|
|
||
|
mutex_lock(&stm_pad_mutex);
|
||
|
|
||
|
list_for_each_entry(state, &stm_pad_list, list) {
|
||
|
struct stm_pad_config *config = state->config;
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < config->gpios_num; i++) {
|
||
|
if (config->gpios[i].gpio == gpio) {
|
||
|
result = state->owner;
|
||
|
if (!result)
|
||
|
result = "?";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (result)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&stm_pad_mutex);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* GPIO interface */
|
||
|
|
||
|
static struct stm_pad_gpio *stm_pad_find_gpio(struct stm_pad_config *config,
|
||
|
const char *name);
|
||
|
|
||
|
static int __stm_pad_gpio_request(struct stm_pad_gpio *pad_gpio,
|
||
|
const char *owner)
|
||
|
{
|
||
|
int result = -EBUSY;
|
||
|
unsigned gpio = pad_gpio->gpio;
|
||
|
|
||
|
BUG_ON(!stm_pad_initialized);
|
||
|
|
||
|
mutex_lock(&stm_pad_mutex);
|
||
|
|
||
|
if (stm_pad_gpios[gpio] == stm_pad_gpio_claimed) {
|
||
|
int err;
|
||
|
|
||
|
stm_pad_gpios[gpio] = stm_pad_gpio_claimed_to_be_requested;
|
||
|
|
||
|
mutex_unlock(&stm_pad_mutex);
|
||
|
|
||
|
err = gpio_request(gpio, owner);
|
||
|
|
||
|
mutex_lock(&stm_pad_mutex);
|
||
|
|
||
|
if (err != 0)
|
||
|
stm_pad_gpios[gpio] = stm_pad_gpio_claimed;
|
||
|
|
||
|
result = err;
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&stm_pad_mutex);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
unsigned stm_pad_gpio_request_input(struct stm_pad_state *state,
|
||
|
const char *name)
|
||
|
{
|
||
|
unsigned result = STM_GPIO_INVALID;
|
||
|
struct stm_pad_gpio *pad_gpio = stm_pad_find_gpio(state->config, name);
|
||
|
|
||
|
if (pad_gpio && __stm_pad_gpio_request(pad_gpio, state->owner) == 0) {
|
||
|
gpio_direction_input(pad_gpio->gpio);
|
||
|
stm_pad_gpio_config(pad_gpio->gpio, stm_pad_gpio_direction_in,
|
||
|
stm_pad_gpio_function_in, pad_gpio->priv);
|
||
|
|
||
|
result = pad_gpio->gpio;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
unsigned stm_pad_gpio_request_output(struct stm_pad_state *state,
|
||
|
const char *name, int value)
|
||
|
{
|
||
|
unsigned result = STM_GPIO_INVALID;
|
||
|
struct stm_pad_gpio *pad_gpio = stm_pad_find_gpio(state->config, name);
|
||
|
|
||
|
if (pad_gpio && __stm_pad_gpio_request(pad_gpio, state->owner) == 0) {
|
||
|
BUG_ON(value < 0);
|
||
|
BUG_ON(value > 1);
|
||
|
gpio_direction_output(pad_gpio->gpio, value);
|
||
|
stm_pad_gpio_config(pad_gpio->gpio, stm_pad_gpio_direction_out,
|
||
|
stm_pad_gpio_function_out, pad_gpio->priv);
|
||
|
|
||
|
result = pad_gpio->gpio;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
EXPORT_SYMBOL(stm_pad_gpio_request_output);
|
||
|
|
||
|
void stm_pad_gpio_free(struct stm_pad_state *state, unsigned gpio)
|
||
|
{
|
||
|
struct stm_pad_config *config;
|
||
|
int i;
|
||
|
|
||
|
BUG_ON(!state);
|
||
|
config = state->config;
|
||
|
|
||
|
for (i = 0; i < config->gpios_num; i++) {
|
||
|
struct stm_pad_gpio *pad_gpio = &config->gpios[i];
|
||
|
|
||
|
if (pad_gpio->gpio == gpio) {
|
||
|
gpio_free(gpio);
|
||
|
|
||
|
stm_pad_gpio_config(gpio, pad_gpio->direction,
|
||
|
pad_gpio->function, pad_gpio->priv);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Should never be here... */
|
||
|
BUG();
|
||
|
}
|
||
|
EXPORT_SYMBOL(stm_pad_gpio_free);
|
||
|
|
||
|
|
||
|
/* debugfs view of used pads */
|
||
|
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
|
||
|
static void *stm_pad_seq_start(struct seq_file *s, loff_t *pos)
|
||
|
{
|
||
|
mutex_lock(&stm_pad_mutex);
|
||
|
|
||
|
return seq_list_start(&stm_pad_list, *pos);
|
||
|
}
|
||
|
|
||
|
static void *stm_pad_seq_next(struct seq_file *s, void *v, loff_t *pos)
|
||
|
{
|
||
|
return seq_list_next(v, &stm_pad_list, pos);
|
||
|
}
|
||
|
|
||
|
static void stm_pad_seq_stop(struct seq_file *s, void *v)
|
||
|
{
|
||
|
mutex_unlock(&stm_pad_mutex);
|
||
|
}
|
||
|
|
||
|
static int stm_pad_seq_show(struct seq_file *s, void *v)
|
||
|
{
|
||
|
struct stm_pad_state *state = list_entry(v, struct stm_pad_state,
|
||
|
list);
|
||
|
struct stm_pad_config *config = state->config;
|
||
|
int i;
|
||
|
|
||
|
seq_printf(s, "*** %s ***\n", state->owner);
|
||
|
|
||
|
for (i = 0; i < config->gpios_num; i++) {
|
||
|
struct stm_pad_gpio *pad_gpio = &config->gpios[i];
|
||
|
static const char *directions[] = {
|
||
|
[stm_pad_gpio_direction_in] = "input",
|
||
|
[stm_pad_gpio_direction_out] = "output",
|
||
|
[stm_pad_gpio_direction_bidir] = "bidirectional",
|
||
|
[stm_pad_gpio_direction_custom] = "custom mode",
|
||
|
};
|
||
|
|
||
|
if (i == 0)
|
||
|
seq_printf(s, "- GPIOs:\n");
|
||
|
|
||
|
if (pad_gpio->direction == stm_pad_gpio_direction_ignored)
|
||
|
continue;
|
||
|
|
||
|
BUG_ON(pad_gpio->direction >= ARRAY_SIZE(directions));
|
||
|
|
||
|
seq_printf(s, " - %d: PIO%d.%d: %s, function %d",
|
||
|
pad_gpio->gpio,
|
||
|
stm_gpio_port(pad_gpio->gpio),
|
||
|
stm_gpio_pin(pad_gpio->gpio),
|
||
|
directions[pad_gpio->direction],
|
||
|
pad_gpio->function);
|
||
|
if (pad_gpio->name)
|
||
|
seq_printf(s, ", '%s'", pad_gpio->name);
|
||
|
seq_printf(s, "\n");
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < config->sysconfs_num; i++) {
|
||
|
struct stm_pad_sysconf *sysconf = &config->sysconfs[i];
|
||
|
const char *name = sysconf_reg_name(sysconf->regtype,
|
||
|
sysconf->regnum);
|
||
|
|
||
|
if (i == 0)
|
||
|
seq_printf(s, "- System Configuration values:\n");
|
||
|
|
||
|
seq_printf(s, " - ");
|
||
|
if (name) {
|
||
|
seq_printf(s, "%s", name);
|
||
|
} else {
|
||
|
name = sysconf_group_name(sysconf->regtype);
|
||
|
if (name)
|
||
|
seq_printf(s, "%s%d", name, sysconf->regnum);
|
||
|
else
|
||
|
seq_printf(s, "%d.%d", sysconf->regtype,
|
||
|
sysconf->regnum);
|
||
|
}
|
||
|
|
||
|
if (sysconf->msb == sysconf->lsb)
|
||
|
seq_printf(s, "[%d]", sysconf->lsb);
|
||
|
else
|
||
|
seq_printf(s, "[%d:%d]", sysconf->msb, sysconf->lsb);
|
||
|
|
||
|
seq_printf(s, " = 0x%0*lx\n",
|
||
|
(sysconf->msb - sysconf->lsb + 4) / 4,
|
||
|
sysconf_read(state->sysconf_fields[i]));
|
||
|
}
|
||
|
|
||
|
if (config->custom_claim) {
|
||
|
seq_printf(s, "- custom claim");
|
||
|
if (!config->custom_release)
|
||
|
seq_printf(s, " and release");
|
||
|
seq_printf(s, "\n");
|
||
|
}
|
||
|
|
||
|
seq_printf(s, "\n");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct seq_operations stm_pad_seq_ops = {
|
||
|
.start = stm_pad_seq_start,
|
||
|
.next = stm_pad_seq_next,
|
||
|
.stop = stm_pad_seq_stop,
|
||
|
.show = stm_pad_seq_show,
|
||
|
};
|
||
|
|
||
|
static int stm_pad_debugfs_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
BUG_ON(!stm_pad_initialized);
|
||
|
|
||
|
return seq_open(file, &stm_pad_seq_ops);
|
||
|
}
|
||
|
|
||
|
static const struct file_operations stm_pad_debugfs_ops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = stm_pad_debugfs_open,
|
||
|
.read = seq_read,
|
||
|
.llseek = seq_lseek,
|
||
|
.release = seq_release,
|
||
|
};
|
||
|
|
||
|
static int __init stm_pad_debugfs_init(void)
|
||
|
{
|
||
|
debugfs_create_file("pads", S_IFREG | S_IRUGO,
|
||
|
NULL, NULL, &stm_pad_debugfs_ops);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
subsys_initcall(stm_pad_debugfs_init);
|
||
|
|
||
|
#endif /* CONFIG_DEBUG_FS */
|
||
|
|
||
|
|
||
|
|
||
|
/* Configuration structure helpers */
|
||
|
|
||
|
static struct stm_pad_gpio *stm_pad_find_gpio(struct stm_pad_config *config,
|
||
|
const char *name)
|
||
|
{
|
||
|
struct stm_pad_gpio *result = NULL;
|
||
|
int i;
|
||
|
|
||
|
BUG_ON(!name);
|
||
|
|
||
|
for (i = 0; i < config->gpios_num; i++) {
|
||
|
struct stm_pad_gpio *pad_gpio = &config->gpios[i];
|
||
|
|
||
|
if (pad_gpio->name && strcmp(name, pad_gpio->name) == 0) {
|
||
|
result = pad_gpio;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
int __init stm_pad_set_gpio(struct stm_pad_config *config, const char *name,
|
||
|
unsigned gpio)
|
||
|
{
|
||
|
int result = -EINVAL;
|
||
|
struct stm_pad_gpio *pad_gpio = stm_pad_find_gpio(config, name);
|
||
|
|
||
|
WARN_ON(!pad_gpio);
|
||
|
if (pad_gpio) {
|
||
|
pad_gpio->gpio = gpio;
|
||
|
result = 0;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
int __init stm_pad_set_priv(struct stm_pad_config *config, const char *name,
|
||
|
void *priv)
|
||
|
{
|
||
|
int result = -EINVAL;
|
||
|
struct stm_pad_gpio *pad_gpio = stm_pad_find_gpio(config, name);
|
||
|
|
||
|
WARN_ON(!pad_gpio);
|
||
|
if (pad_gpio) {
|
||
|
pad_gpio->priv = priv;
|
||
|
result = 0;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
int __init stm_pad_set_direction_function(struct stm_pad_config *config,
|
||
|
const char *name, enum stm_pad_gpio_direction direction,
|
||
|
int out_value, int function)
|
||
|
{
|
||
|
int result = -EINVAL;
|
||
|
struct stm_pad_gpio *pad_gpio = stm_pad_find_gpio(config, name);
|
||
|
|
||
|
WARN_ON(!pad_gpio);
|
||
|
if (pad_gpio) {
|
||
|
pad_gpio->direction = direction;
|
||
|
pad_gpio->out_value = out_value;
|
||
|
pad_gpio->function = function;
|
||
|
result = 0;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* Dynamic configuration routines */
|
||
|
|
||
|
#define to_dynamic(config) \
|
||
|
container_of(config, struct stm_pad_config_dynamic, config)
|
||
|
|
||
|
#if defined(CONFIG_BUG)
|
||
|
#define MAGIC_FIELD enum { magic_good = 0x600df00d } magic;
|
||
|
#define MAGIC_SET(d) d->magic = magic_good
|
||
|
#define MAGIC_CHECK(d) BUG_ON(d->magic != magic_good)
|
||
|
#else
|
||
|
#define MAGIC_FIELD
|
||
|
#define MAGIC_SET(d)
|
||
|
#define MAGIC_CHECK(d)
|
||
|
#endif
|
||
|
|
||
|
struct stm_pad_config_dynamic {
|
||
|
MAGIC_FIELD
|
||
|
int sysconfs_allocated;
|
||
|
int gpios_allocated;
|
||
|
struct stm_pad_config config;
|
||
|
};
|
||
|
|
||
|
struct stm_pad_config * __init stm_pad_config_alloc(int gpios_num,
|
||
|
int sysconfs_num)
|
||
|
{
|
||
|
struct stm_pad_config *config;
|
||
|
struct stm_pad_config_dynamic *dynamic;
|
||
|
|
||
|
dynamic = stm_pad_alloc(sizeof(struct stm_pad_config_dynamic) +
|
||
|
sizeof(struct stm_pad_gpio) * gpios_num +
|
||
|
sizeof(struct stm_pad_sysconf) * sysconfs_num);
|
||
|
if (!dynamic)
|
||
|
return NULL;
|
||
|
MAGIC_SET(dynamic);
|
||
|
|
||
|
/* +------------+
|
||
|
* | dynamic | sizeof(struct stm_pad_config_dynamic)
|
||
|
* | { config } |
|
||
|
* +------------+
|
||
|
* | gpios | sizeof(struct stm_pad_gpio) * gpios_num
|
||
|
* +------------+
|
||
|
* | sysconfs | sizeof(struct stm_pad_sysconf) * sysconfs_num
|
||
|
* +------------+
|
||
|
*/
|
||
|
|
||
|
config = &dynamic->config;
|
||
|
|
||
|
dynamic->gpios_allocated = gpios_num;
|
||
|
config->gpios = (void *)dynamic + sizeof(struct stm_pad_config_dynamic);
|
||
|
|
||
|
dynamic->sysconfs_allocated = sysconfs_num;
|
||
|
config->sysconfs = (void *)config->gpios +
|
||
|
sizeof(struct stm_pad_gpio) * gpios_num;
|
||
|
|
||
|
return config;
|
||
|
}
|
||
|
|
||
|
int __init stm_pad_config_add_sysconf(struct stm_pad_config *config,
|
||
|
int regtype, int regnum, int lsb, int msb, int value)
|
||
|
{
|
||
|
int result = -ENOMEM;
|
||
|
struct stm_pad_config_dynamic *dynamic;
|
||
|
struct stm_pad_sysconf *sysconf;
|
||
|
|
||
|
BUG_ON(!config);
|
||
|
dynamic = to_dynamic(config);
|
||
|
MAGIC_CHECK(dynamic);
|
||
|
|
||
|
WARN_ON(config->sysconfs_num == dynamic->sysconfs_allocated);
|
||
|
if (config->sysconfs_num < dynamic->sysconfs_allocated) {
|
||
|
sysconf = config->sysconfs + config->sysconfs_num++;
|
||
|
|
||
|
sysconf->regtype = regtype;
|
||
|
sysconf->regnum = regnum;
|
||
|
sysconf->lsb = lsb;
|
||
|
sysconf->msb = msb;
|
||
|
sysconf->value = value;
|
||
|
|
||
|
result = 0;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
int __init stm_pad_config_add_gpio_named(struct stm_pad_config *config,
|
||
|
unsigned gpio, enum stm_pad_gpio_direction direction,
|
||
|
int out_value, int function, const char *name)
|
||
|
{
|
||
|
int result = -ENOMEM;
|
||
|
struct stm_pad_config_dynamic *dynamic;
|
||
|
struct stm_pad_gpio *pad_gpio;
|
||
|
|
||
|
BUG_ON(!config);
|
||
|
dynamic = to_dynamic(config);
|
||
|
MAGIC_CHECK(dynamic);
|
||
|
|
||
|
WARN_ON(config->gpios_num == dynamic->gpios_allocated);
|
||
|
if (config->gpios_num < dynamic->gpios_allocated) {
|
||
|
pad_gpio = config->gpios + config->gpios_num++;
|
||
|
|
||
|
pad_gpio->gpio = gpio;
|
||
|
pad_gpio->direction = direction;
|
||
|
pad_gpio->out_value = out_value;
|
||
|
pad_gpio->function = function;
|
||
|
pad_gpio->name = name;
|
||
|
|
||
|
result = 0;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|