Sebastian Reichel

Motorola Droid 4 - Modem

Today I had a first look at the modems of the Droid 4.
Conclusion
  • Modem 1 (2G/3G):
    • Name: MDM 6600
    • USB ID: 22b8:2a70
    • Drivers:
      • bpwake - wakeup ttyO0 via gpio
      • mdm6600_ctrl - modem gpio control via sysfs
      • mdm6600_modem - usb-serial driver (unused?)
      • qcusbnet - qmi driver, mainline provides alternative
      • ts27010mux - 3GPP 27.010 mux ldisc for ttyO0
  • Modem 2 (4G)
    • Name: W3GLTE
    • USB ID: 22b8:4267
    • Drivers:
      • wrigley_ctrl - modem gpio control via sysfs
      • oob-wake - usb wakeup via gpio
      • cdc-ether - usb network device
Both modems require a GPIO control driver for powering them up/down. Then the LTE modem uses another GPIO + cdc-ether based USB connection. It shouldn't be too hard to implement, but its not testable for me, so it must be done by somebody else. The 2G/3G modem on the other hand seems to use 27.010 on ttyO0, a GPIO to wakeup ttyO0 and qmi over USB.

Next Steps
  • check ttyO0 usage -> strace Android's rild, intercept messages
  • check qcusbnet vs qmi_wwan
  • check if modem usb devices appear on mainline if we implement a the control gpio handling
  • check if mainline should use the same sysfs interface to control the modem gpios

Details

First of all I had a look at the running LineageOS.
# dmesg | grep mdm    
[    2.660858,0] mdm6600_ctrl mdm6600_ctrl: mdm_ctrl_probe
[    2.661590,0] radio_dev_register: register mdm6600
[    3.386383,0] mdm6600_ctrl: modem status: undefined -> panic [power on]
[    3.476806,0] mdm_ctrl_powerup: powered Up mdm6600
[    4.159942,0] mdm_ctrl_powerup: re-register bpwake device
[    4.307159,0] usbcore: registered new interface driver mdm6600
[    7.378082,1] mdm6600 2-1:1.0: MDM 6600 modem usb-serial driver converter detected
[    7.379638,1] mdm6600 2-1:1.1: MDM 6600 modem usb-serial driver converter detected
[    7.409698,1] mdm6600 2-1:1.2: MDM 6600 modem usb-serial driver converter detected
[    7.456695,1] mdm6600 2-1:1.3: MDM 6600 modem usb-serial driver converter detected
[    7.575286,1] mdm6600 2-1:1.4: MDM 6600 modem usb-serial driver converter detected
[  136.740722,0] wakeup wake lock: mdm6600_oob.4
[  136.980255,0] active wake lock mdm6600_oob.4, time left 26
[  137.526275,0] wake lock mdm6600.4, expired
[  137.526733,0] wake lock mdm6600.3, expired
[  137.526977,0] wake lock mdm6600.2, expired
[  137.527191,0] wake lock mdm6600.1, expired
[  137.527618,0] wake lock mdm6600.0, expired
# note: rild = android's modem daemon
# lsof | grep "^rild" | grep "/dev/" | grep CHR | grep -v binder | sed "s/^.* \///g" | sort | uniq
dev/null
dev/qcqmi0
dev/qcqmi1
dev/qcqmi2
dev/qcqmi3
dev/ts0710mux0
dev/ts0710mux1
dev/ts0710mux10
dev/ts0710mux11
dev/ts0710mux2
dev/ts0710mux3
dev/ts0710mux4
dev/ts0710mux5
dev/ts0710mux8
dev/ts0710mux9
dev/ttyO0
# lsusb                                                        
Bus 001 Device 001: ID 1d6b:0002	Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0001	Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0002	Linux Foundation 2.0 root hub
Bus 002 Device 002: ID 22b8:2a70	Motorola: MSM6600
Bus 001 Device 002: ID 22b8:4267	Motorola: W3GLTE
# ls -1 /dev/ttyUSB*                                           
/dev/ttyUSB0
/dev/ttyUSB1
/dev/ttyUSB2
/dev/ttyUSB3
/dev/ttyUSB4
# ls -1 /dev/qcqmi*                                            
/dev/qcqmi0
/dev/qcqmi1
/dev/qcqmi2
/dev/qcqmi3
# ls /sys/devices/platform/ | grep mdm                         
mdm6600_ctrl
mdm6600_modem
# ls /sys/devices/platform/ | grep bp                          
mapphone_bpwake
# ls /sys/class/radio
mdm6600 w3g_lte
# ls /sys/class/radio/mdm6600
command power power_status status subsystem uevent
# cat /sys/class/radio/mdm6600/power_status
on
# cat /sys/class/radio/mdm6600/status
awake
Next I had a look at the vendor/stock kernel source to check the related drivers.
mapphone_bpwake
This driver is located in arch/arm/mach-omap2/board-mapphone-bpwake.c and contains a very simple driver, that request apwake_trigger_gpio as IRQ and prevents device sleep for a second. Notes: ap = application processor, bp = baseband processor. Basically the modem uses it to wakeup the OMAP processor.

