diff options
Diffstat (limited to 'drivers/mailbox/pcc.c')
| -rw-r--r-- | drivers/mailbox/pcc.c | 196 |
1 files changed, 167 insertions, 29 deletions
diff --git a/drivers/mailbox/pcc.c b/drivers/mailbox/pcc.c index 94885e411085..ff292b9e0be9 100644 --- a/drivers/mailbox/pcc.c +++ b/drivers/mailbox/pcc.c @@ -117,8 +117,6 @@ struct pcc_chan_info { static struct pcc_chan_info *chan_info; static int pcc_chan_count; -static int pcc_send_data(struct mbox_chan *chan, void *data); - /* * PCC can be used with perf critical drivers such as CPPC * So it makes sense to locally cache the virtual address and @@ -245,13 +243,13 @@ static bool pcc_mbox_cmd_complete_check(struct pcc_chan_info *pchan) u64 val; int ret; + if (!pchan->cmd_complete.gas) + return true; + ret = pcc_chan_reg_read(&pchan->cmd_complete, &val); if (ret) return false; - if (!pchan->cmd_complete.gas) - return true; - /* * Judge if the channel respond the interrupt based on the value of * command complete. @@ -269,6 +267,60 @@ static bool pcc_mbox_cmd_complete_check(struct pcc_chan_info *pchan) return !!val; } +static int pcc_mbox_error_check_and_clear(struct pcc_chan_info *pchan) +{ + u64 val; + int ret; + + ret = pcc_chan_reg_read(&pchan->error, &val); + if (ret) + return ret; + + if (val & pchan->error.status_mask) { + val &= pchan->error.preserve_mask; + pcc_chan_reg_write(&pchan->error, val); + return -EIO; + } + + return 0; +} + +static void pcc_chan_acknowledge(struct pcc_chan_info *pchan) +{ + struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_hdr; + + if (pchan->type != ACPI_PCCT_TYPE_EXT_PCC_SLAVE_SUBSPACE) + return; + + pcc_chan_reg_read_modify_write(&pchan->cmd_update); + + pcc_hdr = pchan->chan.shmem; + + /* + * The PCC slave subspace channel needs to set the command + * complete bit after processing message. If the PCC_ACK_FLAG + * is set, it should also ring the doorbell. + */ + if (ioread32(&pcc_hdr->flags) & PCC_CMD_COMPLETION_NOTIFY) + pcc_chan_reg_read_modify_write(&pchan->db); +} + +static void *write_response(struct pcc_chan_info *pchan) +{ + struct pcc_header pcc_header; + void *buffer; + int data_len; + + memcpy_fromio(&pcc_header, pchan->chan.shmem, + sizeof(pcc_header)); + data_len = pcc_header.length - sizeof(u32) + sizeof(struct pcc_header); + + buffer = pchan->chan.rx_alloc(pchan->chan.mchan->cl, data_len); + if (buffer != NULL) + memcpy_fromio(buffer, pchan->chan.shmem, data_len); + return buffer; +} + /** * pcc_mbox_irq - PCC mailbox interrupt handler * @irq: interrupt number @@ -280,10 +332,14 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) { struct pcc_chan_info *pchan; struct mbox_chan *chan = p; - u64 val; - int ret; + struct pcc_header *pcc_header = chan->active_req; + void *handle = NULL; pchan = chan->con_priv; + + if (pcc_chan_reg_read_modify_write(&pchan->plat_irq_ack)) + return IRQ_NONE; + if (pchan->type == ACPI_PCCT_TYPE_EXT_PCC_MASTER_SUBSPACE && !pchan->chan_in_use) return IRQ_NONE; @@ -291,31 +347,30 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) if (!pcc_mbox_cmd_complete_check(pchan)) return IRQ_NONE; - ret = pcc_chan_reg_read(&pchan->error, &val); - if (ret) - return IRQ_NONE; - val &= pchan->error.status_mask; - if (val) { - val &= ~pchan->error.status_mask; - pcc_chan_reg_write(&pchan->error, val); - return IRQ_NONE; - } - - if (pcc_chan_reg_read_modify_write(&pchan->plat_irq_ack)) + if (pcc_mbox_error_check_and_clear(pchan)) return IRQ_NONE; - mbox_chan_received_data(chan, NULL); - /* - * The PCC slave subspace channel needs to set the command complete bit - * and ring doorbell after processing message. - * - * The PCC master subspace channel clears chan_in_use to free channel. + * Clear this flag after updating interrupt ack register and just + * before mbox_chan_received_data() which might call pcc_send_data() + * where the flag is set again to start new transfer. This is + * required to avoid any possible race in updatation of this flag. */ - if (pchan->type == ACPI_PCCT_TYPE_EXT_PCC_SLAVE_SUBSPACE) - pcc_send_data(chan, NULL); pchan->chan_in_use = false; + if (pchan->chan.rx_alloc) + handle = write_response(pchan); + + if (chan->active_req) { + pcc_header = chan->active_req; + if (pcc_header->flags & PCC_CMD_COMPLETION_NOTIFY) + mbox_chan_txdone(chan, 0); + } + + mbox_chan_received_data(chan, handle); + + pcc_chan_acknowledge(pchan); + return IRQ_HANDLED; } @@ -334,6 +389,7 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) struct pcc_mbox_chan * pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id) { + struct pcc_mbox_chan *pcc_mchan; struct pcc_chan_info *pchan; struct mbox_chan *chan; int rc; @@ -352,7 +408,29 @@ pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id) if (rc) return ERR_PTR(rc); - return &pchan->chan; + pcc_mchan = &pchan->chan; + pcc_mchan->shmem = acpi_os_ioremap(pcc_mchan->shmem_base_addr, + pcc_mchan->shmem_size); + if (!pcc_mchan->shmem) + goto err; + + pcc_mchan->manage_writes = false; + + /* This indicates that the channel is ready to accept messages. + * This needs to happen after the channel has registered + * its callback. There is no access point to do that in + * the mailbox API. That implies that the mailbox client must + * have set the allocate callback function prior to + * sending any messages. + */ + if (pchan->type == ACPI_PCCT_TYPE_EXT_PCC_SLAVE_SUBSPACE) + pcc_chan_reg_read_modify_write(&pchan->cmd_update); + + return pcc_mchan; + +err: + mbox_free_channel(chan); + return ERR_PTR(-ENXIO); } EXPORT_SYMBOL_GPL(pcc_mbox_request_channel); @@ -365,16 +443,54 @@ EXPORT_SYMBOL_GPL(pcc_mbox_request_channel); void pcc_mbox_free_channel(struct pcc_mbox_chan *pchan) { struct mbox_chan *chan = pchan->mchan; + struct pcc_chan_info *pchan_info; + struct pcc_mbox_chan *pcc_mbox_chan; if (!chan || !chan->cl) return; + pchan_info = chan->con_priv; + pcc_mbox_chan = &pchan_info->chan; + if (pcc_mbox_chan->shmem) { + iounmap(pcc_mbox_chan->shmem); + pcc_mbox_chan->shmem = NULL; + } mbox_free_channel(chan); } EXPORT_SYMBOL_GPL(pcc_mbox_free_channel); +static int pcc_write_to_buffer(struct mbox_chan *chan, void *data) +{ + struct pcc_chan_info *pchan = chan->con_priv; + struct pcc_mbox_chan *pcc_mbox_chan = &pchan->chan; + struct pcc_header *pcc_header = data; + + if (!pchan->chan.manage_writes) + return 0; + + /* The PCC header length includes the command field + * but not the other values from the header. + */ + int len = pcc_header->length - sizeof(u32) + sizeof(struct pcc_header); + u64 val; + + pcc_chan_reg_read(&pchan->cmd_complete, &val); + if (!val) { + pr_info("%s pchan->cmd_complete not set", __func__); + return -1; + } + memcpy_toio(pcc_mbox_chan->shmem, data, len); + return 0; +} + + /** - * pcc_send_data - Called from Mailbox Controller code. Used + * pcc_send_data - Called from Mailbox Controller code. If + * pchan->chan.rx_alloc is set, then the command complete + * flag is checked and the data is written to the shared + * buffer io memory. + * + * If pchan->chan.rx_alloc is not set, then it is used * here only to ring the channel doorbell. The PCC client * specific read/write is done in the client driver in * order to maintain atomicity over PCC channel once @@ -390,17 +506,37 @@ static int pcc_send_data(struct mbox_chan *chan, void *data) int ret; struct pcc_chan_info *pchan = chan->con_priv; + ret = pcc_write_to_buffer(chan, data); + if (ret) + return ret; + ret = pcc_chan_reg_read_modify_write(&pchan->cmd_update); if (ret) return ret; ret = pcc_chan_reg_read_modify_write(&pchan->db); + if (!ret && pchan->plat_irq > 0) pchan->chan_in_use = true; return ret; } + +static bool pcc_last_tx_done(struct mbox_chan *chan) +{ + struct pcc_chan_info *pchan = chan->con_priv; + u64 val; + + pcc_chan_reg_read(&pchan->cmd_complete, &val); + if (!val) + return false; + else + return true; +} + + + /** * pcc_startup - Called from Mailbox Controller code. Used here * to request the interrupt. @@ -446,6 +582,7 @@ static const struct mbox_chan_ops pcc_chan_ops = { .send_data = pcc_send_data, .startup = pcc_startup, .shutdown = pcc_shutdown, + .last_tx_done = pcc_last_tx_done, }; /** @@ -607,7 +744,8 @@ static int pcc_parse_subspace_db_reg(struct pcc_chan_info *pchan, ret = pcc_chan_reg_init(&pchan->error, &pcct_ext->error_status_register, - 0, 0, pcct_ext->error_status_mask, + ~pcct_ext->error_status_mask, 0, + pcct_ext->error_status_mask, "Error Status"); } return ret; |
