/* * (c) 2010 STMicroelectronics Limited * * Author: Pawel Moll * * 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 #include #include #include #include #include #include #include #include #include #include /* 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; }