/* * ------------------------------------------------------------------------- * (C) STMicroelectronics 2009 * (C) STMicroelectronics 2010 * Author: Francesco M. Virlinzi * ------------------------------------------------------------------------- * May be copied or modified under the terms of the GNU General Public * License v.2 ONLY. See linux/COPYING for more information. * * ------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../base/power/power.h" #include "../base/base.h" #define PMS_STATE_NAME_SIZE 24 enum pms_type { #ifdef CONFIG_CPU_FREQ PMS_TYPE_CPU, #endif PMS_TYPE_CLK, PMS_TYPE_DEV, PMS_TYPE_MAX }; struct pms_object { int type; union { void *data; int cpu_id; /* all the cpu managed by pms */ struct clk *clk; /* all the clock managed by pms */ struct device *dev; /* all the device managed by pms */ }; struct list_head node; struct list_head constraints; /* all the constraint on this object */ }; struct pms_state { struct kobject kobj; int is_active:1; char name[PMS_STATE_NAME_SIZE]; struct list_head node; /* the states list */ struct list_head constraints; /* the constraint list */ }; struct pms_constraint { struct pms_object *obj; /* the constraint owner */ struct pms_state *state; unsigned long value; /* the constraint value */ struct list_head obj_node; struct list_head state_node; }; static LIST_HEAD(pms_state_list); static DECLARE_MUTEX(pms_sem); static spinlock_t pms_lock = __SPIN_LOCK_UNLOCKED(); static char *pms_active_buf; /* the last command line */ struct kobject *pms_kobj; /* * The PMS uses 'PMS_TYPE_MAX lists' of objects * if CONFIG_CPU_FREQ is defined we have: * - 0. cpus * - 1. clocks * - 2. devices * else we have: * - 0. clocks * - 1. devices */ static struct list_head pms_obj_lists[PMS_TYPE_MAX] = { LIST_HEAD_INIT(pms_obj_lists[0]), LIST_HEAD_INIT(pms_obj_lists[1]), #ifdef CONFIG_CPU_FREQ LIST_HEAD_INIT(pms_obj_lists[2]), #endif }; /* * Utility functions */ static inline char *_strsep(char **s, const char *d) { int i, len = strlen(d); retry: if (!(*s) || !(**s)) return NULL; for (i = 0; i < len; ++i) { if (**s != *(d + i)) continue; ++(*s); goto retry; } return strsep(s, d); } static inline int clk_is_readonly(struct clk *clk) { return !clk->ops || !clk->ops->set_rate; } /* * End Utility functions */ static int pms_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { ssize_t ret = -EIO; struct kobj_attribute *k_attr = container_of(attr, struct kobj_attribute, attr); if (k_attr->show) ret = k_attr->show(kobj, k_attr, buf); return ret; } static ssize_t pms_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { ssize_t ret = -EIO; struct kobj_attribute *k_attr = container_of(attr, struct kobj_attribute, attr); if (k_attr->store) ret = k_attr->store(kobj, k_attr, buf, count); return ret; } static struct sysfs_ops pms_sysfs_ops = { .show = pms_attr_show, .store = pms_attr_store, }; static struct kobj_type ktype_pms = { .sysfs_ops = &pms_sysfs_ops, }; static ssize_t pms_constraint_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int i, ret = 0; struct pms_state *state = (struct pms_state *) container_of(kobj, struct pms_state, kobj); struct pms_object *obj; struct pms_constraint *constr; pr_debug("\n"); ret += sprintf(buf + ret, " -- state: %s --\n", state->name); for (i = 0; i < PMS_TYPE_MAX; ++i) list_for_each_entry(obj, &pms_obj_lists[i], node) list_for_each_entry(constr, &obj->constraints, obj_node) { if (constr->state == state) { switch (obj->type) { #ifdef CONFIG_CPU_FREQ case PMS_TYPE_CPU: ret += sprintf(buf + ret, " + cpu: %10u @ %10u\n", (unsigned int)obj->cpu_id, (unsigned int)constr->value); break; #endif case PMS_TYPE_CLK: ret += sprintf(buf + ret, " + clk: %10s @ %10u\n", obj->clk->name, (unsigned int)constr->value); break; case PMS_TYPE_DEV: ret += sprintf(buf + ret, " + dev: %10s is ", dev_name(obj->dev)); if (constr->value == PM_EVENT_ON) ret += sprintf(buf + ret, "on\n"); else ret += sprintf(buf + ret, "off\n"); } } } return ret; } static struct kobj_attribute pms_constraints = (struct kobj_attribute) __ATTR(constraints, S_IRWXU, pms_constraint_show, NULL); static ssize_t pms_valids_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int ret = 0; struct pms_state *main_state = (struct pms_state *) container_of(kobj, struct pms_state, kobj); struct pms_state *statep; pr_debug("\n"); list_for_each_entry(statep, &pms_state_list, node) { if (statep == main_state) continue; if (!pms_check_valid(main_state, statep)) ret += sprintf(buf + ret, " + %s\n", statep->name); } return ret; } static struct kobj_attribute pms_valids = (struct kobj_attribute) __ATTR(valids, S_IRUGO, pms_valids_show, NULL); struct pms_state *pms_state_get(const char *name) { struct pms_state *statep; if (!name) return NULL; list_for_each_entry(statep, &pms_state_list, node) if (!strcmp(name, statep->name)) return statep; return NULL; } static struct pms_object *pms_find_object(int type, void *data) { struct pms_object *obj; struct list_head *head; head = &pms_obj_lists[type]; list_for_each_entry(obj, head, node) if (obj->data == data) return obj; return NULL; } static int dev_match_address(struct device *dev, void *child) { return (dev == (struct device *)child); } static inline int device_is_parent(struct device *parent, struct device *child) { if (device_find_child(parent, child, dev_match_address)) return 1; return 0; } static int pms_register_object(struct pms_object *obj) { unsigned long flags; void *data_parent; int no_childs; struct pms_object *parent = NULL; struct pms_object *entry; struct list_head *head; pr_debug("\n"); head = &pms_obj_lists[obj->type]; switch (obj->type) { case PMS_TYPE_CLK: data_parent = (void *)obj->clk->parent; no_childs = list_empty(&obj->clk->children); break; case PMS_TYPE_DEV: data_parent = (void *)obj->dev->parent; no_childs = list_empty(&obj->dev->p->klist_children.k_list); break; #ifdef CONFIG_CPU_FREQ case PMS_TYPE_CPU: data_parent = NULL; no_childs = (1 == 1); break; #endif default: pr_err("[STM][PMS]: Error Object type not supported\n"); return -1; } spin_lock_irqsave(&pms_lock, flags); /* objects are in a sorted list */ /* 1. No parent... go in head */ if (!data_parent) { list_add(&obj->node, head); goto reg_complete; } /* 2. with parent and no child... go in tail */ if (no_childs) { list_add_tail(&obj->node, head); goto reg_complete; } /* 3. with parent and child... go after your parent */ /* 3.1 check if the parent is registerd */ list_for_each_entry(entry, head, node) if (entry->data == data_parent) { parent = entry; break; } if (!parent) /* the parent isn't registered... go to the head (safe for child)... */ list_add(&obj->node, head); else /* 3.1.1 added after the parent */ list_add(&obj->node, &parent->node); reg_complete: spin_unlock_irqrestore(&pms_lock, flags); return 0; } static struct pms_object *pms_create_object(int type, void *data) { struct pms_object *obj = NULL; pr_debug("\n"); if (pms_find_object(type, data)) { pr_info("[STM][PMS]: object already registered\n"); goto err_0; } obj = (struct pms_object *) kmalloc(sizeof(struct pms_object), GFP_KERNEL); if (!obj) goto err_0; obj->data = data; obj->type = type; INIT_LIST_HEAD(&obj->constraints); if (pms_register_object(obj)) goto err_1; #if 0 /* Initializes the constraints value for the state already registered */ list_for_each_entry(statep, &pms_state_list, node) { struct pms_constraint *constr; constr = (struct pms_constraint *) kmalloc(sizeof(struct pms_constraint), GFP_KERNEL); constr->obj = obj; constr->state = statep; list_add(&constr->obj_node, &obj->constraints); list_add(&constr->state_node, &statep->constraints); switch (type) { case PMS_TYPE_CLK: constr->value = clk_get_rate(obj->clk); break; case PMS_TYPE_DEV: constr->value = PM_EVENT_ON; break; #ifdef CONFIG_CPU_FREQ case PMS_TYPE_CPU: constr->value = cpufreq_get((unsigned int)obj->cpu_id) * 1000; break; #endif } } #endif return obj; err_1: kfree(obj); err_0: return NULL; } static struct pms_object *pms_check_and_add_object(int type, void *data) { struct pms_object *obj; pr_debug("\n"); /* * If the object is already registerd then returns the object it-self */ obj = pms_find_object(type, data); if (obj) return obj; return pms_create_object(type, data); } struct pms_object *pms_register_clock(struct clk *clk) { return pms_check_and_add_object(PMS_TYPE_CLK, (void *)clk); } EXPORT_SYMBOL(pms_register_clock); struct pms_object *pms_register_device(struct device *dev) { return pms_check_and_add_object(PMS_TYPE_DEV, (void *)dev); } EXPORT_SYMBOL(pms_register_device); struct pms_object *pms_register_cpu(int cpu_id) { #ifdef CONFIG_CPU_FREQ if (cpu_id >= NR_CPUS) return NULL; return pms_check_and_add_object(PMS_TYPE_CPU, (void *)cpu_id); #else return NULL; #endif } EXPORT_SYMBOL(pms_register_cpu); struct pms_object *pms_register_clock_n(char *name) { struct clk *clk; clk = clk_get(NULL, name); if (!clk) { pr_debug("Clock not declared\n"); return NULL; } pr_debug("cmd_add_clk: '%s'\n", name); return pms_register_clock(clk); } EXPORT_SYMBOL(pms_register_clock_n); struct bus_type *find_bus(char *name); struct pms_object *pms_register_device_n(char *name) { struct bus_type *bus; struct device *dev; char *bus_name, *dev_name; char *loc_dev_path = kzalloc(strlen(name) + 1, GFP_KERNEL); if (!loc_dev_path) return NULL; strncpy(loc_dev_path, name, strlen(name)); bus_name = _strsep((char **)&loc_dev_path, "/ \n\t\0"); if (!bus_name) { pr_debug("Error on bus name\n"); goto err_0; } dev_name = _strsep((char **)&loc_dev_path, " \n\t\0"); if (!dev_name) { pr_debug("Error on dev name\n"); goto err_0; } bus = find_bus(bus_name); if (!bus) { pr_debug("Bus not declared\n"); goto err_0; } dev = bus_find_device_by_name(bus, NULL, dev_name); if (!dev) { pr_debug("Device not found\n"); goto err_0; } kfree(loc_dev_path); return pms_register_device(dev); err_0: kfree(loc_dev_path); return NULL; } EXPORT_SYMBOL(pms_register_device_n); static int pms_unregister_object(int type, void *_obj) { struct pms_object *obj; struct pms_constraint *constraint; obj = pms_find_object(type, _obj); if (!obj) return -1; list_del(&obj->node); /* removed in the object list */ list_for_each_entry(constraint, &obj->constraints, obj_node) { list_del(&constraint->state_node); kfree(constraint); } kfree(obj); return 0; } int pms_unregister_clock(struct clk *clk) { return pms_unregister_object(PMS_TYPE_CLK, (void *)clk); } EXPORT_SYMBOL(pms_unregister_clock); int pms_unregister_device(struct device *dev) { return pms_unregister_object(PMS_TYPE_DEV, (void *)dev); } EXPORT_SYMBOL(pms_unregister_device); int pms_unregister_cpu(int cpu_id) { #ifdef CONFIG_CPU_FREQ return pms_unregister_object(PMS_TYPE_CPU, (void *)cpu_id); #else return 0; #endif } EXPORT_SYMBOL(pms_unregister_cpu); int pms_set_wakeup(struct pms_object *obj, int enable) { struct device *dev = (struct device *)obj->data; if (obj->type != PMS_TYPE_DEV) return -EINVAL; if (!device_can_wakeup(dev)) return -EINVAL; device_set_wakeup_enable(dev, enable); return 0; } EXPORT_SYMBOL(pms_set_wakeup); int pms_get_wakeup(struct pms_object *obj) { struct device *dev = (struct device *)obj->data; if (obj->type != PMS_TYPE_DEV) return -EINVAL; return device_may_wakeup(dev); } EXPORT_SYMBOL(pms_get_wakeup); struct pms_state *pms_create_state(char *name) { struct pms_state *state = NULL; unsigned long flags; pr_debug("\n"); if (!name) return state; state = pms_state_get(name); if (state) return state; state = kzalloc(sizeof(struct pms_state), GFP_KERNEL); if (!state) return state; /* Initialise some fields... */ strncpy(state->name, name, PMS_STATE_NAME_SIZE); INIT_LIST_HEAD(&state->constraints); kobject_init(&state->kobj, &ktype_pms); kobject_set_name(&state->kobj, name); if (kobject_add(&state->kobj, pms_kobj, name)) goto error; /* add to the list */ spin_lock_irqsave(&pms_lock, flags); list_add_tail(&state->node, &pms_state_list); spin_unlock_irqrestore(&pms_lock, flags); if (sysfs_create_file(&state->kobj, &pms_constraints.attr)) ; if (sysfs_create_file(&state->kobj, &pms_valids.attr)) ; #if 0 /* Initializes the constraints value for all the * objects already registered */ for (idx = 0; idx < PMS_TYPE_MAX; ++idx) list_for_each_entry(obj, pms_obj_lists[idx], node) { struct pms_constraint *constr; constr = (struct pms_constraint *) kmalloc(sizeof(struct pms_constraint), GFP_KERNEL); constr->obj = obj; constr->state = state; list_add(&constr->obj_node, &obj->constraints); list_add(&constr->state_node, &state->constraints); switch (obj->type) { case PMS_TYPE_CLK: constr->value = clk_get_rate(obj->clk); break; case PMS_TYPE_DEV: constr->value = PM_EVENT_ON; break; #ifdef CONFIG_CPU_FREQ case PMS_TYPE_CPU: constr->value = cpufreq_get((unsigned int)obj->cpu_id) * 1000; break; #endif } /* switch */ } /* list... */ #endif return state; error: pr_debug("Error to register the state %s\n", name); kfree(state); return NULL; } EXPORT_SYMBOL(pms_create_state); int pms_destry_state(struct pms_state *state) { struct pms_constraint *constraint; unsigned long flags; if (!state) return -1; spin_lock_irqsave(&pms_lock, flags); list_del(&state->node); list_for_each_entry(constraint, &state->constraints, state_node) { list_del(&constraint->obj_node); kfree(constraint); } spin_unlock_irqrestore(&pms_lock, flags); kobject_del(&state->kobj); kfree(state); return 0; } EXPORT_SYMBOL(pms_destry_state); int pms_set_constraint(struct pms_state *state, struct pms_object *obj, unsigned long value) { struct pms_constraint *constraint; pr_debug("\n"); if (!obj || !state || obj->type >= PMS_TYPE_MAX) return -1; #if 0 pr_debug("state: %s - obj: %s - data: %u\n", state->name, (obj->type == PMS_TYPE_CLK) ? obj->clk->name : dev_name(obj->dev), (unsigned int)value); #endif list_for_each_entry(constraint, &state->constraints, state_node) if (constraint->obj == obj) { constraint->value = value; return 0; } /* there is no contraint already created * therefore I have to create it */ pr_debug("New constraint created\n"); constraint = (struct pms_constraint *) kmalloc(sizeof(struct pms_constraint), GFP_KERNEL); constraint->obj = obj; constraint->state = state; constraint->value = value; list_add(&constraint->obj_node, &obj->constraints); list_add(&constraint->state_node, &state->constraints); return 0; } EXPORT_SYMBOL(pms_set_constraint); int pms_check_valid(struct pms_state *a, struct pms_state *b) { struct pms_object *obj; struct pms_constraint *constraint; struct pms_constraint *ca, *cb; int idx; pr_debug("\n"); for (idx = 0; idx < PMS_TYPE_MAX; ++idx) list_for_each_entry(obj, &pms_obj_lists[idx], node) { ca = cb = NULL; list_for_each_entry(constraint, &obj->constraints, obj_node) { if (constraint->state == a) ca = constraint; if (constraint->state == b) cb = constraint; if (ca && cb && ca->value != cb->value) /* this means both the states have a * contraint on the object obj */ return -EPERM; } } return 0; } EXPORT_SYMBOL(pms_check_valid); /* * Check if the constraint is already taken */ static int pms_check_constraint(struct pms_constraint *constraint) { switch (constraint->obj->type) { case PMS_TYPE_CLK: return clk_get_rate(constraint->obj->clk) == constraint->value; case PMS_TYPE_DEV: return constraint->obj->dev->power.runtime_status == constraint->value; #ifdef CONFIG_CPU_FREQ case PMS_TYPE_CPU: return cpufreq_get((unsigned int)constraint->obj->cpu_id) * 1000 == constraint->value; #endif default: pr_err("[STM][PMS]: pms_check_constraint: " "Constraint type invalid...\n"); return -1; } return 0; } static void pms_update_constraint(struct pms_constraint *constraint) { struct clk *clk = constraint->obj->clk; struct device *dev = constraint->obj->dev; switch (constraint->obj->type) { #ifdef CONFIG_CPU_FREQ case PMS_TYPE_CPU:{ struct cpufreq_policy policy; cpufreq_get_policy(&policy, (unsigned int)constraint->obj-> cpu_id); if (!strncmp(policy.governor->name, "userspace", 8)) cpufreq_driver_target(&policy, constraint->value / 1000, 0); else pr_err("[STM][PMS]: " "Try to force a cpu rate while using" " a not 'userspace' governor"); } return; #endif case PMS_TYPE_CLK: if (!constraint->value) { clk_disable(clk); return; } if (clk_get_rate(clk) == 0) clk_enable(clk); clk_set_rate(clk, constraint->value); return; case PMS_TYPE_DEV: if (constraint->value == RPM_ACTIVE) { pr_debug("[STM][PMS]: resumes device %s\n", dev_name(dev)); pm_runtime_resume(dev); } else { pm_runtime_suspend(dev); pr_debug("[STM][PMS]: suspends device %s\n", dev_name(dev)); } return; } /* switch... */ return; } static int pms_active_state(struct pms_state *state) { struct pms_constraint *constraint; struct pms_object *obj; int idx; pr_debug("\n"); if (!state) { pr_debug("State NULL\n"); return -1; } /* Check for global agreement */ #if 0 list_for_each_entry(constraintp, &new_state->clk_constr, node) { struct clk *clk = constraintp->obj->clk; if (!constraintp->value) ret |= clk_disable(clk, 0); else { if (clk_get_rate(clk) == 0) ret |= clk_enable(clk, 0); else ret |= clk_set_rate(clk, constraintp->value); } if (ret) return -1; } #endif /* 1.nd step... */ for (idx = 0; idx < PMS_TYPE_MAX; ++idx) list_for_each_entry(obj, &pms_obj_lists[idx], node) list_for_each_entry(constraint, &obj->constraints, obj_node) if (constraint->state == state && !pms_check_constraint(constraint)) pms_update_constraint(constraint); state->is_active = 1; return 0; } int pms_set_current_states(char *buf) { char *buf0, *buf1; int n_state = 0, i; struct pms_state **new_active_states; struct pms_state *state; pr_debug("\n"); if (!buf) return -1; pr_debug("Parsing of: %s\n", buf); buf0 = kmalloc(strlen(buf) + 1, GFP_KERNEL); strcpy(buf0, buf); buf1 = buf0; for (; _strsep(&buf1, " ;\n") != NULL; ++n_state) ; pr_debug("Found %d state\n", n_state); new_active_states = (struct pms_state **) kmalloc(sizeof(struct pms_state *) * n_state, GFP_KERNEL); if (!new_active_states) { pr_debug("No Memory\n"); return -ENOMEM; } strcpy(buf0, buf); buf1 = buf0; for (i = 0; i < n_state; ++i) { new_active_states[i] = pms_state_get(_strsep(&buf1, " ;\n")); if (!new_active_states[i]) goto error_pms_set_current_states; } #ifdef CONFIG_PMS_CHECK_GROUP /* before we active the states we check if * there is no conclict in the set it-self */ { int j; for (i = 0; i < n_state - 1; ++i) for (j = i + 1; j < n_state; ++j) if (pms_check_valid(new_active_states[i], new_active_states[j])) { pr_err("[STM][PMS]: " "pms error: states invalid: %s - %s", new_active_states[i]->name, new_active_states[j]->name); goto error_pms_set_current_states; } } #endif /* declare the previous set as not_active */ list_for_each_entry(state, &pms_state_list, node) state->is_active = 0; /* now migrate to the new 'states' */ for (i = 0; i < n_state; ++i) /* active the state */ pms_active_state(*(new_active_states + i)); kfree(pms_active_buf); kfree(new_active_states); strcpy(buf0, buf); pms_active_buf = buf0; return 0; error_pms_set_current_states: pr_debug("Error in the sets of state required\n"); kfree(buf0); kfree(new_active_states); return -EINVAL; } EXPORT_SYMBOL(pms_set_current_states); char *pms_get_current_state(void) { pr_debug("\n"); return pms_active_buf; } EXPORT_SYMBOL(pms_get_current_state); extern unsigned int wokenup_by; int pms_global_standby(enum pms_standby_e state) { int ret = -EINVAL; switch (state) { #ifdef CONFIG_SUSPEND case PMS_GLOBAL_STANDBY: case PMS_GLOBAL_MEMSTANDBY: ret = pm_suspend(state == PMS_GLOBAL_STANDBY ? PM_SUSPEND_STANDBY : PM_SUSPEND_MEM); if (ret >= 0) ret = (int)wokenup_by; break; #endif #ifdef CONFIG_HIBERNATION case PMS_GLOBAL_HIBERNATION: ret = hibernate(); break; #endif #ifdef CONFIG_HIBERNATION_ON_MEMORY case PMS_GLOBAL_MEMHIBERNATION: ret = hibernate_on_memory(); break; #endif default: pr_err("PMS Error: in %s state 0x%x not supported\n", __func__, state); } return ret; } EXPORT_SYMBOL(pms_global_standby); #ifndef CONFIG_STM_LPC #include #endif int stm_lpc_set(int enable, unsigned long long tick); int pms_set_wakeup_timers(unsigned long long second) { #ifdef CONFIG_STM_LPC return stm_lpc_set(1, second); #else static struct rtc_device *dev; unsigned long secs_wake = 0; struct rtc_wkalrm wake_time; if (!dev) dev = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); pr_info("%s - %d on %s\n", __func__, (int)second, dev_name(&dev->dev)); if (!second) { wake_time.enabled = 0; rtc_tm_to_time(&wake_time.time, &secs_wake); return 0; } rtc_read_time(dev, &wake_time.time); rtc_tm_to_time(&wake_time.time, &secs_wake); secs_wake += second; rtc_time_to_tm(secs_wake, &wake_time.time); wake_time.enabled = 1; rtc_set_alarm(dev, &wake_time); return 0; #endif } EXPORT_SYMBOL(pms_set_wakeup_timers); enum { cmd_add_clk_constr, cmd_add_dev_constr, cmd_add_cpu_constr, }; static match_table_t tokens = { {cmd_add_clk_constr, "clock_rate"}, {cmd_add_dev_constr, "device_state"}, {cmd_add_cpu_constr, "cpu_rate"}, }; static ssize_t pms_control_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int token; char *p; pr_debug(" count = %d\n", count); while ((p = _strsep((char **)&buf, " \t\n"))) { token = match_token(p, tokens, NULL); pr_debug("token: %d\n", token); switch (token) { case cmd_add_clk_constr:{ char *clk_name; char *state_name; char *rate_name; unsigned long rate; struct clk *clk; struct pms_state *state; struct pms_object *obj; /* State.Clock */ pr_debug("Adding ...cmd_add_clock_constraint\n"); state_name = _strsep((char **)&buf, ": \t\n"); if (!state_name) continue; clk_name = _strsep((char **)&buf, " \n\t"); if (!clk_name) continue; rate_name = _strsep((char **)&buf, "- \t\n"); if (!rate_name) continue; rate = simple_strtoul(rate_name, NULL, 10); pr_debug("cmd_add_clock_constraint '%s.%s' @ %10u\n", state_name, clk_name, (unsigned int)rate); clk = clk_get(NULL, clk_name); if (!clk) { pr_debug("Clock not declared\n"); continue; } state = pms_state_get(state_name); if (!state) /* create the state if required */ state = pms_create_state(state_name); if (clk_is_readonly(clk)) { pr_debug("Clock read only\n"); continue; } /* check if the clock is in the pms */ obj = pms_find_object(PMS_TYPE_CLK, (void *)clk); if (!obj) /* register the clock if required */ obj = pms_register_clock(clk); pms_set_constraint(state, obj, rate); } break; case cmd_add_dev_constr:{ char *state_name, *bus_name; char *dev_name, *pmstate_name; struct bus_type *bus; struct device *dev; unsigned long pmstate; struct pms_state *state; struct pms_object *obj; /* State.Bus.Device on/off */ pr_debug("Adding ...cmd_add_dev_constraint\n"); state_name = _strsep((char **)&buf, ": \t\n"); if (!state_name) continue; bus_name = _strsep((char **)&buf, "/ \n\t"); if (!bus_name) continue; dev_name = _strsep((char **)&buf, " \n\t"); if (!dev_name) continue; pmstate_name = _strsep((char **)&buf, " \n\t"); if (!pmstate_name) continue; pr_debug("cmd_add_dev_constraint '%s\n", dev_name); bus = find_bus(bus_name); if (!bus) { pr_debug("State and/or Bus not declared\n"); continue; } dev = bus_find_device_by_name(bus, NULL, dev_name); if (!dev) { pr_debug("Device not found\n"); continue; } state = pms_state_get(state_name); if (!state) /* create the state if required */ state = pms_create_state(state_name); /* check if the device is in the pms */ obj = pms_find_object(PMS_TYPE_DEV, (void *)dev); if (!obj) /* register the devi if required */ obj = pms_register_device(dev); if (!strcmp(pmstate_name, "on")) { pmstate = RPM_ACTIVE; pr_debug("Device state: %s.%s.%s state: on\n", state_name, bus_name, dev_name); } else { pmstate = RPM_SUSPENDED; pr_debug("Device state: %s.%s.%s state: off\n", state_name, bus_name, dev_name); } pms_set_constraint(state, obj, pmstate); } break; case cmd_add_cpu_constr:{ char *state_name, *cpu_id_name, *rate_name; struct pms_state *state; int cpu_id; unsigned long rate; struct pms_object *obj; pr_debug("Adding ...cmd_add_cpu_constraint\n"); state_name = _strsep((char **)&buf, ": \t\n"); if (!state_name) continue; cpu_id_name = _strsep((char **)&buf, " \t\n"); if (!cpu_id_name) continue; rate_name = _strsep((char **)&buf, " \t\n"); if (!rate_name) continue; state = pms_state_get(state_name); if (!state) /* create the state if required */ state = pms_create_state(state_name); rate = simple_strtoul(rate_name, NULL, 10); cpu_id = simple_strtoul(cpu_id_name, NULL, 10); #ifdef CONFIG_CPU_FREQ /* check if the cpu is in the pms */ obj = pms_find_object(PMS_TYPE_CPU, (void *)cpu_id); if (!obj) /* register the cpu if required */ obj = pms_register_cpu(cpu_id); pr_debug("CPU-%d constreint @ %u MHz\n", cpu_id, (unsigned int)rate); pms_set_constraint(state, obj, rate); #endif } break; } /* switch */ } /* while */ return count; } static struct kobj_attribute pms_control_attr = (struct kobj_attribute) __ATTR(control, S_IWUSR, NULL, pms_control_store); static ssize_t pms_current_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int ret = 0; struct pms_state *state; pr_debug("\n"); list_for_each_entry(state, &pms_state_list, node) if (state->is_active) ret += sprintf(buf + ret, "%s;", state->name); ret += sprintf(buf + ret, "\n"); return ret; } static const char *pms_global_state_n[] = { "pms_standby", "pms_memstandby", "pms_hibernation", "pms_memhibernation" }; static ssize_t pms_current_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int i; int tmp; pr_debug("\n"); if (!buf) return -1; for (i = 0; i < ARRAY_SIZE(pms_global_state_n); ++i) { if (!strcmp(pms_global_state_n[i], buf)) { tmp = pms_global_standby((enum pms_standby_e) i); pr_debug("PMS: woken up by %d\n", tmp); return count; } } if (pms_set_current_states((char *)buf) < 0) return -1; return count; } static struct kobj_attribute pms_current_attr = (struct kobj_attribute) __ATTR(current_state, S_IRUSR | S_IWUSR, pms_current_show, pms_current_store); #ifdef CONFIG_STM_LPC static ssize_t pms_timeout_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { unsigned long long second = simple_strtoul(buf, NULL, 10); pr_info("%s\n", __func__); pms_set_wakeup_timers(second); return count; } static struct kobj_attribute pms_timeout_attr = (struct kobj_attribute) __ATTR(timeout, S_IWUSR, NULL, pms_timeout_store); #endif static ssize_t pms_objects_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct pms_object *obj; int i, ret = 0; for (i = 0; i < PMS_TYPE_MAX; ++i) list_for_each_entry(obj, &pms_obj_lists[i], node) switch (obj->type) { #ifdef CONFIG_CPU_FREQ case PMS_TYPE_CPU: ret += sprintf(buf + ret, " + cpu: %10u\n", (unsigned int)obj->cpu_id); break; #endif case PMS_TYPE_CLK: ret += sprintf(buf + ret, " + clk: %10s\n", obj->clk->name); break; case PMS_TYPE_DEV: ret += sprintf(buf + ret, " + dev: %10s\n", dev_name(obj->dev)); break; } return ret; } static struct kobj_attribute pms_objects_attr = (struct kobj_attribute) __ATTR(objects, S_IRUSR, pms_objects_show, NULL); static struct attribute *pms_attrs[] = { &pms_current_attr.attr, &pms_control_attr.attr, &pms_objects_attr.attr, #ifdef CONFIG_STM_LPC &pms_timeout_attr.attr, #endif NULL }; static struct attribute_group pms_attr_group = { .attrs = pms_attrs, .name = "attributes" }; static int __init pms_init(void) { pr_debug("pms initialization\n"); pms_kobj = kobject_create_and_add("pms", NULL); if (!pms_kobj) return -ENOMEM; pms_kobj->ktype = &ktype_pms; sysfs_update_group(pms_kobj, &pms_attr_group); return 0; } subsys_initcall(pms_init);