feature_mdm6600_spi
Next mdm is referenced in arch/arm/mach-omap2/board-mapphone-spi.c. The code checks for "feature_mdm6600_spi" in Motrola's weird DT blob and only does something if it finds the property. Fortunately its not in there.

mapphone-modem
So let's move on to arch/arm/mach-omap2/board-mapphone-modem.c. It's obvious from a quick view, that this file takes care of registering the mdm6600_ctrl device and everything is being used from the last function in the file: mapphone_mdm_ctrl_init(). It starts with a quick check for the powerup reason. Nothing is done, if the kernel was only started because of a charger connection. Then it registers the mapphone_bpwake driver getting the gpio from DT via a new name ("ipc_apwake_trigger"). I already got this from LineageOS using grep "BP -> AP" /sys/kernel/debug/gpio - the used GPIO is 149, so I did not bother to pull this out of DT. Next it gets the modem count from DT (2 on maserati). Then it goes through the modems in a for loop getting the type from DT. On maserati the first modem has type 0x1e0001 (MAPPHONE_BP_MDM6600) and the second modem is of type 0x3000f (MAPPHONE_BP_W3GLTE). The driver is ready for a few other modems, which are most likely used by other Motorola mapphone phones. Anyways in the driver we have two new entrypoint functions: mapphone_mdm6600_mdm_ctrl_init (2G/3G modem) and mapphone_w3glte_mdm_ctrl_init (4G modem). So let's have a look at the mdm6600 first. The related function acquires lots of GPIOs: ipc_o_bp_pwron, ipc_i_bp_resout, ipc_i_bp_ready, ipc_i_bp_ready2, ipc_i_bp_ready3, ipc_o_ap_ready, ipc_o_ap_ready2, ipc_o_ap_ready3, ipc_o_ap_reset_bp, ipc_bpwake_trigger and ipc_apwake_trigger. Finally it registers a platform device named "mdm6600_ctrl" providing all those gpios via platform_data. The 4G modem on the other hand starts with modifying pad control register OMAP4_CTRL_MODULE_PAD_WKUP_CONTROL_USIMIO and OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_PBIASLITE. Then it also acquires a few gpios: lte_w_disable_b, lte_force_flash, lte_power_en and lte_reset_mcu. Last but not least it registers "wrigley_ctrl" with the name "w3g_lte" and gpios in platform_data.

DT GPIOs
I had a quick look how mapphone gets its GPIOs from DT and found out its using a property named "signalmap", which uses a custom encoding: <gpio> <little-endian 20 byte string > So I wrote a small python script decoding all values. This way I have the GPIO number for every call to get_gpio_by_name().
GPIOName
183touch_panel_int
173touch_panel_rst
175akm8975_int
122slider_data
154volume_gpio
34lis3dh_int
172lm3532_reset
174bt_reset_b
148ipc_bpwake_trigger
149ipc_apwake_trigger
54ipc_o_bp_pwron
56ipc_o_bp_flash_en
8ipc_i_bp_resout
52ipc_i_bp_ready
53ipc_i_bp_ready2
55ipc_i_bp_ready3
49ipc_o_ap_reset_bp
103ipc_o_ap_ready
104ipc_o_ap_ready2
142ipc_o_ap_ready3
60ap_usb_otg_en1
0ap_usb_otg_en2
28vib_control_en
50power_off
94wlan_pmena
100wlan_irqena
38kpd_backlight_en
61lte_w_disable_b
3lte_reset_mcu
4lte_wan_usb_en
102lte_force_flash
1lte_power_en
62lte_wan_hostwake
84cpcap-ind-swst0
85cpcap-ind-swst1
93cpcap-ind-reven0
87cpcap-ind-reven1
118cpcap-ind-chrgcmpl
121cpcap-ind-chrgterm
176sd_det_n
177isl29030_int
26spdif_en
111spdif_1v8
9fact_kill_override
178tmp105_int
34lis3dh_int
115mdm6600_usb_rwkup

