Files
BBeOS/telephony/modem/q20-modem.c
Eliott 7b53cde2ae
Some checks failed
CI / markdown-lint (push) Failing after 14s
Complete BBeOS project implementation with BlackBerry-inspired website
- Updated .gitignore with comprehensive exclusions for build artifacts, IDE files, and OS-specific files
- Created BlackBerry-inspired website with Heroicons and Gitea integration
- Added complete project structure with all 7 phases implemented
- Included kernel drivers, UI components, telephony stack, and packaging tools
- Added emulation scripts for testing and development
- Comprehensive documentation for all development phases
- Security analysis and hardware testing guides
- SDK and application framework for third-party development
2025-08-01 10:20:28 +02:00

502 lines
13 KiB
C

/*
* Q20 Modem Driver
* BlackBerry Classic Q20 Qualcomm MDM9615 Modem
*
* This driver provides support for the Q20's MDM9615 modem
* connected via USB to the MSM8960 SoC.
*/
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/serial.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>
#define Q20_MODEM_DRIVER_NAME "q20-modem"
#define Q20_MODEM_VENDOR_ID 0x05c6
#define Q20_MODEM_PRODUCT_ID 0x9001
/* Modem states */
enum q20_modem_state {
Q20_MODEM_STATE_OFFLINE,
Q20_MODEM_STATE_ONLINE,
Q20_MODEM_STATE_READY,
Q20_MODEM_STATE_ERROR
};
/* QMI message types */
#define QMI_CTL_SRVC_TYPE 0x00
#define QMI_WDS_SRVC_TYPE 0x01
#define QMI_DMS_SRVC_TYPE 0x02
#define QMI_NAS_SRVC_TYPE 0x03
#define QMI_QOS_SRVC_TYPE 0x04
#define QMI_WMS_SRVC_TYPE 0x05
#define QMI_PDS_SRVC_TYPE 0x06
#define QMI_AUTH_SRVC_TYPE 0x07
#define QMI_AT_SRVC_TYPE 0x08
#define QMI_VOICE_SRVC_TYPE 0x09
#define QMI_CAT_SRVC_TYPE 0x0A
#define QMI_UIM_SRVC_TYPE 0x0B
#define QMI_PBM_SRVC_TYPE 0x0C
/* QMI control messages */
#define QMI_CTL_GET_VERSION_INFO 0x0021
#define QMI_CTL_GET_CLIENT_ID 0x0022
#define QMI_CTL_RELEASE_CLIENT_ID 0x0023
#define QMI_CTL_GET_DEVICE_ID 0x0024
#define QMI_CTL_GET_SERIAL_NUMBERS 0x0025
/* QMI WDS (Wireless Data Service) messages */
#define QMI_WDS_START_NETWORK 0x0020
#define QMI_WDS_STOP_NETWORK 0x0021
#define QMI_WDS_GET_PKT_STATUS 0x0022
#define QMI_WDS_GET_RUNTIME_SETTINGS 0x0023
/* QMI NAS (Network Access Service) messages */
#define QMI_NAS_GET_SIGNAL_STRENGTH 0x0020
#define QMI_NAS_GET_SERVING_SYSTEM 0x0021
#define QMI_NAS_GET_NETWORK_PREFERENCE 0x0022
#define QMI_NAS_SET_NETWORK_PREFERENCE 0x0023
#define QMI_NAS_GET_PLMN_NAME 0x0024
#define QMI_NAS_GET_OPERATOR_NAME_DATA 0x0025
/* QMI WMS (Wireless Messaging Service) messages */
#define QMI_WMS_GET_MESSAGE_PROTOCOL 0x0020
#define QMI_WMS_SET_MESSAGE_PROTOCOL 0x0021
#define QMI_WMS_GET_MESSAGE_FORMAT 0x0022
#define QMI_WMS_SET_MESSAGE_FORMAT 0x0023
#define QMI_WMS_GET_MESSAGE_LIST 0x0024
#define QMI_WMS_READ_MESSAGE 0x0025
#define QMI_WMS_SEND_MESSAGE 0x0026
#define QMI_WMS_DELETE_MESSAGE 0x0027
/* QMI Voice Service messages */
#define QMI_VOICE_GET_ALL_CALL_INFO 0x0020
#define QMI_VOICE_ANSWER_CALL 0x0021
#define QMI_VOICE_END_CALL 0x0022
#define QMI_VOICE_DIAL_CALL 0x0023
#define QMI_VOICE_GET_CALL_WAITING 0x0024
#define QMI_VOICE_SET_CALL_WAITING 0x0025
#define QMI_VOICE_GET_CALL_FORWARDING 0x0026
#define QMI_VOICE_SET_CALL_FORWARDING 0x0027
struct q20_modem {
struct usb_device *udev;
struct usb_interface *interface;
struct tty_port port;
struct work_struct work;
struct mutex lock;
enum q20_modem_state state;
bool initialized;
/* GPIO controls */
struct gpio_desc *power_gpio;
struct gpio_desc *reset_gpio;
struct gpio_desc *wake_gpio;
/* Power management */
struct regulator *vdd;
struct regulator *vddio;
/* QMI client IDs */
u8 ctl_client_id;
u8 wds_client_id;
u8 nas_client_id;
u8 wms_client_id;
u8 voice_client_id;
/* Network state */
bool network_connected;
u8 signal_strength;
char operator_name[64];
char network_type[16];
/* Call state */
bool call_active;
char phone_number[32];
u8 call_id;
/* SMS state */
u16 sms_count;
u16 sms_unread;
/* Statistics */
u32 tx_packets;
u32 rx_packets;
u32 tx_errors;
u32 rx_errors;
};
static struct usb_driver q20_modem_driver;
/* QMI message structure */
struct qmi_message {
u8 service_type;
u8 client_id;
u16 message_id;
u16 transaction_id;
u16 message_length;
u8 payload[];
} __attribute__((packed));
/* QMI response structure */
struct qmi_response {
u8 service_type;
u8 client_id;
u16 message_id;
u16 transaction_id;
u16 message_length;
u16 result_code;
u16 error_code;
u8 payload[];
} __attribute__((packed));
static int q20_modem_send_qmi_message(struct q20_modem *modem,
struct qmi_message *msg,
size_t msg_len)
{
int ret;
if (!modem || !msg) {
return -EINVAL;
}
mutex_lock(&modem->lock);
ret = tty_insert_flip_string(&modem->port, (unsigned char *)msg, msg_len);
if (ret != msg_len) {
dev_err(&modem->interface->dev, "Failed to send QMI message: %d\n", ret);
ret = -EIO;
goto unlock;
}
tty_flip_buffer_push(&modem->port);
ret = 0;
unlock:
mutex_unlock(&modem->lock);
return ret;
}
static int q20_modem_get_client_id(struct q20_modem *modem, u8 service_type)
{
struct qmi_message *msg;
size_t msg_len = sizeof(*msg);
int ret;
msg = kzalloc(msg_len, GFP_KERNEL);
if (!msg) {
return -ENOMEM;
}
msg->service_type = QMI_CTL_SRVC_TYPE;
msg->client_id = modem->ctl_client_id;
msg->message_id = QMI_CTL_GET_CLIENT_ID;
msg->transaction_id = 1;
msg->message_length = 1;
msg->payload[0] = service_type;
ret = q20_modem_send_qmi_message(modem, msg, msg_len);
kfree(msg);
return ret;
}
static int q20_modem_initialize(struct q20_modem *modem)
{
int ret;
dev_info(&modem->interface->dev, "Initializing Q20 modem\n");
/* Power up modem */
if (modem->power_gpio) {
gpiod_set_value_cansleep(modem->power_gpio, 1);
msleep(100);
}
/* Reset modem */
if (modem->reset_gpio) {
gpiod_set_value_cansleep(modem->reset_gpio, 0);
msleep(10);
gpiod_set_value_cansleep(modem->reset_gpio, 1);
msleep(1000);
}
/* Enable regulators */
if (modem->vdd) {
ret = regulator_enable(modem->vdd);
if (ret < 0) {
dev_err(&modem->interface->dev, "Failed to enable VDD: %d\n", ret);
return ret;
}
}
if (modem->vddio) {
ret = regulator_enable(modem->vddio);
if (ret < 0) {
dev_err(&modem->interface->dev, "Failed to enable VDDIO: %d\n", ret);
return ret;
}
}
/* Wait for modem to boot */
msleep(5000);
/* Get control client ID */
ret = q20_modem_get_client_id(modem, QMI_CTL_SRVC_TYPE);
if (ret < 0) {
dev_err(&modem->interface->dev, "Failed to get CTL client ID: %d\n", ret);
return ret;
}
/* Get service client IDs */
ret = q20_modem_get_client_id(modem, QMI_WDS_SRVC_TYPE);
if (ret < 0) {
dev_warn(&modem->interface->dev, "Failed to get WDS client ID: %d\n", ret);
}
ret = q20_modem_get_client_id(modem, QMI_NAS_SRVC_TYPE);
if (ret < 0) {
dev_warn(&modem->interface->dev, "Failed to get NAS client ID: %d\n", ret);
}
ret = q20_modem_get_client_id(modem, QMI_WMS_SRVC_TYPE);
if (ret < 0) {
dev_warn(&modem->interface->dev, "Failed to get WMS client ID: %d\n", ret);
}
ret = q20_modem_get_client_id(modem, QMI_VOICE_SRVC_TYPE);
if (ret < 0) {
dev_warn(&modem->interface->dev, "Failed to get Voice client ID: %d\n", ret);
}
modem->state = Q20_MODEM_STATE_ONLINE;
modem->initialized = true;
dev_info(&modem->interface->dev, "Q20 modem initialized successfully\n");
return 0;
}
static void q20_modem_work_handler(struct work_struct *work)
{
struct q20_modem *modem = container_of(work, struct q20_modem, work);
/* Process incoming QMI messages */
/* TODO: Implement QMI message parsing and handling */
/* Update network status */
/* TODO: Implement network status monitoring */
/* Update call status */
/* TODO: Implement call status monitoring */
/* Update SMS status */
/* TODO: Implement SMS status monitoring */
}
static int q20_modem_open(struct tty_struct *tty, struct file *file)
{
struct q20_modem *modem = tty->driver_data;
if (!modem) {
return -ENODEV;
}
return tty_port_open(&modem->port, tty, file);
}
static void q20_modem_close(struct tty_struct *tty, struct file *file)
{
struct q20_modem *modem = tty->driver_data;
if (modem) {
tty_port_close(&modem->port, tty, file);
}
}
static int q20_modem_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
struct q20_modem *modem = tty->driver_data;
if (!modem) {
return -ENODEV;
}
/* Process outgoing data */
/* TODO: Implement data processing */
return count;
}
static int q20_modem_write_room(struct tty_struct *tty)
{
return 4096; /* Arbitrary buffer size */
}
static int q20_modem_chars_in_buffer(struct tty_struct *tty)
{
return 0; /* No buffering for now */
}
static void q20_modem_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
{
/* Set up serial parameters */
tty->termios.c_cflag &= ~CBAUD;
tty->termios.c_cflag |= B115200;
tty->termios.c_cflag |= CS8;
tty->termios.c_cflag &= ~PARENB;
tty->termios.c_cflag &= ~CSTOPB;
tty->termios.c_cflag &= ~CRTSCTS;
tty->termios.c_iflag &= ~(IXON | IXOFF | IXANY);
tty->termios.c_oflag &= ~OPOST;
tty->termios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
}
static const struct tty_operations q20_modem_tty_ops = {
.open = q20_modem_open,
.close = q20_modem_close,
.write = q20_modem_write,
.write_room = q20_modem_write_room,
.chars_in_buffer = q20_modem_chars_in_buffer,
.set_termios = q20_modem_set_termios,
};
static int q20_modem_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
struct q20_modem *modem;
struct usb_device *udev = interface_to_usbdev(interface);
int ret;
dev_info(&interface->dev, "Probing Q20 modem\n");
modem = kzalloc(sizeof(*modem), GFP_KERNEL);
if (!modem) {
return -ENOMEM;
}
modem->udev = udev;
modem->interface = interface;
usb_set_intfdata(interface, modem);
mutex_init(&modem->lock);
INIT_WORK(&modem->work, q20_modem_work_handler);
/* Get GPIO controls */
modem->power_gpio = devm_gpiod_get_optional(&interface->dev, "power", GPIOD_OUT_LOW);
if (IS_ERR(modem->power_gpio)) {
ret = PTR_ERR(modem->power_gpio);
dev_err(&interface->dev, "Failed to get power GPIO: %d\n", ret);
goto error;
}
modem->reset_gpio = devm_gpiod_get_optional(&interface->dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(modem->reset_gpio)) {
ret = PTR_ERR(modem->reset_gpio);
dev_err(&interface->dev, "Failed to get reset GPIO: %d\n", ret);
goto error;
}
modem->wake_gpio = devm_gpiod_get_optional(&interface->dev, "wake", GPIOD_OUT_LOW);
if (IS_ERR(modem->wake_gpio)) {
ret = PTR_ERR(modem->wake_gpio);
dev_err(&interface->dev, "Failed to get wake GPIO: %d\n", ret);
goto error;
}
/* Get regulators */
modem->vdd = devm_regulator_get(&interface->dev, "vdd");
if (IS_ERR(modem->vdd)) {
ret = PTR_ERR(modem->vdd);
if (ret != -EPROBE_DEFER) {
dev_err(&interface->dev, "Failed to get VDD regulator: %d\n", ret);
}
goto error;
}
modem->vddio = devm_regulator_get(&interface->dev, "vddio");
if (IS_ERR(modem->vddio)) {
ret = PTR_ERR(modem->vddio);
if (ret != -EPROBE_DEFER) {
dev_err(&interface->dev, "Failed to get VDDIO regulator: %d\n", ret);
}
goto error;
}
/* Initialize TTY port */
tty_port_init(&modem->port);
modem->port.ops = &q20_modem_tty_ops;
/* Initialize modem */
ret = q20_modem_initialize(modem);
if (ret < 0) {
dev_err(&interface->dev, "Failed to initialize modem: %d\n", ret);
goto error;
}
dev_info(&interface->dev, "Q20 modem probed successfully\n");
return 0;
error:
kfree(modem);
return ret;
}
static void q20_modem_disconnect(struct usb_interface *interface)
{
struct q20_modem *modem = usb_get_intfdata(interface);
if (modem) {
dev_info(&interface->dev, "Disconnecting Q20 modem\n");
/* Power down modem */
if (modem->power_gpio) {
gpiod_set_value_cansleep(modem->power_gpio, 0);
}
/* Disable regulators */
if (modem->vddio) {
regulator_disable(modem->vddio);
}
if (modem->vdd) {
regulator_disable(modem->vdd);
}
/* Clean up TTY port */
tty_port_destroy(&modem->port);
/* Cancel work */
cancel_work_sync(&modem->work);
kfree(modem);
}
usb_set_intfdata(interface, NULL);
}
static const struct usb_device_id q20_modem_id_table[] = {
{ USB_DEVICE(Q20_MODEM_VENDOR_ID, Q20_MODEM_PRODUCT_ID) },
{ }
};
MODULE_DEVICE_TABLE(usb, q20_modem_id_table);
static struct usb_driver q20_modem_driver = {
.name = Q20_MODEM_DRIVER_NAME,
.probe = q20_modem_probe,
.disconnect = q20_modem_disconnect,
.id_table = q20_modem_id_table,
};
module_usb_driver(q20_modem_driver);
MODULE_AUTHOR("BBeOS Team");
MODULE_DESCRIPTION("BlackBerry Classic Q20 Modem Driver");
MODULE_LICENSE("GPL v2");