| |
| |
| |
| |
| |
| |
| #include <linux/interrupt.h> |
| #include <linux/list.h> |
| #include <linux/io.h> |
| #include <linux/of.h> |
| #include <linux/irq.h> |
| #include <linux/irqdomain.h> |
| #include <linux/mailbox_client.h> |
| #include <linux/mfd/syscon.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/soc/qcom/smem.h> |
| #include <linux/soc/qcom/smem_state.h> |
| #include <linux/spinlock.h> |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #define SMP2P_MAX_ENTRY 16 |
| #define SMP2P_MAX_ENTRY_NAME 16 |
| |
| #define SMP2P_FEATURE_SSR_ACK 0x1 |
| |
| #define SMP2P_MAGIC 0x504d5324 |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| struct smp2p_smem_item { |
| <------>u32 magic; |
| <------>u8 version; |
| <------>unsigned features:24; |
| <------>u16 local_pid; |
| <------>u16 remote_pid; |
| <------>u16 total_entries; |
| <------>u16 valid_entries; |
| <------>u32 flags; |
| |
| <------>struct { |
| <------><------>u8 name[SMP2P_MAX_ENTRY_NAME]; |
| <------><------>u32 value; |
| <------>} entries[SMP2P_MAX_ENTRY]; |
| } __packed; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| struct smp2p_entry { |
| <------>struct list_head node; |
| <------>struct qcom_smp2p *smp2p; |
| |
| <------>const char *name; |
| <------>u32 *value; |
| <------>u32 last_value; |
| |
| <------>struct irq_domain *domain; |
| <------>DECLARE_BITMAP(irq_enabled, 32); |
| <------>DECLARE_BITMAP(irq_rising, 32); |
| <------>DECLARE_BITMAP(irq_falling, 32); |
| |
| <------>struct qcom_smem_state *state; |
| |
| <------>spinlock_t lock; |
| }; |
| |
| #define SMP2P_INBOUND 0 |
| #define SMP2P_OUTBOUND 1 |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| struct qcom_smp2p { |
| <------>struct device *dev; |
| |
| <------>struct smp2p_smem_item *in; |
| <------>struct smp2p_smem_item *out; |
| |
| <------>unsigned smem_items[SMP2P_OUTBOUND + 1]; |
| |
| <------>unsigned valid_entries; |
| |
| <------>unsigned local_pid; |
| <------>unsigned remote_pid; |
| |
| <------>struct regmap *ipc_regmap; |
| <------>int ipc_offset; |
| <------>int ipc_bit; |
| |
| <------>struct mbox_client mbox_client; |
| <------>struct mbox_chan *mbox_chan; |
| |
| <------>struct list_head inbound; |
| <------>struct list_head outbound; |
| }; |
| |
| static void qcom_smp2p_kick(struct qcom_smp2p *smp2p) |
| { |
| <------> |
| <------>wmb(); |
| |
| <------>if (smp2p->mbox_chan) { |
| <------><------>mbox_send_message(smp2p->mbox_chan, NULL); |
| <------><------>mbox_client_txdone(smp2p->mbox_chan, 0); |
| <------>} else { |
| <------><------>regmap_write(smp2p->ipc_regmap, smp2p->ipc_offset, BIT(smp2p->ipc_bit)); |
| <------>} |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static irqreturn_t qcom_smp2p_intr(int irq, void *data) |
| { |
| <------>struct smp2p_smem_item *in; |
| <------>struct smp2p_entry *entry; |
| <------>struct qcom_smp2p *smp2p = data; |
| <------>unsigned smem_id = smp2p->smem_items[SMP2P_INBOUND]; |
| <------>unsigned pid = smp2p->remote_pid; |
| <------>size_t size; |
| <------>int irq_pin; |
| <------>u32 status; |
| <------>char buf[SMP2P_MAX_ENTRY_NAME]; |
| <------>u32 val; |
| <------>int i; |
| |
| <------>in = smp2p->in; |
| |
| <------> |
| <------>if (!in) { |
| <------><------>in = qcom_smem_get(pid, smem_id, &size); |
| <------><------>if (IS_ERR(in)) { |
| <------><------><------>dev_err(smp2p->dev, |
| <------><------><------><------>"Unable to acquire remote smp2p item\n"); |
| <------><------><------>return IRQ_HANDLED; |
| <------><------>} |
| |
| <------><------>smp2p->in = in; |
| <------>} |
| |
| <------> |
| <------>for (i = smp2p->valid_entries; i < in->valid_entries; i++) { |
| <------><------>list_for_each_entry(entry, &smp2p->inbound, node) { |
| <------><------><------>memcpy(buf, in->entries[i].name, sizeof(buf)); |
| <------><------><------>if (!strcmp(buf, entry->name)) { |
| <------><------><------><------>entry->value = &in->entries[i].value; |
| <------><------><------><------>break; |
| <------><------><------>} |
| <------><------>} |
| <------>} |
| <------>smp2p->valid_entries = i; |
| |
| <------> |
| <------>list_for_each_entry(entry, &smp2p->inbound, node) { |
| <------><------> |
| <------><------>if (!entry->value) |
| <------><------><------>continue; |
| |
| <------><------>val = readl(entry->value); |
| |
| <------><------>status = val ^ entry->last_value; |
| <------><------>entry->last_value = val; |
| |
| <------><------> |
| <------><------>if (!status) |
| <------><------><------>continue; |
| |
| <------><------>for_each_set_bit(i, entry->irq_enabled, 32) { |
| <------><------><------>if (!(status & BIT(i))) |
| <------><------><------><------>continue; |
| |
| <------><------><------>if ((val & BIT(i) && test_bit(i, entry->irq_rising)) || |
| <------><------><------> (!(val & BIT(i)) && test_bit(i, entry->irq_falling))) { |
| <------><------><------><------>irq_pin = irq_find_mapping(entry->domain, i); |
| <------><------><------><------>handle_nested_irq(irq_pin); |
| <------><------><------>} |
| <------><------>} |
| <------>} |
| |
| <------>return IRQ_HANDLED; |
| } |
| |
| static void smp2p_mask_irq(struct irq_data *irqd) |
| { |
| <------>struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd); |
| <------>irq_hw_number_t irq = irqd_to_hwirq(irqd); |
| |
| <------>clear_bit(irq, entry->irq_enabled); |
| } |
| |
| static void smp2p_unmask_irq(struct irq_data *irqd) |
| { |
| <------>struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd); |
| <------>irq_hw_number_t irq = irqd_to_hwirq(irqd); |
| |
| <------>set_bit(irq, entry->irq_enabled); |
| } |
| |
| static int smp2p_set_irq_type(struct irq_data *irqd, unsigned int type) |
| { |
| <------>struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd); |
| <------>irq_hw_number_t irq = irqd_to_hwirq(irqd); |
| |
| <------>if (!(type & IRQ_TYPE_EDGE_BOTH)) |
| <------><------>return -EINVAL; |
| |
| <------>if (type & IRQ_TYPE_EDGE_RISING) |
| <------><------>set_bit(irq, entry->irq_rising); |
| <------>else |
| <------><------>clear_bit(irq, entry->irq_rising); |
| |
| <------>if (type & IRQ_TYPE_EDGE_FALLING) |
| <------><------>set_bit(irq, entry->irq_falling); |
| <------>else |
| <------><------>clear_bit(irq, entry->irq_falling); |
| |
| <------>return 0; |
| } |
| |
| static struct irq_chip smp2p_irq_chip = { |
| <------>.name = "smp2p", |
| <------>.irq_mask = smp2p_mask_irq, |
| <------>.irq_unmask = smp2p_unmask_irq, |
| <------>.irq_set_type = smp2p_set_irq_type, |
| }; |
| |
| static int smp2p_irq_map(struct irq_domain *d, |
| <------><------><------> unsigned int irq, |
| <------><------><------> irq_hw_number_t hw) |
| { |
| <------>struct smp2p_entry *entry = d->host_data; |
| |
| <------>irq_set_chip_and_handler(irq, &smp2p_irq_chip, handle_level_irq); |
| <------>irq_set_chip_data(irq, entry); |
| <------>irq_set_nested_thread(irq, 1); |
| <------>irq_set_noprobe(irq); |
| |
| <------>return 0; |
| } |
| |
| static const struct irq_domain_ops smp2p_irq_ops = { |
| <------>.map = smp2p_irq_map, |
| <------>.xlate = irq_domain_xlate_twocell, |
| }; |
| |
| static int qcom_smp2p_inbound_entry(struct qcom_smp2p *smp2p, |
| <------><------><------><------> struct smp2p_entry *entry, |
| <------><------><------><------> struct device_node *node) |
| { |
| <------>entry->domain = irq_domain_add_linear(node, 32, &smp2p_irq_ops, entry); |
| <------>if (!entry->domain) { |
| <------><------>dev_err(smp2p->dev, "failed to add irq_domain\n"); |
| <------><------>return -ENOMEM; |
| <------>} |
| |
| <------>return 0; |
| } |
| |
| static int smp2p_update_bits(void *data, u32 mask, u32 value) |
| { |
| <------>struct smp2p_entry *entry = data; |
| <------>unsigned long flags; |
| <------>u32 orig; |
| <------>u32 val; |
| |
| <------>spin_lock_irqsave(&entry->lock, flags); |
| <------>val = orig = readl(entry->value); |
| <------>val &= ~mask; |
| <------>val |= value; |
| <------>writel(val, entry->value); |
| <------>spin_unlock_irqrestore(&entry->lock, flags); |
| |
| <------>if (val != orig) |
| <------><------>qcom_smp2p_kick(entry->smp2p); |
| |
| <------>return 0; |
| } |
| |
| static const struct qcom_smem_state_ops smp2p_state_ops = { |
| <------>.update_bits = smp2p_update_bits, |
| }; |
| |
| static int qcom_smp2p_outbound_entry(struct qcom_smp2p *smp2p, |
| <------><------><------><------> struct smp2p_entry *entry, |
| <------><------><------><------> struct device_node *node) |
| { |
| <------>struct smp2p_smem_item *out = smp2p->out; |
| <------>char buf[SMP2P_MAX_ENTRY_NAME] = {}; |
| |
| <------> |
| <------>strlcpy(buf, entry->name, SMP2P_MAX_ENTRY_NAME); |
| <------>memcpy(out->entries[out->valid_entries].name, buf, SMP2P_MAX_ENTRY_NAME); |
| |
| <------> |
| <------>entry->value = &out->entries[out->valid_entries].value; |
| |
| <------>out->valid_entries++; |
| |
| <------>entry->state = qcom_smem_state_register(node, &smp2p_state_ops, entry); |
| <------>if (IS_ERR(entry->state)) { |
| <------><------>dev_err(smp2p->dev, "failed to register qcom_smem_state\n"); |
| <------><------>return PTR_ERR(entry->state); |
| <------>} |
| |
| <------>return 0; |
| } |
| |
| static int qcom_smp2p_alloc_outbound_item(struct qcom_smp2p *smp2p) |
| { |
| <------>struct smp2p_smem_item *out; |
| <------>unsigned smem_id = smp2p->smem_items[SMP2P_OUTBOUND]; |
| <------>unsigned pid = smp2p->remote_pid; |
| <------>int ret; |
| |
| <------>ret = qcom_smem_alloc(pid, smem_id, sizeof(*out)); |
| <------>if (ret < 0 && ret != -EEXIST) { |
| <------><------>if (ret != -EPROBE_DEFER) |
| <------><------><------>dev_err(smp2p->dev, |
| <------><------><------><------>"unable to allocate local smp2p item\n"); |
| <------><------>return ret; |
| <------>} |
| |
| <------>out = qcom_smem_get(pid, smem_id, NULL); |
| <------>if (IS_ERR(out)) { |
| <------><------>dev_err(smp2p->dev, "Unable to acquire local smp2p item\n"); |
| <------><------>return PTR_ERR(out); |
| <------>} |
| |
| <------>memset(out, 0, sizeof(*out)); |
| <------>out->magic = SMP2P_MAGIC; |
| <------>out->local_pid = smp2p->local_pid; |
| <------>out->remote_pid = smp2p->remote_pid; |
| <------>out->total_entries = SMP2P_MAX_ENTRY; |
| <------>out->valid_entries = 0; |
| |
| <------> |
| <------> * Make sure the rest of the header is written before we validate the |
| <------> * item by writing a valid version number. |
| <------> */ |
| <------>wmb(); |
| <------>out->version = 1; |
| |
| <------>qcom_smp2p_kick(smp2p); |
| |
| <------>smp2p->out = out; |
| |
| <------>return 0; |
| } |
| |
| static int smp2p_parse_ipc(struct qcom_smp2p *smp2p) |
| { |
| <------>struct device_node *syscon; |
| <------>struct device *dev = smp2p->dev; |
| <------>const char *key; |
| <------>int ret; |
| |
| <------>syscon = of_parse_phandle(dev->of_node, "qcom,ipc", 0); |
| <------>if (!syscon) { |
| <------><------>dev_err(dev, "no qcom,ipc node\n"); |
| <------><------>return -ENODEV; |
| <------>} |
| |
| <------>smp2p->ipc_regmap = syscon_node_to_regmap(syscon); |
| <------>if (IS_ERR(smp2p->ipc_regmap)) |
| <------><------>return PTR_ERR(smp2p->ipc_regmap); |
| |
| <------>key = "qcom,ipc"; |
| <------>ret = of_property_read_u32_index(dev->of_node, key, 1, &smp2p->ipc_offset); |
| <------>if (ret < 0) { |
| <------><------>dev_err(dev, "no offset in %s\n", key); |
| <------><------>return -EINVAL; |
| <------>} |
| |
| <------>ret = of_property_read_u32_index(dev->of_node, key, 2, &smp2p->ipc_bit); |
| <------>if (ret < 0) { |
| <------><------>dev_err(dev, "no bit in %s\n", key); |
| <------><------>return -EINVAL; |
| <------>} |
| |
| <------>return 0; |
| } |
| |
| static int qcom_smp2p_probe(struct platform_device *pdev) |
| { |
| <------>struct smp2p_entry *entry; |
| <------>struct device_node *node; |
| <------>struct qcom_smp2p *smp2p; |
| <------>const char *key; |
| <------>int irq; |
| <------>int ret; |
| |
| <------>smp2p = devm_kzalloc(&pdev->dev, sizeof(*smp2p), GFP_KERNEL); |
| <------>if (!smp2p) |
| <------><------>return -ENOMEM; |
| |
| <------>smp2p->dev = &pdev->dev; |
| <------>INIT_LIST_HEAD(&smp2p->inbound); |
| <------>INIT_LIST_HEAD(&smp2p->outbound); |
| |
| <------>platform_set_drvdata(pdev, smp2p); |
| |
| <------>key = "qcom,smem"; |
| <------>ret = of_property_read_u32_array(pdev->dev.of_node, key, |
| <------><------><------><------><------> smp2p->smem_items, 2); |
| <------>if (ret) |
| <------><------>return ret; |
| |
| <------>key = "qcom,local-pid"; |
| <------>ret = of_property_read_u32(pdev->dev.of_node, key, &smp2p->local_pid); |
| <------>if (ret) |
| <------><------>goto report_read_failure; |
| |
| <------>key = "qcom,remote-pid"; |
| <------>ret = of_property_read_u32(pdev->dev.of_node, key, &smp2p->remote_pid); |
| <------>if (ret) |
| <------><------>goto report_read_failure; |
| |
| <------>irq = platform_get_irq(pdev, 0); |
| <------>if (irq < 0) |
| <------><------>return irq; |
| |
| <------>smp2p->mbox_client.dev = &pdev->dev; |
| <------>smp2p->mbox_client.knows_txdone = true; |
| <------>smp2p->mbox_chan = mbox_request_channel(&smp2p->mbox_client, 0); |
| <------>if (IS_ERR(smp2p->mbox_chan)) { |
| <------><------>if (PTR_ERR(smp2p->mbox_chan) != -ENODEV) |
| <------><------><------>return PTR_ERR(smp2p->mbox_chan); |
| |
| <------><------>smp2p->mbox_chan = NULL; |
| |
| <------><------>ret = smp2p_parse_ipc(smp2p); |
| <------><------>if (ret) |
| <------><------><------>return ret; |
| <------>} |
| |
| <------>ret = qcom_smp2p_alloc_outbound_item(smp2p); |
| <------>if (ret < 0) |
| <------><------>goto release_mbox; |
| |
| <------>for_each_available_child_of_node(pdev->dev.of_node, node) { |
| <------><------>entry = devm_kzalloc(&pdev->dev, sizeof(*entry), GFP_KERNEL); |
| <------><------>if (!entry) { |
| <------><------><------>ret = -ENOMEM; |
| <------><------><------>goto unwind_interfaces; |
| <------><------>} |
| |
| <------><------>entry->smp2p = smp2p; |
| <------><------>spin_lock_init(&entry->lock); |
| |
| <------><------>ret = of_property_read_string(node, "qcom,entry-name", &entry->name); |
| <------><------>if (ret < 0) |
| <------><------><------>goto unwind_interfaces; |
| |
| <------><------>if (of_property_read_bool(node, "interrupt-controller")) { |
| <------><------><------>ret = qcom_smp2p_inbound_entry(smp2p, entry, node); |
| <------><------><------>if (ret < 0) |
| <------><------><------><------>goto unwind_interfaces; |
| |
| <------><------><------>list_add(&entry->node, &smp2p->inbound); |
| <------><------>} else { |
| <------><------><------>ret = qcom_smp2p_outbound_entry(smp2p, entry, node); |
| <------><------><------>if (ret < 0) |
| <------><------><------><------>goto unwind_interfaces; |
| |
| <------><------><------>list_add(&entry->node, &smp2p->outbound); |
| <------><------>} |
| <------>} |
| |
| <------> |
| <------>qcom_smp2p_kick(smp2p); |
| |
| <------>ret = devm_request_threaded_irq(&pdev->dev, irq, |
| <------><------><------><------><------>NULL, qcom_smp2p_intr, |
| <------><------><------><------><------>IRQF_ONESHOT, |
| <------><------><------><------><------>"smp2p", (void *)smp2p); |
| <------>if (ret) { |
| <------><------>dev_err(&pdev->dev, "failed to request interrupt\n"); |
| <------><------>goto unwind_interfaces; |
| <------>} |
| |
| |
| <------>return 0; |
| |
| unwind_interfaces: |
| <------>list_for_each_entry(entry, &smp2p->inbound, node) |
| <------><------>irq_domain_remove(entry->domain); |
| |
| <------>list_for_each_entry(entry, &smp2p->outbound, node) |
| <------><------>qcom_smem_state_unregister(entry->state); |
| |
| <------>smp2p->out->valid_entries = 0; |
| |
| release_mbox: |
| <------>mbox_free_channel(smp2p->mbox_chan); |
| |
| <------>return ret; |
| |
| report_read_failure: |
| <------>dev_err(&pdev->dev, "failed to read %s\n", key); |
| <------>return -EINVAL; |
| } |
| |
| static int qcom_smp2p_remove(struct platform_device *pdev) |
| { |
| <------>struct qcom_smp2p *smp2p = platform_get_drvdata(pdev); |
| <------>struct smp2p_entry *entry; |
| |
| <------>list_for_each_entry(entry, &smp2p->inbound, node) |
| <------><------>irq_domain_remove(entry->domain); |
| |
| <------>list_for_each_entry(entry, &smp2p->outbound, node) |
| <------><------>qcom_smem_state_unregister(entry->state); |
| |
| <------>mbox_free_channel(smp2p->mbox_chan); |
| |
| <------>smp2p->out->valid_entries = 0; |
| |
| <------>return 0; |
| } |
| |
| static const struct of_device_id qcom_smp2p_of_match[] = { |
| <------>{ .compatible = "qcom,smp2p" }, |
| <------>{} |
| }; |
| MODULE_DEVICE_TABLE(of, qcom_smp2p_of_match); |
| |
| static struct platform_driver qcom_smp2p_driver = { |
| <------>.probe = qcom_smp2p_probe, |
| <------>.remove = qcom_smp2p_remove, |
| <------>.driver = { |
| <------><------>.name = "qcom_smp2p", |
| <------><------>.of_match_table = qcom_smp2p_of_match, |
| <------>}, |
| }; |
| module_platform_driver(qcom_smp2p_driver); |
| |
| MODULE_DESCRIPTION("Qualcomm Shared Memory Point to Point driver"); |
| MODULE_LICENSE("GPL v2"); |
| |