mapphone-usb
Next mdm is referenced in arch/arm/mach-omap2/board-mapphone-usb.c. Again this file has a single entry point function: mapphone_usbhost_init. That function starts with not directly modem related USB initialization (i.e. forcing host mode). Then it tries to get GPIOs for mdm6600_usb_rwkup and lte_wan_hostwake. If it gets them platform devices are created using the GPIO as irq. The related platform device for mdm6600 is named "mdm6600_modem". For the LTE modem an "oob-wake" platform device is registered, which gets the LTE module's USB id (0x22b8:0x4267) as parameter.

MDM6X00 Control Driver & Wrigley Modem Control
mdm6600_ctrl driver is located in drivers/misc/radio_ctrl/mdm6600_ctrl.c. Also there is a companion header in include/linux/radio_ctrl/mdm6600_ctrl.h. This driver implements a state machine for the modem's gpio lines and is interfacing to userspace via /sys/class/radio/<dev>/<file>. It takes a few commands via the command file: "shutdown", "powerup", "bootmode_normal" or "bootmode_flash". The LTE modem uses drivers/misc/radio_ctrl/wrigley_ctrl.c, which provides the same userspace interface.

MDM6600 Serial Driver
mdm6600_modem driver is located in drivers/usb/serial/mdm6600.c. It's a usb-serial driver supporting mdm6600 and mdm9600. This drivers is responsible for the /dev/ttyUSB<num> entries. As far as I can see the related ttyUSB devices are not used by Android, so the driver may not be needed.

Qualcomm Gobi
The /dev/qcqmi<num> is created by a driver, which lives inside drivers/net/usb/qcusbnet. It comes with a nice README file, which describes its purpose:
This directory contains a driver for the Qualcomm Gobi 2000 CDMA/GSM modem. The device speaks a protocol called QMI with its host; this driver's responsibility is to multiplex transactions over QMI connections. Note that the relatively simple encoding defined in qmi.h and qmi.c is only the encapsulating protocol; the device speaks a more complex set of protocols embodied in the closed-source Gobi SDK which are necessary to use advanced features of the device. Furthermore, firmware (also proprietary) is needed to make the device useful. An open-source firmware loader exists: http://www.codon.org.uk/~mjg59/gobi_loader/.
The driver registers as usb_driver under the name "QCUSBNet2k" and among others binds the device USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x2a70, 0xff, 0xfb, 0xff). The mainline kernel also has a driver for QMI, which is located in drivers/net/usb/qmi_wwan.c and supported by ofono!

Mux Driver
The /dev/ts0710mux<num> is created by "Motorola TS 27.010 Mux driver", which lives in drivers/misc/ts27010mux. It's a line discipline, which can be assigned to a UART device speaking 3GPP 27.010 protocol. Most likely ttyO0 is used with the n_ts27010 ldisc. It should be possible to check this using strace. The relevant numbers from the stock kernel are:
Nameldisc #Description
N_TD_TS2710_UART253GPP TS 27.010 MUX over UART
N_TD_TS2710_USB263GPP TS 27.010 MUX over USB
N_TS2710273GPP TS 27.010 MUX

USB Out-of-Bounds Wake
The driver lives in drivers/usb/misc/oob_wake.c and has a useful description in the related Kconfig entry: This driver provides a simple mechanism to support USB wakeups from gpios. This is needed in cases where the usb host or device doesn't properly support remote wakeups.

cdc-ether
The standard cdc-ether (USB network) driver contains a small section for the LTE modem:
#ifdef CONFIG_USB_OOBWAKE
{
	/* Motorola Wrigley LTE CDC Ethernet Device */
	USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x4267, USB_CLASS_COMM,
		USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
	.driver_info = (unsigned long) &wrigley_cdc_info,
},
#endif