/* * (c) 2010 STMicroelectronics Limited * * Author: Stuart Menefy * * 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 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);