|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * System Control and Management Interface (SCMI) Power Protocol | 
|  | * | 
|  | * Copyright (C) 2018-2022 ARM Ltd. | 
|  | */ | 
|  |  | 
|  | #define pr_fmt(fmt) "SCMI Notifications POWER - " fmt | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/scmi_protocol.h> | 
|  |  | 
|  | #include "protocols.h" | 
|  | #include "notify.h" | 
|  |  | 
|  | /* Updated only after ALL the mandatory features for that version are merged */ | 
|  | #define SCMI_PROTOCOL_SUPPORTED_VERSION		0x30001 | 
|  |  | 
|  | enum scmi_power_protocol_cmd { | 
|  | POWER_DOMAIN_ATTRIBUTES = 0x3, | 
|  | POWER_STATE_SET = 0x4, | 
|  | POWER_STATE_GET = 0x5, | 
|  | POWER_STATE_NOTIFY = 0x6, | 
|  | POWER_DOMAIN_NAME_GET = 0x8, | 
|  | }; | 
|  |  | 
|  | struct scmi_msg_resp_power_attributes { | 
|  | __le16 num_domains; | 
|  | __le16 reserved; | 
|  | __le32 stats_addr_low; | 
|  | __le32 stats_addr_high; | 
|  | __le32 stats_size; | 
|  | }; | 
|  |  | 
|  | struct scmi_msg_resp_power_domain_attributes { | 
|  | __le32 flags; | 
|  | #define SUPPORTS_STATE_SET_NOTIFY(x)	((x) & BIT(31)) | 
|  | #define SUPPORTS_STATE_SET_ASYNC(x)	((x) & BIT(30)) | 
|  | #define SUPPORTS_STATE_SET_SYNC(x)	((x) & BIT(29)) | 
|  | #define SUPPORTS_EXTENDED_NAMES(x)	((x) & BIT(27)) | 
|  | u8 name[SCMI_SHORT_NAME_MAX_SIZE]; | 
|  | }; | 
|  |  | 
|  | struct scmi_power_set_state { | 
|  | __le32 flags; | 
|  | #define STATE_SET_ASYNC		BIT(0) | 
|  | __le32 domain; | 
|  | __le32 state; | 
|  | }; | 
|  |  | 
|  | struct scmi_power_state_notify { | 
|  | __le32 domain; | 
|  | __le32 notify_enable; | 
|  | }; | 
|  |  | 
|  | struct scmi_power_state_notify_payld { | 
|  | __le32 agent_id; | 
|  | __le32 domain_id; | 
|  | __le32 power_state; | 
|  | }; | 
|  |  | 
|  | struct power_dom_info { | 
|  | bool state_set_sync; | 
|  | bool state_set_async; | 
|  | bool state_set_notify; | 
|  | char name[SCMI_MAX_STR_SIZE]; | 
|  | }; | 
|  |  | 
|  | struct scmi_power_info { | 
|  | u32 version; | 
|  | bool notify_state_change_cmd; | 
|  | int num_domains; | 
|  | u64 stats_addr; | 
|  | u32 stats_size; | 
|  | struct power_dom_info *dom_info; | 
|  | }; | 
|  |  | 
|  | static int scmi_power_attributes_get(const struct scmi_protocol_handle *ph, | 
|  | struct scmi_power_info *pi) | 
|  | { | 
|  | int ret; | 
|  | struct scmi_xfer *t; | 
|  | struct scmi_msg_resp_power_attributes *attr; | 
|  |  | 
|  | ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, | 
|  | 0, sizeof(*attr), &t); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | attr = t->rx.buf; | 
|  |  | 
|  | ret = ph->xops->do_xfer(ph, t); | 
|  | if (!ret) { | 
|  | pi->num_domains = le16_to_cpu(attr->num_domains); | 
|  | pi->stats_addr = le32_to_cpu(attr->stats_addr_low) | | 
|  | (u64)le32_to_cpu(attr->stats_addr_high) << 32; | 
|  | pi->stats_size = le32_to_cpu(attr->stats_size); | 
|  | } | 
|  |  | 
|  | ph->xops->xfer_put(ph, t); | 
|  |  | 
|  | if (!ret) | 
|  | if (!ph->hops->protocol_msg_check(ph, POWER_STATE_NOTIFY, NULL)) | 
|  | pi->notify_state_change_cmd = true; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int | 
|  | scmi_power_domain_attributes_get(const struct scmi_protocol_handle *ph, | 
|  | u32 domain, struct power_dom_info *dom_info, | 
|  | u32 version, bool notify_state_change_cmd) | 
|  | { | 
|  | int ret; | 
|  | u32 flags; | 
|  | struct scmi_xfer *t; | 
|  | struct scmi_msg_resp_power_domain_attributes *attr; | 
|  |  | 
|  | ret = ph->xops->xfer_get_init(ph, POWER_DOMAIN_ATTRIBUTES, | 
|  | sizeof(domain), sizeof(*attr), &t); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | put_unaligned_le32(domain, t->tx.buf); | 
|  | attr = t->rx.buf; | 
|  |  | 
|  | ret = ph->xops->do_xfer(ph, t); | 
|  | if (!ret) { | 
|  | flags = le32_to_cpu(attr->flags); | 
|  |  | 
|  | if (notify_state_change_cmd) | 
|  | dom_info->state_set_notify = | 
|  | SUPPORTS_STATE_SET_NOTIFY(flags); | 
|  | dom_info->state_set_async = SUPPORTS_STATE_SET_ASYNC(flags); | 
|  | dom_info->state_set_sync = SUPPORTS_STATE_SET_SYNC(flags); | 
|  | strscpy(dom_info->name, attr->name, SCMI_SHORT_NAME_MAX_SIZE); | 
|  | } | 
|  | ph->xops->xfer_put(ph, t); | 
|  |  | 
|  | /* | 
|  | * If supported overwrite short name with the extended one; | 
|  | * on error just carry on and use already provided short name. | 
|  | */ | 
|  | if (!ret && PROTOCOL_REV_MAJOR(version) >= 0x3 && | 
|  | SUPPORTS_EXTENDED_NAMES(flags)) { | 
|  | ph->hops->extended_name_get(ph, POWER_DOMAIN_NAME_GET, | 
|  | domain, NULL, dom_info->name, | 
|  | SCMI_MAX_STR_SIZE); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int scmi_power_state_set(const struct scmi_protocol_handle *ph, | 
|  | u32 domain, u32 state) | 
|  | { | 
|  | int ret; | 
|  | struct scmi_xfer *t; | 
|  | struct scmi_power_set_state *st; | 
|  |  | 
|  | ret = ph->xops->xfer_get_init(ph, POWER_STATE_SET, sizeof(*st), 0, &t); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | st = t->tx.buf; | 
|  | st->flags = cpu_to_le32(0); | 
|  | st->domain = cpu_to_le32(domain); | 
|  | st->state = cpu_to_le32(state); | 
|  |  | 
|  | ret = ph->xops->do_xfer(ph, t); | 
|  |  | 
|  | ph->xops->xfer_put(ph, t); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int scmi_power_state_get(const struct scmi_protocol_handle *ph, | 
|  | u32 domain, u32 *state) | 
|  | { | 
|  | int ret; | 
|  | struct scmi_xfer *t; | 
|  |  | 
|  | ret = ph->xops->xfer_get_init(ph, POWER_STATE_GET, sizeof(u32), sizeof(u32), &t); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | put_unaligned_le32(domain, t->tx.buf); | 
|  |  | 
|  | ret = ph->xops->do_xfer(ph, t); | 
|  | if (!ret) | 
|  | *state = get_unaligned_le32(t->rx.buf); | 
|  |  | 
|  | ph->xops->xfer_put(ph, t); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int scmi_power_num_domains_get(const struct scmi_protocol_handle *ph) | 
|  | { | 
|  | struct scmi_power_info *pi = ph->get_priv(ph); | 
|  |  | 
|  | return pi->num_domains; | 
|  | } | 
|  |  | 
|  | static const char * | 
|  | scmi_power_name_get(const struct scmi_protocol_handle *ph, | 
|  | u32 domain) | 
|  | { | 
|  | struct scmi_power_info *pi = ph->get_priv(ph); | 
|  | struct power_dom_info *dom = pi->dom_info + domain; | 
|  |  | 
|  | return dom->name; | 
|  | } | 
|  |  | 
|  | static const struct scmi_power_proto_ops power_proto_ops = { | 
|  | .num_domains_get = scmi_power_num_domains_get, | 
|  | .name_get = scmi_power_name_get, | 
|  | .state_set = scmi_power_state_set, | 
|  | .state_get = scmi_power_state_get, | 
|  | }; | 
|  |  | 
|  | static int scmi_power_request_notify(const struct scmi_protocol_handle *ph, | 
|  | u32 domain, bool enable) | 
|  | { | 
|  | int ret; | 
|  | struct scmi_xfer *t; | 
|  | struct scmi_power_state_notify *notify; | 
|  |  | 
|  | ret = ph->xops->xfer_get_init(ph, POWER_STATE_NOTIFY, | 
|  | sizeof(*notify), 0, &t); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | notify = t->tx.buf; | 
|  | notify->domain = cpu_to_le32(domain); | 
|  | notify->notify_enable = enable ? cpu_to_le32(BIT(0)) : 0; | 
|  |  | 
|  | ret = ph->xops->do_xfer(ph, t); | 
|  |  | 
|  | ph->xops->xfer_put(ph, t); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static bool scmi_power_notify_supported(const struct scmi_protocol_handle *ph, | 
|  | u8 evt_id, u32 src_id) | 
|  | { | 
|  | struct power_dom_info *dom; | 
|  | struct scmi_power_info *pinfo = ph->get_priv(ph); | 
|  |  | 
|  | if (evt_id != SCMI_EVENT_POWER_STATE_CHANGED || | 
|  | src_id >= pinfo->num_domains) | 
|  | return false; | 
|  |  | 
|  | dom = pinfo->dom_info + src_id; | 
|  | return dom->state_set_notify; | 
|  | } | 
|  |  | 
|  | static int scmi_power_set_notify_enabled(const struct scmi_protocol_handle *ph, | 
|  | u8 evt_id, u32 src_id, bool enable) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = scmi_power_request_notify(ph, src_id, enable); | 
|  | if (ret) | 
|  | pr_debug("FAIL_ENABLE - evt[%X] dom[%d] - ret:%d\n", | 
|  | evt_id, src_id, ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void * | 
|  | scmi_power_fill_custom_report(const struct scmi_protocol_handle *ph, | 
|  | u8 evt_id, ktime_t timestamp, | 
|  | const void *payld, size_t payld_sz, | 
|  | void *report, u32 *src_id) | 
|  | { | 
|  | const struct scmi_power_state_notify_payld *p = payld; | 
|  | struct scmi_power_state_changed_report *r = report; | 
|  |  | 
|  | if (evt_id != SCMI_EVENT_POWER_STATE_CHANGED || sizeof(*p) != payld_sz) | 
|  | return NULL; | 
|  |  | 
|  | r->timestamp = timestamp; | 
|  | r->agent_id = le32_to_cpu(p->agent_id); | 
|  | r->domain_id = le32_to_cpu(p->domain_id); | 
|  | r->power_state = le32_to_cpu(p->power_state); | 
|  | *src_id = r->domain_id; | 
|  |  | 
|  | return r; | 
|  | } | 
|  |  | 
|  | static int scmi_power_get_num_sources(const struct scmi_protocol_handle *ph) | 
|  | { | 
|  | struct scmi_power_info *pinfo = ph->get_priv(ph); | 
|  |  | 
|  | if (!pinfo) | 
|  | return -EINVAL; | 
|  |  | 
|  | return pinfo->num_domains; | 
|  | } | 
|  |  | 
|  | static const struct scmi_event power_events[] = { | 
|  | { | 
|  | .id = SCMI_EVENT_POWER_STATE_CHANGED, | 
|  | .max_payld_sz = sizeof(struct scmi_power_state_notify_payld), | 
|  | .max_report_sz = | 
|  | sizeof(struct scmi_power_state_changed_report), | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static const struct scmi_event_ops power_event_ops = { | 
|  | .is_notify_supported = scmi_power_notify_supported, | 
|  | .get_num_sources = scmi_power_get_num_sources, | 
|  | .set_notify_enabled = scmi_power_set_notify_enabled, | 
|  | .fill_custom_report = scmi_power_fill_custom_report, | 
|  | }; | 
|  |  | 
|  | static const struct scmi_protocol_events power_protocol_events = { | 
|  | .queue_sz = SCMI_PROTO_QUEUE_SZ, | 
|  | .ops = &power_event_ops, | 
|  | .evts = power_events, | 
|  | .num_events = ARRAY_SIZE(power_events), | 
|  | }; | 
|  |  | 
|  | static int scmi_power_protocol_init(const struct scmi_protocol_handle *ph) | 
|  | { | 
|  | int domain, ret; | 
|  | u32 version; | 
|  | struct scmi_power_info *pinfo; | 
|  |  | 
|  | ret = ph->xops->version_get(ph, &version); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | dev_dbg(ph->dev, "Power Version %d.%d\n", | 
|  | PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); | 
|  |  | 
|  | pinfo = devm_kzalloc(ph->dev, sizeof(*pinfo), GFP_KERNEL); | 
|  | if (!pinfo) | 
|  | return -ENOMEM; | 
|  |  | 
|  | ret = scmi_power_attributes_get(ph, pinfo); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | pinfo->dom_info = devm_kcalloc(ph->dev, pinfo->num_domains, | 
|  | sizeof(*pinfo->dom_info), GFP_KERNEL); | 
|  | if (!pinfo->dom_info) | 
|  | return -ENOMEM; | 
|  |  | 
|  | for (domain = 0; domain < pinfo->num_domains; domain++) { | 
|  | struct power_dom_info *dom = pinfo->dom_info + domain; | 
|  |  | 
|  | scmi_power_domain_attributes_get(ph, domain, dom, version, | 
|  | pinfo->notify_state_change_cmd); | 
|  | } | 
|  |  | 
|  | pinfo->version = version; | 
|  |  | 
|  | return ph->set_priv(ph, pinfo, version); | 
|  | } | 
|  |  | 
|  | static const struct scmi_protocol scmi_power = { | 
|  | .id = SCMI_PROTOCOL_POWER, | 
|  | .owner = THIS_MODULE, | 
|  | .instance_init = &scmi_power_protocol_init, | 
|  | .ops = &power_proto_ops, | 
|  | .events = &power_protocol_events, | 
|  | .supported_version = SCMI_PROTOCOL_SUPPORTED_VERSION, | 
|  | }; | 
|  |  | 
|  | DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(power, scmi_power) |