[PATCH v1 6/7] Bluetooth: hci_mediatek: Add protocol support for MediaTek serial devices
Marcel Holtmann
marcel at holtmann.org
Thu Apr 26 22:25:59 PDT 2018
Hi Sean,
>>>>> This adds a driver for the MediaTek serial protocol based on H4 protocol,
>>>>> which can enable the built-in Bluetooth device inside MT7622 SoC.
>>>>>
>>>>> Signed-off-by: Sean Wang <sean.wang at mediatek.com>
>>>>> ---
>
> [... snip ...]
>
>>>>> +
>>>>> + /* Start to build a WMT header and its actual payload. */
>>>>> + whdr = skb_put(skb, MTK_WMT_HDR_SIZE);
>>>>> + whdr->dir = 1;
>>>>> + whdr->op = opcode;
>>>>> + whdr->dlen = cpu_to_le16(plen + 1);
>>>>> + whdr->flag = flag;
>>>>> + skb_put_data(skb, param, plen);
>>>>> +
>>>>> + mtk_enqueue(hu, skb);
>>>>> + hci_uart_tx_wakeup(hu);
>>>>> +
>>>>> + /*
>>>>> + * Waiting a WMT event response, while we must take care in case of
>>>>> + * failures for the wait.
>>>>> + */
>>>>> + ret = wait_for_completion_interruptible_timeout(&btdev->wmt_cmd, HZ);
>>>>> +
>>>>> + return ret > 0 ? 0 : ret < 0 ? ret : -ETIMEDOUT;
>>>>> +}
>>>>
>>>> All in all I am not convinced that this is super clean. I get that we need something special for having this in the ACL data packets, but for the standard HCI command I prefer that __hci_cmd_sync is used. I addition, it seems that patch download is the only special case and that happens before at the setup stage. So we could make things special for that. I need to understand this a bit better. Can I get a btmon -w trace.log file from the whole init procedure.
>>>>
>>>>
>>>
>>> I can try to use __hci_cmd_sync to send those chip-specific hci commands
>>> which only can be recognized by the chip, but no meaningful to bluez
>>> core.
>>>
>>> Is btmon able to capture these cmd/evt between driver and device?
>>>
>>> my question's caused by that mtk_wmt_cmd_sync and its events only happen
>>> between driver and device, it doesn't go to core.
>>
>> for Intel, Broadcom, Qualcomm, Realtek etc. we use __hci_cmd_sync() in the ->setup() callback and it will show up in btmon. For at least Intel and Broadcom, btmon will decode them as well. You can hint the manufacturer id out of band it will be told to btmon. And that is also how I want it. If it is using HCI as transport, I want it to go through the Bluetooth core.
>>
>
> understood. I will allow chip-specific HCI cmd/evt to go to bluetooth
> core.
>
>>>>> +
>>>>> +static int mtk_setup_fw(struct hci_uart *hu)
>>>>> +{
>>>>> + struct mtk_bt_dev *btdev = hu->priv;
>>>>> + const struct firmware *fw;
>>>>> + struct device *dev;
>>>>> + const char *fwname;
>>>>> + const u8 *fw_ptr;
>>>>> + size_t fw_size;
>>>>> + int err, dlen;
>>>>> + u8 flag;
>>>>> +
>>>>> + dev = &hu->serdev->dev;
>>>>> + fwname = FIRMWARE_MT7622;
>>>>> +
>>>>> + init_completion(&btdev->wmt_cmd);
>>>>> +
>>>>> + err = request_firmware(&fw, fwname, dev);
>>>>> + if (err < 0) {
>>>>> + dev_err(dev, "%s: Failed to load firmware file (%d)",
>>>>> + hu->hdev->name, err);
>>>>> + return err;
>>>>> + }
>>>>> +
>>>>> + fw_ptr = fw->data;
>>>>> + fw_size = fw->size;
>>>>> +
>>>>> + /* The size of a patch header at least has 30 bytes. */
>>>>> + if (fw_size < 30)
>>>>> + return -EINVAL;
>>>>> +
>>>>> + while (fw_size > 0) {
>>>>> + dlen = min_t(int, 1000, fw_size);
>>>>> +
>>>>> + /* Tell deivice the position in sequence. */
>>>>> + flag = (fw_size - dlen <= 0) ? 3 :
>>>>> + (fw_size < fw->size) ? 2 : 1;
>>>>> +
>>>>> + err = mtk_wmt_cmd_sync(hu, MTK_WMT_PATCH_DWNLD, flag,
>>>>> + dlen, fw_ptr);
>>>>> + if (err < 0)
>>>>> + break;
>>>>> +
>>>>> + fw_size -= dlen;
>>>>> + fw_ptr += dlen;
>>>>> + }
>>>>> +
>>>>> + release_firmware(fw);
>>>>> +
>>>>> + return err;
>>>>> +}
>>>>> +
>>>>> +static int mtk_open(struct hci_uart *hu)
>>>>> +{
>>>>> + struct mtk_bt_dev *btdev = hu->priv;
>>>>> + struct device *dev;
>>>>> + int err = 0;
>>>>> +
>>>>> + dev = &hu->serdev->dev;
>>>>> +
>>>>> + serdev_device_open(hu->serdev);
>>>>> + skb_queue_head_init(&btdev->txq);
>>>>> +
>>>>> + /* Setup the usage of H4. */
>>>>> + hu->alignment = 1;
>>>>> + hu->padding = 0;
>>>>> + mtk_stp_reset(btdev->sp);
>>>>> +
>>>>> + /* Enable the power domain and clock the device requires */
>>>>> + pm_runtime_enable(dev);
>>>>> + err = pm_runtime_get_sync(dev);
>>>>> + if (err < 0) {
>>>>> + pm_runtime_disable(dev);
>>>>> + return err;
>>>>> + }
>>>>> +
>>>>> + err = clk_prepare_enable(btdev->clk);
>>>>> + if (err < 0) {
>>>>> + pm_runtime_put_sync(dev);
>>>>> + pm_runtime_disable(dev);
>>>>> + }
>>>>> +
>>>>> + return err;
>>>>> +}
>>>>> +
>>>>> +static int mtk_close(struct hci_uart *hu)
>>>>> +{
>>>>> + struct mtk_bt_dev *btdev = hu->priv;
>>>>> + struct device *dev = &hu->serdev->dev;
>>>>> + u8 param = 0x0;
>>>>> +
>>>>> + skb_queue_purge(&btdev->txq);
>>>>> + kfree_skb(btdev->rx_skb);
>>>>> +
>>>>> + /* Disable the device. */
>>>>> + mtk_wmt_cmd_sync(hu, MTK_WMT_FUNC_CTRL, 0x0, sizeof(param), ¶m);
>>>>
>>>> Wouldn’t this require a enable device in open callback?
>>>>
>>>
>>> It's another story ...
>>>
>>> At initial time I began working on the driver. I indeed made the
>>> mtk_open and mtk_close be consistent, but it's required a few patches
>>> for core to solve a problem:
>>>
>>> The problem is that hci_uart resource CAN'T be used in mtk_open to send
>>> these specific command taking to the chip since hci_uart_proto->open()
>>> would be called much earlier in hci_uart_register_device at which
>>> hci_uart is even not being allocated yet.
>>>
>>> So, in the first version I want to make thing be simple, instead, I only
>>> hold mtk_open is doing platform stuff such as clock enablement, and
>>> power enablement and any other thing about configuring chip all is moved
>>> to setup handler.
>>
>> You are also not suppose to be sending HCI commands to close the transport in close(). In close() you are suppose to close the transport. You can use shutdown() if there more commands needed. And setup() if for HCI commands after registration.
>>
>
> okay. in the next changes, I will make that
>
> 1. open(), close() would be handling open/close transport and
> platform-specific thing such clk, pwr setup
>
> 2. setup() would be handling chip-specific commands/firmware download to
> configure the chip
>
> 3. shutdown() would be doing the reverse of setup(),which involve a few
> chip-specific commands to disable something in that chip
just remember that setup() and shutdown() are not symmetric. Tell me if you need to load the firmware every time you do btmgmt power on or if that is kept. Either we add a quirk to always run setup() after open() or we introduce an analogues callback for shutdown().
>> Using hci_uart has a bit of legacy in it due to the line discipline. Maybe using btuart.c (which I posted) might be a better starting point since then you hook directly into it as a plain HCI driver.
>>
>> That all said, I have no issues with extending the core driver framework. I rather do that than having drivers hack around it. That always ends badly.
>>
>
>
> where could I find the newest btuart.c (which seems cannot be found in
> 4.17 rc2)? It seems a time to rewrite the driver based on btuart.c
It is not merged yet. I posted RFC patches to the mailing list.
>
>>>
>>>>> +
>>>>> + /* Shutdown the clock and power domain the device requires. */
>>>>> + clk_disable_unprepare(btdev->clk);
>>>>> +
>>>>> + pm_runtime_put_sync(dev);
>>>>> + pm_runtime_disable(dev);
>>>>> +
>>>>> + serdev_device_close(hu->serdev);
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +int mtk_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
>>>>> +{
>>>>> + struct hci_event_hdr *hdr = (void *)skb->data;
>>>>> + struct hci_uart *hu = hci_get_drvdata(hdev);
>>>>> + struct mtk_bt_dev *btdev = hu->priv;
>>>>> +
>>>>> + if (hci_skb_pkt_type(skb) == HCI_EVENT_PKT &&
>>>>> + hdr->evt == 0xe4) {
>>>>> + complete(&btdev->wmt_cmd);
>>>>> + kfree_skb(skb);
>>>>> + return 0;
>>>>> + }
>>>>> +
>>>>> + return hci_recv_frame(hdev, skb);
>>>>> +}
>>>>
>>>> actually this is overdoing it. You can use hci_recv_frame for ACL and SCO and just a special one for events. However all in all I question the need for the special handling anyway. As I said above, I have the feeling this can be done via __hci_cmd_sync or __hci_cmd_sync_ev. So I really like to see a btmon -w trace.log for these so that I can see how they look.
>>>
>>> in the next version, I would only use a special way for events; ACL and
>>> SCO can just use a generic way.
>>>
>>> BTW, add more words for the mtk_recv_frame what it's doing for
>>>
>>> * The special handling with hdr->evt 0xe4 in mtk_recv_frame is
>>> corresponding to mtk_wmt_cmd_sync.
>>>
>>> * It is expected to not to propagate the chip-specific events to bluez
>>> core since it's only meaningful data between driver and device.
>>
>> No, HCI vendor commands and events can happily go through the core. I actually prefer it that way. Mind you that the core HCI handling is rock solid. So trying to hack around it will just lead to bugs. And no other manufacturer is doing that.
>
>
>
> understood. I will allow chip-specific HCI cmd/evt to go to bluetooth
> core. The discard for the specific event can be prevented.
>
>>>>> +
>>>>> +static const struct h4_recv_pkt mtk_recv_pkts[] = {
>>>>> + { H4_RECV_ACL, .recv = mtk_recv_frame },
>>>>> + { H4_RECV_SCO, .recv = mtk_recv_frame },
>>>>> + { H4_RECV_EVENT, .recv = mtk_recv_frame },
>>>>> +};
>>>>> +
>>>>> +static int mtk_recv(struct hci_uart *hu, const void *data, int count)
>>>>> +{
>>>>> + const unsigned char *p_left = data, *p_h4;
>>>>> + struct mtk_bt_dev *btdev = hu->priv;
>>>>> + int sz_left = count, sz_h4, adv;
>>>>> + struct device *dev;
>>>>> + int err;
>>>>> +
>>>>> + if (!test_bit(HCI_UART_REGISTERED, &hu->flags))
>>>>> + return -EUNATCH;
>>>>> +
>>>>> + dev = &hu->serdev->dev;
>>>>> +
>>>>> + while (sz_left > 0) {
>>>>> + p_h4 = mtk_stp_split(dev, btdev->sp, p_left, sz_left, &sz_h4);
>>>>> + if (!p_h4)
>>>>> + break;
>>>>
>>>> Now this is troubling me a bit. While there are some H:4 frames somewhere in here, you need to do another set of framing. So what is the framing that comes from the serial part in the first place?
>>>>
>>>
>>> the chip always adds a fews bytes before and after the H:4 data, it's
>>> something like that.
>>>
>>> -------------------------------------
>>> | STP header | H:4 | STP tailer |
>>> -------------------------------------
>>>
>>> mtk_stp_split no looked into the details of H:4, only is used to find
>>> where is the start of H:4 part and keep info. from STP header and then
>>> feed the H:4 part into h4_recv_buf to reuse the extent logic to process
>>> HCI/ACL/SCO pkts. So, I think it should be unnecessary to create a new
>>> framing for the current usage.
>>
>> I would disagree right now. I think taking btuart.c and writing your own driver might be a lot simpler. You have less legacy and you have less things to hack around.
>>
>> If you have framing around H:4 packets anyway, then why bother trying to use h4_recv_buf anyway (which you could even from a separate driver, see bpa10x.c). The reason behind h4_recv_buf is that we have no framing and need to understand H:4 to find the end of a packet. If you know it, then there is not point in that (see bfusb.c driver).
>>
>
> Thanks for the advice. I will check with btuart.c first and to see if I
> have a clean and better solution for the parse logic.
>
>>>> Btw. it is fine if hci_uart is not a good fit. I actually think it is a bad fit and using the btuart driver I send around a few weeks ago is a better starting point. However if the framing itself is more elaborate actually, then even a brand new driver and just re-using h4_recv.h would be good. If however the extra framing already does a real framing job, then even h4_recv.h is useless and we can just pick the frames right out of serial device. For example bfusb.c had such an extra framing on top of H:4.
>>>
>>>
>>>
>>>>> +
>>>>> + adv = p_h4 - p_left;
>>>>> + sz_left -= adv;
>>>>> + p_left += adv;
>>>>> +
>>>>> + btdev->rx_skb = h4_recv_buf(hu->hdev, btdev->rx_skb,
>>>>> + p_h4, sz_h4, mtk_recv_pkts,
>>>>> + ARRAY_SIZE(mtk_recv_pkts));
>>>>> + if (IS_ERR(btdev->rx_skb)) {
>>>>> + err = PTR_ERR(btdev->rx_skb);
>>>>> + dev_err(dev, "Frame reassembly failed (%d)", err);
>>>>> + btdev->rx_skb = NULL;
>>>>> + return err;
>>>>> + }
>>>>> +
>>>>> + sz_left -= sz_h4;
>>>>> + p_left += sz_h4;
>>>>> + }
>>>>> +
>>>>> + return count;
>>>>> +}
>>>>> +
>>>>> +static struct sk_buff *mtk_dequeue(struct hci_uart *hu)
>>>>> +{
>>>>> + struct mtk_bt_dev *btdev = hu->priv;
>>>>> +
>>>>> + return skb_dequeue(&btdev->txq);
>>>>> +}
>>>>> +
>>>>> +static int mtk_flush(struct hci_uart *hu)
>>>>> +{
>>>>> + struct mtk_bt_dev *btdev = hu->priv;
>>>>> +
>>>>> + skb_queue_purge(&btdev->txq);
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int mtk_setup(struct hci_uart *hu)
>>>>> +{
>>>>> + struct device *dev;
>>>>> + u8 param = 0x1;
>>>>> + int err;
>>>>> +
>>>>> + dev = &hu->serdev->dev;
>>>>> +
>>>>> + /* Setup a firmware which the device definitely requires. */
>>>>> + err = mtk_setup_fw(hu);
>>>>> + if (err < 0) {
>>>>> + dev_err(dev, "Fail at setup FW (%d): %d", __LINE__, err);
>>>>> + return err;
>>>>> + }
>>>>> +
>>>>> + /* Activate funciton the firmware provides to. */
>>>>> + err = mtk_wmt_cmd_sync(hu, MTK_WMT_RST, 0x4, 0, 0);
>>>>> + if (err < 0) {
>>>>> + dev_err(dev, "Fail at WMT RST (%d): %d", __LINE__, err);
>>>>> + return err;
>>>>> + }
>>>>> +
>>>>> + /* Enable Bluetooth protocol. */
>>>>> + err = mtk_wmt_cmd_sync(hu, MTK_WMT_FUNC_CTRL, 0x0, sizeof(param),
>>>>> + ¶m);
>>>>> + if (err < 0)
>>>>> + dev_err(dev, "Fail at FUNC CTRL (%d): %d", __LINE__, err);
>>>>
>>>> Just as a reminder, setup callback is only called once. You should enable the transport in open callback.
>>>>
>>>
>>> If capable, I would like to move all chip initialization stuff to
>>> mtk_open and de-initialization stuff to mtk_close.
>>
>> So open/close is for establishing the HCI transport. Sending commands there is something we have not yet encountered.
>>
>>> In that way, a user power up/down with bluetoothctl or hcitool can
>>> completely recover the chip from any fatal errors. and even it seem at
>>> bluez core, there's workqueue in charge of recovery thing.
>>>
>>> But the core seems not supports me to move chip handling in
>>> mtk_open :-(
>>
>> What do you need for your driver. On every power on, you need to execute a HCI command to enable the chip? Do you need to re-load the firmware or does it survive a power down/up cycle?
>>
>
> the Bluetooth device can't survive in a power/down cycle and
>
> * power on should be including
> - enable clk and power domain
> - download firmware through specific ACL command
> - send specific commands to configure bluetooth (Required to note that
> the steps should be after downloading firmware because the behavior for
> the command might be changed by the firmware)
Then this sounds like you need a quirk that runs setup() after every open() and not just after the first open(). You would be the first hardware that looses their firmware, but that is fine, I almost expect that at some point someone comes along and requires this. So just create a new HCI_QUIRK_NON_PERSISTENT_SETUP.
> * power off should be including
> - send specific commands, such as to disable bluetooth
So try to put these in shutdown()
> - disable power domain and clk
And do this in close().
>
>>>
>>>>> +
>>>>> + return err;
>>>>> +}
>>>>> +
>>>>> +static const struct hci_uart_proto mtk_proto = {
>>>>> + .id = HCI_UART_MTK,
>>>>> + .name = "MediaTek",
>>>>> + .open = mtk_open,
>>>>> + .close = mtk_close,
>>>>> + .recv = mtk_recv,
>>>>> + .enqueue = mtk_enqueue,
>>>>> + .dequeue = mtk_dequeue,
>>>>> + .flush = mtk_flush,
>>>>> + .setup = mtk_setup,
>>>>> + .manufacturer = 1,
>>>>
>>>> Unless you are Nokia, this id is wrong.
>>>>
>>>
>>> okay, i will remove it
>>>
>>>>> +};
>>>>> +
>>>>> +static int mtk_bluetooth_serdev_probe(struct serdev_device *serdev)
>>>>> +{
>>>>> + struct device *dev = &serdev->dev;
>>>>> + struct mtk_bt_dev *btdev;
>>>>> + int err = 0;
>>>>> +
>>>>> + btdev = devm_kzalloc(dev, sizeof(*btdev), GFP_KERNEL);
>>>>> + if (!btdev)
>>>>> + return -ENOMEM;
>>>>> +
>>>>> + btdev->sp = devm_kzalloc(dev, sizeof(*btdev->sp), GFP_KERNEL);
>>>>> + if (!btdev->sp)
>>>>> + return -ENOMEM;
>>>>> +
>>>>> + btdev->clk = devm_clk_get(dev, "ref");
>>>>> + if (IS_ERR(btdev->clk))
>>>>> + return PTR_ERR(btdev->clk);
>>>>> +
>>>>> + btdev->hu.serdev = serdev;
>>>>> + btdev->hu.priv = btdev;
>>>>> +
>>>>> + serdev_device_set_drvdata(serdev, btdev);
>>>>> +
>>>>> + err = hci_uart_register_device(&btdev->hu, &mtk_proto);
>>>>> + if (err)
>>>>> + dev_err(dev, "Could not register bluetooth uart: %d", err);
>>>>> +
>>>>> + return err;
>>>>> +}
>>>>> +
>>>>> +static void mtk_bluetooth_serdev_remove(struct serdev_device *serdev)
>>>>> +{
>>>>> + struct mtk_bt_dev *btdev = serdev_device_get_drvdata(serdev);
>>>>> +
>>>>> + hci_uart_unregister_device(&btdev->hu);
>>>>> +}
>>>>> +
>>>>> +static const struct of_device_id mtk_bluetooth_of_match[] = {
>>>>> + { .compatible = "mediatek,mt7622-bluetooth" },
>>>>> + {},
>>>>> +};
>>>>> +MODULE_DEVICE_TABLE(of, mtk_bluetooth_of_match);
>>>>> +
>>>>> +static struct serdev_device_driver mtk_bluetooth_serdev_driver = {
>>>>> + .probe = mtk_bluetooth_serdev_probe,
>>>>> + .remove = mtk_bluetooth_serdev_remove,
>>>>> + .driver = {
>>>>> + .name = "mediatek-bluetooth",
>>>>> + .of_match_table = of_match_ptr(mtk_bluetooth_of_match),
>>>>> + },
>>>>> +};
>>>>> +
>>>>> +module_serdev_device_driver(mtk_bluetooth_serdev_driver);
>>>>> +
>>>>> +MODULE_AUTHOR("Sean Wang <sean.wang at mediatek.com>");
>>>>> +MODULE_DESCRIPTION("Bluetooth HCI Serial driver for MediaTek SoC");
>>>>> +MODULE_LICENSE("GPL v2");
>>>>> diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h
>>>>> index 66e8c68..3f0911e 100644
>>>>> --- a/drivers/bluetooth/hci_uart.h
>>>>> +++ b/drivers/bluetooth/hci_uart.h
>>>>> @@ -35,7 +35,7 @@
>>>>> #define HCIUARTGETFLAGS _IOR('U', 204, int)
>>>>>
>>>>> /* UART protocols */
>>>>> -#define HCI_UART_MAX_PROTO 12
>>>>> +#define HCI_UART_MAX_PROTO 13
>>>>>
>>>>> #define HCI_UART_H4 0
>>>>> #define HCI_UART_BCSP 1
>>>>> @@ -49,6 +49,7 @@
>>>>> #define HCI_UART_AG6XX 9
>>>>> #define HCI_UART_NOKIA 10
>>>>> #define HCI_UART_MRVL 11
>>>>> +#define HCI_UART_MTK 12
>>>>
>>>> I am really trying to avoid new id assignments here and go for serdev only drivers.
>>>>
>>>
>>> it seems no necessary for a serdev device. can i remove it from driver?
>>> but what id should be filled in hci_uart_proto ?
>>>
>>> 430 static const struct hci_uart_proto mtk_proto = {
>>> 431 .id = HCI_UART_MTK,
>>> 432 .name = "MediaTek",
>>> 433 .open = mtk_open,
>>
>> We can work on something to allow an unassigned id here, but I still feel that you should go for a separate driver in the first place.
>>
>> If you only have a hammer, then everything looks like a nail. hci_uart being the hammer here. I think you would be better served with starting with btuart.c and then evolve it into your own driver. Try that and see where your problems are.
>>
>
> Okay, lets see whether the addressed problems are still present on 2n
> version based on btuart.c to evolve
Go with a brand new btmtkuart.c driver. I really sounds you don’t want the hci_ldisc.c framework in your way. You want direct control over the core callbacks.
Regards
Marcel
More information about the Linux-mediatek
mailing list