123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- /*
- * cdc-dlm.c
- *
- * CDC Direct Line Modem control for MC97 modem
- *
- * Copyright (C) 2021 Sylvain Munaut
- * SPDX-License-Identifier: LGPL-3.0-or-later
- */
- #include <string.h>
- #include <no2usb/usb.h>
- #include <no2usb/usb_hw.h>
- #include <no2usb/usb_priv.h>
- #include <no2usb/usb_cdc_proto.h>
- #include "cdc-dlm.h"
- #include "mc97.h"
- #define INTF_CDC_DLM 4
- #define EP_CDC_DLM_NOTIF 0x83
- static void
- dlm_send_notif_ring_detect(void)
- {
- const struct usb_ctrl_req notif = {
- .bmRequestType = USB_REQ_READ | USB_REQ_TYPE_CLASS | USB_REQ_RCPT_INTF,
- .bRequest = USB_NOTIF_CDC_RING_DETECT,
- .wValue = 0,
- .wIndex = INTF_CDC_DLM,
- .wLength = 0,
- };
- usb_data_write(usb_ep_regs[3].in.bd[0].ptr, ¬if, sizeof(struct usb_ctrl_req));
- usb_ep_regs[3].in.bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(sizeof(struct usb_ctrl_req));
- }
- static int16_t g_cc = 0;
- static bool
- dlm_set_comm_feature_country_cb(struct usb_xfer *xfer)
- {
- uint16_t new_cc;
- if (xfer->len != sizeof(uint16_t))
- return USB_FND_ERROR;
- memcpy(&new_cc, xfer->data, sizeof(uint16_t));
- if (mc97_select_country(new_cc)) {
- g_cc = new_cc;
- return USB_FND_SUCCESS;
- }
- return USB_FND_ERROR;
- }
- static enum usb_fnd_resp
- dlm_set_comm_feature(struct usb_ctrl_req *req, struct usb_xfer *xfer)
- {
- /* Only country selection supported */
- if (req->wValue != 0x02 /* COUNTRY_SETTING */)
- return USB_FND_ERROR;
- /* Setup call back */
- xfer->len = sizeof(uint16_t);
- xfer->cb_done = dlm_set_comm_feature_country_cb;
- return USB_FND_SUCCESS;
- }
- static enum usb_fnd_resp
- dlm_get_comm_feature(struct usb_ctrl_req *req, struct usb_xfer *xfer)
- {
- /* Only country selection supported */
- if (req->wValue != 0x02 /* COUNTRY_SETTING */)
- return USB_FND_ERROR;
- /* Send the currently selected country code */
- xfer->len = sizeof(uint16_t);
- memcpy(xfer->data, &g_cc, sizeof(uint16_t));
- return USB_FND_SUCCESS;
- }
- static enum usb_fnd_resp
- dlm_clear_comm_feature(struct usb_ctrl_req *req, struct usb_xfer *xfer)
- {
- /* Only country selection supported */
- if (req->wValue != 0x02 /* COUNTRY_SETTING */)
- return USB_FND_ERROR;
- /* Restore default */
- g_cc = 0;
- mc97_select_country(0);
- return USB_FND_SUCCESS;
- }
- static enum usb_fnd_resp
- dlm_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
- {
- /* Check it's a class request to an interface */
- if (USB_REQ_TYPE_RCPT(req) != (USB_REQ_TYPE_CLASS | USB_REQ_RCPT_INTF))
- return USB_FND_CONTINUE;
- /* Check it's for the DLM interface */
- if ((req->wIndex & 0xff) != INTF_CDC_DLM)
- return USB_FND_CONTINUE;
- switch (req->wRequestAndType)
- {
- case USB_RT_CDC_SET_HOOK_STATE:
- switch (req->wValue) {
- case 0: mc97_set_hook(ON_HOOK); break;
- case 1: mc97_set_hook(OFF_HOOK); break;
- case 2: mc97_set_hook(CALLER_ID); break;
- default: return USB_FND_ERROR;
- }
- return USB_FND_SUCCESS;
- case USB_RT_CDC_SET_AUX_LINE_STATE:
- /* Control the relay with that */
- mc97_set_aux_relay(!req->wValue);
- return USB_FND_SUCCESS;
- case USB_RT_CDC_RING_AUX_JACK:
- /* Can't do that ... */
- return USB_FND_SUCCESS;
- /* Pulse is not supported yet (also disabled in bmCapabilities) */
- case USB_RT_CDC_PULSE_SETUP:
- case USB_RT_CDC_SEND_PULSE:
- case USB_RT_CDC_SET_PULSE_TIME:
- return USB_FND_ERROR;
- /* Implement SET_COMM_FEATURE for country selection ? */
- /* In theory not part of DLM but it's the closest to a standard
- * thing to support tweaking the codec params to match local specs
- * for a phone line */
- case USB_RT_CDC_SET_COMM_FEATURE:
- return dlm_set_comm_feature(req, xfer);
- case USB_RT_CDC_GET_COMM_FEATURE:
- return dlm_get_comm_feature(req, xfer);
- case USB_RT_CDC_CLEAR_COMM_FEATURE:
- return dlm_clear_comm_feature(req, xfer);
- }
- return USB_FND_ERROR;
- }
- static enum usb_fnd_resp
- dlm_set_conf(const struct usb_conf_desc *conf)
- {
- const struct usb_intf_desc *intf;
- intf = usb_desc_find_intf(conf, INTF_CDC_DLM, 0, NULL);
- usb_ep_boot(intf, EP_CDC_DLM_NOTIF, false);
- return USB_FND_SUCCESS;
- }
- static struct usb_fn_drv _dlm_drv = {
- .ctrl_req = dlm_ctrl_req,
- .set_conf = dlm_set_conf,
- };
- void
- cdc_dlm_init(void)
- {
- /* Register function driver */
- usb_register_function_driver(&_dlm_drv);
- }
- void
- cdc_dlm_poll(void)
- {
- /* Ring detection */
- static bool ring;
- if (mc97_get_ring_detect()) {
- if (!ring) {
- dlm_send_notif_ring_detect();
- ring = true;
- }
- } else {
- ring = false;
- }
- /* TODO:
- * - Pulse timing when pulse is implemented
- */
- }
|