drivers: modem: HL78XX Modem Driver

Adding HL78XX Modem Driver Implementation Using Modem Chat Framework

Signed-off-by: Zafer SEN <zafersn93@gmail.com>
diff --git a/drivers/modem/CMakeLists.txt b/drivers/modem/CMakeLists.txt
index 5553cb2..7666aa6 100644
--- a/drivers/modem/CMakeLists.txt
+++ b/drivers/modem/CMakeLists.txt
@@ -33,6 +33,8 @@
 
 add_subdirectory(simcom)
 
+add_subdirectory_ifdef(CONFIG_MODEM_HL78XX hl78xx)
+
 zephyr_library_sources_ifdef(CONFIG_MODEM_CELLULAR modem_cellular.c)
 zephyr_library_sources_ifdef(CONFIG_MODEM_AT_USER_PIPE modem_at_user_pipe.c)
 zephyr_library_sources_ifdef(CONFIG_MODEM_AT_SHELL modem_at_shell.c)
diff --git a/drivers/modem/Kconfig b/drivers/modem/Kconfig
index 02de18f..6fc60cb 100644
--- a/drivers/modem/Kconfig
+++ b/drivers/modem/Kconfig
@@ -193,7 +193,7 @@
 source "drivers/modem/Kconfig.wncm14a2a"
 source "drivers/modem/Kconfig.cellular"
 source "drivers/modem/Kconfig.at_shell"
-
+source "drivers/modem/hl78xx/Kconfig.hl78xx"
 source "drivers/modem/Kconfig.hl7800"
 source "drivers/modem/simcom/Kconfig"
 
diff --git a/drivers/modem/hl78xx/CMakeLists.txt b/drivers/modem/hl78xx/CMakeLists.txt
new file mode 100644
index 0000000..1320925
--- /dev/null
+++ b/drivers/modem/hl78xx/CMakeLists.txt
@@ -0,0 +1,28 @@
+#
+#  Copyright (c) 2025 Netfeasa Ltd.
+#
+#  SPDX-License-Identifier: Apache-2.0
+#
+zephyr_library()
+
+zephyr_library_sources(
+    hl78xx.c
+    hl78xx_sockets.c
+    hl78xx_cfg.c
+    hl78xx_chat.c
+    hl78xx_apis.c
+)
+
+add_subdirectory_ifdef(CONFIG_HL78XX_EVT_MONITOR hl78xx_evt_monitor)
+
+zephyr_library_include_directories(
+  ./
+  # IP headers
+  ${ZEPHYR_BASE}/subsys/net/ip
+  ${ZEPHYR_BASE}/subsys/net/lib/sockets
+)
+
+zephyr_library_include_directories_ifdef(
+  CONFIG_NET_SOCKETS_SOCKOPT_TLS
+  ${ZEPHYR_BASE}/subsys/net/lib/tls_credentials
+)
diff --git a/drivers/modem/hl78xx/Kconfig.hl78xx b/drivers/modem/hl78xx/Kconfig.hl78xx
new file mode 100644
index 0000000..bed24a0
--- /dev/null
+++ b/drivers/modem/hl78xx/Kconfig.hl78xx
@@ -0,0 +1,822 @@
+# Sierra Wireless HL78XX driver driver options
+
+# Copyright (c) 2025 Netfeasa Ltd.
+# SPDX-License-Identifier: Apache-2.0
+
+config MODEM_HL78XX
+	bool "HL78XX modem driver"
+	select MODEM_MODULES
+	select MODEM_CHAT
+	select MODEM_PIPE
+	select MODEM_PIPELINK
+	select MODEM_BACKEND_UART
+	select RING_BUFFER
+	select MODEM_SOCKET
+	select NET_OFFLOAD
+	select MODEM_CONTEXT
+	select EXPERIMENTAL
+	depends on !MODEM_CELLULAR
+	imply GPIO
+	help
+	  Choose this setting to enable Sierra Wireless HL78XX driver LTE-CatM1/NB-IoT modem
+	  driver.
+
+if MODEM_HL78XX
+
+choice MODEM_HL78XX_VARIANT
+	bool "Sierra Wireless hl78xx variant selection"
+	default MODEM_HL78XX_12 if DT_HAS_SWIR_HL7812_ENABLED
+	default MODEM_HL78XX_00 if DT_HAS_SWIR_HL7800_ENABLED
+	default MODEM_HL78XX_AUTODETECT_VARIANT
+
+config MODEM_HL78XX_12
+	bool "Sierra Wireless hl7812"
+	help
+	  Support for hl7812 modem
+
+config MODEM_HL78XX_00
+	bool "Sierra Wireless hl7800"
+	help
+	  Support for hl7800 modem
+
+config MODEM_HL78XX_AUTODETECT_VARIANT
+	bool "detect automatically"
+	help
+	  Automatic detection of modem variant (HL7812 or HL7800)
+
+endchoice
+
+if MODEM_HL78XX_12
+
+config MODEM_HL78XX_12_FW_R6
+	bool "Modem firmware R6"
+	help
+	  Only for HL7812, enable this setting to use NBNTN rat.
+	  This is required for NBNTN mode.
+	  NBNTN mode is supported with R6 firmware.
+
+endif # MODEM_HL78XX_12
+
+config MODEM_HL78XX_UART_BUFFER_SIZES
+	int "The UART receive and transmit buffer sizes in bytes."
+	default 512
+
+config MODEM_HL78XX_CHAT_BUFFER_SIZES
+	int "The size of the buffers used for the chat scripts in bytes."
+	default 512
+
+config MODEM_HL78XX_USER_PIPE_BUFFER_SIZES
+	int "The size of the buffers used for each user pipe in bytes."
+	default 512
+
+config MODEM_HL78XX_RECV_BUF_CNT
+	int "The number of allocated network buffers"
+	default 30
+
+config MODEM_HL78XX_RECV_BUF_SIZE
+	int "The size of the network buffers in bytes"
+	default 128
+
+config MODEM_HL78XX_RX_WORKQ_STACK_SIZE
+	int "Stack size for the Sierra Wireless HL78XX driver modem driver work queue"
+	default 2048
+	help
+	  This stack is used by the work queue to pass off net_pkt data
+	  to the rest of the network stack, letting the rx thread continue
+	  processing data.
+
+choice MODEM_HL78XX_ADDRESS_FAMILY
+	prompt "IP Address family"
+	default MODEM_HL78XX_ADDRESS_FAMILY_IPV4V6
+	help
+	  The address family for IP connections.
+
+config MODEM_HL78XX_ADDRESS_FAMILY_IPV4
+	bool "IPv4"
+
+config MODEM_HL78XX_ADDRESS_FAMILY_IPV6
+	bool "IPv6"
+
+config MODEM_HL78XX_ADDRESS_FAMILY_IPV4V6
+	bool "IPv4v6"
+
+endchoice
+
+choice MODEM_HL78XX_BOOT_MODE
+	prompt "Modem Boot Type"
+	default MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE
+	help
+	  Set Modem Functionality see, AT+CFUN
+	  Consider reset conditions after settings, second parameter of cfun
+	  0 — Do not reset the MT before setting it to <fun> power level.
+	  1 — Reset the MT before setting it to <fun> power level.
+
+config MODEM_HL78XX_BOOT_IN_MINIMUM_FUNCTIONAL_MODE
+	bool "MINIMUM FUNCTIONAL MODE"
+	help
+	  - AT+CFUN = 0,0
+	  — Minimum functionality, SIM powered off
+	  - Consider reset conditions second parameter of cfun
+
+config MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE
+	bool "FULL FUNCTIONAL MODE"
+	help
+	  - AT+CFUN = 1,0
+	  - Full functionality, starts cellular searching
+	  - Consider reset conditions after settings, second parameter of cfun
+
+config MODEM_HL78XX_BOOT_IN_AIRPLANE_MODE
+	bool "AIRPLANE MODE"
+	help
+	  - AT+CFUN = 4,0
+	  - Disable radio transmit and receive; SIM powered on. (i.e. "Airplane
+	  Mode")
+	  - Consider reset conditions after settings, second parameter of cfun
+endchoice
+
+if MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE
+
+config MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
+	bool "WAIT FOR ROAMING"
+	help
+	  Keep the device in boot mode until have +CREG/+CEREG: 1(normal) or 5(roaming)
+endif # MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE
+
+config MODEM_HL78XX_PERIODIC_SCRIPT_MS
+	int "Periodic script interval in milliseconds"
+	default 2000
+
+choice MODEM_HL78XX_APN_SOURCE
+	prompt "APN SOURCE"
+	default MODEM_HL78XX_APN_SOURCE_NETWORK
+	help
+	  Select the source for automatically detecting the APN.
+	  You can choose between IMSI (International Mobile Subscriber Identity)
+	  or ICCID (Integrated Circuit Card Identifier) as the reference for APN association.
+
+config MODEM_HL78XX_APN_SOURCE_ICCID
+	bool "CCID Associated APN"
+	help
+	  - AT+CCID
+	  - Multiple ICCID and APN combinations can be stored in APN PROFILE configuration
+	  see MODEM_HL78XX_APN_PROFILES
+
+config MODEM_HL78XX_APN_SOURCE_IMSI
+	bool "CIMI Associated APN"
+	help
+	  - AT+CIMI
+	  - Multiple CIMI and APN combinations can be stored in APN PROFILE configuration
+	  see MODEM_HL78XX_APN_PROFILES
+
+config MODEM_HL78XX_APN_SOURCE_KCONFIG
+	bool "User defined Single APN"
+	help
+	  - Use the APN defined in MODEM_HL78XX_APN
+	  - Supports only one APN
+
+config MODEM_HL78XX_APN_SOURCE_NETWORK
+	bool "Network Provided APN"
+	help
+	  - AT+CGCONTRDP=1
+	  - Use the APN provided by the network
+endchoice
+
+if MODEM_HL78XX_APN_SOURCE_KCONFIG
+
+config MODEM_HL78XX_APN
+	string "APN for establishing network connection"
+	default "xxxxxxxx"
+	help
+	  This setting is used in the AT+CGDCONT command to set the APN name
+	  for the network connection context.  This value is specific to
+	  the network provider and has to be changed.
+
+endif # MODEM_HL78XX_APN_SOURCE_KCONFIG
+
+if MODEM_HL78XX_APN_SOURCE_ICCID || MODEM_HL78XX_APN_SOURCE_IMSI
+
+config MODEM_HL78XX_APN_PROFILES
+	string "list of profiles to search when autodetecting APN"
+	default "hologram=23450, wm=20601, int=29505"
+	help
+	  Set a comma separated list of profiles, each containing of:
+	  <apn>=<IMSI_1> ... <IMSI_n>
+	  <apn>=<ICCID_1> ... <ICCID_n>
+
+endif # MODEM_HL78XX_APN_SOURCE_ICCID || MODEM_HL78XX_APN_SOURCE_IMSI
+
+config MODEM_HL78XX_RSSI_WORK
+	bool "RSSI polling work"
+	default y
+	help
+	  Sierra Wireless HL78XX driver device is configured to poll for RSSI
+
+config MODEM_HL78XX_RSSI_WORK_PERIOD
+	int "Configure RSSI WORK polling frequency"
+	depends on MODEM_HL78XX_RSSI_WORK
+	default 30
+	help
+	  This settings is used to configure the period of RSSI polling
+
+config MODEM_HL78XX_AUTORAT
+	bool "automatic RAT switching and set the PRL profiles"
+	default y
+	help
+	  AT+KSRAT is provided for backwards compatibility only. AT+KSELACQ is recommended for RAT switching.
+	  (See RAT Switching Application Note (Doc# 2174296) for details.)
+
+if MODEM_HL78XX_AUTORAT
+
+config MODEM_HL78XX_AUTORAT_OVER_WRITE_PRL
+	bool "Overwrite PRL profiles always at boot"
+	help
+	  If you enable this option, the PRL profiles on the modem will be overwritten by the app
+	  with the PRL profile values at boot everytime.
+
+config MODEM_HL78XX_AUTORAT_PRL_PROFILES
+	string "Configure Preferred Radio Access Technology List"
+	default "1,2,3" if MODEM_HL78XX_12
+	default "1,2,1" if MODEM_HL78XX_00
+	help
+	  AT+KSELACQ=0,1,2,3 , MODEM HL7812,CAT-M1, NB-IoT, GSM
+	  AT+KSELACQ=0,1,2,1 , MODEM HL7800,CAT-M1, NB-IoT, CAT-M1
+
+config MODEM_HL78XX_AUTORAT_NB_BAND_CFG
+	string "NB-IoT band configuration (comma-separated list)"
+	default "1,2,3,4,5,8,12,13,20,28"
+	help
+	  Specify which LTE bands (e.g., 8,20,28) to use for NB-IoT when using Auto RAT.
+	  This string is parsed at runtime or build-time.
+
+config MODEM_HL78XX_AUTORAT_M1_BAND_CFG
+	string "Cat-M1 band configuration (comma-separated list)"
+	default "1,2,3,4,5,8,12,13,20,28"
+	help
+	  Specify which LTE bands (e.g., 3,5,12) to use for Cat-M1 when using Auto RAT.
+
+endif # MODEM_HL78XX_AUTORAT
+
+choice MODEM_HL78XX_RAT
+	bool "Radio Access Technology Mode"
+	default MODEM_HL78XX_RAT_M1
+	depends on !MODEM_HL78XX_AUTORAT
+
+config MODEM_HL78XX_RAT_M1
+	bool "LTE-M1"
+	help
+	  Enable LTE Cat-M1 mode during modem init.
+	  In the Read response, '0' indicates CAT-M1.
+
+config MODEM_HL78XX_RAT_NB1
+	bool "NB-IoT"
+	help
+	  Enable LTE Cat-NB1 mode during modem init.
+	  1 — NB-IoT (HL78XX/HL7802/HL7810/HL7845/HL7812 only)
+
+config MODEM_HL78XX_RAT_GSM
+	bool "GSM"
+	depends on MODEM_HL78XX_12
+	help
+	  Enable GSM mode during modem init.
+	  2 — GSM (for HL7802/HL7812 only)
+
+config MODEM_HL78XX_RAT_NBNTN
+	bool "NB-NTN"
+	depends on MODEM_HL78XX_12_FW_R6
+	help
+	  Enable NBNTN mode during modem init.
+	  3 — NBNTN (for HL7810/HL7812 only), It does not support <reboot> = 1
+
+endchoice
+
+menuconfig MODEM_HL78XX_CONFIGURE_BANDS
+	bool "Configure modem bands"
+	depends on !MODEM_HL78XX_AUTORAT
+	default y if !MODEM_HL78XX_AUTORAT
+	help
+	  Choose this setting to configure which LTE bands the
+	  HL78XX modem should use at boot time.
+
+if MODEM_HL78XX_CONFIGURE_BANDS
+if !MODEM_HL78XX_RAT_NBNTN
+config MODEM_HL78XX_BAND_1
+	bool "Band 1  (2000MHz)"
+	default y
+	help
+	  Enable Band 1 (2000MHz)
+
+config MODEM_HL78XX_BAND_2
+	bool "Band 2  (1900MHz)"
+	default y
+	help
+	  Enable Band 2 (1900MHz)
+
+config MODEM_HL78XX_BAND_3
+	bool "Band 3  (1800MHz)"
+	default y
+	help
+	  Enable Band 3 (1800MHz)
+
+config MODEM_HL78XX_BAND_4
+	bool "Band 4  (1700MHz)"
+	default y
+	help
+	  Enable Band 4 (1700MHz)
+
+config MODEM_HL78XX_BAND_5
+	bool "Band 5  (850MHz)"
+	default y
+	help
+	  Enable Band 5 (850MHz)
+
+config MODEM_HL78XX_BAND_8
+	bool "Band 8  (900MHz)"
+	default y
+	help
+	  Enable Band 8 (900MHz)
+
+config MODEM_HL78XX_BAND_9
+	bool "Band 9  (1900MHz)"
+	help
+	  Enable Band 9 (1900MHz)
+
+config MODEM_HL78XX_BAND_10
+	bool "Band 10 (2100MHz)"
+	help
+	  Enable Band 10 (2100MHz)
+
+config MODEM_HL78XX_BAND_12
+	bool "Band 12 (700MHz)"
+	default y
+	help
+	  Enable Band 12 (700MHz)
+
+config MODEM_HL78XX_BAND_13
+	bool "Band 13 (700MHz)"
+	default y
+	help
+	  Enable Band 13 (700MHz)
+
+config MODEM_HL78XX_BAND_17
+	bool "Band 17 (700MHz)"
+	help
+	  Enable Band 17 (700MHz)
+
+config MODEM_HL78XX_BAND_18
+	bool "Band 18 (800MHz)"
+	help
+	  Enable Band 18 (800MHz)
+
+config MODEM_HL78XX_BAND_19
+	bool "Band 19 (800MHz)"
+	help
+	  Enable Band 19 (800MHz)
+
+config MODEM_HL78XX_BAND_20
+	bool "Band 20 (800MHz)"
+	default y
+	help
+	  Enable Band 20 (800MHz)
+endif # !MODEM_HL78XX_RAT_NBNTN
+config MODEM_HL78XX_BAND_23
+	bool "Band 23 (2000MHz)"
+	default y if MODEM_HL78XX_RAT_NBNTN
+	help
+	  Enable Band 23 (2000MHz)
+if !MODEM_HL78XX_RAT_NBNTN
+config MODEM_HL78XX_BAND_25
+	bool "Band 25 (1900MHz)"
+	help
+	  Enable Band 25 (1900MHz)
+
+config MODEM_HL78XX_BAND_26
+	bool "Band 26 (800MHz)"
+	help
+	  Enable Band 26 (800MHz)
+
+config MODEM_HL78XX_BAND_27
+	bool "Band 27 (800MHz)"
+	help
+	  Enable Band 27 (800MHz)
+
+config MODEM_HL78XX_BAND_28
+	bool "Band 28 (700MHz)"
+	default y
+	help
+	  Enable Band 28 (700MHz)
+
+config MODEM_HL78XX_BAND_31
+	bool "Band 31 (450MHz)"
+	help
+	  Enable Band 31 (450MHz)
+
+config MODEM_HL78XX_BAND_66
+	bool "Band 66 (1800MHz)"
+	help
+	  Enable Band 66 (1800MHz)
+
+config MODEM_HL78XX_BAND_72
+	bool "Band 72 (450MHz)"
+	help
+	  Enable Band 72 (450MHz)
+
+config MODEM_HL78XX_BAND_73
+	bool "Band 73 (450MHz)"
+	help
+	  Enable Band 73 (450MHz)
+
+config MODEM_HL78XX_BAND_85
+	bool "Band 85 (700MHz)"
+	help
+	  Enable Band 85 (700MHz)
+
+config MODEM_HL78XX_BAND_87
+	bool "Band 87 (410MHz)"
+	help
+	  Enable Band 87 (410MHz)
+
+config MODEM_HL78XX_BAND_88
+	bool "Band 88 (410MHz)"
+	help
+	  Enable Band 88 (410MHz)
+
+config MODEM_HL78XX_BAND_106
+	bool "Band 106 (900MHz)"
+	help
+	  Enable Band 106 (900MHz)
+
+config MODEM_HL78XX_BAND_107
+	bool "Band 107 (1800MHz)"
+	help
+	  Enable Band 107 (1800MHz)
+endif # !MODEM_HL78XX_RAT_NBNTN
+config MODEM_HL78XX_BAND_255
+	bool "Band 255 (1500MHz)"
+	default y if MODEM_HL78XX_RAT_NBNTN
+	help
+	  Enable Band 255 (1500MHz)
+
+config MODEM_HL78XX_BAND_256
+	bool "Band 256 (2000MHz)"
+	default y if MODEM_HL78XX_RAT_NBNTN
+	help
+	  Enable Band 256 (2000MHz)
+
+endif # MODEM_HL78XX_CONFIGURE_BAND
+
+# NB-IoT NTN Position Configuration
+if MODEM_HL78XX_RAT_NBNTN
+menuconfig MODEM_HL78XX_NBNTN_POSITIONING
+	bool "NB-IoT NTN Position Configuration"
+	depends on !MODEM_HL78XX_AUTORAT
+	default y if !MODEM_HL78XX_AUTORAT
+	help
+	  Choose this setting to configure NB-IoT NTN Positioning parameters.
+	  Only applicable if NB-NTN mode is selected (MODEM_HL78XX_RAT_NBNTN).
+
+if MODEM_HL78XX_NBNTN_POSITIONING
+
+choice
+	prompt "Position Source"
+	default NTN_POSITION_SOURCE_IGNSS
+	help
+	  Select the source of UE (User Equipment) position for NTN TA calculation.
+	  IGNSS: Use HL781x internal GNSS to acquire position automatically.
+	  MANUAL: Manually enter UE position using +KNTNCMD AT command.
+
+config NTN_POSITION_SOURCE_IGNSS
+	bool "IGNSS (internal GNSS)"
+	help
+	  Use HL781x internal GNSS to acquire position automatically.
+
+config NTN_POSITION_SOURCE_MANUAL
+	bool "MANUAL (manual entry)"
+	help
+	  Manually enter UE position using +KNTNCMD AT command.
+
+endchoice
+
+choice
+	prompt "Mobility Type"
+	default NTN_MOBILITY_TYPE_STATIC
+	help
+	  Specify the UE's mobility type.
+	  STATIC: Position remains fixed (within 600 meters of initial fix).
+	  DYNAMIC: Position may change, requires update for each network operation.
+
+config NTN_MOBILITY_TYPE_STATIC
+	bool "STATIC (fixed position)"
+	help
+	  Position remains fixed (within 600 meters of initial fix).
+
+config NTN_MOBILITY_TYPE_DYNAMIC
+	bool "DYNAMIC (moving position)"
+	help
+	  Position may change, requires update for each network operation.
+
+endchoice
+
+config NTN_STATIC_THRESHOLD
+	int "Static Mode Threshold (meters)"
+	default 600
+	help
+	  Distance between current position and initial fix that determines static/dynamic mode.
+	  If position variation exceeds this threshold, dynamic mode is recommended.
+	  For documentation/reference only.
+
+config NTN_DYNAMIC_POSREQ_UPDATE
+	bool "Require position update on POSREQ in Dynamic Mode"
+	default y
+	help
+	  If enabled, the UE position must be updated after receiving unsolicited +KNTNEV:
+	  'POSREQ' indication, before any outgoing network operation in dynamic mode.
+endif # MODEM_HL78XX_NBNTN_POSITIONING
+endif # MODEM_HL78XX_RAT_NBNTN
+
+config MODEM_HL78XX_LOW_POWER_MODE
+	bool "Low power modes"
+	help
+	  Choose this setting to enable a low power mode for the HL78XX modem
+
+if MODEM_HL78XX_LOW_POWER_MODE
+
+config MODEM_HL78XX_EDRX
+	bool "eDRX"
+	help
+	  Enable LTE eDRX
+
+config MODEM_HL78XX_PSM
+	bool "PSM"
+	default y
+	help
+	  Enable Power Save Mode (PSM)
+
+if MODEM_HL78XX_EDRX
+
+config MODEM_HL78XX_EDRX_VALUE
+	string "Requested eDRX timer"
+	default "0101"
+	help
+	  Half a byte in a 4-bit format. The eDRX value refers to bit 4 to 1
+	  of octet 3 of the Extended DRX parameters information element.
+	  Default value is 81.92 seconds.
+
+endif # MODEM_HL78XX_EDRX
+
+if MODEM_HL78XX_PSM
+
+config MODEM_HL78XX_PSM_PERIODIC_TAU
+	string "Requested extended periodic TAU timer"
+	default "10000010"
+	help
+	  Requested extended periodic TAU (tracking area update) value (T3412)
+	  to be allocated to the UE in E-UTRAN. One byte in an 8-bit format.
+	  Default value is 1 minute.
+
+config MODEM_HL78XX_PSM_ACTIVE_TIME
+	string "Requested active time"
+	default "00001111"
+	help
+	  Requested Active Time value (T3324) to be allocated to the UE.
+	  One byte in an 8-bit format. Default value is 30 seconds.
+
+endif # MODEM_HL78XX_PSM
+
+choice MODEM_DEFAULT_SLEEP_LEVEL
+	prompt "Default Sleep Level"
+	default MODEM_HL78XX_SLEEP_LEVEL_HIBERNATE
+	help
+	  The application can override this setting
+
+config MODEM_HL78XX_SLEEP_LEVEL_HIBERNATE
+	bool "Hibernate"
+	help
+	  Lowest power consumption
+	  IO state not retained
+	  Application subsystem OFF
+
+config MODEM_HL78XX_SLEEP_LEVEL_LITE_HIBERNATE
+	bool "Lite Hibernate"
+	help
+	  IO state retained
+	  Application subsystem OFF
+
+config MODEM_HL78XX_SLEEP_LEVEL_SLEEP
+	bool "Sleep"
+	help
+	  Highest power consumption of modem sleep states
+	  IO state retained
+	  Application subsystem ON
+	  Allows sockets to remain open
+
+endchoice
+
+config MODEM_HL78XX_SLEEP_DELAY_AFTER_REBOOT
+	int "Delay in seconds before sleep after reboot"
+	default 10
+
+endif # MODEM_HL78XX_LOW_POWER_MODE
+
+choice MODEM_HL78XX_NETWORK_REG_STATUS_REPORT_CFG
+	prompt "Network Registration Status Report Configuration"
+	default MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM_AND_CAUSE
+	help
+	  · 0 — Disable network registration unsolicited result code.
+	  · 1 — Enable network registration unsolicited result code +CEREG: <stat>
+	  · 2 — Enable network registration and location information unsolicited result
+	  code:
+	  +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>]]
+	  · 3 — Enable network registration, location information and EMM cause value
+	  information unsolicited result code:
+	  +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>][,<cause_type>, <reject_cause>]]
+	  · 4 — For a UE that wants to apply PSM, enable network registration and
+	  location information unsolicited result code:
+	  +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>][,,[,[<Active- Time>],[<PeriodicTAU>]]]]
+	  · 5 — For a UE that wants to apply PSM, enable network registration, location
+	  information and EMM cause value information unsolicited result code:
+	  +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>][,[<cause_type>],[<reject_-
+	  cause>][,[<Active-Time>] [<Periodic-TAU>]]]]
+
+config MODEM_HL78XX_DISABLE_NETWORK_STATUS_URC_REPORT
+	bool "Disable network status URC report"
+	help
+	  Disable network registration unsolicited result code.
+
+config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT
+	bool "Network status URC report"
+	help
+	  Enable network registration unsolicited result code +CEREG: <stat>
+
+config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION
+	bool "Network status URC report with location"
+	help
+	  Enable network registration and location information unsolicited result
+	  +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>]]
+
+config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION_AND_CAUSE
+	bool "Network status URC report with location and cause"
+	help
+	  Enable network registration, location information and EMM cause value
+	  information unsolicited result code:
+	  +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>][,<cause_type>, <reject_cause>]]
+
+config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM
+	bool "Network status URC report with PSM"
+	help
+	  For a UE that wants to apply PSM, enable network registration and
+	  location information unsolicited result code:
+	  +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>][,,[,[<Active- Time>],[<PeriodicTAU>]]]]
+
+config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM_AND_CAUSE
+	bool "Network status URC report with PSM and cause"
+	help
+	  For a UE that wants to apply PSM, enable network registration, location
+	  information and EMM cause value information unsolicited result code:
+	  +CEREG: <stat>[,[<tac>],[<ci>],[<AcT>][,[<cause_type>],[<reject_-
+	  cause>][,[<Active-Time>] [<Periodic-TAU>]]]]
+
+endchoice
+
+config MODEM_HL78XX_NETWORK_REG_STATUS_REPORT_CFG_CODE
+	string
+	default "5" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM_AND_CAUSE
+	default "4" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM
+	default "3" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION_AND_CAUSE
+	default "2" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION
+	default "1" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT
+	default "0" if MODEM_HL78XX_DISABLE_NETWORK_STATUS_URC_REPORT
+	help
+	  This setting is used to configure the network registration status report
+	  configuration code. It is used in the AT+CREG/CEREG command to set the network
+	  registration status report configuration.
+
+config MODEM_MIN_ALLOWED_SIGNAL_STRENGTH
+	int "Minimum allowed RSRP signal strength (dBm)"
+	default -140
+	range -140 0
+	help
+	  The average power received from a single Reference signal,
+	  and Its typical range is around -44dbm (good) to -140dbm(bad).
+	  Note: (Anything < - 115 dBm unusable/unreliable)
+	  EXCELLENT_SIGNAL_STRENGTH
+	  bool ">= -80(dBm)"
+	    default -80
+	    range - 80 0
+	  GOOD_SIGNAL_STRENGTH
+	  bool ">= -90(dBm)"
+	    default -90
+	    range - 90 0
+	  MID_CELL_SIGNAL_STRENGTH
+	  bool ">= -100(dBm)"
+	    default -100
+	    range - 100 0
+	  CELL_EDGE_SIGNAL_STRENGTH
+	  bool "<= -100(dBm)"
+	    default -110
+	    range - 110 0
+	  POOR_SIGNAL_STRENGTH
+	  bool ">= -140(dBm)"
+	    default -140
+	    range - 140 0
+
+config MODEM_HL78XX_ADVANCED_SOCKET_CONFIG
+	bool "Advanced socket configuration"
+	help
+	  Enable advanced socket configuration options
+
+if MODEM_HL78XX_ADVANCED_SOCKET_CONFIG
+
+config MODEM_HL78XX_SOCKET_UDP_DISPLAY_DATA_URC
+	int "display data in URC"
+	default 0
+	help
+	  0 — Do not display data in URC
+	  1 — Display data in URC automatically
+	  2 — Do not display data in URC and KUDPRCV command is required to dump
+	  data. If there is no KUDPRCV command after rcv_timeout, the original data is
+	  dropped and URC re-enabled.
+
+config MODEM_HL78XX_SOCKET_RESTORE_ON_BOOT
+	bool "Restore sockets on boot"
+	help
+	  only the first session is restored
+	  For HL780x, restore_on_boot is required to restore the first session across
+	  eDRX/PSM hibernate cycles or reset.
+	  • For HL781x/45, all sessions are maintained across eDRX/PSM hibernate cycles
+	  independent of this configuration. It is only required for reset cases.
+	  • For a restored client session (e.g. after a reset or exiting hibernation), +KTCPCNX
+	  must be used to establish a connection before sending/receiving any data.
+	  0 — Do not restore sockets on boot
+	  1 — Restore sockets on boot
+
+endif # MODEM_HL78XX_ADVANCED_SOCKET_CONFIG
+
+config MODEM_HL78XX_NUM_SOCKETS
+	int "Maximum number of sockets"
+	default 6
+	range 6 6 if !MODEM_HL78XX_ADVANCED_SOCKET_CONFIG
+	range 1 6 if MODEM_HL78XX_ADVANCED_SOCKET_CONFIG
+	help
+	  Maximum number of sockets that can be opened at the same time
+
+config MODEM_HL78XX_SOCKETS_SOCKOPT_TLS
+	bool "TLS for sockets"
+	depends on NET_SOCKETS_SOCKOPT_TLS
+	help
+	  This option enables TLS (Transport Layer Security) for sockets
+	  on the HL78xx modem.
+
+config MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+	bool "Verbose debug output in the HL78xx"
+	depends on MODEM_MODULES_LOG_LEVEL_DBG
+	help
+	  Enabling this setting will turn on VERY heavy debugging from the
+	  modem.  Do NOT leave on for production.
+	  This setting is depends on global log level debug.
+
+config MODEM_HL78XX_DEV_POWER_PULSE_DURATION
+	int "Duration of the power-on pulse in milliseconds."
+	default 1500
+	help
+	  Trigger a power-on sequence by setting a power on GPIO pin high
+	  for a specific amount of time.
+
+config MODEM_HL78XX_DEV_RESET_PULSE_DURATION
+	int "Duration of the power-reset pulse in milliseconds."
+	default 100
+	help
+	  Trigger a power-reset sequence by setting a reset GPIO pin high
+	  for a specific amount of time.
+
+config MODEM_HL78XX_DEV_STARTUP_TIME
+	int "Wait before assuming the device is ready."
+	default 1000
+	help
+	  The expected time (in milliseconds) the modem needs to fully power on
+	  and become operational.
+
+config MODEM_HL78XX_DEV_SHUTDOWN_TIME
+	int "Wait before assuming the device is completely off."
+	default 1000
+	help
+	  The amount of time (in milliseconds) the system should wait for the modem
+	  to fully shut down
+
+config MODEM_HL78XX_DEV_INIT_PRIORITY
+	int "Sierra Wireless HL78XX device driver init priority"
+	default 80
+	help
+	  Sierra Wireless HL78XX device driver initialization priority.
+	  Do not mess with it unless you know what you are doing.
+
+config MODEM_HL78XX_OFFLOAD_INIT_PRIORITY
+	int "Sierra Wireless HL78XX offload driver init priority"
+	default 81
+	help
+	  Sierra Wireless HL78XX driver device driver initialization priority.
+	  Do not mess with it unless you know what you are doing.
+	  Make sure offload init priority higher than dev init priority
+
+rsource "hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor"
+
+endif # MODEM_HL78XX
diff --git a/drivers/modem/hl78xx/hl78xx.c b/drivers/modem/hl78xx/hl78xx.c
new file mode 100644
index 0000000..92542a4
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx.c
@@ -0,0 +1,1860 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <zephyr/modem/chat.h>
+#include <zephyr/modem/backend/uart.h>
+#include <zephyr/kernel.h>
+#include <zephyr/drivers/gpio.h>
+#include <zephyr/net/net_if.h>
+#include <zephyr/net/net_offload.h>
+#include <zephyr/net/offloaded_netdev.h>
+#include <zephyr/net/socket_offload.h>
+#include <zephyr/posix/fcntl.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/pm/device.h>
+#include <zephyr/pm/device_runtime.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "hl78xx.h"
+#include "hl78xx_chat.h"
+#include "hl78xx_cfg.h"
+
+#define MAX_SCRIPT_AT_CMD_RETRY 3
+
+#define MDM_NODE                         DT_ALIAS(modem)
+/* Check phandle target status for a specific phandle index */
+#define HAS_GPIO_IDX(node_id, prop, idx) DT_PROP_HAS_IDX(node_id, prop, idx)
+
+/* GPIO availability macros */
+#define HAS_RESET_GPIO      HAS_GPIO_IDX(MDM_NODE, mdm_reset_gpios, 0)
+#define HAS_WAKE_GPIO       HAS_GPIO_IDX(MDM_NODE, mdm_wake_gpios, 0)
+#define HAS_VGPIO_GPIO      HAS_GPIO_IDX(MDM_NODE, mdm_vgpio_gpios, 0)
+#define HAS_UART_CTS_GPIO   HAS_GPIO_IDX(MDM_NODE, mdm_uart_cts_gpios, 0)
+#define HAS_GPIO6_GPIO      HAS_GPIO_IDX(MDM_NODE, mdm_gpio6_gpios, 0)
+#define HAS_PWR_ON_GPIO     HAS_GPIO_IDX(MDM_NODE, mdm_pwr_on_gpios, 0)
+#define HAS_FAST_SHUTD_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_fast_shutd_gpios, 0)
+#define HAS_UART_DSR_GPIO   HAS_GPIO_IDX(MDM_NODE, mdm_uart_dsr_gpios, 0)
+#define HAS_UART_DTR_GPIO   HAS_GPIO_IDX(MDM_NODE, mdm_uart_dtr_gpios, 0)
+#define HAS_GPIO8_GPIO      HAS_GPIO_IDX(MDM_NODE, mdm_gpio8_gpios, 0)
+#define HAS_SIM_SWITCH_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_sim_switch_gpios, 0)
+
+/* GPIO count macro */
+#define GPIO_CONFIG_LEN                                                                            \
+	(HAS_RESET_GPIO + HAS_WAKE_GPIO + HAS_VGPIO_GPIO + HAS_UART_CTS_GPIO + HAS_GPIO6_GPIO +    \
+	 HAS_PWR_ON_GPIO + HAS_FAST_SHUTD_GPIO + HAS_UART_DSR_GPIO + HAS_UART_DTR_GPIO +           \
+	 HAS_GPIO8_GPIO + HAS_SIM_SWITCH_GPIO)
+
+LOG_MODULE_REGISTER(hl78xx_dev, CONFIG_MODEM_LOG_LEVEL);
+/* RX thread work queue */
+K_KERNEL_STACK_DEFINE(modem_workq_stack, CONFIG_MODEM_HL78XX_RX_WORKQ_STACK_SIZE);
+
+static struct k_work_q modem_workq;
+hl78xx_evt_monitor_dispatcher_t event_dispatcher;
+
+static void hl78xx_event_handler(struct hl78xx_data *data, enum hl78xx_event evt);
+static int hl78xx_on_idle_state_enter(struct hl78xx_data *data);
+
+struct hl78xx_state_handlers {
+	int (*on_enter)(struct hl78xx_data *data);
+	int (*on_leave)(struct hl78xx_data *data);
+	void (*on_event)(struct hl78xx_data *data, enum hl78xx_event evt);
+};
+
+/* Forward declare the table so functions earlier in this file can reference
+ * it. The table itself is defined later in the file (without 'static').
+ */
+const static struct hl78xx_state_handlers hl78xx_state_table[];
+/** Dispatch an event to the registered event dispatcher, if any.
+ *
+ */
+static void event_dispatcher_dispatch(struct hl78xx_evt *notif)
+{
+	if (event_dispatcher != NULL) {
+		event_dispatcher(notif);
+	}
+}
+/* -------------------------------------------------------------------------
+ * Utilities
+ * - small helpers and local utility functions
+ * -------------------------------------------------------------------------
+ */
+
+static const char *hl78xx_state_str(enum hl78xx_state state)
+{
+	switch (state) {
+	case MODEM_HL78XX_STATE_IDLE:
+		return "idle";
+	case MODEM_HL78XX_STATE_RESET_PULSE:
+		return "reset pulse";
+	case MODEM_HL78XX_STATE_POWER_ON_PULSE:
+		return "power pulse";
+	case MODEM_HL78XX_STATE_AWAIT_POWER_ON:
+		return "await power on";
+	case MODEM_HL78XX_STATE_SET_BAUDRATE:
+		return "set baudrate";
+	case MODEM_HL78XX_STATE_RUN_INIT_SCRIPT:
+		return "run init script";
+	case MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT:
+		return "init fail diagnostic script ";
+	case MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT:
+		return "run rat cfg script";
+	case MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT:
+		return "run enable gprs script";
+	case MODEM_HL78XX_STATE_AWAIT_REGISTERED:
+		return "await registered";
+	case MODEM_HL78XX_STATE_CARRIER_ON:
+		return "carrier on";
+	case MODEM_HL78XX_STATE_CARRIER_OFF:
+		return "carrier off";
+	case MODEM_HL78XX_STATE_SIM_POWER_OFF:
+		return "sim power off";
+	case MODEM_HL78XX_STATE_AIRPLANE:
+		return "airplane mode";
+	case MODEM_HL78XX_STATE_INIT_POWER_OFF:
+		return "init power off";
+	case MODEM_HL78XX_STATE_POWER_OFF_PULSE:
+		return "power off pulse";
+	case MODEM_HL78XX_STATE_AWAIT_POWER_OFF:
+		return "await power off";
+	default:
+		return "UNKNOWN state";
+	}
+
+	return "";
+}
+
+static const char *hl78xx_event_str(enum hl78xx_event event)
+{
+	switch (event) {
+	case MODEM_HL78XX_EVENT_RESUME:
+		return "resume";
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		return "suspend";
+	case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
+		return "script success";
+	case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
+		return "script failed";
+	case MODEM_HL78XX_EVENT_SCRIPT_REQUIRE_RESTART:
+		return "script require restart";
+	case MODEM_HL78XX_EVENT_TIMEOUT:
+		return "timeout";
+	case MODEM_HL78XX_EVENT_REGISTERED:
+		return "registered";
+	case MODEM_HL78XX_EVENT_DEREGISTERED:
+		return "deregistered";
+	case MODEM_HL78XX_EVENT_BUS_OPENED:
+		return "bus opened";
+	case MODEM_HL78XX_EVENT_BUS_CLOSED:
+		return "bus closed";
+	case MODEM_HL78XX_EVENT_SOCKET_READY:
+		return "socket ready";
+	default:
+		return "unknown event";
+	}
+
+	return "";
+}
+
+static bool hl78xx_gpio_is_enabled(const struct gpio_dt_spec *gpio)
+{
+	return (gpio->port != NULL);
+}
+
+static void hl78xx_log_event(enum hl78xx_event evt)
+{
+	LOG_DBG("event %s", hl78xx_event_str(evt));
+}
+
+static void hl78xx_start_timer(struct hl78xx_data *data, k_timeout_t timeout)
+{
+	k_work_schedule(&data->timeout_work, timeout);
+}
+
+static void hl78xx_stop_timer(struct hl78xx_data *data)
+{
+	k_work_cancel_delayable(&data->timeout_work);
+}
+
+static void hl78xx_timeout_handler(struct k_work *item)
+{
+	struct k_work_delayable *dwork = k_work_delayable_from_work(item);
+	struct hl78xx_data *data = CONTAINER_OF(dwork, struct hl78xx_data, timeout_work);
+
+	hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_TIMEOUT);
+}
+
+static void hl78xx_bus_pipe_handler(struct modem_pipe *pipe, enum modem_pipe_event event,
+				    void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	switch (event) {
+	case MODEM_PIPE_EVENT_OPENED:
+		hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_BUS_OPENED);
+		break;
+
+	case MODEM_PIPE_EVENT_CLOSED:
+		hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_BUS_CLOSED);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static void hl78xx_log_state_changed(enum hl78xx_state last_state, enum hl78xx_state new_state)
+{
+	LOG_INF("switch from %s to %s", hl78xx_state_str(last_state), hl78xx_state_str(new_state));
+}
+
+static void hl78xx_event_dispatch_handler(struct k_work *item)
+{
+	struct hl78xx_data *data =
+		CONTAINER_OF(item, struct hl78xx_data, events.event_dispatch_work);
+	uint8_t events[sizeof(data->events.event_buf)];
+	uint8_t events_cnt;
+
+	k_mutex_lock(&data->events.event_rb_lock, K_FOREVER);
+	events_cnt = (uint8_t)ring_buf_get(&data->events.event_rb, events,
+					   sizeof(data->events.event_buf));
+	k_mutex_unlock(&data->events.event_rb_lock);
+	LOG_DBG("dequeued %d events", events_cnt);
+
+	for (uint8_t i = 0; i < events_cnt; i++) {
+		hl78xx_event_handler(data, (enum hl78xx_event)events[i]);
+	}
+}
+
+void hl78xx_delegate_event(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	k_mutex_lock(&data->events.event_rb_lock, K_FOREVER);
+	ring_buf_put(&data->events.event_rb, (uint8_t *)&evt, 1);
+	k_mutex_unlock(&data->events.event_rb_lock);
+	k_work_submit_to_queue(&modem_workq, &data->events.event_dispatch_work);
+}
+/* -------------------------------------------------------------------------
+ * Chat callbacks / URC handlers
+ * - unsolicited response handlers and chat-related parsers
+ * -------------------------------------------------------------------------
+ */
+void hl78xx_on_cxreg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+	enum cellular_registration_status registration_status = 0;
+	struct hl78xx_evt event = {.type = HL78XX_LTE_REGISTRATION_STAT_UPDATE};
+#ifndef CONFIG_MODEM_HL78XX_12
+	enum hl78xx_cell_rat_mode rat_mode = HL78XX_RAT_MODE_NONE;
+	struct hl78xx_evt rat_event;
+	bool rat_mode_updated = false;
+	int act_value = -1;
+#endif /* CONFIG_MODEM_HL78XX_12 */
+	if (argc < 2) {
+		return;
+	}
+	/* +CXREG: <stat>[,<tac>[...]] */
+	if (argc > 2 && strlen(argv[1]) == 1 && strlen(argv[2]) == 1) {
+		/* This is a condition to distinguish received message between URC message and User
+		 * request network status request. If the message is User message, then argv[1] and
+		 * argv[2] will be 1 character long. If the message is URC request network status
+		 * request, then argv[1] will be 1 character long while argv[2] will be 2 characters
+		 * long.
+		 */
+		registration_status = ATOI(argv[2], 0, "registration_status");
+#ifndef CONFIG_MODEM_HL78XX_12
+		if (argc > 4 && strlen(argv[5]) == 1) {
+			act_value = ATOI(argv[5], -1, "act_value");
+			LOG_DBG("act_value: %d, argc: %d, argv[5]: %s", act_value, argc, argv[5]);
+			switch (act_value) {
+			case 7:
+				rat_mode = HL78XX_RAT_CAT_M1;
+				break;
+			case 9:
+				rat_mode = HL78XX_RAT_NB1;
+				break;
+			default:
+				rat_mode = HL78XX_RAT_MODE_NONE;
+				break;
+			}
+			rat_mode_updated = true;
+			LOG_DBG("RAT mode from response: %d", rat_mode);
+		}
+#endif /* CONFIG_MODEM_HL78XX_12 */
+	} else {
+		registration_status = ATOI(argv[1], 0, "registration_status");
+#ifndef CONFIG_MODEM_HL78XX_12
+		if (argc > 3 && strlen(argv[4]) == 1) {
+			act_value = ATOI(argv[4], -1, "act_value");
+			LOG_DBG("act_value: %d, argc: %d, argv[4]: %s", act_value, argc, argv[4]);
+			switch (act_value) {
+			case 7:
+				rat_mode = HL78XX_RAT_CAT_M1;
+				break;
+			case 9:
+				rat_mode = HL78XX_RAT_NB1;
+				break;
+			default:
+				rat_mode = HL78XX_RAT_MODE_NONE;
+				break;
+			}
+			rat_mode_updated = true;
+			LOG_DBG("RAT mode from URC: %d", rat_mode);
+		}
+#endif /* CONFIG_MODEM_HL78XX_12 */
+	}
+	HL78XX_LOG_DBG("%s: %d", argv[0], registration_status);
+	if (registration_status == data->status.registration.network_state_current) {
+#ifndef CONFIG_MODEM_HL78XX_12
+		/* Check if RAT mode changed even if registration status didn't */
+		if (rat_mode_updated && rat_mode != -1 &&
+		    rat_mode != data->status.registration.rat_mode) {
+			data->status.registration.rat_mode = rat_mode;
+			rat_event.type = HL78XX_LTE_RAT_UPDATE;
+			rat_event.content.rat_mode = rat_mode;
+			event_dispatcher_dispatch(&rat_event);
+		}
+#endif /* CONFIG_MODEM_HL78XX_12 */
+		return;
+	}
+	/* Update the previous registration state */
+	data->status.registration.network_state_previous =
+		data->status.registration.network_state_current;
+	/* Update the current registration state */
+	data->status.registration.network_state_current = registration_status;
+	event.content.reg_status = data->status.registration.network_state_current;
+
+	data->status.registration.is_registered_previously =
+		data->status.registration.is_registered_currently;
+#ifndef CONFIG_MODEM_HL78XX_12
+	/* Update RAT mode if parsed */
+	if (rat_mode_updated && rat_mode != -1 && rat_mode != data->status.registration.rat_mode) {
+		data->status.registration.rat_mode = rat_mode;
+		rat_event.type = HL78XX_LTE_RAT_UPDATE;
+		rat_event.content.rat_mode = rat_mode;
+		event_dispatcher_dispatch(&rat_event);
+	}
+#endif /* CONFIG_MODEM_HL78XX_12 */
+	/* Update current registration flag */
+	if (hl78xx_is_registered(data)) {
+		data->status.registration.is_registered_currently = true;
+		hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_REGISTERED);
+#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
+		k_sem_give(&data->stay_in_boot_mode_sem);
+#endif /* CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING */
+	} else {
+		data->status.registration.is_registered_currently = false;
+		hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_DEREGISTERED);
+	}
+	event_dispatcher_dispatch(&event);
+}
+
+void hl78xx_on_ksup(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	int module_status;
+	struct hl78xx_evt event = {.type = HL78XX_LTE_MODEM_STARTUP};
+
+	if (argc != 2) {
+		return;
+	}
+	module_status = ATOI(argv[1], 0, "module_status");
+	event.content.value = module_status;
+	event_dispatcher_dispatch(&event);
+	HL78XX_LOG_DBG("Module status: %d", module_status);
+}
+
+void hl78xx_on_imei(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc != 2) {
+		return;
+	}
+	HL78XX_LOG_DBG("IMEI: %s %s", argv[0], argv[1]);
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	safe_strncpy((char *)data->identity.imei, argv[1], sizeof(data->identity.imei));
+	k_mutex_unlock(&data->api_lock);
+}
+
+void hl78xx_on_cgmm(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc != 2) {
+		return;
+	}
+	HL78XX_LOG_DBG("cgmm: %s %s", argv[0], argv[1]);
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	safe_strncpy((char *)data->identity.model_id, argv[1], sizeof(data->identity.model_id));
+	k_mutex_unlock(&data->api_lock);
+}
+
+void hl78xx_on_imsi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc != 2) {
+		return;
+	}
+	HL78XX_LOG_DBG("IMSI: %s %s", argv[0], argv[1]);
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	safe_strncpy((char *)data->identity.imsi, argv[1], sizeof(data->identity.imsi));
+	k_mutex_unlock(&data->api_lock);
+#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI)
+	/* set the APN automatically */
+	modem_detect_apn(data, argv[1]);
+#endif /* CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI */
+}
+
+void hl78xx_on_cgmi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc != 2) {
+		return;
+	}
+	HL78XX_LOG_DBG("cgmi: %s %s", argv[0], argv[1]);
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	safe_strncpy((char *)data->identity.manufacturer, argv[1],
+		     sizeof(data->identity.manufacturer));
+	k_mutex_unlock(&data->api_lock);
+}
+
+void hl78xx_on_cgmr(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc != 2) {
+		return;
+	}
+	HL78XX_LOG_DBG("cgmr: %s %s", argv[0], argv[1]);
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	safe_strncpy((char *)data->identity.fw_version, argv[1], sizeof(data->identity.fw_version));
+	k_mutex_unlock(&data->api_lock);
+}
+
+void hl78xx_on_iccid(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc != 2) {
+		return;
+	}
+	HL78XX_LOG_DBG("ICCID: %s %s", argv[0], argv[1]);
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	safe_strncpy((char *)data->identity.iccid, argv[1], sizeof(data->identity.iccid));
+	k_mutex_unlock(&data->api_lock);
+
+#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID)
+	/* set the APN automatically */
+	modem_detect_apn(data, argv[1]);
+#endif /* CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID */
+}
+
+#if defined(CONFIG_MODEM_HL78XX_12)
+void hl78xx_on_kstatev(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+	enum hl78xx_cell_rat_mode rat_mode = HL78XX_RAT_MODE_NONE;
+	struct hl78xx_evt event = {.type = HL78XX_LTE_RAT_UPDATE};
+
+	if (argc != 3) {
+		return;
+	}
+	rat_mode = ATOI(argv[2], 0, "rat_mode");
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+	hl78xx_on_kstatev_parser(data, (enum hl78xx_info_transfer_event)ATOI(argv[1], 0, "status"),
+				 rat_mode);
+#endif /* CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG */
+	if (rat_mode != data->status.registration.rat_mode) {
+		data->status.registration.rat_mode = rat_mode;
+		event.content.rat_mode = data->status.registration.rat_mode;
+		event_dispatcher_dispatch(&event);
+	}
+}
+#endif
+
+void hl78xx_on_ksrep(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc < 2) {
+		return;
+	}
+	data->status.ksrep = ATOI(argv[1], 0, "ksrep");
+	HL78XX_LOG_DBG("KSREP: %s %s", argv[0], argv[1]);
+}
+void hl78xx_on_ksrat(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+	struct hl78xx_evt event = {.type = HL78XX_LTE_RAT_UPDATE};
+
+	if (argc < 2) {
+		return;
+	}
+	data->status.registration.rat_mode = (uint8_t)ATOI(argv[1], 0, "rat_mode");
+	event.content.rat_mode = data->status.registration.rat_mode;
+	event_dispatcher_dispatch(&event);
+	HL78XX_LOG_DBG("KSRAT: %s %s", argv[0], argv[1]);
+}
+
+void hl78xx_on_kselacq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc < 2) {
+		return;
+	}
+	if (argc > 3) {
+		data->kselacq_data.mode = 0;
+		data->kselacq_data.rat1 = ATOI(argv[1], 0, "rat1");
+		data->kselacq_data.rat2 = ATOI(argv[2], 0, "rat2");
+		data->kselacq_data.rat3 = ATOI(argv[3], 0, "rat3");
+	} else {
+		data->kselacq_data.mode = 0;
+		data->kselacq_data.rat1 = 0;
+		data->kselacq_data.rat2 = 0;
+		data->kselacq_data.rat3 = 0;
+	}
+}
+
+void hl78xx_on_kbndcfg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+	uint8_t rat_id;
+	uint8_t kbnd_bitmap_size;
+
+	if (argc < 3) {
+		return;
+	}
+
+	rat_id = ATOI(argv[1], 0, "rat");
+	kbnd_bitmap_size = strlen(argv[2]);
+	HL78XX_LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[2]);
+	if (kbnd_bitmap_size >= MDM_BAND_HEX_STR_LEN) {
+		LOG_ERR("%d %s Unexpected band bitmap length of %d", __LINE__, __func__,
+			kbnd_bitmap_size);
+		return;
+	}
+	if (rat_id >= HL78XX_RAT_COUNT) {
+		return;
+	}
+	data->status.kbndcfg[rat_id].rat = rat_id;
+	strncpy(data->status.kbndcfg[rat_id].bnd_bitmap, argv[2], kbnd_bitmap_size);
+	data->status.kbndcfg[rat_id].bnd_bitmap[kbnd_bitmap_size] = '\0';
+}
+
+void hl78xx_on_csq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc < 3) {
+		return;
+	}
+	data->status.rssi = ATOI(argv[1], 0, "rssi");
+}
+
+void hl78xx_on_cesq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc < 7) {
+		return;
+	}
+	data->status.rsrq = ATOI(argv[5], 0, "rsrq");
+	data->status.rsrp = ATOI(argv[6], 0, "rsrp");
+}
+
+void hl78xx_on_cfun(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc < 2) {
+		return;
+	}
+	data->status.phone_functionality.functionality = ATOI(argv[1], 0, "phone_func");
+	data->status.phone_functionality.in_progress = false;
+}
+
+void hl78xx_on_cops(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (argc < 3) {
+		return;
+	}
+	safe_strncpy((char *)data->status.network_operator.operator, argv[3],
+		     sizeof(data->status.network_operator.operator));
+	data->status.network_operator.format = ATOI(argv[2], 0, "network_operator_format");
+}
+
+/* -------------------------------------------------------------------------
+ * Pipe & chat initialization
+ * - modem backend pipe setup and chat initialisation helpers
+ * -------------------------------------------------------------------------
+ */
+static void hl78xx_init_pipe(const struct device *dev)
+{
+	const struct hl78xx_config *cfg = dev->config;
+	struct hl78xx_data *data = dev->data;
+
+	const struct modem_backend_uart_config uart_backend_config = {
+		.uart = cfg->uart,
+		.receive_buf = data->buffers.uart_rx,
+		.receive_buf_size = sizeof(data->buffers.uart_rx),
+		.transmit_buf = data->buffers.uart_tx,
+		.transmit_buf_size = ARRAY_SIZE(data->buffers.uart_tx),
+	};
+
+	data->uart_pipe = modem_backend_uart_init(&data->uart_backend, &uart_backend_config);
+}
+
+/* Initialize the modem chat subsystem using wrappers from hl78xx_chat.c */
+static int modem_init_chat(const struct device *dev)
+{
+	struct hl78xx_data *data = dev->data;
+
+	const struct modem_chat_config chat_config = {
+		.user_data = data,
+		.receive_buf = data->buffers.chat_rx,
+		.receive_buf_size = sizeof(data->buffers.chat_rx),
+		.delimiter = data->buffers.delimiter,
+		.delimiter_size = strlen(data->buffers.delimiter),
+		.filter = data->buffers.filter,
+		.filter_size = data->buffers.filter ? strlen(data->buffers.filter) : 0,
+		.argv = data->buffers.argv,
+		.argv_size = (uint16_t)ARRAY_SIZE(data->buffers.argv),
+		.unsol_matches = hl78xx_get_unsol_matches(),
+		.unsol_matches_size = (uint16_t)hl78xx_get_unsol_matches_size(),
+	};
+
+	return modem_chat_init(&data->chat, &chat_config);
+}
+
+/* clang-format off */
+int modem_dynamic_cmd_send(
+			struct hl78xx_data *data,
+			modem_chat_script_callback script_user_callback,
+			const uint8_t *cmd, uint16_t cmd_size,
+			const struct modem_chat_match *response_matches, uint16_t matches_size,
+			bool user_cmd
+		)
+{
+	int ret = 0;
+	int script_ret = 0;
+	/* validate input parameters */
+	if (data == NULL) {
+		LOG_ERR("%d %s Invalid parameter", __LINE__, __func__);
+		errno = EINVAL;
+		return -1;
+	}
+	struct modem_chat_script_chat dynamic_script = {
+		.request = cmd,
+		.request_size = cmd_size,
+		.response_matches = response_matches,
+		.response_matches_size = matches_size,
+		.timeout = 1000,
+	};
+	struct modem_chat_script chat_script = {
+		.name = "dynamic_script",
+		.script_chats = &dynamic_script,
+		.script_chats_size = 1,
+		.abort_matches = hl78xx_get_abort_matches(),
+		.abort_matches_size = hl78xx_get_abort_matches_size(),
+		.callback = script_user_callback,
+		.timeout = 1000
+	};
+
+	ret = k_mutex_lock(&data->tx_lock, K_NO_WAIT);
+	if (ret < 0) {
+		if (user_cmd == false) {
+			errno = -ret;
+		}
+		return -1;
+	}
+	/* run the chat script */
+	script_ret = modem_chat_run_script(&data->chat, &chat_script);
+	if (script_ret < 0) {
+		LOG_ERR("%d %s Failed to run at command: %d", __LINE__, __func__, script_ret);
+	} else {
+		LOG_DBG("Chat script executed successfully.");
+	}
+	ret = k_mutex_unlock(&data->tx_lock);
+	if (ret < 0) {
+		if (user_cmd == false) {
+			errno = -ret;
+		}
+		/* we still return the script result if available, prioritize script_ret */
+		return script_ret < 0 ? -1 : script_ret;
+	}
+	return script_ret;
+}
+/* clang-format on */
+
+/* -------------------------------------------------------------------------
+ * GPIO ISR callbacks
+ * - lightweight wrappers for GPIO interrupts (logging & event dispatch)
+ * -------------------------------------------------------------------------
+ */
+void mdm_vgpio_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
+{
+	struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.vgpio_cb);
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+	const struct gpio_dt_spec *spec = &config->mdm_gpio_vgpio;
+
+	if (spec == NULL || spec->port == NULL) {
+		LOG_ERR("VGPIO GPIO spec is not configured properly");
+		return;
+	}
+	if (!(pins & BIT(spec->pin))) {
+		return; /*  not our pin */
+	}
+	LOG_DBG("VGPIO ISR callback %s %d %d", spec->port->name, spec->pin, gpio_pin_get_dt(spec));
+}
+
+#if HAS_UART_DSR_GPIO
+void mdm_uart_dsr_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
+{
+	struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.vgpio_cb);
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+	const struct gpio_dt_spec *spec = &config->mdm_gpio_uart_dsr;
+
+	if (spec == NULL || spec->port == NULL) {
+		LOG_ERR("DSR GPIO spec is not configured properly");
+		return;
+	}
+	if (!(pins & BIT(spec->pin))) {
+		return; /*  not our pin */
+	}
+	LOG_DBG("DSR ISR callback %d", gpio_pin_get_dt(spec));
+}
+#endif
+
+void mdm_gpio6_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
+{
+	struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.gpio6_cb);
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+	const struct gpio_dt_spec *spec = &config->mdm_gpio_gpio6;
+
+	if (spec == NULL || spec->port == NULL) {
+		LOG_ERR("GPIO6 GPIO spec is not configured properly");
+		return;
+	}
+	if (!(pins & BIT(spec->pin))) {
+		return; /*  not our pin */
+	}
+	LOG_DBG("GPIO6 ISR callback %s %d %d", spec->port->name, spec->pin, gpio_pin_get_dt(spec));
+}
+
+void mdm_uart_cts_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
+{
+	struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.gpio6_cb);
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+	const struct gpio_dt_spec *spec = &config->mdm_gpio_uart_cts;
+
+	if (spec == NULL || spec->port == NULL) {
+		LOG_ERR("CTS GPIO spec is not configured properly");
+		return;
+	}
+	if (!(pins & BIT(spec->pin))) {
+		return; /*  not our pin */
+	}
+	LOG_DBG("CTS ISR callback %d", gpio_pin_get_dt(spec));
+}
+
+bool hl78xx_is_registered(struct hl78xx_data *data)
+{
+	return (data->status.registration.network_state_current ==
+		CELLULAR_REGISTRATION_REGISTERED_HOME) ||
+	       (data->status.registration.network_state_current ==
+		CELLULAR_REGISTRATION_REGISTERED_ROAMING);
+}
+
+/*
+ * hl78xx_is_registered - convenience helper
+ *
+ * Simple predicate to test if the modem reports a registered state.
+ */
+
+static int hl78xx_on_reset_pulse_state_enter(struct hl78xx_data *data)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) {
+		gpio_pin_set_dt(&config->mdm_gpio_wake, 0);
+	}
+	gpio_pin_set_dt(&config->mdm_gpio_reset, 1);
+	hl78xx_start_timer(data, K_MSEC(config->reset_pulse_duration_ms));
+	return 0;
+}
+
+/* -------------------------------------------------------------------------
+ * State machine handlers
+ * - state enter/leave and per-state event handlers
+ * -------------------------------------------------------------------------
+ */
+
+static void hl78xx_reset_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_TIMEOUT:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON);
+		break;
+
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static int hl78xx_on_reset_pulse_state_leave(struct hl78xx_data *data)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
+		gpio_pin_set_dt(&config->mdm_gpio_reset, 0);
+	}
+	if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) {
+		gpio_pin_set_dt(&config->mdm_gpio_wake, 1);
+	}
+	hl78xx_stop_timer(data);
+	return 0;
+}
+
+static int hl78xx_on_power_on_pulse_state_enter(struct hl78xx_data *data)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
+		gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 1);
+	}
+	hl78xx_start_timer(data, K_MSEC(config->power_pulse_duration_ms));
+	return 0;
+}
+
+static void hl78xx_power_on_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_TIMEOUT:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON);
+		break;
+
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static int hl78xx_on_power_on_pulse_state_leave(struct hl78xx_data *data)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
+		gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 0);
+	}
+	hl78xx_stop_timer(data);
+	return 0;
+}
+
+static int hl78xx_on_await_power_on_state_enter(struct hl78xx_data *data)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	hl78xx_start_timer(data, K_MSEC(config->startup_time_ms));
+	return 0;
+}
+
+static void hl78xx_await_power_on_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_TIMEOUT:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_SCRIPT);
+		break;
+
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
+		break;
+
+	default:
+		break;
+	}
+}
+static int hl78xx_on_run_init_script_state_enter(struct hl78xx_data *data)
+{
+	modem_pipe_attach(data->uart_pipe, hl78xx_bus_pipe_handler, data);
+	return modem_pipe_open_async(data->uart_pipe);
+}
+
+static void hl78xx_run_init_script_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_BUS_OPENED:
+		modem_chat_attach(&data->chat, data->uart_pipe);
+		/* Run init script via chat TU wrapper (script symbols live in hl78xx_chat.c) */
+		hl78xx_run_init_script_async(data);
+		break;
+
+	case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT);
+		break;
+
+	case MODEM_HL78XX_EVENT_BUS_CLOSED:
+		break;
+
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
+		break;
+
+	case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static int hl78xx_on_run_init_diagnose_script_state_enter(struct hl78xx_data *data)
+{
+	hl78xx_run_init_fail_script_async(data);
+	return 0;
+}
+
+static void hl78xx_run_init_fail_script_event_handler(struct hl78xx_data *data,
+						      enum hl78xx_event evt)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
+		if (data->status.ksrep == 0) {
+			hl78xx_run_enable_ksup_urc_script_async(data);
+			hl78xx_start_timer(data, K_MSEC(config->shutdown_time_ms));
+		} else {
+			if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
+				hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE);
+			}
+		}
+		break;
+	case MODEM_HL78XX_EVENT_TIMEOUT:
+		if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
+			hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE);
+			break;
+		}
+
+		if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
+			hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE);
+			break;
+		}
+
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
+		break;
+	case MODEM_HL78XX_EVENT_BUS_CLOSED:
+		break;
+
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
+		break;
+
+	case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
+		if (!hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) {
+			LOG_ERR("modem wake pin is not enabled, make sure modem low power is "
+				"disabled, if you are not sure enable wake up pin by adding it "
+				"dts!!");
+		}
+
+		if (data->status.script_fail_counter++ < MAX_SCRIPT_AT_CMD_RETRY) {
+			if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
+				hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE);
+				break;
+			}
+			if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
+				hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE);
+				break;
+			}
+		}
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
+		break;
+	default:
+		break;
+	}
+}
+
+static int hl78xx_on_rat_cfg_script_state_enter(struct hl78xx_data *data)
+{
+	int ret = 0;
+	bool modem_require_restart = false;
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+	enum hl78xx_cell_rat_mode rat_config_request = HL78XX_RAT_MODE_NONE;
+	const char *cmd_restart = (const char *)SET_AIRPLANE_MODE_CMD;
+
+	ret = hl78xx_rat_cfg(data, &modem_require_restart, &rat_config_request);
+	if (ret < 0) {
+		goto error;
+	}
+
+	ret = hl78xx_band_cfg(data, &modem_require_restart, rat_config_request);
+	if (ret < 0) {
+		goto error;
+	}
+
+	if (modem_require_restart) {
+		ret = modem_dynamic_cmd_send(data, NULL, cmd_restart, strlen(cmd_restart),
+					     hl78xx_get_ok_match(), 1, false);
+		if (ret < 0) {
+			goto error;
+		}
+		hl78xx_start_timer(data,
+				   K_MSEC(config->shutdown_time_ms + config->startup_time_ms));
+		return 0;
+	}
+	hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS, data);
+	return 0;
+error:
+	hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_ABORT, data);
+	LOG_ERR("%d %s Failed to send command: %d", __LINE__, __func__, ret);
+	return ret;
+}
+
+static void hl78xx_run_rat_cfg_script_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	int ret = 0;
+
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_TIMEOUT:
+		LOG_DBG("Rebooting modem to apply new RAT settings");
+		ret = hl78xx_run_post_restart_script_async(data);
+		if (ret < 0) {
+			hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SUSPEND);
+		}
+		break;
+
+	case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT);
+		break;
+
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF);
+		break;
+	default:
+		break;
+	}
+}
+
+static int hl78xx_on_await_power_off_state_enter(struct hl78xx_data *data)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	hl78xx_start_timer(data, K_MSEC(config->shutdown_time_ms));
+	return 0;
+}
+
+static void hl78xx_await_power_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	if (evt == MODEM_HL78XX_EVENT_TIMEOUT) {
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
+	}
+}
+
+static int hl78xx_on_enable_gprs_state_enter(struct hl78xx_data *data)
+{
+	int ret = 0;
+	/* Apply the APN if not configured yet */
+	if (data->status.apn.state == APN_STATE_REFRESH_REQUESTED) {
+		/* APN has been updated by the user, apply the new APN */
+		HL78XX_LOG_DBG("APN refresh requested, applying new APN: \"%s\"",
+			       data->identity.apn);
+		data->status.apn.state = APN_STATE_NOT_CONFIGURED;
+	} else {
+#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_KCONFIG)
+		snprintf(data->identity.apn, sizeof(data->identity.apn), "%s",
+			 CONFIG_MODEM_HL78XX_APN);
+#elif defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) || defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI)
+		/* autodetect APN from IMSI */
+		/* the list of SIM profiles. Global scope, so the app can change it */
+		/* AT+CCID or AT+CIMI needs to be run here if it is not ran in the init script */
+		if (strlen(data->identity.apn) < 1) {
+			LOG_WRN("%d %s APN is left blank", __LINE__, __func__);
+		}
+#else /* defined(CONFIG_MODEM_HL78XX_APN_SOURCE_NETWORK) */
+/* set blank string to get apn from network */
+#endif
+	}
+	ret = hl78xx_api_func_set_phone_functionality(data->dev, HL78XX_AIRPLANE, false);
+	if (ret) {
+		goto error;
+	}
+	ret = hl78xx_set_apn_internal(data, data->identity.apn, strlen(data->identity.apn));
+	if (ret) {
+		goto error;
+	}
+#if defined(CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE)
+	ret = hl78xx_api_func_set_phone_functionality(data->dev, HL78XX_FULLY_FUNCTIONAL, false);
+	if (ret) {
+		goto error;
+	}
+#endif /* CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE */
+	hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS, data);
+	return 0;
+error:
+	hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_ABORT, data);
+	LOG_ERR("%d %s Failed to send command: %d", __LINE__, __func__, ret);
+	return ret;
+}
+
+static void hl78xx_enable_gprs_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
+	case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
+		hl78xx_start_timer(data, MODEM_HL78XX_PERIODIC_SCRIPT_TIMEOUT);
+		break;
+
+	case MODEM_HL78XX_EVENT_TIMEOUT:
+		break;
+
+	case MODEM_HL78XX_EVENT_REGISTERED:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_ON);
+		break;
+
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static int hl78xx_on_await_registered_state_enter(struct hl78xx_data *data)
+{
+	return 0;
+}
+
+static void hl78xx_await_registered_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
+	case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
+		hl78xx_start_timer(data, K_SECONDS(MDM_REGISTRATION_TIMEOUT));
+		break;
+
+	case MODEM_HL78XX_EVENT_TIMEOUT:
+		/**
+		 * No need to run periodic script to check registration status because URC is used
+		 * to notify the status change.
+		 *
+		 * If the modem is not registered within the timeout period, it will stay in this
+		 * state forever.
+		 *
+		 * @attention MDM_REGISTRATION_TIMEOUT should be long enough to allow the modem to
+		 * register to the network, especially for the first time registration. And also
+		 * consider the network conditions / number of bands etc.. that may affect
+		 * the registration process.
+		 *
+		 * TODO: add a mechanism to exit this state and retry the registration process
+		 *
+		 */
+		LOG_WRN("Modem failed to register to the network within %d seconds",
+			MDM_REGISTRATION_TIMEOUT);
+
+		break;
+
+	case MODEM_HL78XX_EVENT_REGISTERED:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_ON);
+		break;
+
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static int hl78xx_on_await_registered_state_leave(struct hl78xx_data *data)
+{
+	hl78xx_stop_timer(data);
+	return 0;
+}
+
+static int hl78xx_on_carrier_on_state_enter(struct hl78xx_data *data)
+{
+	notif_carrier_on(data->dev);
+	iface_status_work_cb(data, hl78xx_chat_callback_handler);
+	return 0;
+}
+
+static void hl78xx_carrier_on_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
+		hl78xx_start_timer(data, K_SECONDS(2));
+
+		break;
+	case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
+		break;
+
+	case MODEM_HL78XX_EVENT_TIMEOUT:
+		dns_work_cb(data->dev, true);
+		break;
+
+	case MODEM_HL78XX_EVENT_DEREGISTERED:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_REGISTERED);
+		break;
+
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static int hl78xx_on_carrier_on_state_leave(struct hl78xx_data *data)
+{
+	hl78xx_stop_timer(data);
+	return 0;
+}
+
+static int hl78xx_on_carrier_off_state_enter(struct hl78xx_data *data)
+{
+	notif_carrier_off(data->dev);
+	/* Check whether or not there is any sockets are connected,
+	 * if true, wait until sockets are closed properly
+	 */
+	if (check_if_any_socket_connected(data->dev) == false) {
+		hl78xx_start_timer(data, K_MSEC(100));
+	} else {
+		/* There are still active sockets, wait until they are closed */
+		hl78xx_start_timer(data, K_MSEC(5000));
+	}
+	return 0;
+}
+
+static void hl78xx_carrier_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
+	case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
+	case MODEM_HL78XX_EVENT_TIMEOUT:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT);
+		break;
+
+	case MODEM_HL78XX_EVENT_DEREGISTERED:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_REGISTERED);
+		break;
+
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static int hl78xx_on_carrier_off_state_leave(struct hl78xx_data *data)
+{
+	hl78xx_stop_timer(data);
+	return 0;
+}
+
+/* pwroff script moved to hl78xx_chat.c */
+static int hl78xx_on_init_power_off_state_enter(struct hl78xx_data *data)
+{
+	/**
+	 * Eventhough you have power switch or etc.., start the power off script first
+	 * to gracefully disconnect from the network
+	 *
+	 * IMSI detach before powering down IS recommended by the AT command manual
+	 *
+	 */
+	return hl78xx_run_pwroff_script_async(data);
+}
+
+static void hl78xx_init_power_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
+		break;
+
+	case MODEM_HL78XX_EVENT_TIMEOUT:
+		break;
+
+	case MODEM_HL78XX_EVENT_DEREGISTERED:
+		hl78xx_stop_timer(data);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static int hl78xx_on_init_power_off_state_leave(struct hl78xx_data *data)
+{
+	return 0;
+}
+
+static int hl78xx_on_power_off_pulse_state_enter(struct hl78xx_data *data)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
+		gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 1);
+	}
+	hl78xx_start_timer(data, K_MSEC(config->power_pulse_duration_ms));
+	return 0;
+}
+
+static void hl78xx_power_off_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	if (evt == MODEM_HL78XX_EVENT_TIMEOUT) {
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_OFF);
+	}
+}
+
+static int hl78xx_on_power_off_pulse_state_leave(struct hl78xx_data *data)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
+		gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 0);
+	}
+	hl78xx_stop_timer(data);
+	return 0;
+}
+
+static int hl78xx_on_idle_state_enter(struct hl78xx_data *data)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) {
+		gpio_pin_set_dt(&config->mdm_gpio_wake, 0);
+	}
+
+	if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
+		gpio_pin_set_dt(&config->mdm_gpio_reset, 1);
+	}
+	modem_chat_release(&data->chat);
+	modem_pipe_attach(data->uart_pipe, hl78xx_bus_pipe_handler, data);
+	modem_pipe_close_async(data->uart_pipe);
+	k_sem_give(&data->suspended_sem);
+	return 0;
+}
+
+static void hl78xx_idle_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	switch (evt) {
+	case MODEM_HL78XX_EVENT_BUS_CLOSED:
+		break;
+	case MODEM_HL78XX_EVENT_RESUME:
+		if (config->autostarts) {
+			hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON);
+			break;
+		}
+
+		if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
+			hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE);
+			break;
+		}
+
+		if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
+			hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON);
+			break;
+		}
+		hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT);
+		break;
+
+	case MODEM_HL78XX_EVENT_SUSPEND:
+		k_sem_give(&data->suspended_sem);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static int hl78xx_on_idle_state_leave(struct hl78xx_data *data)
+{
+	const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
+
+	k_sem_take(&data->suspended_sem, K_NO_WAIT);
+
+	if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
+		gpio_pin_set_dt(&config->mdm_gpio_reset, 0);
+	}
+	if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) {
+		gpio_pin_set_dt(&config->mdm_gpio_wake, 1);
+	}
+
+	return 0;
+}
+
+static int hl78xx_on_state_enter(struct hl78xx_data *data)
+{
+	int ret = 0;
+	enum hl78xx_state s = data->status.state;
+
+	/* Use an explicit bounds check against the last enum value so this
+	 * code can reference the table even though the table is defined later
+	 * in the file. MODEM_HL78XX_STATE_AWAIT_POWER_OFF is the last value in
+	 * the `enum hl78xx_state`.
+	 */
+	if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_enter) {
+		ret = hl78xx_state_table[s].on_enter(data);
+	}
+
+	return ret;
+}
+
+static int hl78xx_on_state_leave(struct hl78xx_data *data)
+{
+	int ret = 0;
+	enum hl78xx_state s = data->status.state;
+
+	if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_leave) {
+		ret = hl78xx_state_table[s].on_leave(data);
+	}
+
+	return ret;
+}
+
+void hl78xx_enter_state(struct hl78xx_data *data, enum hl78xx_state state)
+{
+	int ret;
+
+	ret = hl78xx_on_state_leave(data);
+
+	if (ret < 0) {
+		LOG_WRN("failed to leave state, error: %i", ret);
+
+		return;
+	}
+
+	data->status.state = state;
+	ret = hl78xx_on_state_enter(data);
+
+	if (ret < 0) {
+		LOG_WRN("failed to enter state error: %i", ret);
+	}
+}
+
+static void hl78xx_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
+{
+	enum hl78xx_state state;
+	enum hl78xx_state s;
+
+	hl78xx_log_event(evt);
+	s = data->status.state;
+	state = data->status.state;
+	if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_event) {
+		hl78xx_state_table[s].on_event(data, evt);
+	} else {
+		LOG_ERR("%d %s unknown event", __LINE__, __func__);
+	}
+	if (state != s) {
+		hl78xx_log_state_changed(state, s);
+	}
+}
+
+#ifdef CONFIG_PM_DEVICE
+
+/* -------------------------------------------------------------------------
+ * Power management
+ * -------------------------------------------------------------------------
+ */
+
+static int hl78xx_driver_pm_action(const struct device *dev, enum pm_device_action action)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+	int ret = 0;
+
+	LOG_WRN("%d %s PM_DEVICE_ACTION: %d", __LINE__, __func__, action);
+	switch (action) {
+	case PM_DEVICE_ACTION_SUSPEND:
+		/* suspend the device */
+		LOG_DBG("%d PM_DEVICE_ACTION_SUSPEND", __LINE__);
+		hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SUSPEND);
+		ret = k_sem_take(&data->suspended_sem, K_SECONDS(30));
+		break;
+	case PM_DEVICE_ACTION_RESUME:
+		LOG_DBG("%d PM_DEVICE_ACTION_RESUME", __LINE__);
+		/* resume the device */
+		hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_RESUME);
+		break;
+	case PM_DEVICE_ACTION_TURN_ON:
+		/*
+		 * powered on the device, used when the power
+		 * domain this device belongs is resumed.
+		 */
+		LOG_DBG("%d PM_DEVICE_ACTION_TURN_ON", __LINE__);
+		break;
+	case PM_DEVICE_ACTION_TURN_OFF:
+		/*
+		 * power off the device, used when the power
+		 * domain this device belongs is suspended.
+		 */
+		LOG_DBG("%d PM_DEVICE_ACTION_TURN_OFF", __LINE__);
+		break;
+	default:
+		return -ENOTSUP;
+	}
+	return ret;
+}
+#endif /* CONFIG_PM_DEVICE */
+
+/* -------------------------------------------------------------------------
+ * Initialization
+ * -------------------------------------------------------------------------
+ */
+static int hl78xx_init(const struct device *dev)
+{
+	int ret;
+	const struct hl78xx_config *config = (const struct hl78xx_config *)dev->config;
+	struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+
+	k_mutex_init(&data->api_lock);
+	k_mutex_init(&data->tx_lock);
+	/* Initialize work queue and event handling */
+	k_work_queue_start(&modem_workq, modem_workq_stack,
+			   K_KERNEL_STACK_SIZEOF(modem_workq_stack), K_PRIO_COOP(7), NULL);
+	k_work_init_delayable(&data->timeout_work, hl78xx_timeout_handler);
+	k_work_init(&data->events.event_dispatch_work, hl78xx_event_dispatch_handler);
+	ring_buf_init(&data->events.event_rb, sizeof(data->events.event_buf),
+		      data->events.event_buf);
+	k_sem_init(&data->suspended_sem, 0, 1);
+#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
+	k_sem_init(&data->stay_in_boot_mode_sem, 0, 1);
+#endif /* CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING */
+	k_sem_init(&data->script_stopped_sem_tx_int, 0, 1);
+	k_sem_init(&data->script_stopped_sem_rx_int, 0, 1);
+	data->dev = dev;
+	/* reset to default  */
+	data->buffers.eof_pattern_size = strlen(data->buffers.eof_pattern);
+	data->buffers.termination_pattern_size = strlen(data->buffers.termination_pattern);
+	memset(data->identity.apn, 0, MDM_APN_MAX_LENGTH);
+	/* GPIO validation */
+	const struct gpio_dt_spec *gpio_pins[GPIO_CONFIG_LEN] = {
+#if HAS_RESET_GPIO
+		&config->mdm_gpio_reset,
+#endif
+#if HAS_WAKE_GPIO
+		&config->mdm_gpio_wake,
+#endif
+#if HAS_VGPIO_GPIO
+		&config->mdm_gpio_vgpio,
+#endif
+#if HAS_UART_CTS_GPIO
+		&config->mdm_gpio_uart_cts,
+#endif
+#if HAS_GPIO6_GPIO
+		&config->mdm_gpio_gpio6,
+#endif
+#if HAS_PWR_ON_GPIO
+		&config->mdm_gpio_pwr_on,
+#endif
+#if HAS_FAST_SHUTD_GPIO
+		&config->mdm_gpio_fast_shutdown,
+#endif
+#if HAS_UART_DSR_GPIO
+		&config->mdm_gpio_uart_dsr,
+#endif
+#if HAS_UART_DTR_GPIO
+		&config->mdm_gpio_uart_dtr,
+#endif
+#if HAS_GPIO8_GPIO
+		&config->mdm_gpio_gpio8,
+#endif
+#if HAS_SIM_SWITCH_GPIO
+		&config->mdm_gpio_sim_switch,
+#endif
+	};
+	for (int i = 0; i < ARRAY_SIZE(gpio_pins); i++) {
+		if (gpio_pins[i] == NULL || !gpio_is_ready_dt(gpio_pins[i])) {
+			const char *port_name = "unknown";
+
+			if (gpio_pins[i] != NULL && gpio_pins[i]->port != NULL) {
+				port_name = gpio_pins[i]->port->name;
+			}
+			LOG_ERR("GPIO port (%s) not ready!", port_name);
+			return -ENODEV;
+		}
+	}
+	/* GPIO configuration */
+	struct {
+		const struct gpio_dt_spec *spec;
+		gpio_flags_t flags;
+		const char *name;
+	} gpio_config[GPIO_CONFIG_LEN] = {
+#if HAS_RESET_GPIO
+		{&config->mdm_gpio_reset, GPIO_OUTPUT, "reset"},
+#endif
+#if HAS_WAKE_GPIO
+		{&config->mdm_gpio_wake, GPIO_OUTPUT, "wake"},
+#endif
+#if HAS_VGPIO_GPIO
+		{&config->mdm_gpio_vgpio, GPIO_INPUT, "VGPIO"},
+#endif
+#if HAS_UART_CTS_GPIO
+		{&config->mdm_gpio_uart_cts, GPIO_INPUT, "CTS"},
+#endif
+#if HAS_GPIO6_GPIO
+		{&config->mdm_gpio_gpio6, GPIO_INPUT, "GPIO6"},
+#endif
+#if HAS_PWR_ON_GPIO
+		{&config->mdm_gpio_pwr_on, GPIO_OUTPUT, "pwr_on"},
+#endif
+#if HAS_FAST_SHUTD_GPIO
+		{&config->mdm_gpio_fast_shutdown, GPIO_OUTPUT, "fast_shutdown"},
+#endif
+#if HAS_UART_DSR_GPIO
+		{&config->mdm_gpio_uart_dsr, GPIO_INPUT, "DSR"},
+#endif
+#if HAS_UART_DTR_GPIO
+		{&config->mdm_gpio_uart_dtr, GPIO_OUTPUT, "DTR"},
+#endif
+#if HAS_GPIO8_GPIO
+		{&config->mdm_gpio_gpio8, GPIO_INPUT, "GPIO8"},
+#endif
+#if HAS_SIM_SWITCH_GPIO
+		{&config->mdm_gpio_sim_switch, GPIO_INPUT, "SIM_SWITCH"},
+#endif
+	};
+	for (int i = 0; i < ARRAY_SIZE(gpio_config); i++) {
+		ret = gpio_pin_configure_dt(gpio_config[i].spec, gpio_config[i].flags);
+		if (ret < 0) {
+			LOG_ERR("Failed to configure %s pin", gpio_config[i].name);
+			goto error;
+		}
+	}
+#if HAS_VGPIO_GPIO
+	/* VGPIO interrupt setup */
+	gpio_init_callback(&data->gpio_cbs.vgpio_cb, mdm_vgpio_callback_isr,
+			   BIT(config->mdm_gpio_vgpio.pin));
+
+	ret = gpio_add_callback(config->mdm_gpio_vgpio.port, &data->gpio_cbs.vgpio_cb);
+	if (ret) {
+		LOG_ERR("Cannot setup VGPIO callback! (%d)", ret);
+		goto error;
+	}
+	ret = gpio_pin_interrupt_configure_dt(&config->mdm_gpio_vgpio, GPIO_INT_EDGE_BOTH);
+	if (ret) {
+		LOG_ERR("Error configuring VGPIO interrupt! (%d)", ret);
+		goto error;
+	}
+#endif /* HAS_VGPIO_GPIO */
+#if HAS_GPIO6_GPIO
+	/* GPIO6 interrupt setup */
+	gpio_init_callback(&data->gpio_cbs.gpio6_cb, mdm_gpio6_callback_isr,
+			   BIT(config->mdm_gpio_gpio6.pin));
+
+	ret = gpio_add_callback(config->mdm_gpio_gpio6.port, &data->gpio_cbs.gpio6_cb);
+	if (ret) {
+		LOG_ERR("Cannot setup GPIO6 callback! (%d)", ret);
+		goto error;
+	}
+
+	ret = gpio_pin_interrupt_configure_dt(&config->mdm_gpio_gpio6, GPIO_INT_EDGE_BOTH);
+	if (ret) {
+		LOG_ERR("Error configuring GPIO6 interrupt! (%d)", ret);
+		goto error;
+	}
+#endif /* HAS_GPIO6_GPIO */
+	/* UART pipe initialization */
+	(void)hl78xx_init_pipe(dev);
+
+	ret = modem_init_chat(dev);
+	if (ret < 0) {
+		goto error;
+	}
+
+#ifndef CONFIG_PM_DEVICE
+	hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_RESUME);
+#else
+	pm_device_init_suspended(dev);
+#endif /* CONFIG_PM_DEVICE */
+
+#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
+	k_sem_take(&data->stay_in_boot_mode_sem, K_FOREVER);
+#endif
+	return 0;
+error:
+	return ret;
+}
+
+int hl78xx_evt_notif_handler_set(hl78xx_evt_monitor_dispatcher_t handler)
+{
+	event_dispatcher = handler;
+	return 0;
+}
+
+/*
+ * State handler table
+ * Maps each hl78xx_state to optional enter/leave/event handlers. NULL
+ * entries mean the state has no action for that phase.
+ */
+
+/* clang-format off */
+const static struct hl78xx_state_handlers hl78xx_state_table[] = {
+	[MODEM_HL78XX_STATE_IDLE] = {
+		hl78xx_on_idle_state_enter,
+		hl78xx_on_idle_state_leave,
+		hl78xx_idle_event_handler
+	},
+	[MODEM_HL78XX_STATE_RESET_PULSE] = {
+		hl78xx_on_reset_pulse_state_enter,
+		hl78xx_on_reset_pulse_state_leave,
+		hl78xx_reset_pulse_event_handler
+	},
+	[MODEM_HL78XX_STATE_POWER_ON_PULSE] = {
+		hl78xx_on_power_on_pulse_state_enter,
+		hl78xx_on_power_on_pulse_state_leave,
+		hl78xx_power_on_pulse_event_handler
+	},
+	[MODEM_HL78XX_STATE_AWAIT_POWER_ON] = {
+		hl78xx_on_await_power_on_state_enter,
+		NULL,
+		hl78xx_await_power_on_event_handler
+	},
+	[MODEM_HL78XX_STATE_SET_BAUDRATE] = {
+		NULL,
+		NULL,
+		NULL
+	},
+	[MODEM_HL78XX_STATE_RUN_INIT_SCRIPT] = {
+		hl78xx_on_run_init_script_state_enter,
+		NULL,
+		hl78xx_run_init_script_event_handler
+	},
+	[MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT] = {
+		hl78xx_on_run_init_diagnose_script_state_enter,
+		NULL,
+		hl78xx_run_init_fail_script_event_handler
+	},
+	[MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT] = {
+		hl78xx_on_rat_cfg_script_state_enter,
+		NULL,
+		hl78xx_run_rat_cfg_script_event_handler
+	},
+	[MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT] = {
+		hl78xx_on_enable_gprs_state_enter,
+		NULL,
+		hl78xx_enable_gprs_event_handler
+	},
+	[MODEM_HL78XX_STATE_AWAIT_REGISTERED] = {
+		hl78xx_on_await_registered_state_enter,
+		hl78xx_on_await_registered_state_leave,
+		hl78xx_await_registered_event_handler
+	},
+	[MODEM_HL78XX_STATE_CARRIER_ON] = {
+		hl78xx_on_carrier_on_state_enter,
+		hl78xx_on_carrier_on_state_leave,
+		hl78xx_carrier_on_event_handler
+	},
+	[MODEM_HL78XX_STATE_CARRIER_OFF] = {
+		hl78xx_on_carrier_off_state_enter,
+		hl78xx_on_carrier_off_state_leave,
+		hl78xx_carrier_off_event_handler
+	},
+	[MODEM_HL78XX_STATE_SIM_POWER_OFF] = {
+		NULL,
+		NULL,
+		NULL
+	},
+	[MODEM_HL78XX_STATE_AIRPLANE] = {
+		NULL,
+		NULL,
+		NULL
+	},
+	[MODEM_HL78XX_STATE_INIT_POWER_OFF] = {
+		hl78xx_on_init_power_off_state_enter,
+		hl78xx_on_init_power_off_state_leave,
+		hl78xx_init_power_off_event_handler
+	},
+	[MODEM_HL78XX_STATE_POWER_OFF_PULSE] = {
+		hl78xx_on_power_off_pulse_state_enter,
+		hl78xx_on_power_off_pulse_state_leave,
+		hl78xx_power_off_pulse_event_handler
+	},
+	[MODEM_HL78XX_STATE_AWAIT_POWER_OFF] = {
+		hl78xx_on_await_power_off_state_enter,
+		NULL,
+		hl78xx_await_power_off_event_handler
+	},
+};
+/* clang-format on */
+static DEVICE_API(cellular, hl78xx_api) = {
+	.get_signal = hl78xx_api_func_get_signal,
+	.get_modem_info = hl78xx_api_func_get_modem_info_standard,
+	.get_registration_status = hl78xx_api_func_get_registration_status,
+	.set_apn = hl78xx_api_func_set_apn,
+	.set_callback = NULL,
+};
+/* -------------------------------------------------------------------------
+ * Device API and DT registration
+ * -------------------------------------------------------------------------
+ */
+#define MODEM_HL78XX_DEFINE_INSTANCE(inst, power_ms, reset_ms, startup_ms, shutdown_ms, start,     \
+				     init_script, periodic_script)                                 \
+	static const struct hl78xx_config hl78xx_cfg_##inst = {                                    \
+		.uart = DEVICE_DT_GET(DT_INST_BUS(inst)),                                          \
+		.mdm_gpio_reset = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_reset_gpios, {}),             \
+		.mdm_gpio_wake = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_wake_gpios, {}),               \
+		.mdm_gpio_pwr_on = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_pwr_on_gpios, {}),           \
+		.mdm_gpio_fast_shutdown =                                                          \
+			GPIO_DT_SPEC_INST_GET_OR(inst, mdm_fast_shutd_gpios, {}),                  \
+		.mdm_gpio_uart_dtr = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_dtr_gpios, {}),       \
+		.mdm_gpio_uart_dsr = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_dsr_gpios, {}),       \
+		.mdm_gpio_uart_cts = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_cts_gpios, {}),       \
+		.mdm_gpio_vgpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_vgpio_gpios, {}),             \
+		.mdm_gpio_gpio6 = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_gpio6_gpios, {}),             \
+		.mdm_gpio_gpio8 = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_gpio8_gpios, {}),             \
+		.mdm_gpio_sim_switch = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_sim_select_gpios, {}),   \
+		.power_pulse_duration_ms = (power_ms),                                             \
+		.reset_pulse_duration_ms = (reset_ms),                                             \
+		.startup_time_ms = (startup_ms),                                                   \
+		.shutdown_time_ms = (shutdown_ms),                                                 \
+		.autostarts = (start),                                                             \
+		.init_chat_script = (init_script),                                                 \
+		.periodic_chat_script = (periodic_script),                                         \
+	};                                                                                         \
+	static struct hl78xx_data hl78xx_data_##inst = {                                           \
+		.buffers.delimiter = "\r\n",                                                       \
+		.buffers.eof_pattern = EOF_PATTERN,                                                \
+		.buffers.termination_pattern = TERMINATION_PATTERN,                                \
+	};                                                                                         \
+                                                                                                   \
+	PM_DEVICE_DT_INST_DEFINE(inst, hl78xx_driver_pm_action);                                   \
+                                                                                                   \
+	DEVICE_DT_INST_DEFINE(inst, hl78xx_init, PM_DEVICE_DT_INST_GET(inst), &hl78xx_data_##inst, \
+			      &hl78xx_cfg_##inst, POST_KERNEL,                                     \
+			      CONFIG_MODEM_HL78XX_DEV_INIT_PRIORITY, &hl78xx_api);
+
+#define MODEM_DEVICE_SWIR_HL78XX(inst)                                                             \
+	MODEM_HL78XX_DEFINE_INSTANCE(inst, CONFIG_MODEM_HL78XX_DEV_POWER_PULSE_DURATION,           \
+				     CONFIG_MODEM_HL78XX_DEV_RESET_PULSE_DURATION,                 \
+				     CONFIG_MODEM_HL78XX_DEV_STARTUP_TIME,                         \
+				     CONFIG_MODEM_HL78XX_DEV_SHUTDOWN_TIME, false, NULL, NULL)
+
+#define DT_DRV_COMPAT swir_hl7812
+DT_INST_FOREACH_STATUS_OKAY(MODEM_DEVICE_SWIR_HL78XX)
+#undef DT_DRV_COMPAT
+
+#define DT_DRV_COMPAT swir_hl7800
+DT_INST_FOREACH_STATUS_OKAY(MODEM_DEVICE_SWIR_HL78XX)
+#undef DT_DRV_COMPAT
diff --git a/drivers/modem/hl78xx/hl78xx.h b/drivers/modem/hl78xx/hl78xx.h
new file mode 100644
index 0000000..d48f5b2
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx.h
@@ -0,0 +1,652 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef HL78XX_H
+#define HL78XX_H
+
+#include <zephyr/kernel.h>
+#include <zephyr/device.h>
+#include <zephyr/drivers/gpio.h>
+#include <zephyr/drivers/uart.h>
+#include <zephyr/modem/chat.h>
+#include <zephyr/modem/pipe.h>
+#include <zephyr/modem/pipelink.h>
+#include <zephyr/modem/backend/uart.h>
+#include <zephyr/pm/device.h>
+#include <zephyr/sys/atomic.h>
+#include <zephyr/net/net_if.h>
+#include <zephyr/net/net_offload.h>
+#include <zephyr/net/offloaded_netdev.h>
+#include <zephyr/net/socket_offload.h>
+#include <zephyr/drivers/modem/hl78xx_apis.h>
+
+#include "../modem_context.h"
+#include "../modem_socket.h"
+#include <stdint.h>
+
+#define MDM_CMD_TIMEOUT                      (10)  /*K_SECONDS*/
+#define MDM_DNS_TIMEOUT                      (70)  /*K_SECONDS*/
+#define MDM_CELL_BAND_SEARCH_TIMEOUT         (60)  /*K_SECONDS*/
+#define MDM_CMD_CONN_TIMEOUT                 (120) /*K_SECONDS*/
+#define MDM_REGISTRATION_TIMEOUT             (180) /*K_SECONDS*/
+#define MDM_PROMPT_CMD_DELAY                 (50)  /*K_MSEC*/
+#define MDM_RESET_LOW_TIME                   (1)   /*K_MSEC*/
+#define MDM_RESET_HIGH_TIME                  (10)  /*K_MSEC*/
+#define MDM_BOOT_TIME                        (12)  /*K_SECONDS*/
+#define MDM_DNS_ADD_TIMEOUT                  (100) /*K_MSEC*/
+#define MODEM_HL78XX_PERIODIC_SCRIPT_TIMEOUT K_MSEC(CONFIG_MODEM_HL78XX_PERIODIC_SCRIPT_MS)
+
+#define MDM_MAX_DATA_LENGTH CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES
+
+#define MDM_MAX_SOCKETS           CONFIG_MODEM_HL78XX_NUM_SOCKETS
+#define MDM_BASE_SOCKET_NUM       1
+#define MDM_BAND_BITMAP_LEN_BYTES 32
+#define MDM_BAND_HEX_STR_LEN      (MDM_BAND_BITMAP_LEN_BYTES * 2 + 1)
+
+#define MDM_KBND_BITMAP_MAX_ARRAY_SIZE 64
+
+#define ADDRESS_FAMILY_IP         "IP"
+#define ADDRESS_FAMILY_IP4        "IPV4"
+#define ADDRESS_FAMILY_IPV6       "IPV6"
+#define ADDRESS_FAMILY_IPV4V6     "IPV4V6"
+#define MDM_HL78XX_SOCKET_AF_IPV4 0
+#define MDM_HL78XX_SOCKET_AF_IPV6 1
+#if defined(CONFIG_MODEM_HL78XX_ADDRESS_FAMILY_IPV4V6)
+#define MODEM_HL78XX_ADDRESS_FAMILY        ADDRESS_FAMILY_IPV4V6
+#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT "####:####:####:####:####:####:####:####"
+#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT_LEN                                                     \
+	sizeof("a01.a02.a03.a04.a05.a06.a07.a08.a09.a10.a11.a12.a13.a14.a15.a16")
+#elif defined(CONFIG_MODEM_HL78XX_ADDRESS_FAMILY_IPV4)
+#define MODEM_HL78XX_ADDRESS_FAMILY            ADDRESS_FAMILY_IPV4
+#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT     "###.###.###.###"
+#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT_LEN sizeof(MODEM_HL78XX_ADDRESS_FAMILY_FORMAT)
+
+#else
+#define MODEM_HL78XX_ADDRESS_FAMILY ADDRESS_FAMILY_IPV6
+#endif
+
+/* Modem Communication Patterns */
+#define EOF_PATTERN         "--EOF--Pattern--"
+#define TERMINATION_PATTERN "+++"
+#define CONNECT_STRING      "CONNECT"
+#define CME_ERROR_STRING    "+CME ERROR: "
+#define OK_STRING           "OK"
+
+/* RAT (Radio Access Technology) commands */
+#define SET_RAT_M1_CMD_LEGACY    "AT+KSRAT=0"
+#define SET_RAT_NB1_CMD_LEGACY   "AT+KSRAT=1"
+#define SET_RAT_GSM_CMD_LEGACY   "AT+KSRAT=2"
+#define SET_RAT_NBNTN_CMD_LEGACY "AT+KSRAT=3"
+
+#define KSRAT_QUERY      "AT+KSRAT?"
+#define DISABLE_RAT_AUTO "AT+KSELACQ=0,0"
+
+#define SET_RAT_M1_CMD    "AT+KSRAT=0,1"
+#define SET_RAT_NB1_CMD   "AT+KSRAT=1,1"
+#define SET_RAT_GMS_CMD   "AT+KSRAT=2,1"
+#define SET_RAT_NBNTN_CMD "AT+KSRAT=3,1"
+
+/* Power mode commands */
+#define SET_AIRPLANE_MODE_CMD_LEGACY       "AT+CFUN=4,0"
+#define SET_AIRPLANE_MODE_CMD              "AT+CFUN=4,1"
+#define SET_FULLFUNCTIONAL_MODE_CMD_LEGACY "AT+CFUN=1,0"
+#define SET_FULLFUNCTIONAL_MODE_CMD        "AT+CFUN=1,1"
+#define SET_SIM_PWR_OFF_MODE_CMD           "AT+CFUN=0"
+#define GET_FULLFUNCTIONAL_MODE_CMD        "AT+CFUN?"
+#define MDM_POWER_OFF_CMD_LEGACY           "AT+CPWROFF"
+#define MDM_POWER_FAST_OFF_CMD_LEGACY      "AT+CPWROFF=1"
+/* PDP Context commands */
+#define DEACTIVATE_PDP_CONTEXT             "AT+CGACT=0"
+#define ACTIVATE_PDP_CONTEXT               "AT+CGACT=1"
+
+/* Helper macros */
+#define ATOI(s_, value_, desc_) modem_atoi(s_, value_, desc_, __func__)
+#define ATOD(s_, value_, desc_) modem_atod(s_, value_, desc_, __func__)
+
+#define HL78XX_LOG_DBG(str, ...)                                                                   \
+	COND_CODE_1(CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG, \
+		    (LOG_DBG(str, ##__VA_ARGS__)), \
+		    ((void)0))
+
+/* Enums */
+enum hl78xx_state {
+	MODEM_HL78XX_STATE_IDLE = 0,
+	MODEM_HL78XX_STATE_RESET_PULSE,
+	MODEM_HL78XX_STATE_POWER_ON_PULSE,
+	MODEM_HL78XX_STATE_AWAIT_POWER_ON,
+	MODEM_HL78XX_STATE_SET_BAUDRATE,
+	MODEM_HL78XX_STATE_RUN_INIT_SCRIPT,
+	MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT,
+	MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT,
+	MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT,
+	/* Full functionality, searching
+	 * CFUN=1
+	 */
+	MODEM_HL78XX_STATE_AWAIT_REGISTERED,
+	MODEM_HL78XX_STATE_CARRIER_ON,
+	/* Minimum functionality, SIM powered off, Modem Power down
+	 * CFUN=0
+	 */
+	MODEM_HL78XX_STATE_CARRIER_OFF,
+	MODEM_HL78XX_STATE_SIM_POWER_OFF,
+	/* Minimum functionality / Airplane mode
+	 * Sim still powered on
+	 * CFUN=4
+	 */
+	MODEM_HL78XX_STATE_AIRPLANE,
+	MODEM_HL78XX_STATE_INIT_POWER_OFF,
+	MODEM_HL78XX_STATE_POWER_OFF_PULSE,
+	MODEM_HL78XX_STATE_AWAIT_POWER_OFF,
+};
+
+enum hl78xx_event {
+	MODEM_HL78XX_EVENT_RESUME = 0,
+	MODEM_HL78XX_EVENT_SUSPEND,
+	MODEM_HL78XX_EVENT_SCRIPT_SUCCESS,
+	MODEM_HL78XX_EVENT_SCRIPT_FAILED,
+	MODEM_HL78XX_EVENT_SCRIPT_REQUIRE_RESTART,
+	MODEM_HL78XX_EVENT_TIMEOUT,
+	MODEM_HL78XX_EVENT_REGISTERED,
+	MODEM_HL78XX_EVENT_DEREGISTERED,
+	MODEM_HL78XX_EVENT_BUS_OPENED,
+	MODEM_HL78XX_EVENT_BUS_CLOSED,
+	MODEM_HL78XX_EVENT_SOCKET_READY,
+};
+
+enum hl78xx_tcp_notif {
+	TCP_NOTIF_NETWORK_ERROR = 0,
+	TCP_NOTIF_NO_MORE_SOCKETS = 1,
+	TCP_NOTIF_MEMORY_PROBLEM = 2,
+	TCP_NOTIF_DNS_ERROR = 3,
+	TCP_NOTIF_REMOTE_DISCONNECTION = 4,
+	TCP_NOTIF_CONNECTION_ERROR = 5,
+	TCP_NOTIF_GENERIC_ERROR = 6,
+	TCP_NOTIF_ACCEPT_FAILED = 7,
+	TCP_NOTIF_SEND_MISMATCH = 8,
+	TCP_NOTIF_BAD_SESSION_ID = 9,
+	TCP_NOTIF_SESSION_ALREADY_RUNNING = 10,
+	TCP_NOTIF_ALL_SESSIONS_USED = 11,
+	TCP_NOTIF_CONNECTION_TIMEOUT = 12,
+	TCP_NOTIF_SSL_CONNECTION_ERROR = 13,
+	TCP_NOTIF_SSL_INIT_ERROR = 14,
+	TCP_NOTIF_SSL_CERT_ERROR = 15
+};
+/** Enum representing information transfer capability events */
+enum hl78xx_info_transfer_event {
+	EVENT_START_SCAN = 0,
+	EVENT_FAIL_SCAN,
+	EVENT_ENTER_CAMPED,
+	EVENT_CONNECTION_ESTABLISHMENT,
+	EVENT_START_RESCAN,
+	EVENT_RRC_CONNECTED,
+	EVENT_NO_SUITABLE_CELLS,
+	EVENT_ALL_REGISTRATION_FAILED
+};
+
+struct kselacq_syntax {
+	bool mode;
+	enum hl78xx_cell_rat_mode rat1;
+	enum hl78xx_cell_rat_mode rat2;
+	enum hl78xx_cell_rat_mode rat3;
+};
+
+struct kband_syntax {
+	uint8_t rat;
+	/* Max 64 digits representation format is supported
+	 * i.e: LTE Band 256 (2000MHz) :
+	 * 80000000 00000000 00000000 00000000
+	 * 00000000 00000000 00000000 00000000
+	 * +
+	 * NULL terminate
+	 */
+	uint8_t bnd_bitmap[MDM_BAND_HEX_STR_LEN];
+};
+
+enum apn_state_enum_t {
+	APN_STATE_NOT_CONFIGURED = 0,
+	APN_STATE_CONFIGURED,
+	APN_STATE_REFRESH_REQUESTED,
+	APN_STATE_REFRESH_IN_PROGRESS,
+	APN_STATE_REFRESH_COMPLETED,
+};
+
+struct apn_state {
+	enum apn_state_enum_t state;
+};
+struct registration_status {
+	bool is_registered_currently;
+	bool is_registered_previously;
+	enum cellular_registration_status network_state_current;
+	enum cellular_registration_status network_state_previous;
+	enum hl78xx_cell_rat_mode rat_mode;
+};
+/* driver data */
+struct modem_buffers {
+	uint8_t uart_rx[CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES];
+	uint8_t uart_tx[CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES];
+	uint8_t chat_rx[CONFIG_MODEM_HL78XX_CHAT_BUFFER_SIZES];
+	uint8_t *delimiter;
+	uint8_t *filter;
+	uint8_t *argv[32];
+	uint8_t *eof_pattern;
+	uint8_t eof_pattern_size;
+	uint8_t *termination_pattern;
+	uint8_t termination_pattern_size;
+};
+
+struct modem_identity {
+	uint8_t imei[MDM_IMEI_LENGTH];
+	uint8_t model_id[MDM_MODEL_LENGTH];
+	uint8_t imsi[MDM_IMSI_LENGTH];
+	uint8_t iccid[MDM_ICCID_LENGTH];
+	uint8_t manufacturer[MDM_MANUFACTURER_LENGTH];
+	uint8_t fw_version[MDM_REVISION_LENGTH];
+	char apn[MDM_APN_MAX_LENGTH];
+};
+struct hl78xx_phone_functionality_work {
+	enum hl78xx_phone_functionality functionality;
+	bool in_progress;
+};
+
+struct hl78xx_network_operator {
+	char operator[MDM_MODEL_LENGTH];
+	uint8_t format;
+};
+
+struct modem_status {
+	struct registration_status registration;
+	int16_t rssi;
+	uint8_t ksrep;
+	int16_t rsrp;
+	int16_t rsrq;
+	uint16_t script_fail_counter;
+	int variant;
+	enum hl78xx_state state;
+	struct kband_syntax kbndcfg[HL78XX_RAT_COUNT];
+	struct hl78xx_phone_functionality_work phone_functionality;
+	struct apn_state apn;
+	struct hl78xx_network_operator network_operator;
+};
+
+struct modem_gpio_callbacks {
+	struct gpio_callback vgpio_cb;
+	struct gpio_callback uart_dsr_cb;
+	struct gpio_callback gpio6_cb;
+	struct gpio_callback uart_cts_cb;
+};
+
+struct modem_event_system {
+	struct k_work event_dispatch_work;
+	uint8_t event_buf[8];
+	struct ring_buf event_rb;
+	struct k_mutex event_rb_lock;
+};
+
+struct hl78xx_data {
+	struct modem_pipe *uart_pipe;
+	struct modem_backend_uart uart_backend;
+	struct modem_chat chat;
+
+	struct k_mutex tx_lock;
+	struct k_mutex api_lock;
+	struct k_sem script_stopped_sem_tx_int;
+	struct k_sem script_stopped_sem_rx_int;
+	struct k_sem suspended_sem;
+#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
+	struct k_sem stay_in_boot_mode_sem;
+#endif /* CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING */
+
+	struct modem_buffers buffers;
+	struct modem_identity identity;
+	struct modem_status status;
+	struct modem_gpio_callbacks gpio_cbs;
+	struct modem_event_system events;
+	struct k_work_delayable timeout_work;
+	/* Track leftover socket data state previously stored as a TU-global.
+	 * Moving this into the per-modem data reduces global BSS and keeps
+	 * state colocated with the modem instance.
+	 */
+	atomic_t state_leftover;
+#if defined(CONFIG_MODEM_HL78XX_RSSI_WORK)
+	struct k_work_delayable rssi_query_work;
+#endif
+
+	const struct device *dev;
+	/* GNSS device */
+	const struct device *gnss_dev;
+	/* Offload device */
+	const struct device *offload_dev;
+
+	struct kselacq_syntax kselacq_data;
+};
+
+struct hl78xx_config {
+	const struct device *uart;
+	struct gpio_dt_spec mdm_gpio_reset;
+	struct gpio_dt_spec mdm_gpio_wake;
+	struct gpio_dt_spec mdm_gpio_pwr_on;
+	struct gpio_dt_spec mdm_gpio_vgpio;
+	struct gpio_dt_spec mdm_gpio_uart_cts;
+	struct gpio_dt_spec mdm_gpio_gpio6;
+	struct gpio_dt_spec mdm_gpio_fast_shutdown;
+	struct gpio_dt_spec mdm_gpio_uart_dtr;
+	struct gpio_dt_spec mdm_gpio_uart_dsr;
+	struct gpio_dt_spec mdm_gpio_gpio8;
+	struct gpio_dt_spec mdm_gpio_sim_switch;
+	uint16_t power_pulse_duration_ms;
+	uint16_t reset_pulse_duration_ms;
+	uint16_t startup_time_ms;
+	uint16_t shutdown_time_ms;
+
+	bool autostarts;
+
+	const struct modem_chat_script *init_chat_script;
+	const struct modem_chat_script *periodic_chat_script;
+};
+/* socket read callback data */
+struct socket_read_data {
+	char *recv_buf;
+	size_t recv_buf_len;
+	struct sockaddr *recv_addr;
+	uint16_t recv_read_len;
+};
+
+/**
+ * @brief Check if the cellular modem is registered on the network.
+ *
+ * This function checks the modem's current registration status and
+ * returns true if the device is registered with a cellular network.
+ *
+ * @param data Pointer to the modem HL78xx driver data structure.
+ *
+ * @retval true if the modem is registered.
+ * @retval false otherwise.
+ */
+bool hl78xx_is_registered(struct hl78xx_data *data);
+
+/**
+ * @brief DNS resolution work callback.
+ *
+ * @param dev Pointer to the device structure.
+ * @param hard_reset Boolean indicating if a hard reset is required.
+ * Should be used internally to handle DNS resolution events.
+ */
+void dns_work_cb(const struct device *dev, bool hard_reset);
+
+/**
+ * @brief Callback to update and handle network interface status.
+ *
+ * This function is typically scheduled as work to check and respond to changes
+ * in the modem's network interface state, such as registration, link readiness,
+ * or disconnection events.
+ *
+ * @param data Pointer to the modem HL78xx driver data structure.
+ */
+void iface_status_work_cb(struct hl78xx_data *data,
+			  modem_chat_script_callback script_user_callback);
+
+/**
+ * @brief Send a command to the modem and wait for matching response(s).
+ *
+ * This function sends a raw command to the modem and processes its response using
+ * the provided match patterns. It supports asynchronous notification via callback.
+ *
+ * @param data Pointer to the modem HL78xx driver data structure.
+ * @param script_user_callback Callback function invoked on matched responses or errors.
+ * @param cmd Pointer to the command buffer to send.
+ * @param cmd_len Length of the command in bytes.
+ * @param response_matches Array of expected response match patterns.
+ * @param matches_size Number of elements in the response_matches array.
+ *
+ * @return 0 on success, negative errno code on failure.
+ */
+int modem_dynamic_cmd_send(struct hl78xx_data *data,
+			   modem_chat_script_callback script_user_callback, const uint8_t *cmd,
+			   uint16_t cmd_len, const struct modem_chat_match *response_matches,
+			   uint16_t matches_size, bool user_cmd);
+
+#define HASH_MULTIPLIER 37
+/**
+ * @brief Generate a 32-bit hash from a string.
+ *
+ * Useful for generating identifiers (e.g., MAC address suffix) from a given string.
+ *
+ * @param str Input string to hash.
+ * @param len Length of the input string.
+ *
+ * @return 32-bit hash value.
+ */
+static inline uint32_t hash32(const char *str, int len)
+{
+	uint32_t h = 0;
+
+	for (int i = 0; i < len; ++i) {
+		h = (h * HASH_MULTIPLIER) + str[i];
+	}
+	return h;
+}
+
+/**
+ * @brief Generate a pseudo-random MAC address based on the modem's IMEI.
+ *
+ * This function creates a MAC address using a fixed prefix and a hash of the IMEI.
+ * The resulting address is consistent for the same IMEI and suitable for use
+ * in virtual or emulated network interfaces.
+ *
+ * @param mac_addr Pointer to a 6-byte buffer where the generated MAC address will be stored.
+ * @param imei Null-terminated string containing the modem's IMEI.
+ *
+ * @return Pointer to the MAC address buffer.
+ */
+static inline uint8_t *modem_get_mac(uint8_t *mac_addr, char *imei)
+{
+	uint32_t hash_value;
+	/* Define MAC address prefix */
+	mac_addr[0] = 0x00;
+	mac_addr[1] = 0x10;
+
+	/* Generate MAC address based on IMEI */
+	hash_value = hash32(imei, strlen(imei));
+	UNALIGNED_PUT(hash_value, (uint32_t *)(mac_addr + 2));
+
+	return mac_addr;
+}
+
+/**
+ * @brief  Convert string to long integer, but handle errors
+ *
+ * @param  s: string with representation of integer number
+ * @param  err_value: on error return this value instead
+ * @param  desc: name the string being converted
+ * @param  func: function where this is called (typically __func__)
+ *
+ * @retval return integer conversion on success, or err_value on error
+ */
+static inline int modem_atoi(const char *s, const int err_value, const char *desc, const char *func)
+{
+	int ret;
+	char *endptr;
+
+	ret = (int)strtol(s, &endptr, 10);
+	if (!endptr || *endptr != '\0') {
+		return err_value;
+	}
+	return ret;
+}
+
+/**
+ * @brief Convert a string to an double with error handling.
+ *
+ * Similar to atoi, but allows specifying an error fallback and logs errors.
+ *
+ * @param s Input string to convert.
+ * @param err_value Value to return on failure.
+ * @param desc Description of the value for logging purposes.
+ * @param func Function name for logging purposes.
+ *
+ * @return Converted double on success, or err_value on failure.
+ */
+static inline double modem_atod(const char *s, const double err_value, const char *desc,
+				const char *func)
+{
+	double ret;
+	char *endptr;
+
+	ret = strtod(s, &endptr);
+	if (!endptr || *endptr != '\0') {
+		return err_value;
+	}
+	return ret;
+}
+/**
+ * @brief Small utility: safe strncpy that always NUL-terminates the destination.
+ * This function copies a string from src to dst, ensuring that the destination
+ * buffer is always NUL-terminated, even if the source string is longer than
+ * the destination buffer.
+ * @param dst Destination buffer.
+ * @param src Source string.
+ * @param dst_size Size of the destination buffer.
+ */
+static inline void safe_strncpy(char *dst, const char *src, size_t dst_size)
+{
+	size_t len = 0;
+
+	if (dst == NULL || dst_size == 0) {
+		return;
+	}
+	if (src == NULL) {
+		dst[0] = '\0';
+		return;
+	}
+	len = strlen(src);
+	if (len >= dst_size) {
+		len = dst_size - 1;
+	}
+	memcpy(dst, src, len);
+	dst[len] = '\0';
+}
+
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+/**
+ * @brief Handle modem state update from +KSTATE URC (unsolicited result code).
+ *
+ * This function is called when a +KSTATE URC is received, indicating a change
+ * in the modem's internal state. It updates the modem driver's state machine
+ * accordingly.
+ *
+ * @param data Pointer to the HL78xx modem driver data structure.
+ * @param state Integer value representing the new modem state as reported by the URC.
+ */
+void hl78xx_on_kstatev_parser(struct hl78xx_data *data, int state, int rat_mode);
+#endif
+
+#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) || defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI)
+/**
+ * @brief Automatically detect and configure the modem's APN setting.
+ *
+ * Uses internal logic to determine the correct APN based on the modem's context
+ * and network registration information.
+ *
+ * @param data Pointer to the modem HL78xx driver data structure.
+ * @param associated_number Identifier (e.g., MCCMNC or IMSI) used for APN detection.
+ *
+ * @return 0 on success, negative errno code on failure.
+ */
+int modem_detect_apn(struct hl78xx_data *data, const char *associated_number);
+#endif
+/**
+ * @brief Get the default band configuration in hexadecimal string format for each band.
+ *
+ * Retrieves the modem's default band configuration as a hex string,
+ * used for configuring or restoring band settings.
+ *
+ * @param rat The radio access technology mode for which to get the band configuration.
+ * @param hex_bndcfg Buffer to store the resulting hex band configuration string.
+ * @param size_in_bytes Size of the buffer in bytes.
+ *
+ * @retval 0 on success.
+ * @retval Negative errno code on failure.
+ */
+int hl78xx_get_band_default_config_for_rat(enum hl78xx_cell_rat_mode rat, char *hex_bndcfg,
+					   size_t size_in_bytes);
+
+/**
+ * @brief Convert a hexadecimal string to a binary bitmap.
+ *
+ * Parses a hexadecimal string and converts it into a binary bitmap array.
+ *
+ * @param hex_str Null-terminated string containing hexadecimal data.
+ * @param bitmap_out Output buffer to hold the resulting binary bitmap.
+ *
+ * @retval 0 on success.
+ * @retval Negative errno code on failure (e.g., invalid characters, overflow).
+ */
+int hl78xx_hex_string_to_bitmap(const char *hex_str, uint8_t *bitmap_out);
+
+/**
+ * @brief hl78xx_api_func_get_registration_status - Brief description of the function.
+ * @param dev Description of dev.
+ * @param tech Description of tech.
+ * @param status Description of status.
+ * @return int Description of return value.
+ */
+int hl78xx_api_func_get_registration_status(const struct device *dev,
+					    enum cellular_access_technology tech,
+					    enum cellular_registration_status *status);
+
+/**
+ * @brief hl78xx_api_func_set_apn - Brief description of the function.
+ * @param dev Description of dev.
+ * @param apn Description of apn.
+ * @return int Description of return value.
+ */
+int hl78xx_api_func_set_apn(const struct device *dev, const char *apn);
+
+/**
+ * @brief hl78xx_api_func_get_modem_info_standard - Brief description of the function.
+ * @param dev Description of dev.
+ * @param type Description of type.
+ * @param info Description of info.
+ * @param size Description of size.
+ * @return int Description of return value.
+ */
+int hl78xx_api_func_get_modem_info_standard(const struct device *dev,
+					    enum cellular_modem_info_type type, char *info,
+					    size_t size);
+
+/**
+ * @brief hl78xx_enter_state - Brief description of the function.
+ * @param data Description of data.
+ * @param state Description of state.
+ */
+void hl78xx_enter_state(struct hl78xx_data *data, enum hl78xx_state state);
+
+/**
+ * @brief hl78xx_delegate_event - Brief description of the function.
+ * @param data Description of data.
+ * @param evt Description of evt.
+ */
+void hl78xx_delegate_event(struct hl78xx_data *data, enum hl78xx_event evt);
+
+/**
+ * @brief notif_carrier_off - Brief description of the function.
+ * @param dev Description of dev.
+ */
+void notif_carrier_off(const struct device *dev);
+
+/**
+ * @brief notif_carrier_on - Brief description of the function.
+ * @param dev Description of dev.
+ */
+void notif_carrier_on(const struct device *dev);
+
+/**
+ * @brief check_if_any_socket_connected - Brief description of the function.
+ * @param dev Description of dev.
+ * @return int Description of return value.
+ */
+int check_if_any_socket_connected(const struct device *dev);
+
+#endif /* HL78XX_H */
diff --git a/drivers/modem/hl78xx/hl78xx_apis.c b/drivers/modem/hl78xx/hl78xx_apis.c
new file mode 100644
index 0000000..e1af412
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_apis.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <zephyr/kernel.h>
+#include <zephyr/logging/log.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "hl78xx.h"
+#include "hl78xx_chat.h"
+
+LOG_MODULE_REGISTER(hl78xx_apis, CONFIG_MODEM_LOG_LEVEL);
+
+/* Wrapper to centralize modem_dynamic_cmd_send calls and reduce repetition.
+ * returns negative errno on failure or the value returned by modem_dynamic_cmd_send.
+ */
+static int hl78xx_send_cmd(struct hl78xx_data *data, const char *cmd,
+			   void (*chat_cb)(struct modem_chat *, enum modem_chat_script_result,
+					   void *),
+			   const struct modem_chat_match *matches, uint16_t match_count)
+{
+	if (data == NULL || cmd == NULL) {
+		return -EINVAL;
+	}
+	return modem_dynamic_cmd_send(data, chat_cb, cmd, (uint16_t)strlen(cmd), matches,
+				      match_count, true);
+}
+
+int hl78xx_api_func_get_signal(const struct device *dev, const enum cellular_signal_type type,
+			       int16_t *value)
+{
+	int ret = -ENOTSUP;
+	struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+	const char *signal_cmd_csq = "AT+CSQ";
+	const char *signal_cmd_cesq = "AT+CESQ";
+
+	/* quick check of state under api_lock */
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	if (data->status.state != MODEM_HL78XX_STATE_CARRIER_ON) {
+		k_mutex_unlock(&data->api_lock);
+		return -ENODATA;
+	}
+	k_mutex_unlock(&data->api_lock);
+
+	/* Run chat script */
+	switch (type) {
+	case CELLULAR_SIGNAL_RSSI:
+		ret = hl78xx_send_cmd(data, signal_cmd_csq, NULL, hl78xx_get_allow_match(),
+				      hl78xx_get_allow_match_size());
+		break;
+
+	case CELLULAR_SIGNAL_RSRP:
+	case CELLULAR_SIGNAL_RSRQ:
+		ret = hl78xx_send_cmd(data, signal_cmd_cesq, NULL, hl78xx_get_allow_match(),
+				      hl78xx_get_allow_match_size());
+		break;
+
+	default:
+		ret = -ENOTSUP;
+		break;
+	}
+	/* Verify chat script ran successfully */
+	if (ret < 0) {
+		return ret;
+	}
+	/* Parse received value */
+	switch (type) {
+	case CELLULAR_SIGNAL_RSSI:
+		ret = hl78xx_parse_rssi(data->status.rssi, value);
+		break;
+
+	case CELLULAR_SIGNAL_RSRP:
+		ret = hl78xx_parse_rsrp(data->status.rsrp, value);
+		break;
+
+	case CELLULAR_SIGNAL_RSRQ:
+		ret = hl78xx_parse_rsrq(data->status.rsrq, value);
+		break;
+
+	default:
+		ret = -ENOTSUP;
+		break;
+	}
+	return ret;
+}
+
+/** Convert hl78xx RAT mode to cellular access technology */
+enum cellular_access_technology hl78xx_rat_to_access_tech(enum hl78xx_cell_rat_mode rat_mode)
+{
+	switch (rat_mode) {
+	case HL78XX_RAT_CAT_M1:
+		return CELLULAR_ACCESS_TECHNOLOGY_E_UTRAN;
+	case HL78XX_RAT_NB1:
+		return CELLULAR_ACCESS_TECHNOLOGY_E_UTRAN_NB_S1;
+#ifdef CONFIG_MODEM_HL78XX_12
+	case HL78XX_RAT_GSM:
+		return CELLULAR_ACCESS_TECHNOLOGY_GSM;
+#ifdef CONFIG_MODEM_HL78XX_12_FW_R6
+	case HL78XX_RAT_NBNTN:
+		/** NBNTN might not have a direct mapping; choose closest or define new */
+		return CELLULAR_ACCESS_TECHNOLOGY_NG_RAN_SAT;
+#endif
+#endif
+#ifdef CONFIG_MODEM_HL78XX_AUTORAT
+	case HL78XX_RAT_MODE_AUTO:
+		/** AUTO mode doesn't map directly; return LTE as default or NONE */
+		return CELLULAR_ACCESS_TECHNOLOGY_E_UTRAN;
+#endif
+	case HL78XX_RAT_MODE_NONE:
+	default:
+		return -ENODATA;
+	}
+}
+
+int hl78xx_api_func_get_registration_status(const struct device *dev,
+					    enum cellular_access_technology tech,
+					    enum cellular_registration_status *status)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+
+	if (status == NULL) {
+		return -EINVAL;
+	}
+	LOG_DBG("Requested tech: %d, current rat mode: %d REG: %d %d", tech,
+		data->status.registration.rat_mode, data->status.registration.network_state_current,
+		hl78xx_rat_to_access_tech(data->status.registration.rat_mode));
+	if (tech != hl78xx_rat_to_access_tech(data->status.registration.rat_mode)) {
+		return -ENODATA;
+	}
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	*status = data->status.registration.network_state_current;
+	k_mutex_unlock(&data->api_lock);
+	return 0;
+}
+
+int hl78xx_api_func_get_modem_info_vendor(const struct device *dev,
+					  enum hl78xx_modem_info_type type, void *info, size_t size)
+{
+	int ret = 0;
+	struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+	const char *network_operator = "AT+COPS?";
+
+	if (info == NULL || size == 0) {
+		return -EINVAL;
+	}
+	/* copy identity under api lock to a local buffer then write to caller
+	 * prevents holding lock during the return/caller access
+	 */
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	switch (type) {
+	case HL78XX_MODEM_INFO_APN:
+		if (data->status.apn.state != APN_STATE_CONFIGURED) {
+			ret = -ENODATA;
+			break;
+		}
+		safe_strncpy(info, (const char *)data->identity.apn, size);
+		break;
+
+	case HL78XX_MODEM_INFO_CURRENT_RAT:
+		*(enum hl78xx_cell_rat_mode *)info = data->status.registration.rat_mode;
+		break;
+
+	case HL78XX_MODEM_INFO_NETWORK_OPERATOR:
+		/* Network operator not currently tracked; return empty or implement tracking */
+		ret = hl78xx_send_cmd(data, network_operator, NULL, hl78xx_get_allow_match(),
+				      hl78xx_get_allow_match_size());
+		if (ret < 0) {
+			LOG_ERR("Failed to get network operator");
+		}
+		safe_strncpy(info, (const char *)data->status.network_operator.operator,
+			     MIN(size, sizeof(data->status.network_operator.operator)));
+		break;
+
+	default:
+		break;
+	}
+	k_mutex_unlock(&data->api_lock);
+	return ret;
+}
+
+int hl78xx_api_func_get_modem_info_standard(const struct device *dev,
+					    enum cellular_modem_info_type type, char *info,
+					    size_t size)
+{
+	int ret = 0;
+	struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+
+	if (info == NULL || size == 0) {
+		return -EINVAL;
+	}
+	/* copy identity under api lock to a local buffer then write to caller
+	 * prevents holding lock during the return/caller access
+	 */
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	switch (type) {
+	case CELLULAR_MODEM_INFO_IMEI:
+		safe_strncpy(info, (const char *)data->identity.imei,
+			     MIN(size, sizeof(data->identity.imei)));
+		break;
+	case CELLULAR_MODEM_INFO_SIM_IMSI:
+		safe_strncpy(info, (const char *)data->identity.imsi,
+			     MIN(size, sizeof(data->identity.imsi)));
+		break;
+	case CELLULAR_MODEM_INFO_MANUFACTURER:
+		safe_strncpy(info, (const char *)data->identity.manufacturer,
+			     MIN(size, sizeof(data->identity.manufacturer)));
+		break;
+	case CELLULAR_MODEM_INFO_FW_VERSION:
+		safe_strncpy(info, (const char *)data->identity.fw_version,
+			     MIN(size, sizeof(data->identity.fw_version)));
+		break;
+	case CELLULAR_MODEM_INFO_MODEL_ID:
+		safe_strncpy(info, (const char *)data->identity.model_id,
+			     MIN(size, sizeof(data->identity.model_id)));
+		break;
+	case CELLULAR_MODEM_INFO_SIM_ICCID:
+		safe_strncpy(info, (const char *)data->identity.iccid,
+			     MIN(size, sizeof(data->identity.iccid)));
+		break;
+	default:
+		ret = -ENOTSUP;
+		break;
+	}
+	k_mutex_unlock(&data->api_lock);
+	return ret;
+}
+
+int hl78xx_api_func_set_apn(const struct device *dev, const char *apn)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+	/**
+	 * Validate APN
+	 * APN can be empty string to clear it
+	 * to request it from network
+	 * If the value is null or omitted, then the subscription
+	 * value will be requested
+	 */
+	if (apn == NULL) {
+		return -EINVAL;
+	}
+	if (strlen(apn) >= MDM_APN_MAX_LENGTH) {
+		return -EINVAL;
+	}
+	/* Update in-memory APN under api lock */
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	safe_strncpy(data->identity.apn, apn, sizeof(data->identity.apn));
+	data->status.apn.state = APN_STATE_REFRESH_REQUESTED;
+	k_mutex_unlock(&data->api_lock);
+	hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_OFF);
+	return 0;
+}
+
+int hl78xx_api_func_set_phone_functionality(const struct device *dev,
+					    enum hl78xx_phone_functionality functionality,
+					    bool reset)
+{
+	char cmd_string[sizeof(SET_FULLFUNCTIONAL_MODE_CMD) + sizeof(int)] = {0};
+	struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+	/* configure modem functionality with/without restart  */
+	snprintf(cmd_string, sizeof(cmd_string), "AT+CFUN=%d,%d", functionality, reset);
+	return hl78xx_send_cmd(data, cmd_string, NULL, hl78xx_get_ok_match(), 1);
+}
+
+int hl78xx_api_func_get_phone_functionality(const struct device *dev,
+					    enum hl78xx_phone_functionality *functionality)
+{
+	const char *cmd_string = GET_FULLFUNCTIONAL_MODE_CMD;
+	struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+	/* get modem phone functionality */
+	return hl78xx_send_cmd(data, cmd_string, NULL, hl78xx_get_ok_match(), 1);
+}
+
+int hl78xx_api_func_modem_dynamic_cmd_send(const struct device *dev, const char *cmd,
+					   uint16_t cmd_size,
+					   const struct modem_chat_match *response_matches,
+					   uint16_t matches_size)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
+
+	if (cmd == NULL) {
+		return -EINVAL;
+	}
+	/* respect provided matches_size and serialize modem access */
+	return modem_dynamic_cmd_send(data, NULL, cmd, cmd_size, response_matches, matches_size,
+				      true);
+}
diff --git a/drivers/modem/hl78xx/hl78xx_cfg.c b/drivers/modem/hl78xx/hl78xx_cfg.c
new file mode 100644
index 0000000..461d066
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_cfg.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/*
+ * hl78xx_cfg.c
+ *
+ * Extracted helper implementations for RAT, band and APN configuration to
+ * keep the main state-machine TU small and maintainable.
+ */
+#include "hl78xx.h"
+#include "hl78xx_cfg.h"
+#include "hl78xx_chat.h"
+#include <zephyr/logging/log.h>
+
+LOG_MODULE_DECLARE(hl78xx_dev);
+
+#define ICCID_PREFIX_LEN            7
+#define IMSI_PREFIX_LEN             6
+#define MAX_BANDS                   32
+#define MDM_APN_FULL_STRING_MAX_LEN 256
+
+int hl78xx_rat_cfg(struct hl78xx_data *data, bool *modem_require_restart,
+		   enum hl78xx_cell_rat_mode *rat_request)
+{
+	int ret = 0;
+
+#if defined(CONFIG_MODEM_HL78XX_AUTORAT)
+	/* Check autorat status/configs */
+	if (IS_ENABLED(CONFIG_MODEM_HL78XX_AUTORAT_OVER_WRITE_PRL) ||
+	    (data->kselacq_data.rat1 == 0 && data->kselacq_data.rat2 == 0 &&
+	     data->kselacq_data.rat3 == 0)) {
+		char cmd_kselq[] = "AT+KSELACQ=0," CONFIG_MODEM_HL78XX_AUTORAT_PRL_PROFILES;
+
+		ret = modem_dynamic_cmd_send(data, NULL, cmd_kselq, strlen(cmd_kselq),
+					     hl78xx_get_ok_match(), 1, false);
+		if (ret < 0) {
+			goto error;
+		} else {
+			*modem_require_restart = true;
+		}
+	}
+
+	*rat_request = HL78XX_RAT_MODE_AUTO;
+#else
+	char const *cmd_ksrat_query = (const char *)KSRAT_QUERY;
+	char const *cmd_kselq_disable = (const char *)DISABLE_RAT_AUTO;
+	const char *cmd_set_rat = NULL;
+	/* Check if auto rat are disabled */
+	if (data->kselacq_data.rat1 != 0 && data->kselacq_data.rat2 != 0 &&
+	    data->kselacq_data.rat3 != 0) {
+		ret = modem_dynamic_cmd_send(data, NULL, cmd_kselq_disable,
+					     strlen(cmd_kselq_disable), hl78xx_get_ok_match(), 1,
+					     false);
+		if (ret < 0) {
+			goto error;
+		}
+	}
+	/* Query current rat */
+	ret = modem_dynamic_cmd_send(data, NULL, cmd_ksrat_query, strlen(cmd_ksrat_query),
+				     hl78xx_get_ksrat_match(), 1, false);
+	if (ret < 0) {
+		goto error;
+	}
+
+#if !defined(CONFIG_MODEM_HL78XX_RAT_M1) && !defined(CONFIG_MODEM_HL78XX_RAT_NB1) &&               \
+	!defined(CONFIG_MODEM_HL78XX_RAT_GSM) && !defined(CONFIG_MODEM_HL78XX_RAT_NBNTN)
+#error "No rat has been selected."
+#endif
+
+	if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_M1)) {
+		cmd_set_rat = (const char *)SET_RAT_M1_CMD_LEGACY;
+		*rat_request = HL78XX_RAT_CAT_M1;
+	} else if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_NB1)) {
+		cmd_set_rat = (const char *)SET_RAT_NB1_CMD_LEGACY;
+		*rat_request = HL78XX_RAT_NB1;
+	}
+#ifdef CONFIG_MODEM_HL78XX_12
+	else if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_GSM)) {
+		cmd_set_rat = (const char *)SET_RAT_GSM_CMD_LEGACY;
+		*rat_request = HL78XX_RAT_GSM;
+	}
+#ifdef CONFIG_MODEM_HL78XX_12_FW_R6
+	else if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_NBNTN)) {
+		cmd_set_rat = (const char *)SET_RAT_NBNTN_CMD_LEGACY;
+		*rat_request = HL78XX_RAT_NBNTN;
+	}
+#endif
+#endif
+
+	if (cmd_set_rat == NULL || *rat_request == HL78XX_RAT_MODE_NONE) {
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (*rat_request != data->status.registration.rat_mode) {
+		ret = modem_dynamic_cmd_send(data, NULL, cmd_set_rat, strlen(cmd_set_rat),
+					     hl78xx_get_ok_match(), 1, false);
+		if (ret < 0) {
+			goto error;
+		} else {
+			*modem_require_restart = true;
+		}
+	}
+#endif
+
+error:
+	return ret;
+}
+
+int hl78xx_band_cfg(struct hl78xx_data *data, bool *modem_require_restart,
+		    enum hl78xx_cell_rat_mode rat_config_request)
+{
+	int ret = 0;
+	char bnd_bitmap[MDM_BAND_HEX_STR_LEN] = {0};
+	const char *modem_trimmed;
+	const char *expected_trimmed;
+
+	if (rat_config_request == HL78XX_RAT_MODE_NONE) {
+		return -EINVAL;
+	}
+#ifdef CONFIG_MODEM_HL78XX_AUTORAT
+	for (int rat = HL78XX_RAT_CAT_M1; rat <= HL78XX_RAT_NB1; rat++) {
+#else
+	int rat = rat_config_request;
+
+#endif
+		ret = hl78xx_get_band_default_config_for_rat(rat, bnd_bitmap,
+							     ARRAY_SIZE(bnd_bitmap));
+		if (ret) {
+			LOG_ERR("%d %s error get band default config %d", __LINE__, __func__, ret);
+			goto error;
+		}
+		modem_trimmed = hl78xx_trim_leading_zeros(data->status.kbndcfg[rat].bnd_bitmap);
+		expected_trimmed = hl78xx_trim_leading_zeros(bnd_bitmap);
+
+		if (strcmp(modem_trimmed, expected_trimmed) != 0) {
+			char cmd_bnd[80] = {0};
+
+			snprintf(cmd_bnd, sizeof(cmd_bnd), "AT+KBNDCFG=%d,%s", rat, bnd_bitmap);
+			ret = modem_dynamic_cmd_send(data, NULL, cmd_bnd, strlen(cmd_bnd),
+						     hl78xx_get_ok_match(), 1, false);
+			if (ret < 0) {
+				goto error;
+			} else {
+				*modem_require_restart |= true;
+			}
+		} else {
+			LOG_DBG("The band configs (%s) matched with exist configs (%s) for rat: "
+				"[%d]",
+				modem_trimmed, expected_trimmed, rat);
+		}
+#ifdef CONFIG_MODEM_HL78XX_AUTORAT
+	}
+#endif
+error:
+	return ret;
+}
+
+int hl78xx_set_apn_internal(struct hl78xx_data *data, const char *apn, uint16_t size)
+{
+	int ret = 0;
+	char cmd_string[sizeof("AT+KCNXCFG=,\"\",\"\"") + sizeof(uint8_t) +
+			MODEM_HL78XX_ADDRESS_FAMILY_FORMAT_LEN + MDM_APN_MAX_LENGTH] = {0};
+	int cmd_max_len = sizeof(cmd_string) - 1;
+	int apn_size = strlen(apn);
+
+	if (apn == NULL || size >= MDM_APN_MAX_LENGTH) {
+		return -EINVAL;
+	}
+
+	k_mutex_lock(&data->api_lock, K_FOREVER);
+	if (strncmp(data->identity.apn, apn, apn_size) != 0) {
+		safe_strncpy(data->identity.apn, apn, sizeof(data->identity.apn));
+	}
+	k_mutex_unlock(&data->api_lock);
+
+	snprintk(cmd_string, cmd_max_len, "AT+CGDCONT=1,\"%s\",\"%s\"", MODEM_HL78XX_ADDRESS_FAMILY,
+		 apn);
+
+	ret = modem_dynamic_cmd_send(data, NULL, cmd_string, strlen(cmd_string),
+				     hl78xx_get_ok_match(), 1, false);
+	if (ret < 0) {
+		goto error;
+	}
+	snprintk(cmd_string, cmd_max_len,
+		 "AT+KCNXCFG=1,\"GPRS\",\"%s\",,,\"" MODEM_HL78XX_ADDRESS_FAMILY "\"", apn);
+	ret = modem_dynamic_cmd_send(data, NULL, cmd_string, strlen(cmd_string),
+				     hl78xx_get_ok_match(), 1, false);
+	if (ret < 0) {
+		goto error;
+	}
+	data->status.apn.state = APN_STATE_CONFIGURED;
+	return 0;
+error:
+	LOG_ERR("Set APN to %s, result: %d", apn, ret);
+	return ret;
+}
+
+#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) || defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI)
+int find_apn(const char *profile, const char *associated_number, char *apn_buff, uint8_t prefix_len)
+{
+	char buffer[512];
+	char *saveptr;
+
+	if (prefix_len > strlen(associated_number)) {
+		return -1;
+	}
+
+	strncpy(buffer, profile, sizeof(buffer) - 1);
+	buffer[sizeof(buffer) - 1] = '\0';
+
+	char *token = strtok_r(buffer, ",", &saveptr);
+
+	while (token != NULL) {
+		char *equal_sign = strchr(token, '=');
+
+		if (equal_sign != NULL) {
+			*equal_sign = '\0';
+			char *p_apn = token;
+			char *associated_number_prefix = equal_sign + 1;
+
+			/*  Trim leading whitespace */
+			while (*p_apn == ' ') {
+				p_apn++;
+			}
+			while (*associated_number_prefix == ' ') {
+				associated_number_prefix++;
+			}
+			if (strncmp(associated_number, associated_number_prefix, prefix_len) == 0) {
+				strncpy(apn_buff, p_apn, MDM_APN_MAX_LENGTH - 1);
+				apn_buff[MDM_APN_MAX_LENGTH - 1] = '\0';
+				return 0;
+			}
+		}
+		token = strtok_r(NULL, ",", &saveptr);
+	}
+	/* No match found, clear apn_buff */
+	apn_buff[0] = '\0';
+	return -1; /* not found */
+}
+
+/* try to detect APN automatically, based on IMSI / ICCID */
+int modem_detect_apn(struct hl78xx_data *data, const char *associated_number)
+{
+	int rc = -1;
+
+	if (associated_number != NULL && strlen(associated_number) >= 5) {
+/* extract MMC and MNC from IMSI */
+#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI)
+		/*
+		 * First 5 digits (e.g. 31026) → often sufficient to identify carrier.
+		 * However, in some regions (like the US), MNCs can be 3 digits (e.g. 310260).
+		 */
+		char mmcmnc[7] = {0}; /* IMSI */
+#define APN_PREFIX_LEN IMSI_PREFIX_LEN
+#else
+		/* These 7 digits are generally sufficient to identify the SIM provider.
+		 */
+		char mmcmnc[8] = {0}; /* ICCID */
+#define APN_PREFIX_LEN ICCID_PREFIX_LEN
+#endif
+		strncpy(mmcmnc, associated_number, sizeof(mmcmnc) - 1);
+		mmcmnc[sizeof(mmcmnc) - 1] = '\0';
+		/* try to find a matching IMSI/ICCID, and assign the APN */
+		rc = find_apn(CONFIG_MODEM_HL78XX_APN_PROFILES, mmcmnc, data->identity.apn,
+			      APN_PREFIX_LEN);
+		if (rc < 0) {
+			LOG_ERR("%d %s APN Parser error %d", __LINE__, __func__, rc);
+		}
+	}
+	if (rc == 0) {
+		LOG_INF("Assign APN: \"%s\"", data->identity.apn);
+	} else {
+		LOG_INF("No assigned APN: \"%d\"", rc);
+	}
+	return rc;
+}
+#endif
+
+void set_band_bit(uint8_t *bitmap, uint16_t band_num)
+{
+	uint16_t bit_pos;
+	uint16_t byte_index;
+	uint8_t bit_index;
+
+	if (band_num < 1 || band_num > 256) {
+		return; /* Out of range */
+	}
+	/* Calculate byte and bit positions */
+	bit_pos = band_num - 1;
+	byte_index = bit_pos / 8;
+	bit_index = bit_pos % 8;
+	/* Big-endian format: band 1 in byte 31, band 256 in byte 0 */
+	bitmap[byte_index] |= (1 << bit_index);
+}
+
+#ifdef CONFIG_MODEM_HL78XX_CONFIGURE_BANDS
+static uint8_t hl78xx_generate_band_bitmap(uint8_t *bitmap)
+{
+	memset(bitmap, 0, MDM_BAND_BITMAP_LEN_BYTES);
+	/* Index is reversed: Band 1 is LSB of byte 31, Band 256 is MSB of byte 0 */
+#if CONFIG_MODEM_HL78XX_BAND_1
+	set_band_bit(bitmap, 1);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_2
+	set_band_bit(bitmap, 2);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_3
+	set_band_bit(bitmap, 3);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_4
+	set_band_bit(bitmap, 4);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_5
+	set_band_bit(bitmap, 5);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_8
+	set_band_bit(bitmap, 8);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_9
+	set_band_bit(bitmap, 9);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_10
+	set_band_bit(bitmap, 10);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_12
+	set_band_bit(bitmap, 12);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_13
+	set_band_bit(bitmap, 13);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_17
+	set_band_bit(bitmap, 17);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_18
+	set_band_bit(bitmap, 18);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_19
+	set_band_bit(bitmap, 19);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_20
+	set_band_bit(bitmap, 20);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_23
+	set_band_bit(bitmap, 23);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_25
+	set_band_bit(bitmap, 25);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_26
+	set_band_bit(bitmap, 26);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_27
+	set_band_bit(bitmap, 27);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_28
+	set_band_bit(bitmap, 28);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_31
+	set_band_bit(bitmap, 31);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_66
+	set_band_bit(bitmap, 66);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_72
+	set_band_bit(bitmap, 72);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_73
+	set_band_bit(bitmap, 73);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_85
+	set_band_bit(bitmap, 85);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_87
+	set_band_bit(bitmap, 87);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_88
+	set_band_bit(bitmap, 88);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_106
+	set_band_bit(bitmap, 106);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_107
+	set_band_bit(bitmap, 107);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_255
+	set_band_bit(bitmap, 255);
+#endif
+#if CONFIG_MODEM_HL78XX_BAND_256
+	set_band_bit(bitmap, 256);
+#endif
+	/* Add additional bands similarly... */
+	return 0;
+}
+#endif /* CONFIG_MODEM_HL78XX_CONFIGURE_BANDS */
+
+#if defined(CONFIG_MODEM_HL78XX_AUTORAT)
+/**
+ * @brief Parse a comma-separated list of bands from a string.
+ *
+ * @param band_str The input string containing band numbers.
+ * @param bands Output array to store parsed band numbers.
+ * @param max_bands Maximum number of bands that can be stored in the output array.
+ *
+ * @return Number of bands parsed, or negative error code on failure.
+ */
+static int parse_band_list(const char *band_str, int *bands, size_t max_bands)
+{
+	char buf[128] = {0};
+	char *token;
+	char *rest;
+	int count = 0;
+	int band = 0;
+
+	if (!band_str || !bands || max_bands == 0) {
+		return -EINVAL;
+	}
+	strncpy(buf, band_str, sizeof(buf) - 1);
+	buf[sizeof(buf) - 1] = '\0';
+	rest = buf;
+	while ((token = strtok_r(rest, ",", &rest))) {
+		band = ATOI(token, -1, "band");
+		if (band <= 0) {
+			printk("Invalid band number: %s\n", token);
+			continue;
+		}
+		if (count >= max_bands) {
+			printk("Too many bands, max is %d\n", (int)max_bands);
+			break;
+		}
+		bands[count++] = band;
+	}
+	return count;
+}
+#endif /* CONFIG_MODEM_HL78XX_AUTORAT */
+
+int hl78xx_generate_bitmap_from_config(enum hl78xx_cell_rat_mode rat, uint8_t *bitmap_out)
+{
+	if (!bitmap_out) {
+		return -EINVAL;
+	}
+	memset(bitmap_out, 0, MDM_BAND_BITMAP_LEN_BYTES);
+#if defined(CONFIG_MODEM_HL78XX_AUTORAT)
+	/* Auto-RAT: read bands from string configs */
+	const char *band_str = NULL;
+
+	switch (rat) {
+	case HL78XX_RAT_CAT_M1:
+#ifdef CONFIG_MODEM_HL78XX_AUTORAT_M1_BAND_CFG
+		band_str = CONFIG_MODEM_HL78XX_AUTORAT_M1_BAND_CFG;
+#endif
+		break;
+
+	case HL78XX_RAT_NB1:
+#ifdef CONFIG_MODEM_HL78XX_AUTORAT_NB_BAND_CFG
+		band_str = CONFIG_MODEM_HL78XX_AUTORAT_NB_BAND_CFG;
+#endif
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	if (band_str) {
+		int bands[MAX_BANDS];
+		int count = parse_band_list(band_str, bands, MAX_BANDS);
+
+		if (count < 0) {
+			return -EINVAL;
+		}
+		for (int i = 0; i < count; i++) {
+			set_band_bit(bitmap_out, bands[i]);
+		}
+		return 0;
+	}
+#else
+	/* Else: use standalone config */
+	return hl78xx_generate_band_bitmap(bitmap_out);
+#endif /* CONFIG_MODEM_HL78XX_AUTORAT */
+	return -EINVAL;
+}
+
+void hl78xx_bitmap_to_hex_string_trimmed(const uint8_t *bitmap, char *hex_str, size_t hex_str_len)
+{
+	int started = 0;
+	size_t offset = 0;
+
+	for (int i = MDM_BAND_BITMAP_LEN_BYTES - 1; i >= 0; i--) {
+		if (!started && bitmap[i] == 0) {
+			continue; /*  Skip leading zero bytes */
+		}
+		started = 1;
+		if (offset + 2 >= hex_str_len) {
+			break;
+		}
+		offset += snprintk(&hex_str[offset], hex_str_len - offset, "%02X", bitmap[i]);
+	}
+	if (!started) {
+		strcpy(hex_str, "0");
+	}
+}
+
+int hl78xx_hex_string_to_bitmap(const char *hex_str, uint8_t *bitmap_out)
+{
+	if (strlen(hex_str) >= MDM_BAND_HEX_STR_LEN) {
+		LOG_ERR("Invalid hex string length: %zu", strlen(hex_str));
+		return -EINVAL;
+	}
+
+	for (int i = 0; i < MDM_BAND_BITMAP_LEN_BYTES; i++) {
+		unsigned int byte_val;
+
+		if (sscanf(&hex_str[i * 2], "%2x", &byte_val) != 1) {
+			LOG_ERR("Failed to parse byte at position %d", i);
+			return -EINVAL;
+		}
+		bitmap_out[i] = (uint8_t)byte_val;
+	}
+	return 0;
+}
+
+int hl78xx_get_band_default_config_for_rat(enum hl78xx_cell_rat_mode rat, char *hex_bndcfg,
+					   size_t size_in_bytes)
+{
+	uint8_t bitmap[MDM_BAND_BITMAP_LEN_BYTES] = {0};
+	char hex_str[MDM_BAND_HEX_STR_LEN] = {0};
+
+	if (size_in_bytes < MDM_BAND_HEX_STR_LEN || hex_bndcfg == NULL) {
+		return -EINVAL;
+	}
+	if (hl78xx_generate_bitmap_from_config(rat, bitmap) != 0) {
+		return -EINVAL;
+	}
+	hl78xx_bitmap_to_hex_string_trimmed(bitmap, hex_str, sizeof(hex_str));
+	LOG_INF("Default band config: %s", hex_str);
+	strncpy(hex_bndcfg, hex_str, MDM_BAND_HEX_STR_LEN);
+	return 0;
+}
+
+const char *hl78xx_trim_leading_zeros(const char *hex_str)
+{
+	while (*hex_str == '0' && *(hex_str + 1) != '\0') {
+		hex_str++;
+	}
+	return hex_str;
+}
+
+static void strip_quotes(char *str)
+{
+	size_t len = strlen(str);
+
+	if (len >= 2 && str[0] == '"' && str[len - 1] == '"') {
+		/*  Shift string left by 1 and null-terminate earlier */
+		memmove(str, str + 1, len - 2);
+		str[len - 2] = '\0';
+	}
+}
+
+void hl78xx_extract_essential_part_apn(const char *full_apn, char *essential_apn, size_t max_len)
+{
+	char apn_buf[MDM_APN_FULL_STRING_MAX_LEN] = {0};
+	size_t len;
+	const char *mnc_ptr;
+
+	if (full_apn == NULL || essential_apn == NULL || max_len == 0) {
+		return;
+	}
+	strncpy(apn_buf, full_apn, sizeof(apn_buf) - 1);
+	apn_buf[sizeof(apn_buf) - 1] = '\0';
+	/*  Remove surrounding quotes if any */
+	strip_quotes(apn_buf);
+	mnc_ptr = strstr(apn_buf, ".mnc");
+	if (mnc_ptr != NULL) {
+		len = mnc_ptr - apn_buf;
+		if (len >= max_len) {
+			len = max_len - 1;
+		}
+		strncpy(essential_apn, apn_buf, len);
+		essential_apn[len] = '\0';
+	} else {
+		/* No ".mnc" found, copy entire string */
+		strncpy(essential_apn, apn_buf, max_len - 1);
+		essential_apn[max_len - 1] = '\0';
+	}
+}
diff --git a/drivers/modem/hl78xx/hl78xx_cfg.h b/drivers/modem/hl78xx/hl78xx_cfg.h
new file mode 100644
index 0000000..5d8be2a
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_cfg.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/*
+ * hl78xx_cfg.h
+ *
+ * Helper APIs for RAT, band and APN configuration extracted from hl78xx.c
+ * to keep the state machine file smaller and easier to read.
+ */
+#ifndef ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CFG_H_
+#define ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CFG_H_
+
+#include <zephyr/types.h>
+#include <stdbool.h>
+#include "hl78xx.h"
+
+int hl78xx_rat_cfg(struct hl78xx_data *data, bool *modem_require_restart,
+		   enum hl78xx_cell_rat_mode *rat_request);
+
+int hl78xx_band_cfg(struct hl78xx_data *data, bool *modem_require_restart,
+		    enum hl78xx_cell_rat_mode rat_config_request);
+
+int hl78xx_set_apn_internal(struct hl78xx_data *data, const char *apn, uint16_t size);
+
+/**
+ * @brief Convert a binary bitmap to a trimmed hexadecimal string.
+ *
+ * Converts a bitmap into a hex string, removing leading zeros for a
+ * compact representation. Useful for modem configuration commands.
+ *
+ * @param bitmap Pointer to the input binary bitmap.
+ * @param hex_str Output buffer for the resulting hex string.
+ * @param hex_str_len Size of the output buffer in bytes.
+ */
+void hl78xx_bitmap_to_hex_string_trimmed(const uint8_t *bitmap, char *hex_str, size_t hex_str_len);
+
+/**
+ * @brief Trim leading zeros from a hexadecimal string.
+ *
+ * Removes any '0' characters from the beginning of the provided hex string,
+ * returning a pointer to the first non-zero character.
+ *
+ * @param hex_str Null-terminated hexadecimal string.
+ *
+ * @return Pointer to the first non-zero digit in the string,
+ *         or the last zero if the string is all zeros.
+ */
+const char *hl78xx_trim_leading_zeros(const char *hex_str);
+
+/**
+ * @brief hl78xx_extract_essential_part_apn - Extract the essential part of the APN.
+ * @param full_apn Full APN string.
+ * @param essential_apn Buffer to store the essential part of the APN.
+ * @param max_len Maximum length of the essential APN buffer.
+ */
+void hl78xx_extract_essential_part_apn(const char *full_apn, char *essential_apn, size_t max_len);
+
+#endif /* ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CFG_H_ */
diff --git a/drivers/modem/hl78xx/hl78xx_chat.c b/drivers/modem/hl78xx/hl78xx_chat.c
new file mode 100644
index 0000000..5b94791
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_chat.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/*
+ *****************************************************************************
+ * hl78xx_chat.c
+ *
+ * Centralized translation unit for MODEM_CHAT_* macro-generated objects and
+ * chat scripts for the HL78xx driver. This file contains the MODEM_CHAT
+ * matches and script definitions and exposes runtime wrapper functions
+ * declared in hl78xx_chat.h.
+ *
+ * Contract:
+ *  - Other translation units MUST NOT take addresses of the MODEM_CHAT_*
+ *    symbols or use ARRAY_SIZE() on them at file scope. Use the getters
+ *    (hl78xx_get_*) and runners (hl78xx_ run_*_script[_async]) instead.
+ *****************************************************************************
+ */
+
+#include "hl78xx.h"
+#include "hl78xx_chat.h"
+#include <zephyr/modem/chat.h>
+#include <zephyr/logging/log.h>
+
+LOG_MODULE_DECLARE(hl78xx_dev);
+
+/* Forward declarations of handlers implemented in hl78xx.c (extern linkage) */
+void hl78xx_on_cxreg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+/* +CGCONTRDP handler implemented in hl78xx_sockets.c - declared here so the
+ * chat match may reference it. This handler parses PDP context response and
+ * updates DNS / interface state for the driver instance.
+ */
+void hl78xx_on_cgdcontrdp(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+#if defined(CONFIG_MODEM_HL78XX_12)
+void hl78xx_on_kstatev(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+#endif
+void hl78xx_on_socknotifydata(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_ktcpnotif(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+/* Handler implemented to assign modem-provided udp socket ids */
+void hl78xx_on_kudpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc,
+				 void *user_data);
+void hl78xx_on_ktcpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc,
+				 void *user_data);
+/* Handler implemented to assign modem-provided tcp socket ids */
+void hl78xx_on_ktcpind(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+/*
+ * Chat script and URC match definitions - extracted from hl78xx.c
+ */
+void hl78xx_on_udprcv(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_kbndcfg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_csq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cesq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cfun(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cops(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_ksup(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_imei(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cgmm(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_imsi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cgmi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_cgmr(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_iccid(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_ksrep(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_ksrat(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+void hl78xx_on_kselacq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data);
+
+MODEM_CHAT_MATCH_DEFINE(hl78xx_ok_match, "OK", "", NULL);
+MODEM_CHAT_MATCHES_DEFINE(hl78xx_allow_match, MODEM_CHAT_MATCH("OK", "", NULL),
+			  MODEM_CHAT_MATCH(CME_ERROR_STRING, "", NULL));
+
+MODEM_CHAT_MATCHES_DEFINE(hl78xx_unsol_matches, MODEM_CHAT_MATCH("+CREG: ", ",", hl78xx_on_cxreg),
+			  MODEM_CHAT_MATCH("+CEREG: ", ",", hl78xx_on_cxreg),
+#if defined(CONFIG_MODEM_HL78XX_12)
+			  MODEM_CHAT_MATCH("+KSTATEV: ", ",", hl78xx_on_kstatev),
+#endif
+			  MODEM_CHAT_MATCH("+KUDP_DATA: ", ",", hl78xx_on_socknotifydata),
+			  MODEM_CHAT_MATCH("+KTCP_DATA: ", ",", hl78xx_on_socknotifydata),
+			  MODEM_CHAT_MATCH("+KTCP_NOTIF: ", ",", hl78xx_on_ktcpnotif),
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+			  MODEM_CHAT_MATCH("+KUDP_RCV: ", ",", hl78xx_on_udprcv),
+#endif
+			  MODEM_CHAT_MATCH("+KBNDCFG: ", ",", hl78xx_on_kbndcfg),
+			  MODEM_CHAT_MATCH("+CSQ: ", ",", hl78xx_on_csq),
+			  MODEM_CHAT_MATCH("+CESQ: ", ",", hl78xx_on_cesq),
+			  MODEM_CHAT_MATCH("+CFUN: ", "", hl78xx_on_cfun),
+			  MODEM_CHAT_MATCH("+COPS: ", ",", hl78xx_on_cops));
+
+MODEM_CHAT_MATCHES_DEFINE(hl78xx_abort_matches, MODEM_CHAT_MATCH("+CME ERROR: ", "", NULL));
+MODEM_CHAT_MATCH_DEFINE(hl78xx_at_ready_match, "+KSUP: ", "", hl78xx_on_ksup);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_imei_match, "", "", hl78xx_on_imei);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_cgmm_match, "", "", hl78xx_on_cgmm);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_cimi_match, "", "", hl78xx_on_imsi);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_cgmi_match, "", "", hl78xx_on_cgmi);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_cgmr_match, "", "", hl78xx_on_cgmr);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_iccid_match, "+CCID: ", "", hl78xx_on_iccid);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_ksrep_match, "+KSREP: ", ",", hl78xx_on_ksrep);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_ksrat_match, "+KSRAT: ", "", hl78xx_on_ksrat);
+MODEM_CHAT_MATCH_DEFINE(hl78xx_kselacq_match, "+KSELACQ: ", ",", hl78xx_on_kselacq);
+
+/* Chat script matches / definitions */
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_periodic_chat_script_cmds,
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEREG?", hl78xx_ok_match));
+
+MODEM_CHAT_SCRIPT_DEFINE(hl78xx_periodic_chat_script, hl78xx_periodic_chat_script_cmds,
+			 hl78xx_abort_matches, hl78xx_chat_callback_handler, 4);
+
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_init_chat_script_cmds,
+			      MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_at_ready_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KHWIOCFG=3,1,6", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("ATE0", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CFUN=4,0", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSLEEP=2", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CPSMS=0", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEDRXS=0", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KPATTERN=\"--EOF--Pattern--\"",
+							 hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CCID", hl78xx_iccid_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CMEE=1", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGSN", hl78xx_imei_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMM", hl78xx_cgmm_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMI", hl78xx_cgmi_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMR", hl78xx_cgmr_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CIMI", hl78xx_cimi_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match),
+#if defined(CONFIG_MODEM_HL78XX_12)
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSTATEV=1", hl78xx_ok_match),
+#endif
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGEREP=2", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSELACQ?", hl78xx_kselacq_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSRAT?", hl78xx_ksrat_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KBNDCFG?", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGACT?", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CREG=0", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEREG=5", hl78xx_ok_match));
+
+MODEM_CHAT_SCRIPT_DEFINE(hl78xx_init_chat_script, hl78xx_init_chat_script_cmds,
+			 hl78xx_abort_matches, hl78xx_chat_callback_handler, 10);
+
+/* Post-restart script (moved from hl78xx.c) */
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_post_restart_chat_script_cmds,
+			      MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_at_ready_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSRAT?", hl78xx_ksrat_match),
+#if defined(CONFIG_MODEM_HL78XX_12)
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSTATEV=1", hl78xx_ok_match)
+#endif
+);
+
+MODEM_CHAT_SCRIPT_DEFINE(hl78xx_post_restart_chat_script, hl78xx_post_restart_chat_script_cmds,
+			 hl78xx_abort_matches, hl78xx_chat_callback_handler, 1000);
+
+/* init_fail_script moved from hl78xx.c */
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(init_fail_script_cmds,
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP?", hl78xx_ksrep_match));
+
+MODEM_CHAT_SCRIPT_DEFINE(init_fail_script, init_fail_script_cmds, hl78xx_abort_matches,
+			 hl78xx_chat_callback_handler, 10);
+
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_enable_ksup_urc_cmds,
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP=1", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP?", hl78xx_ksrep_match));
+
+MODEM_CHAT_SCRIPT_DEFINE(hl78xx_enable_ksup_urc_script, hl78xx_enable_ksup_urc_cmds,
+			 hl78xx_abort_matches, hl78xx_chat_callback_handler, 4);
+
+/* power-off script moved from hl78xx.c */
+MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_pwroff_cmds,
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CFUN=0", hl78xx_ok_match),
+			      MODEM_CHAT_SCRIPT_CMD_RESP("AT+CPWROFF", hl78xx_ok_match));
+
+MODEM_CHAT_SCRIPT_DEFINE(hl78xx_pwroff_script, hl78xx_pwroff_cmds, hl78xx_abort_matches,
+			 hl78xx_chat_callback_handler, 4);
+
+/* Socket-specific matches and wrappers exposed for the sockets translation
+ * unit. These were extracted from hl78xx_sockets.c to centralize chat
+ * definitions.
+ */
+MODEM_CHAT_MATCHES_DEFINE(connect_matches, MODEM_CHAT_MATCH(CONNECT_STRING, "", NULL),
+			  MODEM_CHAT_MATCH(CME_ERROR_STRING, "", NULL));
+MODEM_CHAT_MATCH_DEFINE(kudpind_match, "+KUDP_IND: ", ",", hl78xx_on_kudpsocket_create);
+MODEM_CHAT_MATCH_DEFINE(ktcpind_match, "+KTCP_IND: ", ",", hl78xx_on_ktcpind);
+MODEM_CHAT_MATCH_DEFINE(ktcpcfg_match, "+KTCPCFG: ", "", hl78xx_on_ktcpsocket_create);
+MODEM_CHAT_MATCH_DEFINE(cgdcontrdp_match, "+CGCONTRDP: ", ",", hl78xx_on_cgdcontrdp);
+MODEM_CHAT_MATCH_DEFINE(ktcp_state_match, "+KTCPSTAT: ", ",", NULL);
+
+const struct modem_chat_match *hl78xx_get_sockets_ok_match(void)
+{
+	return &hl78xx_ok_match;
+}
+
+const struct modem_chat_match *hl78xx_get_connect_matches(void)
+{
+	return connect_matches;
+}
+
+size_t hl78xx_get_connect_matches_size(void)
+{
+	return (size_t)ARRAY_SIZE(connect_matches);
+}
+
+const struct modem_chat_match *hl78xx_get_sockets_allow_matches(void)
+{
+	return hl78xx_allow_match;
+}
+
+size_t hl78xx_get_sockets_allow_matches_size(void)
+{
+	return (size_t)ARRAY_SIZE(hl78xx_allow_match);
+}
+
+const struct modem_chat_match *hl78xx_get_kudpind_match(void)
+{
+	return &kudpind_match;
+}
+
+const struct modem_chat_match *hl78xx_get_ktcpind_match(void)
+{
+	return &ktcpind_match;
+}
+
+const struct modem_chat_match *hl78xx_get_ktcpcfg_match(void)
+{
+	return &ktcpcfg_match;
+}
+
+const struct modem_chat_match *hl78xx_get_cgdcontrdp_match(void)
+{
+	return &cgdcontrdp_match;
+}
+
+const struct modem_chat_match *hl78xx_get_ktcp_state_match(void)
+{
+	return &ktcp_state_match;
+}
+
+/* modem_init_chat is implemented in hl78xx.c so it can construct the
+ * modem_chat_config with device-local buffer sizes (argv_size) without
+ * relying on ARRAY_SIZE at file scope inside this translation unit.
+ */
+
+/* Bridge function - modem_chat callback */
+void hl78xx_chat_callback_handler(struct modem_chat *chat, enum modem_chat_script_result result,
+				  void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	if (result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS) {
+		hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SCRIPT_SUCCESS);
+	} else {
+		hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SCRIPT_FAILED);
+	}
+}
+
+/* --- Wrapper helpers -------------------------------------------------- */
+const struct modem_chat_match *hl78xx_get_ok_match(void)
+{
+	return &hl78xx_ok_match;
+}
+
+const struct modem_chat_match *hl78xx_get_abort_matches(void)
+{
+	return hl78xx_abort_matches;
+}
+
+const struct modem_chat_match *hl78xx_get_unsol_matches(void)
+{
+	return hl78xx_unsol_matches;
+}
+
+size_t hl78xx_get_unsol_matches_size(void)
+{
+	/* Return size as a runtime value to avoid constant-expression errors
+	 * in translation units that include this header.
+	 */
+	return (size_t)(ARRAY_SIZE(hl78xx_unsol_matches));
+}
+
+size_t hl78xx_get_abort_matches_size(void)
+{
+	return (size_t)(ARRAY_SIZE(hl78xx_abort_matches));
+}
+
+const struct modem_chat_match *hl78xx_get_allow_match(void)
+{
+	return hl78xx_allow_match;
+}
+
+size_t hl78xx_get_allow_match_size(void)
+{
+	return (size_t)(ARRAY_SIZE(hl78xx_allow_match));
+}
+
+/* Run the predefined init script for the given device */
+int hl78xx_run_init_script(struct hl78xx_data *data)
+{
+	if (!data) {
+		return -EINVAL;
+	}
+	return modem_chat_run_script(&data->chat, &hl78xx_init_chat_script);
+}
+
+/* Run the periodic script */
+int hl78xx_run_periodic_script(struct hl78xx_data *data)
+{
+	if (!data) {
+		return -EINVAL;
+	}
+	return modem_chat_run_script(&data->chat, &hl78xx_periodic_chat_script);
+}
+
+int hl78xx_run_init_script_async(struct hl78xx_data *data)
+{
+	if (!data) {
+		return -EINVAL;
+	}
+	return modem_chat_run_script_async(&data->chat, &hl78xx_init_chat_script);
+}
+
+int hl78xx_run_periodic_script_async(struct hl78xx_data *data)
+{
+	if (!data) {
+		return -EINVAL;
+	}
+	return modem_chat_run_script_async(&data->chat, &hl78xx_periodic_chat_script);
+}
+
+const struct modem_chat_match *hl78xx_get_ksrat_match(void)
+{
+	return &hl78xx_ksrat_match;
+}
+
+int hl78xx_run_post_restart_script(struct hl78xx_data *data)
+{
+	if (!data) {
+		return -EINVAL;
+	}
+	return modem_chat_run_script(&data->chat, &hl78xx_post_restart_chat_script);
+}
+
+int hl78xx_run_post_restart_script_async(struct hl78xx_data *data)
+{
+	if (!data) {
+		return -EINVAL;
+	}
+	return modem_chat_run_script_async(&data->chat, &hl78xx_post_restart_chat_script);
+}
+
+int hl78xx_run_init_fail_script_async(struct hl78xx_data *data)
+{
+	if (!data) {
+		return -EINVAL;
+	}
+	return modem_chat_run_script_async(&data->chat, &init_fail_script);
+}
+
+int hl78xx_run_enable_ksup_urc_script_async(struct hl78xx_data *data)
+{
+	if (!data) {
+		return -EINVAL;
+	}
+	return modem_chat_run_script_async(&data->chat, &hl78xx_enable_ksup_urc_script);
+}
+
+int hl78xx_run_pwroff_script_async(struct hl78xx_data *data)
+{
+	if (!data) {
+		return -EINVAL;
+	}
+	return modem_chat_run_script_async(&data->chat, &hl78xx_pwroff_script);
+}
diff --git a/drivers/modem/hl78xx/hl78xx_chat.h b/drivers/modem/hl78xx/hl78xx_chat.h
new file mode 100644
index 0000000..eb3a1de
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_chat.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/*
+ * hl78xx_chat.h
+ *
+ * Wrapper accessors for MODEM_CHAT_* objects that live in a dedicated
+ * translation unit (hl78xx_chat.c). Other driver TUs should only call
+ * these functions instead of taking addresses or using sizeof/ARRAY_SIZE
+ * on the macro-generated objects.
+ */
+#ifndef ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CHAT_H_
+#define ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CHAT_H_
+
+#include <stddef.h>
+#include <zephyr/modem/chat.h>
+
+/* Forward declare driver data type to keep this header lightweight and avoid
+ * circular includes. The implementation file (hl78xx_chat.c) includes
+ * hl78xx.h for full driver visibility.
+ */
+struct hl78xx_data;
+
+/* Chat callback bridge used by driver TUs to receive script results. */
+void hl78xx_chat_callback_handler(struct modem_chat *chat, enum modem_chat_script_result result,
+				  void *user_data);
+
+/* Wrapper helpers so other translation units don't need compile-time
+ * visibility of the MODEM_CHAT_* macro-generated symbols.
+ */
+const struct modem_chat_match *hl78xx_get_ok_match(void);
+const struct modem_chat_match *hl78xx_get_abort_matches(void);
+const struct modem_chat_match *hl78xx_get_unsol_matches(void);
+size_t hl78xx_get_unsol_matches_size(void);
+size_t hl78xx_get_abort_matches_size(void);
+const struct modem_chat_match *hl78xx_get_allow_match(void);
+size_t hl78xx_get_allow_match_size(void);
+
+/* Run predefined scripts from other units */
+int hl78xx_run_init_script(struct hl78xx_data *data);
+int hl78xx_run_periodic_script(struct hl78xx_data *data);
+int hl78xx_run_post_restart_script(struct hl78xx_data *data);
+int hl78xx_run_init_fail_script_async(struct hl78xx_data *data);
+int hl78xx_run_enable_ksup_urc_script_async(struct hl78xx_data *data);
+int hl78xx_run_pwroff_script_async(struct hl78xx_data *data);
+int hl78xx_run_post_restart_script_async(struct hl78xx_data *data);
+/* Async runners for init/periodic scripts */
+int hl78xx_run_init_script_async(struct hl78xx_data *data);
+int hl78xx_run_periodic_script_async(struct hl78xx_data *data);
+
+/* Getter for ksrat match (moved into chat TU) */
+const struct modem_chat_match *hl78xx_get_ksrat_match(void);
+
+/* Socket-related chat matches used by the sockets TU */
+const struct modem_chat_match *hl78xx_get_sockets_ok_match(void);
+const struct modem_chat_match *hl78xx_get_connect_matches(void);
+size_t hl78xx_get_connect_matches_size(void);
+const struct modem_chat_match *hl78xx_get_sockets_allow_matches(void);
+size_t hl78xx_get_sockets_allow_matches_size(void);
+const struct modem_chat_match *hl78xx_get_kudpind_match(void);
+const struct modem_chat_match *hl78xx_get_ktcpind_match(void);
+const struct modem_chat_match *hl78xx_get_ktcpcfg_match(void);
+const struct modem_chat_match *hl78xx_get_cgdcontrdp_match(void);
+const struct modem_chat_match *hl78xx_get_ktcp_state_match(void);
+
+#endif /* ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CHAT_H_ */
diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/CMakeLists.txt b/drivers/modem/hl78xx/hl78xx_evt_monitor/CMakeLists.txt
new file mode 100644
index 0000000..ea00a6b
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/CMakeLists.txt
@@ -0,0 +1,10 @@
+#
+#  Copyright (c) 2025 Netfeasa Ltd.
+#
+#  SPDX-License-Identifier: Apache-2.0
+#
+
+zephyr_library()
+zephyr_library_sources(hl78xx_evt_monitor.c)
+# Event monitors data must be in RAM
+zephyr_linker_sources(RWDATA hl78xx_evt_monitor.ld)
diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor b/drivers/modem/hl78xx/hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor
new file mode 100644
index 0000000..e002f24
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor
@@ -0,0 +1,29 @@
+#
+#  Copyright (c) 2025 Netfeasa Ltd.
+#
+#  SPDX-License-Identifier: Apache-2.0
+#
+
+menuconfig HL78XX_EVT_MONITOR
+	bool "HL78XX AT notification monitor"
+
+if HL78XX_EVT_MONITOR
+
+config HL78XX_EVT_MONITOR_HEAP_SIZE
+	int "Heap size for notifications"
+	range 64 4096
+	default 256
+
+config HL78XX_EVT_MONITOR_APP_INIT_PRIORITY
+	int "Sierra Wireless HL78XX event monitor app init priority"
+	default 0
+	help
+	  Sierra Wireless HL78XX event monitor app initialization priority.
+	  Do not mess with it unless you know what you are doing.
+
+module=HL78XX_EVT_MONITOR
+module-dep=LOG
+module-str= Event notification monitor library
+source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config"
+
+endif # HL78XX_EVT_MONITOR
diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.c b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.c
new file mode 100644
index 0000000..7bb2e68
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <zephyr/kernel.h>
+#include <zephyr/init.h>
+#include <zephyr/device.h>
+#include <zephyr/toolchain.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/spinlock.h>
+#include <zephyr/drivers/modem/hl78xx_apis.h>
+
+LOG_MODULE_REGISTER(hl78xx_evt_monitor, CONFIG_HL78XX_EVT_MONITOR_LOG_LEVEL);
+
+struct evt_notif_fifo {
+	void *fifo_reserved;
+	struct hl78xx_evt data;
+};
+
+static struct hl78xx_evt_monitor_entry *monitor_list_head;
+static struct k_spinlock monitor_list_lock;
+
+static void hl78xx_evt_monitor_task(struct k_work *work);
+
+static K_FIFO_DEFINE(hl78xx_evt_monitor_fifo);
+static K_HEAP_DEFINE(hl78xx_evt_monitor_heap, CONFIG_HL78XX_EVT_MONITOR_HEAP_SIZE);
+static K_WORK_DEFINE(hl78xx_evt_monitor_work, hl78xx_evt_monitor_task);
+
+static bool is_paused(const struct hl78xx_evt_monitor_entry *mon)
+{
+	return mon->flags.paused;
+}
+
+static bool is_direct(const struct hl78xx_evt_monitor_entry *mon)
+{
+	return mon->flags.direct;
+}
+
+/* Register an event monitor */
+int hl78xx_evt_monitor_register(struct hl78xx_evt_monitor_entry *mon)
+{
+	k_spinlock_key_t key = k_spin_lock(&monitor_list_lock);
+
+	mon->next = monitor_list_head;
+	monitor_list_head = mon;
+	k_spin_unlock(&monitor_list_lock, key);
+	return 0;
+}
+
+/* Unregister an event monitor */
+int hl78xx_evt_monitor_unregister(struct hl78xx_evt_monitor_entry *mon)
+{
+	k_spinlock_key_t key = k_spin_lock(&monitor_list_lock);
+	struct hl78xx_evt_monitor_entry **pp = &monitor_list_head;
+
+	while (*pp) {
+		if (*pp == mon) {
+			*pp = mon->next;
+			mon->next = NULL;
+			k_spin_unlock(&monitor_list_lock, key);
+			return 0;
+		}
+		pp = &(*pp)->next;
+	}
+
+	k_spin_unlock(&monitor_list_lock, key);
+	return -ENOENT;
+}
+/* Dispatch EVT notifications immediately, or schedules a workqueue task to do that.
+ * Keep this function public so that it can be called by tests.
+ * This function is called from an ISR.
+ */
+void hl78xx_evt_monitor_dispatch(struct hl78xx_evt *notif)
+{
+	bool monitored;
+	struct evt_notif_fifo *evt_notif;
+	size_t sz_needed;
+
+	__ASSERT_NO_MSG(notif != NULL);
+
+	monitored = false;
+	/* Global monitors: SECTION_ITERABLE */
+	STRUCT_SECTION_FOREACH(hl78xx_evt_monitor_entry, e) {
+		if (!is_paused(e)) {
+			if (is_direct(e)) {
+				LOG_DBG("calling direct global handler %p",
+					e->handler);
+				e->handler(notif, NULL); /* NULL context for global listeners */
+			} else {
+				monitored = true;
+			}
+		}
+	}
+
+	k_spinlock_key_t key = k_spin_lock(&monitor_list_lock);
+
+	for (struct hl78xx_evt_monitor_entry *e = monitor_list_head; e; e = e->next) {
+		if (!is_paused(e)) {
+			if (is_direct(e)) {
+				LOG_DBG("calling direct instance handler %p "
+					"(ctx=%p)",
+					e->handler, e);
+				e->handler(notif, e);
+			} else {
+				monitored = true;
+			}
+		}
+	}
+	k_spin_unlock(&monitor_list_lock, key);
+
+	if (!monitored) {
+		/* Only copy monitored notifications to save heap */
+		return;
+	}
+
+	sz_needed = sizeof(struct evt_notif_fifo) + sizeof(notif);
+
+	evt_notif = k_heap_alloc(&hl78xx_evt_monitor_heap, sz_needed, K_NO_WAIT);
+	if (!evt_notif) {
+		LOG_WRN("No heap space for incoming notification: %d", notif->type);
+		__ASSERT(evt_notif, "No heap space for incoming notification: %d", notif->type);
+		return;
+	}
+
+	evt_notif->data = *notif;
+
+	k_fifo_put(&hl78xx_evt_monitor_fifo, evt_notif);
+	k_work_submit(&hl78xx_evt_monitor_work);
+}
+
+static void hl78xx_evt_monitor_task(struct k_work *work)
+{
+	struct evt_notif_fifo *evt_notif;
+
+	while ((evt_notif = k_fifo_get(&hl78xx_evt_monitor_fifo, K_NO_WAIT))) {
+		/* Dispatch notification with all monitors */
+		LOG_DBG("EVT notif: %d", evt_notif->data.type);
+		STRUCT_SECTION_FOREACH(hl78xx_evt_monitor_entry, e) {
+			if (!is_paused(e) && !is_direct(e)) {
+				LOG_DBG("Dispatching to %p", e->handler);
+				e->handler(&evt_notif->data, e);
+			}
+		}
+		/* Instance/context monitors */
+		k_spinlock_key_t key = k_spin_lock(&monitor_list_lock);
+
+		for (struct hl78xx_evt_monitor_entry *e = monitor_list_head; e; e = e->next) {
+			if (!is_paused(e) && !is_direct(e)) {
+				e->handler(&evt_notif->data, e);
+			}
+		}
+		k_spin_unlock(&monitor_list_lock, key);
+
+		k_heap_free(&hl78xx_evt_monitor_heap, evt_notif);
+	}
+}
+
+static int hl78xx_evt_monitor_sys_init(void)
+{
+	int err = 0;
+
+	err = hl78xx_evt_notif_handler_set(hl78xx_evt_monitor_dispatch);
+	if (err) {
+		LOG_ERR("Failed to hook the dispatch function, err %d", err);
+	}
+
+	return 0;
+}
+
+/* Initialize during SYS_INIT */
+SYS_INIT(hl78xx_evt_monitor_sys_init, APPLICATION, CONFIG_HL78XX_EVT_MONITOR_APP_INIT_PRIORITY);
diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.ld b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.ld
new file mode 100644
index 0000000..c6c3294
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.ld
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/*HL78XX event monitors */
+. = ALIGN(4);
+_hl78xx_evt_monitor_entry_list_start = .;
+KEEP(*(SORT_BY_NAME("._hl78xx_evt_monitor_entry.*")));
+_hl78xx_evt_monitor_entry_list_end = .;
diff --git a/drivers/modem/hl78xx/hl78xx_sockets.c b/drivers/modem/hl78xx/hl78xx_sockets.c
new file mode 100644
index 0000000..9840ea9
--- /dev/null
+++ b/drivers/modem/hl78xx/hl78xx_sockets.c
@@ -0,0 +1,2608 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include <zephyr/modem/chat.h>
+#include <zephyr/modem/backend/uart.h>
+#include <zephyr/kernel.h>
+#include <zephyr/drivers/gpio.h>
+#include <zephyr/net/net_if.h>
+#include <zephyr/net/net_offload.h>
+#include <zephyr/net/offloaded_netdev.h>
+#include <zephyr/net/socket_offload.h>
+#include <zephyr/posix/fcntl.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/pm/device.h>
+#include <zephyr/pm/device_runtime.h>
+
+#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS)
+#include "tls_internal.h"
+#include <zephyr/net/tls_credentials.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include "hl78xx.h"
+#include "hl78xx_chat.h"
+#include "hl78xx_cfg.h"
+
+LOG_MODULE_REGISTER(hl78xx_socket, CONFIG_MODEM_LOG_LEVEL);
+
+/*
+ * hl78xx_sockets.c
+ *
+ * Responsibilities:
+ *  - Provide the socket offload integration for the HL78xx modem.
+ *  - Parse modem URC/chat replies used to transfer payloads over the UART pipe.
+ *  - Format and send AT commands for socket lifecycle (create, connect, send, recv,
+ *    close, delete) and handle their confirmation/URC callbacks.
+ *  - Provide TLS credential handling when enabled.
+ */
+
+/* Helper macros and constants */
+#define MODEM_STREAM_STARTER_WORD "\r\n" CONNECT_STRING "\r\n"
+#define MODEM_STREAM_END_WORD     "\r\n" OK_STRING "\r\n"
+
+#define MODEM_SOCKET_DATA_LEFTOVER_STATE_BIT     (0)
+#define HL78XX_UART_PIPE_WORK_SOCKET_BUFFER_SIZE 32
+/* modem socket id is 1-based */
+#define HL78XX_TCP_STATUS_ID(x)                  ((x > 1 ? (x) - 1 : 0))
+/* modem socket id is 1-based */
+#define HL78XX_UDP_STATUS_ID(x)                  ((x > 1 ? (x) - 1 : 0))
+
+#define DNS_SERVERS_COUNT                                                                          \
+	(0 + (IS_ENABLED(CONFIG_NET_IPV6) ? 1 : 0) + (IS_ENABLED(CONFIG_NET_IPV4) ? 1 : 0) +       \
+	 1 /* for NULL terminator */                                                               \
+	)
+RING_BUF_DECLARE(mdm_recv_pool, CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES);
+
+struct hl78xx_dns_info {
+#ifdef CONFIG_NET_IPV4
+	char v4_string[NET_IPV4_ADDR_LEN];
+	struct in_addr v4;
+#endif
+#ifdef CONFIG_NET_IPV6
+	char v6_string[NET_IPV6_ADDR_LEN];
+	struct in6_addr v6;
+#endif
+	bool ready;
+};
+
+/* IPv4 information is optional and only present when IPv4 is enabled */
+#ifdef CONFIG_NET_IPV4
+struct hl78xx_ipv4_info {
+	struct in_addr addr;
+	struct in_addr subnet;
+	struct in_addr gateway;
+	struct in_addr new_addr;
+};
+#endif
+/* IPv6 information is optional and only present when IPv6 is enabled */
+#ifdef CONFIG_NET_IPV6
+struct hl78xx_ipv6_info {
+	struct in6_addr addr;
+	struct in6_addr subnet;
+	struct in6_addr gateway;
+	struct in6_addr new_addr;
+};
+#endif
+/* TLS information is optional and only present when TLS is enabled */
+struct hl78xx_tls_info {
+	char hostname[MDM_MAX_HOSTNAME_LEN];
+	bool hostname_set;
+};
+
+enum hl78xx_tcp_socket_status_code {
+	/** Error occurred, socket is not usable */
+	TCP_SOCKET_ERROR = 0,
+	/** Connection is up, socket can be used to send/receive data */
+	TCP_SOCKET_CONNECTED,
+};
+
+enum hl78xx_udp_socket_status_code {
+	UDP_SOCKET_ERROR = 0, /* Error occurred, socket is not usable */
+	/** Connection is up, socket can be used to send/receive data */
+	UDP_SOCKET_CREATED,
+};
+struct hl78xx_tcp_status {
+	enum hl78xx_tcp_socket_status_code err_code;
+	bool is_connected;
+	bool is_created;
+};
+struct hl78xx_udp_status {
+	enum hl78xx_udp_socket_status_code err_code;
+	bool is_created;
+};
+
+struct receive_socket_data {
+	char buf[MDM_MAX_DATA_LENGTH + ARRAY_SIZE(MODEM_STREAM_STARTER_WORD) +
+		 ARRAY_SIZE(MODEM_STREAM_END_WORD)];
+	uint16_t len;
+};
+struct hl78xx_socket_data {
+	struct net_if *net_iface;
+	uint8_t mac_addr[6];
+	/* socket data */
+	struct modem_socket_config socket_config;
+	struct modem_socket sockets[MDM_MAX_SOCKETS];
+	int current_sock_fd;
+	int sizeof_socket_data;
+	int requested_socket_id;
+	bool socket_data_error;
+#if defined(CONFIG_NET_IPV4) || defined(CONFIG_NET_IPV6)
+	struct hl78xx_dns_info dns;
+#endif
+#ifdef CONFIG_NET_IPV4
+	struct hl78xx_ipv4_info ipv4;
+#endif
+#ifdef CONFIG_NET_IPV6
+	struct hl78xx_ipv6_info ipv6;
+#endif
+	/* rx net buffer */
+	struct ring_buf *buf_pool;
+	uint32_t expected_buf_len;
+	uint32_t collected_buf_len;
+	struct receive_socket_data receive_buf;
+	/* device information */
+	const struct device *modem_dev;
+	const struct device *offload_dev;
+	struct hl78xx_data *mdata_global;
+	/* socket state */
+	struct hl78xx_tls_info tls;
+	struct hl78xx_tcp_status tcp_conn_status[MDM_MAX_SOCKETS];
+	struct hl78xx_udp_status udp_conn_status[MDM_MAX_SOCKETS];
+	/* per-socket parser state (migrated from globals) - use a small enum to
+	 * make the parser's intent explicit and easier to read.
+	 */
+	enum {
+		HL78XX_PARSER_IDLE = 0,
+		HL78XX_PARSER_CONNECT_MATCHED,
+		HL78XX_PARSER_EOF_OK_MATCHED,
+		HL78XX_PARSER_ERROR_MATCHED,
+	} parser_state;
+	/* transient: prevents further parsing until parser_reset clears it */
+	bool parser_match_found;
+	uint16_t parser_start_index_eof;
+	uint16_t parser_size_of_socketdata;
+	/* true once payload has been pushed into ring_buf */
+	bool parser_socket_data_received;
+	/* set when EOF pattern was found and payload pushed */
+	bool parser_eof_detected;
+	/* set when OK token was matched after payload */
+	bool parser_ok_detected;
+};
+
+static struct hl78xx_socket_data *socket_data_global;
+
+/* ===== Utils ==========================================================
+ * Small, stateless utility helpers used across this file.
+ * Grouping here reduces cognitive load when navigating the file.
+ */
+static inline void hl78xx_set_socket_global(struct hl78xx_socket_data *d)
+{
+	socket_data_global = d;
+}
+
+static inline struct hl78xx_socket_data *hl78xx_get_socket_global(void)
+{
+	return socket_data_global;
+}
+
+/* Helper: map an internal return code into POSIX errno and set errno.
+ * - negative values are assumed to be negative errno semantics -> map to positive
+ * - positive values are assumed already POSIX errno -> pass through
+ * - zero or unknown -> fallback to EIO
+ */
+static inline void hl78xx_set_errno_from_code(int code)
+{
+	if (code < 0) {
+		errno = -code;
+	} else if (code > 0) {
+		errno = code;
+	} else {
+		errno = EIO;
+	}
+}
+/* ===== Forward declarations ==========================================
+ * Group commonly used static helper prototypes here so callers can be
+ * reordered without implicit-declaration warnings. Keep this section
+ * compact. When moving functions into groups, add any new prototypes
+ * here first.
+ */
+static void check_tcp_state_if_needed(struct hl78xx_socket_data *socket_data,
+				      struct modem_socket *sock);
+/* Parser helpers */
+static bool split_ipv4_and_subnet(const char *combined, char *ip_out, size_t ip_out_len,
+				  char *subnet_out, size_t subnet_out_len);
+static bool parse_ip(bool is_ipv4, const char *ip_str, void *out_addr);
+static bool update_dns(struct hl78xx_socket_data *socket_data, bool is_ipv4, const char *dns_str);
+static void set_iface(struct hl78xx_socket_data *socket_data, bool is_ipv4);
+static void parser_reset(struct hl78xx_socket_data *socket_data);
+static void found_reset(struct hl78xx_socket_data *socket_data);
+static bool modem_chat_parse_end_del_start(struct hl78xx_socket_data *socket_data,
+					   struct modem_chat *chat);
+static bool modem_chat_parse_end_del_complete(struct hl78xx_socket_data *socket_data,
+					      struct modem_chat *chat);
+static bool modem_chat_match_matches_received(struct hl78xx_socket_data *socket_data,
+					      const char *match, uint16_t match_size);
+
+/* Receive / parser entrypoints */
+static void socket_process_bytes(struct hl78xx_socket_data *socket_data, char byte);
+static int modem_process_handler(struct hl78xx_data *data);
+static void modem_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
+				void *user_data);
+
+/* Socket I/O helpers */
+static int on_cmd_sockread_common(int socket_id, uint16_t socket_data_length, uint16_t len,
+				  void *user_data);
+static ssize_t offload_recvfrom(void *obj, void *buf, size_t len, int flags, struct sockaddr *from,
+				socklen_t *fromlen);
+static int prepare_send_cmd(const struct modem_socket *sock, const struct sockaddr *dst_addr,
+			    size_t buf_len, char *cmd_buf, size_t cmd_buf_size);
+static int send_data_buffer(struct hl78xx_socket_data *socket_data, const char *buf,
+			    const size_t buf_len, int *sock_written);
+
+/* Socket lifecycle */
+static int create_socket(struct modem_socket *sock, const struct sockaddr *addr,
+			 struct hl78xx_socket_data *data);
+static int socket_close(struct hl78xx_socket_data *socket_data, struct modem_socket *sock);
+static int socket_delete(struct hl78xx_socket_data *socket_data, struct modem_socket *sock);
+static void socket_notify_data(int socket_id, int new_total, void *user_data);
+/* ===== TLS prototypes (conditional) ==================================
+ * Forward declarations for TLS-related helpers. Grouped separately so
+ * TLS-specific code paths are easy to find.
+ */
+#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS)
+static int map_credentials(struct hl78xx_socket_data *socket_data, const void *optval,
+			   socklen_t optlen);
+static int hl78xx_configure_chipper_suit(struct hl78xx_socket_data *socket_data);
+#endif /* CONFIG_NET_SOCKETS_SOCKOPT_TLS */
+
+/* ===== Container helpers =============================================
+ * Small helpers used to map between container structures and their
+ * member pointers (eg. `modem_socket` -> `hl78xx_socket_data`).
+ */
+static inline struct hl78xx_socket_data *hl78xx_socket_data_from_sock(struct modem_socket *sock)
+{
+	/* Robustly recover the parent `hl78xx_socket_data` for any element
+	 * address within the `sockets[]` array. Using CONTAINER_OF with
+	 * `sockets[0]` is not safe when `sock` points to `sockets[i]` (i>0),
+	 * because CONTAINER_OF assumes the pointer is to the member named
+	 * in the macro (sockets[0]). That yields a pointer offset by
+	 * i * sizeof(sockets[0]).
+	 *
+	 * Strategy: for each possible index i, compute the candidate parent
+	 * base address so that &candidate->sockets[i] == sock. If the math
+	 * yields a candidate that looks like a valid container, return it.
+	 */
+	if (!sock) {
+		return NULL;
+	}
+
+	const size_t elem_size = sizeof(((struct hl78xx_socket_data *)0)->sockets[0]);
+	const size_t sockets_off = offsetof(struct hl78xx_socket_data, sockets);
+	struct hl78xx_socket_data *result = NULL;
+
+	for (int i = 0; i < MDM_MAX_SOCKETS; i++) {
+		struct hl78xx_socket_data *candidate =
+			(struct hl78xx_socket_data *)((char *)sock -
+						      (ptrdiff_t)(sockets_off +
+								  (size_t)i * elem_size));
+		/* Quick sanity: does candidate->sockets[i] point back to sock? */
+		if ((struct modem_socket *)&candidate->sockets[i] != sock) {
+			continue;
+		}
+		if (candidate->offload_dev && candidate->mdata_global) {
+			return candidate;
+		}
+
+		/* Remember the first match as a fallback */
+		if (!result) {
+			result = candidate;
+		}
+	}
+	return result;
+}
+
+/* ===== Chat callbacks (grouped) =====================================
+ * Group all chat/URC handlers together to make the socket TU easier to
+ * scan. These handlers are registered via hl78xx_chat getters in
+ * `hl78xx_chat.c` and forward URC context into the socket layer.
+ */
+void hl78xx_on_socknotifydata(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	int socket_id = -1;
+	int new_total = -1;
+
+	if (argc < 2) {
+		return;
+	}
+
+	socket_id = ATOI(argv[1], -1, "socket_id");
+	new_total = ATOI(argv[2], -1, "length");
+	if (socket_id < 0 || new_total < 0) {
+		return;
+	}
+	HL78XX_LOG_DBG("%d %d %d", __LINE__, socket_id, new_total);
+	/* Notify the socket layer that data is available */
+	socket_notify_data(socket_id, new_total, user_data);
+}
+
+/** +KTCP_NOTIF: <session_id>, <tcp_notif> */
+void hl78xx_on_ktcpnotif(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+	enum hl78xx_tcp_notif tcp_notif_received;
+	int socket_id = -1;
+	int tcp_notif = -1;
+
+	if (!data || !socket_data) {
+		LOG_ERR("%s: invalid user_data", __func__);
+		return;
+	}
+	if (argc < 2) {
+		return;
+	}
+	socket_id = ATOI(argv[1], -1, "socket_id");
+	tcp_notif = ATOI(argv[2], -1, "tcp_notif");
+	if (tcp_notif == -1) {
+		return;
+	}
+	tcp_notif_received = (enum hl78xx_tcp_notif)tcp_notif;
+	/* Store the socket id for the notification */
+	socket_data->requested_socket_id = socket_id;
+	switch (tcp_notif_received) {
+	case TCP_NOTIF_REMOTE_DISCONNECTION:
+		/**
+		 * To Handle remote disconnection
+		 * give a dummy packet size of 1
+		 *
+		 */
+		socket_notify_data(socket_id, 1, user_data);
+		break;
+	case TCP_NOTIF_NETWORK_ERROR:
+		/* Handle network error */
+		break;
+	default:
+		break;
+	}
+}
+
+void hl78xx_on_ktcpind(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+	struct modem_socket *sock = NULL;
+	int socket_id = -1;
+	int tcp_conn_stat = -1;
+
+	if (!data || !socket_data) {
+		LOG_ERR("%s: invalid user_data", __func__);
+		return;
+	}
+	if (argc < 3 || !argv[1] || !argv[2]) {
+		LOG_ERR("TCP_IND: Incomplete response");
+		goto exit;
+	}
+	socket_id = ATOI(argv[1], -1, "socket_id");
+	if (socket_id == -1) {
+		goto exit;
+	}
+	sock = modem_socket_from_id(&socket_data->socket_config, socket_id);
+	tcp_conn_stat = ATOI(argv[2], -1, "tcp_status");
+	if (tcp_conn_stat == TCP_SOCKET_CONNECTED) {
+		socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].err_code =
+			tcp_conn_stat;
+		socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_connected = true;
+		return;
+	}
+exit:
+	socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].err_code = tcp_conn_stat;
+	socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_connected = false;
+	if (socket_id != -1) {
+		modem_socket_put(&socket_data->socket_config, sock->sock_fd);
+	}
+}
+
+/* Chat/URC handler for socket-create/indication responses
+ * Matches +KTCPCFG: <id>
+ */
+void hl78xx_on_ktcpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc,
+				 void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+	struct modem_socket *sock = NULL;
+	int socket_id = -1;
+
+	if (!data || !socket_data) {
+		LOG_ERR("%s: invalid user_data", __func__);
+		return;
+	}
+	if (argc < 2 || !argv[1]) {
+		LOG_ERR("%s: Incomplete response", __func__);
+		goto exit;
+	}
+	/* argv[0] may contain extra CSV fields; parse leading integer */
+	socket_id = ATOI(argv[1], -1, "socket_id");
+	if (socket_id <= 0) {
+		LOG_DBG("unable to parse socket id from '%s'", argv[1]);
+		goto exit;
+	}
+	/* Try to find a reserved/new socket slot and assign the modem-provided id. */
+	sock = modem_socket_from_newid(&socket_data->socket_config);
+	if (!sock) {
+		goto exit;
+	}
+
+	if (modem_socket_id_assign(&socket_data->socket_config, sock, socket_id) < 0) {
+		LOG_ERR("Failed to assign modem socket id %d to fd %d", socket_id, sock->sock_fd);
+		goto exit;
+	} else {
+		LOG_DBG("Assigned modem socket id %d to fd %d", socket_id, sock->sock_fd);
+	}
+
+	socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_created = true;
+	return;
+
+exit:
+	socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].err_code = TCP_SOCKET_ERROR;
+	socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_created = false;
+	if (socket_id != -1 && sock) {
+		modem_socket_put(&socket_data->socket_config, sock->sock_fd);
+	}
+}
+/* Chat/URC handler for socket-create/indication responses
+ * Matches +KUDPCFG: <id>
+ *         +KUDP_IND: <id>,... (or +KTCP_IND)
+ */
+void hl78xx_on_kudpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc,
+				 void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+	struct modem_socket *sock = NULL;
+	int socket_id = -1;
+	int udp_create_stat = -1;
+
+	if (!data || !socket_data) {
+		LOG_ERR("%s: invalid user_data", __func__);
+		return;
+	}
+	if (argc < 2 || !argv[1]) {
+		LOG_ERR("%s: Incomplete response", __func__);
+		goto exit;
+	}
+	/* argv[0] may contain extra CSV fields; parse leading integer */
+	socket_id = ATOI(argv[1], -1, "socket_id");
+	if (socket_id <= 0) {
+		LOG_DBG("unable to parse socket id from '%s'", argv[1]);
+		goto exit;
+	}
+	/* Try to find a reserved/new socket slot and assign the modem-provided id. */
+	sock = modem_socket_from_newid(&socket_data->socket_config);
+	if (!sock) {
+		goto exit;
+	}
+
+	if (modem_socket_id_assign(&socket_data->socket_config, sock, socket_id) < 0) {
+		LOG_ERR("Failed to assign modem socket id %d to fd %d", socket_id, sock->sock_fd);
+		goto exit;
+	} else {
+		LOG_DBG("Assigned modem socket id %d to fd %d", socket_id, sock->sock_fd);
+	}
+	/* Parse connection status: 1=created, otherwise=error */
+	udp_create_stat = ATOI(argv[2], 0, "udp_status");
+	if (udp_create_stat == UDP_SOCKET_CREATED) {
+		socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].err_code =
+			udp_create_stat;
+		socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].is_created = true;
+		return;
+	}
+exit:
+	socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].err_code = UDP_SOCKET_ERROR;
+	socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].is_created = false;
+	if (socket_id != -1 && sock) {
+		modem_socket_put(&socket_data->socket_config, sock->sock_fd);
+	}
+}
+
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+#ifdef CONFIG_MODEM_HL78XX_12
+/**
+ * @brief Handle modem state update from +KSTATE URC of RAT Scan Finish.
+ * This command is intended to report events for different important state transitions and system
+ * occurrences.
+ * Actually this eventc'state is really important functionality to understand networks
+ * searching phase of the modem.
+ * Verbose debug logging for KSTATEV events
+ */
+void hl78xx_on_kstatev_parser(struct hl78xx_data *data, int state, int rat_mode)
+{
+	switch (state) {
+	case EVENT_START_SCAN:
+		break;
+	case EVENT_FAIL_SCAN:
+		LOG_DBG("Modem failed to find a suitable network");
+		break;
+	case EVENT_ENTER_CAMPED:
+		LOG_DBG("Modem entered camped state on a suitable or acceptable cell");
+		break;
+	case EVENT_CONNECTION_ESTABLISHMENT:
+		LOG_DBG("Modem successfully established a connection to the network");
+		break;
+	case EVENT_START_RESCAN:
+		LOG_DBG("Modem is starting a rescan for available networks");
+		break;
+	case EVENT_RRC_CONNECTED:
+		LOG_DBG("Modem has established an RRC connection with the network");
+		break;
+	case EVENT_NO_SUITABLE_CELLS:
+		LOG_DBG("Modem did not find any suitable cells during the scan");
+		break;
+	case EVENT_ALL_REGISTRATION_FAILED:
+		LOG_DBG("Modem failed to register to any network");
+		break;
+	default:
+		LOG_DBG("Unhandled KSTATEV for state %d", state);
+		break;
+	}
+}
+#endif
+/**
+ * @brief This function doesn't handle incoming UDP data.
+ * It is just a placeholder for verbose debug logging of incoming UDP data.
+ * +KUDP_RCV: <remote_addr>,<remote_port>,
+ */
+void hl78xx_on_udprcv(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	if (argc < 2) {
+		return;
+	}
+	HL78XX_LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[2]);
+}
+#endif
+/* Handler for +CGCONTRDP: <cid>,<bearer>,<apn>,<addr>,<dcomp>,<hcomp>,<dns1>[,<dns2>]
+ * This function is invoked by the chat layer when a CGCONTRDP URC is matched.
+ * It extracts the PDP context address, gateway and DNS servers and updates the
+ * per-instance socket_data DNS fields so dns_work_cb() can apply them.
+ */
+void hl78xx_on_cgdcontrdp(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+	const char *addr_field = NULL;
+	const char *gw_field = NULL;
+	const char *dns_field = NULL;
+	const char *apn_field = NULL;
+
+	/* Accept both comma-split argv[] or a single raw token that needs tokenizing */
+	if (argc >= 7) {
+		apn_field = argv[3];
+		addr_field = argv[4];
+		gw_field = argv[5];
+		dns_field = argv[6];
+	} else {
+		LOG_ERR("Incomplete CGCONTRDP response: argc=%d", argc);
+		return;
+	}
+
+	LOG_INF("Apn=%s", apn_field);
+	LOG_INF("Addr=%s", addr_field);
+	LOG_INF("Gw=%s", gw_field);
+	LOG_INF("DNS=%s", dns_field);
+#ifdef CONFIG_MODEM_HL78XX_APN_SOURCE_NETWORK
+	if (apn_field) {
+		hl78xx_extract_essential_part_apn(apn_field, data->identity.apn,
+						  sizeof(data->identity.apn));
+	}
+#endif
+	/* Handle address parsing: IPv4 replies sometimes embed subnet as extra
+	 * octets concatenated after the IP (e.g. "10.149.122.90.255.255.255.252").
+	 * Split and parse into the instance IPv4 fields so the interface can be
+	 * configured before the DNS resolver is invoked.
+	 */
+#ifdef CONFIG_NET_IPV4
+	if (addr_field && strchr(addr_field, '.') && !strchr(addr_field, ':')) {
+		char ip_addr[NET_IPV6_ADDR_LEN] = {0};
+		char subnet_mask[NET_IPV6_ADDR_LEN] = {0};
+
+		if (!split_ipv4_and_subnet(addr_field, ip_addr, sizeof(ip_addr), subnet_mask,
+					   sizeof(subnet_mask))) {
+			LOG_ERR("CGCONTRDP: failed to split IPv4+subnet: %s", addr_field);
+			return;
+		}
+		if (!parse_ip(true, ip_addr, &socket_data->ipv4.new_addr)) {
+			return;
+		}
+		if (!parse_ip(true, subnet_mask, &socket_data->ipv4.subnet)) {
+			return;
+		}
+		if (gw_field && !parse_ip(true, gw_field, &socket_data->ipv4.gateway)) {
+			return;
+		}
+	}
+#else
+	ARG_UNUSED(gw_field);
+#endif
+
+#ifdef CONFIG_NET_IPV6
+	if (addr_field && strchr(addr_field, ':') &&
+	    !parse_ip(false, addr_field, &socket_data->ipv6.new_addr)) {
+		return;
+	}
+#endif
+	/* Update DNS and configure interface */
+	if (!update_dns(socket_data,
+#ifdef CONFIG_NET_IPV4
+			(addr_field && strchr(addr_field, '.') && !strchr(addr_field, ':')),
+#else
+			false,
+#endif
+			dns_field ? dns_field : "")) {
+		return;
+	}
+	/* Configure the interface addresses so net_if_is_up()/address selection
+	 * will succeed before attempting to reconfigure the resolver.
+	 */
+#ifdef CONFIG_NET_IPV4
+	set_iface(socket_data, (addr_field && strchr(addr_field, '.') && !strchr(addr_field, ':')));
+#elif defined(CONFIG_NET_IPV6)
+	set_iface(socket_data, false);
+#endif
+
+	socket_data->dns.ready = false;
+	LOG_DBG("CGCONTRDP processed, dns strings: v4=%s v6=%s",
+#ifdef CONFIG_NET_IPV4
+		socket_data->dns.v4_string,
+#else
+		"<no-v4>",
+#endif
+#ifdef CONFIG_NET_IPV6
+		socket_data->dns.v6_string
+#else
+		"<no-v6>"
+#endif
+	);
+}
+/* ===== Network / Parsing Utilities ===================================
+ * Helpers that operate on IP address parsing and DNS/address helpers.
+ */
+static bool parse_ip(bool is_ipv4, const char *ip_str, void *out_addr)
+{
+	int ret = net_addr_pton(is_ipv4 ? AF_INET : AF_INET6, ip_str, out_addr);
+
+	LOG_DBG("Parsing %s address: %s -> %s", is_ipv4 ? "IPv4" : "IPv6", ip_str,
+		(ret < 0) ? "FAIL" : "OK");
+	if (ret < 0) {
+		LOG_ERR("Invalid IP address: %s", ip_str);
+		return false;
+	}
+	return true;
+}
+
+static bool update_dns(struct hl78xx_socket_data *socket_data, bool is_ipv4, const char *dns_str)
+{
+	int ret;
+
+	/* ===== Interface helpers ==============================================
+	 * Helpers that configure the network interface for IPv4/IPv6.
+	 */
+	LOG_DBG("Updating DNS (%s): %s", is_ipv4 ? "IPv4" : "IPv6", dns_str);
+#ifdef CONFIG_NET_IPV4
+	if (is_ipv4) {
+		ret = strncmp(dns_str, socket_data->dns.v4_string, strlen(dns_str));
+		if (ret != 0) {
+			LOG_DBG("New IPv4 DNS differs from current, marking dns_ready = false");
+			socket_data->dns.ready = false;
+		}
+		strncpy(socket_data->dns.v4_string, dns_str, sizeof(socket_data->dns.v4_string));
+		socket_data->dns.v4_string[sizeof(socket_data->dns.v4_string) - 1] = '\0';
+		return parse_ip(true, socket_data->dns.v4_string, &socket_data->dns.v4);
+	}
+#else
+	if (is_ipv4) {
+		LOG_DBG("IPv4 DNS reported but IPv4 disabled in build; ignoring");
+		return false;
+	}
+#endif /* CONFIG_NET_IPV4 */
+#ifdef CONFIG_NET_IPV6
+	else {
+		ret = strncmp(dns_str, socket_data->dns.v6_string, strlen(dns_str));
+		if (ret != 0) {
+			LOG_DBG("New IPv6 DNS differs from current, marking dns_ready = false");
+			socket_data->dns.ready = false;
+		}
+		strncpy(socket_data->dns.v6_string, dns_str, sizeof(socket_data->dns.v6_string));
+		socket_data->dns.v6_string[sizeof(socket_data->dns.v6_string) - 1] = '\0';
+
+		if (!parse_ip(false, socket_data->dns.v6_string, &socket_data->dns.v6)) {
+			return false;
+		}
+
+		net_addr_ntop(AF_INET6, &socket_data->dns.v6, socket_data->dns.v6_string,
+			      sizeof(socket_data->dns.v6_string));
+		LOG_DBG("Parsed IPv6 DNS: %s", socket_data->dns.v6_string);
+	}
+#endif /* CONFIG_NET_IPV6 */
+	return true;
+}
+
+static void set_iface(struct hl78xx_socket_data *socket_data, bool is_ipv4)
+{
+	if (!socket_data->net_iface) {
+		LOG_DBG("No network interface set. Skipping iface config.");
+		return;
+	}
+	LOG_DBG("Setting %s interface address...", is_ipv4 ? "IPv4" : "IPv6");
+	if (is_ipv4) {
+#ifdef CONFIG_NET_IPV4
+		if (socket_data->ipv4.addr.s_addr != 0) {
+			net_if_ipv4_addr_rm(socket_data->net_iface, &socket_data->ipv4.addr);
+		}
+		/* Use MANUAL so the stack treats this as a configured address and it is
+		 * available for source address selection immediately.
+		 */
+		if (!net_if_ipv4_addr_add(socket_data->net_iface, &socket_data->ipv4.new_addr,
+					  NET_ADDR_MANUAL, 0)) {
+			LOG_ERR("Failed to set IPv4 interface address.");
+		}
+
+		net_if_ipv4_set_netmask_by_addr(socket_data->net_iface, &socket_data->ipv4.new_addr,
+						&socket_data->ipv4.subnet);
+		net_if_ipv4_set_gw(socket_data->net_iface, &socket_data->ipv4.gateway);
+
+		net_ipaddr_copy(&socket_data->ipv4.addr, &socket_data->ipv4.new_addr);
+		LOG_DBG("IPv4 interface configuration complete.");
+
+		(void)net_if_up(socket_data->net_iface);
+#else
+		LOG_DBG("IPv4 disabled: skipping IPv4 interface configuration");
+#endif /* CONFIG_NET_IPV4 */
+	}
+#ifdef CONFIG_NET_IPV6
+	else {
+		net_if_ipv6_addr_rm(socket_data->net_iface, &socket_data->ipv6.addr);
+
+		if (!net_if_ipv6_addr_add(socket_data->net_iface, &socket_data->ipv6.new_addr,
+					  NET_ADDR_MANUAL, 0)) {
+			LOG_ERR("Failed to set IPv6 interface address.");
+		} else {
+			LOG_DBG("IPv6 interface configuration complete.");
+		}
+		/* Ensure iface up after adding address */
+		(void)net_if_up(socket_data->net_iface);
+	}
+#endif /* CONFIG_NET_IPV6 */
+}
+
+static bool split_ipv4_and_subnet(const char *combined, char *ip_out, size_t ip_out_len,
+				  char *subnet_out, size_t subnet_out_len)
+{
+	int dot_count = 0;
+	const char *ptr = combined;
+	const char *split = NULL;
+	size_t ip_len = 0;
+
+	while (*ptr && dot_count < 4) {
+		if (*ptr == '.') {
+			dot_count++;
+			if (dot_count == 4) {
+				split = ptr;
+				break;
+			}
+		}
+		ptr++;
+	}
+	if (!split) {
+		LOG_ERR("Invalid IPv4 + subnet format: %s", combined);
+		return false;
+	}
+
+	ip_len = split - combined;
+	if (ip_len >= ip_out_len) {
+		ip_len = ip_out_len - 1;
+	}
+	strncpy(ip_out, combined, ip_len);
+	ip_out[ip_len] = '\0';
+	strncpy(subnet_out, split + 1, subnet_out_len);
+	subnet_out[subnet_out_len - 1] = '\0';
+	LOG_DBG("Extracted IP: %s, Subnet: %s", ip_out, subnet_out);
+	return true;
+}
+
+/* ===== Validation ====================================================
+ * Small validation helpers used by send/recv paths.
+ */
+static int validate_socket(const struct modem_socket *sock, struct hl78xx_socket_data *socket_data)
+{
+	if (!sock) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	bool not_connected = (!sock->is_connected && sock->type != SOCK_DGRAM);
+	bool tcp_disconnected =
+		(sock->type == SOCK_STREAM &&
+		 !socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].is_connected);
+	bool udp_not_created =
+		(sock->type == SOCK_DGRAM &&
+		 !socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(sock->id)].is_created);
+
+	if (not_connected || tcp_disconnected || udp_not_created) {
+		errno = ENOTCONN;
+		return -1;
+	}
+
+	return 0;
+}
+
+/* ===== Parser helpers ================================================
+ * Helpers that implement the streaming parser for incoming socket payloads
+ * and chat end-delimiter/EOF matching logic.
+ */
+static void parser_reset(struct hl78xx_socket_data *socket_data)
+{
+	memset(&socket_data->receive_buf, 0, sizeof(socket_data->receive_buf));
+	socket_data->parser_match_found = false;
+}
+
+static void found_reset(struct hl78xx_socket_data *socket_data)
+{
+	if (!socket_data) {
+		return;
+	}
+	/* Clear all parser progress state so a new transfer can start cleanly. */
+	socket_data->parser_state = HL78XX_PARSER_IDLE;
+	socket_data->parser_match_found = false;
+	socket_data->parser_socket_data_received = false;
+	socket_data->parser_eof_detected = false;
+	socket_data->parser_ok_detected = false;
+}
+
+static bool modem_chat_parse_end_del_start(struct hl78xx_socket_data *socket_data,
+					   struct modem_chat *chat)
+{
+	if (socket_data->receive_buf.len == 0) {
+		return false;
+	}
+	/* If the last received byte matches any of the delimiter bytes, we are
+	 * starting the end-delimiter sequence. Use memchr to avoid an explicit
+	 * loop and to be clearer about intent.
+	 */
+	return memchr(chat->delimiter,
+		      socket_data->receive_buf.buf[socket_data->receive_buf.len - 1],
+		      chat->delimiter_size) != NULL;
+}
+
+static bool modem_chat_parse_end_del_complete(struct hl78xx_socket_data *socket_data,
+					      struct modem_chat *chat)
+{
+	if (socket_data->receive_buf.len < chat->delimiter_size) {
+		return false;
+	}
+
+	return memcmp(&socket_data->receive_buf
+			       .buf[socket_data->receive_buf.len - chat->delimiter_size],
+		      chat->delimiter, chat->delimiter_size) == 0;
+}
+
+static bool modem_chat_match_matches_received(struct hl78xx_socket_data *socket_data,
+					      const char *match, uint16_t match_size)
+{
+	if (socket_data->receive_buf.len < match_size) {
+		return false;
+	}
+	return memcmp(socket_data->receive_buf.buf, match, match_size) == 0;
+}
+
+static bool is_receive_buffer_full(struct hl78xx_socket_data *socket_data)
+{
+	return socket_data->receive_buf.len >= ARRAY_SIZE(socket_data->receive_buf.buf);
+}
+
+static void handle_expected_length_decrement(struct hl78xx_socket_data *socket_data)
+{
+	/* Decrement expected length if CONNECT matched and expected length > 0 */
+	if (socket_data->parser_state == HL78XX_PARSER_CONNECT_MATCHED &&
+	    socket_data->expected_buf_len > 0) {
+		socket_data->expected_buf_len--;
+	}
+}
+
+static bool is_end_delimiter_only(struct hl78xx_socket_data *socket_data)
+{
+	return socket_data->receive_buf.len == socket_data->mdata_global->chat.delimiter_size;
+}
+
+static bool is_valid_eof_index(struct hl78xx_socket_data *socket_data, uint8_t size_match)
+{
+	socket_data->parser_start_index_eof = socket_data->receive_buf.len - size_match - 2;
+	return socket_data->parser_start_index_eof < ARRAY_SIZE(socket_data->receive_buf.buf);
+}
+
+/* Handle EOF pattern: if EOF_PATTERN is found at the expected location,
+ * push socket payload (excluding EOF marker) into the ring buffer.
+ * Returns number of bytes pushed on success, 0 otherwise.
+ */
+static int handle_eof_pattern(struct hl78xx_socket_data *socket_data)
+{
+	uint8_t size_match = strlen(EOF_PATTERN);
+
+	if (socket_data->receive_buf.len < size_match + 2) {
+		return 0;
+	}
+	if (!is_valid_eof_index(socket_data, size_match)) {
+		return 0;
+	}
+	if (strncmp(&socket_data->receive_buf.buf[socket_data->parser_start_index_eof], EOF_PATTERN,
+		    size_match) == 0) {
+		int ret = ring_buf_put(socket_data->buf_pool, socket_data->receive_buf.buf,
+				       socket_data->parser_start_index_eof);
+
+		if (ret <= 0) {
+			LOG_ERR("ring_buf_put failed: %d", ret);
+			return 0;
+		}
+
+		/* Mark that payload was successfully pushed and EOF was detected */
+		socket_data->parser_socket_data_received = true;
+		socket_data->parser_eof_detected = true;
+		LOG_DBG("pushed %d bytes to ring_buf; "
+			"collected_buf_len(before)=%u",
+			ret, socket_data->collected_buf_len);
+		socket_data->collected_buf_len += ret;
+		LOG_DBG("parser_socket_data_received=1 "
+			"collected_buf_len(after)=%u",
+			socket_data->collected_buf_len);
+		return ret;
+	}
+	return 0;
+}
+
+/* Helper: centralize handling when the chat end-delimiter has been fully
+ * received. Returns true if caller should return immediately after handling.
+ */
+static bool handle_delimiter_complete(struct hl78xx_socket_data *socket_data,
+				      struct modem_chat *chat)
+{
+	if (!modem_chat_parse_end_del_complete(socket_data, chat)) {
+		return false;
+	}
+
+	if (is_end_delimiter_only(socket_data)) {
+		parser_reset(socket_data);
+		return true;
+	}
+
+	socket_data->parser_size_of_socketdata = socket_data->receive_buf.len;
+	if (socket_data->parser_state == HL78XX_PARSER_CONNECT_MATCHED &&
+	    socket_data->parser_state != HL78XX_PARSER_EOF_OK_MATCHED) {
+		size_t connect_len = strlen(CONNECT_STRING);
+		size_t connect_plus_delim = connect_len + chat->delimiter_size;
+
+		/* Case 1: Drop the initial "CONNECT" line including its CRLF */
+		if (socket_data->receive_buf.len == connect_plus_delim &&
+		    modem_chat_match_matches_received(socket_data, CONNECT_STRING,
+						      (uint16_t)connect_len)) {
+			parser_reset(socket_data);
+			return true;
+		}
+
+		/* Case 2: Try to handle EOF; only reset if EOF was actually found/pushed */
+		if (handle_eof_pattern(socket_data) > 0) {
+			parser_reset(socket_data);
+			return true;
+		}
+
+		/* Not the initial CONNECT+CRLF and no EOF yet -> keep accumulating */
+		return false;
+	}
+
+	/* For other states, treat CRLF as end-of-line and reset as before */
+	parser_reset(socket_data);
+	return true;
+}
+
+/* Convenience helper for matching an exact string against the receive buffer.
+ * This consolidates the repeated pattern of checking length and content.
+ */
+static inline bool modem_chat_match_exact(struct hl78xx_socket_data *socket_data, const char *match)
+{
+	size_t size_match = strlen(match);
+
+	if (socket_data->receive_buf.len != size_match) {
+		return false;
+	}
+	return modem_chat_match_matches_received(socket_data, match, (uint16_t)size_match);
+}
+
+static void socket_process_bytes(struct hl78xx_socket_data *socket_data, char byte)
+{
+	const size_t cme_size = strlen(CME_ERROR_STRING);
+
+	if (is_receive_buffer_full(socket_data)) {
+		LOG_WRN("Receive buffer overrun");
+		parser_reset(socket_data);
+		return;
+	}
+	socket_data->receive_buf.buf[socket_data->receive_buf.len++] = byte;
+	handle_expected_length_decrement(socket_data);
+	if (handle_delimiter_complete(socket_data, &socket_data->mdata_global->chat)) {
+		return;
+	}
+	if (modem_chat_parse_end_del_start(socket_data, &socket_data->mdata_global->chat)) {
+		return;
+	}
+	if (socket_data->parser_state != HL78XX_PARSER_ERROR_MATCHED &&
+	    socket_data->parser_state != HL78XX_PARSER_CONNECT_MATCHED) {
+		/* Exact CONNECT match: length must equal CONNECT string length */
+		if (modem_chat_match_exact(socket_data, CONNECT_STRING)) {
+			socket_data->parser_state = HL78XX_PARSER_CONNECT_MATCHED;
+			LOG_DBG("CONNECT matched. Expecting %d more bytes.",
+				socket_data->expected_buf_len);
+			return;
+		}
+		/* Partial CME ERROR match: length must be at least CME string length */
+		if (socket_data->receive_buf.len >= cme_size &&
+		    modem_chat_match_matches_received(socket_data, CME_ERROR_STRING,
+						      (uint16_t)cme_size)) {
+			socket_data->parser_state =
+				HL78XX_PARSER_ERROR_MATCHED; /* prevent further parsing */
+			LOG_ERR("CME ERROR received. Connection failed.");
+			socket_data->expected_buf_len = 0;
+			socket_data->collected_buf_len = 0;
+			parser_reset(socket_data);
+			socket_data->socket_data_error = true;
+			k_sem_give(&socket_data->mdata_global->script_stopped_sem_rx_int);
+			return;
+		}
+	}
+	if (socket_data->parser_state == HL78XX_PARSER_CONNECT_MATCHED &&
+	    socket_data->parser_state != HL78XX_PARSER_EOF_OK_MATCHED &&
+	    modem_chat_match_exact(socket_data, OK_STRING)) {
+		socket_data->parser_state = HL78XX_PARSER_EOF_OK_MATCHED;
+		/* Mark that OK was observed. Payload may have already been pushed by EOF handler.
+		 */
+		socket_data->parser_ok_detected = true;
+		LOG_DBG("OK matched. parser_ok_detected=%d parser_socket_data_received=%d "
+			"collected=%u",
+			socket_data->parser_ok_detected, socket_data->parser_socket_data_received,
+			socket_data->collected_buf_len);
+	}
+}
+
+/* ===== Modem pipe handlers ===========================================
+ * Handlers and callbacks for modem pipe events (receive/transmit).
+ */
+static int modem_process_handler(struct hl78xx_data *data)
+{
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+	char work_buf_local[HL78XX_UART_PIPE_WORK_SOCKET_BUFFER_SIZE] = {0};
+	int recv_len = 0;
+	int work_len = 0;
+	/* If no more data is expected, set leftover state and return */
+	if (socket_data->expected_buf_len == 0) {
+		LOG_DBG("No more data expected");
+		atomic_set_bit(&socket_data->mdata_global->state_leftover,
+			       MODEM_SOCKET_DATA_LEFTOVER_STATE_BIT);
+		return 0;
+	}
+
+	/* Use a small stack buffer for the pipe read to avoid TU-global BSS */
+	work_len = MIN(sizeof(work_buf_local), socket_data->expected_buf_len);
+	recv_len =
+		modem_pipe_receive(socket_data->mdata_global->uart_pipe, work_buf_local, work_len);
+	if (recv_len <= 0) {
+		return recv_len;
+	}
+
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+	LOG_HEXDUMP_DBG(work_buf_local, recv_len, "Received bytes:");
+#endif /* CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG */
+	for (int i = 0; i < recv_len; i++) {
+		socket_process_bytes(socket_data, work_buf_local[i]);
+	}
+
+	LOG_DBG("post-process state=%d recv_len=%d recv_buf.len=%u "
+		"expected=%u collected=%u socket_data_received=%d",
+		socket_data->parser_state, recv_len, socket_data->receive_buf.len,
+		socket_data->expected_buf_len, socket_data->collected_buf_len,
+		socket_data->parser_socket_data_received);
+
+	/* Check if we've completed reception */
+	if (socket_data->parser_eof_detected && socket_data->parser_ok_detected &&
+	    socket_data->parser_socket_data_received) {
+		LOG_DBG("All data received: %d bytes", socket_data->parser_size_of_socketdata);
+		socket_data->expected_buf_len = 0;
+		LOG_DBG("About to give RX semaphore (eof=%d ok=%d socket_data_received=%d "
+			"collected=%u)",
+			socket_data->parser_eof_detected, socket_data->parser_ok_detected,
+			socket_data->parser_socket_data_received, socket_data->collected_buf_len);
+		k_sem_give(&socket_data->mdata_global->script_stopped_sem_rx_int);
+		/* Clear parser progress after the receiver has been notified */
+		found_reset(socket_data);
+	}
+	return 0;
+}
+
+static void modem_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
+				void *user_data)
+{
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+
+	switch (event) {
+	case MODEM_PIPE_EVENT_RECEIVE_READY:
+		(void)modem_process_handler(data);
+		break;
+
+	case MODEM_PIPE_EVENT_TRANSMIT_IDLE:
+		k_sem_give(&data->script_stopped_sem_tx_int);
+		break;
+
+	default:
+		LOG_DBG("Unhandled event: %d", event);
+		break;
+	}
+}
+
+void notif_carrier_off(const struct device *dev)
+{
+	struct hl78xx_data *data = dev->data;
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+
+	net_if_carrier_off(socket_data->net_iface);
+}
+
+void notif_carrier_on(const struct device *dev)
+{
+	struct hl78xx_data *data = dev->data;
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+
+	net_if_carrier_on(socket_data->net_iface);
+}
+
+void iface_status_work_cb(struct hl78xx_data *data, modem_chat_script_callback script_user_callback)
+{
+
+	const char *cmd = "AT+CGCONTRDP=1";
+	int ret = 0;
+
+	ret = modem_dynamic_cmd_send(data, script_user_callback, cmd, strlen(cmd),
+				     hl78xx_get_cgdcontrdp_match(), 1, false);
+	if (ret < 0) {
+		LOG_ERR("Failed to send AT+CGCONTRDP command: %d", ret);
+		return;
+	}
+}
+
+void dns_work_cb(const struct device *dev, bool hard_reset)
+{
+#if defined(CONFIG_DNS_RESOLVER) && !defined(CONFIG_DNS_SERVER_IP_ADDRESSES)
+	int ret;
+	struct hl78xx_data *data = dev->data;
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+	struct dns_resolve_context *dnsCtx;
+	struct sockaddr temp_addr;
+	bool valid_address = false;
+	bool retry = false;
+	const char *const dns_servers_str[DNS_SERVERS_COUNT] = {
+#ifdef CONFIG_NET_IPV6
+		socket_data->dns.v6_string,
+#endif
+#ifdef CONFIG_NET_IPV4
+		socket_data->dns.v4_string,
+#endif
+		NULL};
+	const char *dns_servers_wrapped[ARRAY_SIZE(dns_servers_str)];
+
+	if (hard_reset) {
+		LOG_DBG("Resetting DNS resolver");
+		dnsCtx = dns_resolve_get_default();
+		if (!dnsCtx) {
+			LOG_WRN("No default DNS resolver context available; skipping "
+				"reconfigure");
+			socket_data->dns.ready = true;
+			return;
+		}
+		if (dnsCtx->state != DNS_RESOLVE_CONTEXT_INACTIVE) {
+			dns_resolve_close(dnsCtx);
+		}
+		socket_data->dns.ready = false;
+	}
+
+#ifdef CONFIG_NET_IPV6
+	valid_address = net_ipaddr_parse(socket_data->dns.v6_string,
+					 strlen(socket_data->dns.v6_string), &temp_addr);
+	if (!valid_address && IS_ENABLED(CONFIG_NET_IPV4)) {
+		/* IPv6 DNS string is not valid, replace it with IPv4 address and recheck */
+#ifdef CONFIG_NET_IPV4
+		strncpy(socket_data->dns.v6_string, socket_data->dns.v4_string,
+			sizeof(socket_data->dns.v4_string) - 1);
+		valid_address = net_ipaddr_parse(socket_data->dns.v6_string,
+						 strlen(socket_data->dns.v6_string), &temp_addr);
+#endif
+	}
+#elif defined(CONFIG_NET_IPV4)
+	valid_address = net_ipaddr_parse(socket_data->dns.v4_string,
+					 strlen(socket_data->dns.v4_string), &temp_addr);
+#else
+	/* No IP stack configured */
+	valid_address = false;
+#endif
+	if (!valid_address) {
+		LOG_WRN("No valid DNS address!");
+		return;
+	}
+	if (!socket_data->net_iface || !net_if_is_up(socket_data->net_iface) ||
+	    socket_data->dns.ready) {
+		LOG_DBG("DNS already ready or net_iface problem %d %d %d", !socket_data->net_iface,
+			!net_if_is_up(socket_data->net_iface), socket_data->dns.ready);
+		return;
+	}
+	memcpy(dns_servers_wrapped, dns_servers_str, sizeof(dns_servers_wrapped));
+	/* set new DNS addr in DNS resolver */
+	LOG_DBG("Refresh DNS resolver");
+	dnsCtx = dns_resolve_get_default();
+	ret = dns_resolve_reconfigure(dnsCtx, dns_servers_wrapped, NULL, DNS_SOURCE_MANUAL);
+	if (ret < 0) {
+		LOG_ERR("dns_resolve_reconfigure fail (%d)", ret);
+		retry = true;
+	} else {
+		LOG_DBG("DNS ready");
+		socket_data->dns.ready = true;
+	}
+	if (retry) {
+		LOG_WRN("DNS not ready, scheduling a retry");
+	}
+#endif
+}
+
+static int on_cmd_sockread_common(int socket_id, uint16_t socket_data_length, uint16_t len,
+				  void *user_data)
+{
+	struct modem_socket *sock;
+	struct socket_read_data *sock_data;
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+	int ret = 0;
+
+	sock = modem_socket_from_fd(&socket_data->socket_config, socket_id);
+	if (!sock) {
+		LOG_ERR("Socket not found! (%d)", socket_id);
+		return -EINVAL;
+	}
+	sock_data = sock->data;
+	if (!sock_data) {
+		LOG_ERR("Socket data missing! Ignoring (%d)", socket_id);
+		return -EINVAL;
+	}
+	if (socket_data->socket_data_error && socket_data->collected_buf_len == 0) {
+		errno = ECONNABORTED;
+		return -ECONNABORTED;
+	}
+	if ((len <= 0) || socket_data_length <= 0 || socket_data->collected_buf_len < (size_t)len) {
+		LOG_ERR("%d Invalid data length: %d %d %d Aborting!", __LINE__, socket_data_length,
+			(int)len, socket_data->collected_buf_len);
+		return -EAGAIN;
+	}
+	if (len < socket_data_length) {
+		LOG_DBG("Incomplete data received! Expected: %d, Received: %d", socket_data_length,
+			len);
+		return -EAGAIN;
+	}
+	ret = ring_buf_get(socket_data->buf_pool, sock_data->recv_buf, len);
+	if (ret != len) {
+		LOG_ERR("%d Data retrieval mismatch: expected %u, got %d", __LINE__, len, ret);
+		return -EAGAIN;
+	}
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+	LOG_HEXDUMP_DBG(sock_data->recv_buf, ret, "Received Data:");
+#endif /* CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG */
+	if (sock_data->recv_buf_len < (size_t)len) {
+		LOG_ERR("Buffer overflow! Received: %zu vs. Available: %zu", len,
+			sock_data->recv_buf_len);
+		return -EINVAL;
+	}
+	if ((size_t)len != (size_t)socket_data_length) {
+		LOG_ERR("Data mismatch! Copied: %zu vs. Received: %d", len, socket_data_length);
+		return -EINVAL;
+	}
+	sock_data->recv_read_len = len;
+	/* Remove packet from list */
+	modem_socket_next_packet_size(&socket_data->socket_config, sock);
+	modem_socket_packet_size_update(&socket_data->socket_config, sock, -socket_data_length);
+	socket_data->collected_buf_len = 0;
+	return len;
+}
+
+int modem_handle_data_capture(size_t target_len, struct hl78xx_data *data)
+{
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+
+	return on_cmd_sockread_common(socket_data->current_sock_fd, socket_data->sizeof_socket_data,
+				      target_len, data);
+}
+
+static int extract_ip_family_and_port(const struct sockaddr *addr, int *af, uint16_t *port)
+{
+#if defined(CONFIG_NET_IPV6)
+	if (addr->sa_family == AF_INET6) {
+		*port = ntohs(net_sin6(addr)->sin6_port);
+		*af = MDM_HL78XX_SOCKET_AF_IPV6;
+	} else {
+#endif /* CONFIG_NET_IPV6 */
+#if defined(CONFIG_NET_IPV4)
+		if (addr->sa_family == AF_INET) {
+			*port = ntohs(net_sin(addr)->sin_port);
+			*af = MDM_HL78XX_SOCKET_AF_IPV4;
+		} else {
+#endif /* CONFIG_NET_IPV4 */
+			errno = EAFNOSUPPORT;
+			return -1;
+#if defined(CONFIG_NET_IPV4)
+		}
+#endif /* CONFIG_NET_IPV4 */
+#if defined(CONFIG_NET_IPV6)
+	}
+#endif /* CONFIG_NET_IPV6 */
+	return 0;
+}
+
+static int format_ip_and_setup_tls(struct hl78xx_socket_data *socket_data,
+				   const struct sockaddr *addr, char *ip_str, size_t ip_str_len,
+				   struct modem_socket *sock)
+{
+	int ret = modem_context_sprint_ip_addr(addr, ip_str, ip_str_len);
+
+	if (ret != 0) {
+		LOG_ERR("Failed to format IP!");
+		errno = ENOMEM;
+		return -1;
+	}
+	if (sock->ip_proto == IPPROTO_TCP) {
+		/* Determine actual length of the formatted IP string (it may be
+		 * shorter than the provided buffer size). Copy at most
+		 * MDM_MAX_HOSTNAME_LEN - 1 bytes and ensure NUL-termination to
+		 * avoid writing past the hostname buffer.
+		 */
+		size_t actual_len = strnlen(ip_str, ip_str_len);
+		size_t copy_len = MIN(actual_len, (size_t)MDM_MAX_HOSTNAME_LEN - 1);
+
+		if (copy_len > 0) {
+			memcpy(socket_data->tls.hostname, ip_str, copy_len);
+		}
+		socket_data->tls.hostname[copy_len] = '\0';
+		socket_data->tls.hostname_set = false;
+	}
+	return 0;
+}
+
+static int send_tcp_or_tls_config(struct modem_socket *sock, uint16_t dst_port, int af, int mode,
+				  struct hl78xx_socket_data *socket_data)
+{
+	int ret = 0;
+	char cmd_buf[sizeof("AT+KTCPCFG=#,#,\"" MODEM_HL78XX_ADDRESS_FAMILY_FORMAT
+			    "\",#####,,,,#,,#") +
+		     MDM_MAX_HOSTNAME_LEN + NET_IPV6_ADDR_LEN];
+
+	snprintk(cmd_buf, sizeof(cmd_buf), "AT+KTCPCFG=1,%d,\"%s\",%u,,,,%d,%s,0", mode,
+		 socket_data->tls.hostname, dst_port, af, mode == 3 ? "0" : "");
+	ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf),
+				     hl78xx_get_ktcpcfg_match(), 1, false);
+	if (ret < 0 ||
+	    socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].is_created == false) {
+		LOG_ERR("%s ret:%d", cmd_buf, ret);
+		modem_socket_put(&socket_data->socket_config, sock->sock_fd);
+		/* Map negative internal return codes to positive errno; fall back to EIO
+		 * when the code is non-negative but the operation failed.
+		 */
+		hl78xx_set_errno_from_code(ret);
+		return -1;
+	}
+	return 0;
+}
+
+static int send_udp_config(const struct sockaddr *addr, struct hl78xx_socket_data *socket_data,
+			   struct modem_socket *sock)
+{
+	int ret = 0;
+	char cmd_buf[64];
+	uint8_t display_data_urc = 0;
+
+#if defined(CONFIG_MODEM_HL78XX_SOCKET_UDP_DISPLAY_DATA_URC)
+	display_data_urc = CONFIG_MODEM_HL78XX_SOCKET_UDP_DISPLAY_DATA_URC;
+#endif
+	snprintk(cmd_buf, sizeof(cmd_buf), "AT+KUDPCFG=1,%u,,%d,,,%d,%d", 0, display_data_urc,
+		 (addr->sa_family - 1), 0);
+
+	ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf),
+				     hl78xx_get_kudpind_match(), 1, false);
+	if (ret < 0) {
+		goto error;
+	}
+	return 0;
+error:
+	LOG_ERR("%s ret:%d", cmd_buf, ret);
+	modem_socket_put(&socket_data->socket_config, sock->sock_fd);
+	hl78xx_set_errno_from_code(ret);
+	return -1;
+}
+
+static int create_socket(struct modem_socket *sock, const struct sockaddr *addr,
+			 struct hl78xx_socket_data *data)
+{
+	LOG_DBG("entry fd=%d id=%d", sock->sock_fd, sock->id);
+	int af;
+	uint16_t dst_port;
+	char ip_str[NET_IPV6_ADDR_LEN];
+	bool is_udp;
+	int mode;
+	int ret;
+	/* save destination address */
+	memcpy(&sock->dst, addr, sizeof(*addr));
+	if (extract_ip_family_and_port(addr, &af, &dst_port) < 0) {
+		return -1;
+	}
+	if (format_ip_and_setup_tls(data, addr, ip_str, sizeof(ip_str), sock) < 0) {
+		return -1;
+	}
+	is_udp = (sock->ip_proto == IPPROTO_UDP);
+	if (is_udp) {
+		ret = send_udp_config(addr, data, sock);
+		LOG_DBG("send_udp_config returned %d", ret);
+		return ret;
+	}
+	mode = (sock->ip_proto == IPPROTO_TLS_1_2) ? 3 : 0;
+	/* only TCP and TLS are supported */
+	if (sock->ip_proto != IPPROTO_TCP && sock->ip_proto != IPPROTO_TLS_1_2) {
+		LOG_ERR("Unsupported protocol: %d", sock->ip_proto);
+		errno = EPROTONOSUPPORT;
+		return -1;
+	}
+	LOG_DBG("TCP/TLS socket, calling send_tcp_or_tls_config af=%d port=%u "
+		"mode=%d",
+		af, dst_port, mode);
+	ret = send_tcp_or_tls_config(sock, dst_port, af, mode, data);
+	LOG_DBG("send_tcp_or_tls_config returned %d", ret);
+	return ret;
+}
+
+static int socket_close(struct hl78xx_socket_data *socket_data, struct modem_socket *sock)
+{
+	char buf[sizeof("AT+KTCPCLOSE=##\r")];
+	int ret = 0;
+
+	if (sock->ip_proto == IPPROTO_UDP) {
+		snprintk(buf, sizeof(buf), "AT+KUDPCLOSE=%d", sock->id);
+	} else {
+		snprintk(buf, sizeof(buf), "AT+KTCPCLOSE=%d", sock->id);
+	}
+	ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, buf, strlen(buf),
+				     hl78xx_get_sockets_allow_matches(),
+				     hl78xx_get_sockets_allow_matches_size(), false);
+	if (ret < 0) {
+		LOG_ERR("%s ret:%d", buf, ret);
+	}
+	return ret;
+}
+
+static int socket_delete(struct hl78xx_socket_data *socket_data, struct modem_socket *sock)
+{
+	char buf[sizeof("AT+KTCPDEL=##\r")];
+	int ret = 0;
+
+	if (sock->ip_proto == IPPROTO_UDP) {
+		/**
+		 * snprintk(buf, sizeof(buf), "AT+KUDPDEL=%d", sock->id);
+		 * No need to delete udp config here according to ref guide. The at UDPCLOSE
+		 * automatically deletes the session
+		 */
+		return 0;
+	}
+	snprintk(buf, sizeof(buf), "AT+KTCPDEL=%d", sock->id);
+	ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, buf, strlen(buf),
+				     hl78xx_get_sockets_allow_matches(),
+				     hl78xx_get_sockets_allow_matches_size(), false);
+	if (ret < 0) {
+		LOG_ERR("%s ret:%d", buf, ret);
+	}
+	return ret;
+}
+
+/* ===== Socket Offload OPS ======================================== */
+
+static int offload_socket(int family, int type, int proto)
+{
+	int ret;
+	/* defer modem's socket create call to bind(); use accessor and check */
+	struct hl78xx_socket_data *g = hl78xx_get_socket_global();
+
+	HL78XX_LOG_DBG("%d %d %d %d", __LINE__, family, type, proto);
+
+	if (!g) {
+		LOG_ERR("Socket global not initialized");
+		errno = ENODEV;
+		return -1;
+	}
+	ret = modem_socket_get(&g->socket_config, family, type, proto);
+	if (ret < 0) {
+		hl78xx_set_errno_from_code(ret);
+		return -1;
+	}
+	errno = 0;
+	return ret;
+}
+
+static int offload_close(void *obj)
+{
+	struct modem_socket *sock = (struct modem_socket *)obj;
+	struct hl78xx_socket_data *socket_data = NULL;
+
+	if (!sock) {
+		return -EINVAL;
+	}
+	/* Recover the containing instance; guard in case sock isn't from this driver */
+	socket_data = hl78xx_get_socket_global();
+	if (!socket_data || !socket_data->offload_dev ||
+	    socket_data->offload_dev->data != socket_data) {
+		LOG_WRN("parent mismatch: parent != offload_dev->data (%p != %p)", socket_data,
+			socket_data ? socket_data->offload_dev->data : NULL);
+		errno = EINVAL;
+		return -1;
+	}
+	/* make sure socket is allocated and assigned an id */
+	if (modem_socket_id_is_assigned(&socket_data->socket_config, sock) == false) {
+		return 0;
+	}
+	if (validate_socket(sock, socket_data) == 0) {
+		socket_close(socket_data, sock);
+		socket_delete(socket_data, sock);
+		modem_socket_put(&socket_data->socket_config, sock->sock_fd);
+		sock->is_connected = false;
+	}
+	/* Consider here successfully socket is closed */
+	return 0;
+}
+
+static int offload_bind(void *obj, const struct sockaddr *addr, socklen_t addrlen)
+{
+	struct modem_socket *sock = (struct modem_socket *)obj;
+	struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+	int ret = 0;
+
+	if (!sock || !socket_data || !socket_data->offload_dev) {
+		errno = EINVAL;
+		return -1;
+	}
+	LOG_DBG("entry for socket fd=%d id=%d", ((struct modem_socket *)obj)->sock_fd,
+		((struct modem_socket *)obj)->id);
+	/*  Save bind address information */
+	memcpy(&sock->src, addr, sizeof(*addr));
+	/* Check if socket is allocated */
+	if (modem_socket_is_allocated(&socket_data->socket_config, sock)) {
+		/* Trigger socket creation */
+		ret = create_socket(sock, addr, socket_data);
+		LOG_DBG("create_socket returned %d", ret);
+		if (ret < 0) {
+			LOG_ERR("%d %s SOCKET CREATION", __LINE__, __func__);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int offload_connect(void *obj, const struct sockaddr *addr, socklen_t addrlen)
+{
+	struct modem_socket *sock = (struct modem_socket *)obj;
+	struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+	int ret = 0;
+	char cmd_buf[sizeof("AT+KTCPCFG=#\r")];
+	char ip_str[NET_IPV6_ADDR_LEN];
+
+	if (!addr || !socket_data || !socket_data->offload_dev) {
+		errno = EINVAL;
+		return -1;
+	}
+	if (!hl78xx_is_registered(socket_data->mdata_global)) {
+		errno = ENETUNREACH;
+		return -1;
+	}
+	/* make sure socket has been allocated */
+	if (modem_socket_is_allocated(&socket_data->socket_config, sock) == false) {
+		LOG_ERR("Invalid socket_id(%d) from fd:%d", sock->id, sock->sock_fd);
+		errno = EINVAL;
+		return -1;
+	}
+	/* make sure we've created the socket */
+	if (modem_socket_id_is_assigned(&socket_data->socket_config, sock) == false) {
+		LOG_DBG("%d no socket assigned", __LINE__);
+		if (create_socket(sock, addr, socket_data) < 0) {
+			return -1;
+		}
+	}
+	memcpy(&sock->dst, addr, sizeof(*addr));
+	/* skip socket connect if UDP */
+	if (sock->ip_proto == IPPROTO_UDP) {
+		errno = 0;
+		return 0;
+	}
+	ret = modem_context_sprint_ip_addr(addr, ip_str, sizeof(ip_str));
+	if (ret != 0) {
+		hl78xx_set_errno_from_code(ret);
+		LOG_ERR("Error formatting IP string %d", ret);
+		return -1;
+	}
+	/* send connect command */
+	snprintk(cmd_buf, sizeof(cmd_buf), "AT+KTCPCNX=%d", sock->id);
+	ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf),
+				     hl78xx_get_ktcpind_match(), 1, false);
+	if (ret < 0 ||
+	    socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].is_connected == false) {
+		sock->is_connected = false;
+		LOG_ERR("%s ret:%d", cmd_buf, ret);
+		/* Map tcp_conn_status.err_code:
+		 * - positive values are assumed to be direct POSIX errno values -> pass
+		 * through
+		 * - zero or unknown -> use conservative EIO
+		 */
+		errno = (socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].err_code > 0)
+				? socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)]
+					  .err_code
+				: EIO;
+		return -1;
+	}
+	sock->is_connected = true;
+	errno = 0;
+	return 0;
+}
+
+static bool validate_recv_args(void *buf, size_t len, int flags)
+{
+	if (!buf || len == 0) {
+		errno = EINVAL;
+		return false;
+	}
+	if (flags & ZSOCK_MSG_PEEK) {
+		errno = ENOTSUP;
+		return false;
+	}
+	return true;
+}
+
+static int wait_for_data_if_needed(struct hl78xx_socket_data *socket_data,
+				   struct modem_socket *sock, int flags)
+{
+	int size = modem_socket_next_packet_size(&socket_data->socket_config, sock);
+
+	if (size > 0) {
+		return size;
+	}
+	if (flags & ZSOCK_MSG_DONTWAIT) {
+		errno = EAGAIN;
+		return -1;
+	}
+	if (validate_socket(sock, socket_data) == -1) {
+		errno = 0;
+		return 0;
+	}
+
+	modem_socket_wait_data(&socket_data->socket_config, sock);
+	return modem_socket_next_packet_size(&socket_data->socket_config, sock);
+}
+
+static void prepare_read_command(struct hl78xx_socket_data *socket_data, char *sendbuf,
+				 size_t bufsize, struct modem_socket *sock, size_t read_size)
+{
+	snprintk(sendbuf, bufsize, "AT+K%sRCV=%d,%zd%s",
+		 sock->ip_proto == IPPROTO_UDP ? "UDP" : "TCP", sock->id, read_size,
+		 socket_data->mdata_global->chat.delimiter);
+}
+
+/* Perform the receive transaction: release chat, attach pipe, wait for tx sem,
+ * transmit read command, wait for rx sem and capture data. Returns 0 on
+ * success or a negative code which will be mapped by caller.
+ */
+static int hl78xx_perform_receive_transaction(struct hl78xx_socket_data *socket_data,
+					      const char *sendbuf)
+{
+	int rv;
+	int ret;
+
+	modem_chat_release(&socket_data->mdata_global->chat);
+	modem_pipe_attach(socket_data->mdata_global->uart_pipe, modem_pipe_callback,
+			  socket_data->mdata_global);
+
+	rv = k_sem_take(&socket_data->mdata_global->script_stopped_sem_tx_int, K_FOREVER);
+	if (rv < 0) {
+		LOG_ERR("%s: k_sem_take(tx) returned %d", __func__, rv);
+		return rv;
+	}
+
+	ret = modem_pipe_transmit(socket_data->mdata_global->uart_pipe, (const uint8_t *)sendbuf,
+				  strlen(sendbuf));
+	if (ret < 0) {
+		LOG_ERR("Error sending read command: %d", ret);
+		return ret;
+	}
+	rv = k_sem_take(&socket_data->mdata_global->script_stopped_sem_rx_int, K_FOREVER);
+	if (rv < 0) {
+		return rv;
+	}
+
+	rv = modem_handle_data_capture(socket_data->sizeof_socket_data, socket_data->mdata_global);
+	if (rv < 0) {
+		return rv;
+	}
+
+	return 0;
+}
+
+static void setup_socket_data(struct hl78xx_socket_data *socket_data, struct modem_socket *sock,
+			      struct socket_read_data *sock_data, void *buf, size_t len,
+			      struct sockaddr *from, uint16_t read_size)
+{
+	memset(sock_data, 0, sizeof(*sock_data));
+	sock_data->recv_buf = buf;
+	sock_data->recv_buf_len = len;
+	sock_data->recv_addr = from;
+	sock->data = sock_data;
+
+	socket_data->sizeof_socket_data = read_size;
+	socket_data->requested_socket_id = sock->id;
+	socket_data->current_sock_fd = sock->sock_fd;
+	socket_data->expected_buf_len = read_size + sizeof("\r\n") - 1 +
+					socket_data->mdata_global->buffers.eof_pattern_size +
+					sizeof(MODEM_STREAM_END_WORD) - 1;
+	socket_data->collected_buf_len = 0;
+	socket_data->socket_data_error = false;
+}
+
+static void check_tcp_state_if_needed(struct hl78xx_socket_data *socket_data,
+				      struct modem_socket *sock)
+{
+	const char *check_ktcp_stat = "AT+KTCPSTAT";
+	/* Only check for TCP sockets */
+	if (sock->type != SOCK_STREAM) {
+		return;
+	}
+	if (atomic_test_and_clear_bit(&socket_data->mdata_global->state_leftover,
+				      MODEM_SOCKET_DATA_LEFTOVER_STATE_BIT) &&
+	    sock && sock->ip_proto == IPPROTO_TCP) {
+		modem_dynamic_cmd_send(socket_data->mdata_global, NULL, check_ktcp_stat,
+				       strlen(check_ktcp_stat), hl78xx_get_ktcp_state_match(), 1,
+				       true);
+	}
+}
+
+static ssize_t offload_recvfrom(void *obj, void *buf, size_t len, int flags, struct sockaddr *from,
+				socklen_t *fromlen)
+{
+	struct modem_socket *sock = (struct modem_socket *)obj;
+	struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+	char sendbuf[sizeof("AT+KUDPRCV=#,##########\r\n")];
+	struct socket_read_data sock_data;
+	int next_packet_size = 0;
+	uint32_t max_data_length = 0;
+	uint16_t read_size = 0;
+	int trv = 0;
+	int ret;
+
+	if (!sock || !socket_data || !socket_data->offload_dev) {
+		errno = EINVAL;
+		return -1;
+	}
+	/* If modem is not registered yet, propagate EAGAIN to indicate try again
+	 * later. However, if the socket simply isn't connected (validate_socket
+	 * returns -1) we return 0 with errno cleared so upper layers (eg. DNS
+	 * dispatcher) treat this as no data available rather than an error and
+	 * avoid noisy repeated error logs.
+	 */
+	if (!hl78xx_is_registered(socket_data->mdata_global)) {
+		errno = EAGAIN;
+		return -1;
+	}
+	if (validate_socket(sock, socket_data) == -1) {
+		errno = 0;
+		return 0;
+	}
+
+	if (!validate_recv_args(buf, len, flags)) {
+		return -1;
+	}
+	ret = k_mutex_lock(&socket_data->mdata_global->tx_lock, K_SECONDS(1));
+	if (ret < 0) {
+		LOG_ERR("Failed to acquire TX lock: %d", ret);
+		hl78xx_set_errno_from_code(ret);
+		return -1;
+	}
+	next_packet_size = wait_for_data_if_needed(socket_data, sock, flags);
+	if (next_packet_size <= 0) {
+		ret = next_packet_size;
+		goto exit;
+	}
+	max_data_length =
+		MDM_MAX_DATA_LENGTH - (socket_data->mdata_global->buffers.eof_pattern_size +
+				       sizeof(MODEM_STREAM_STARTER_WORD) - 1);
+	/* limit read size to modem max data length */
+	next_packet_size = MIN(next_packet_size, max_data_length);
+	/* limit read size to user buffer length */
+	read_size = MIN(next_packet_size, len);
+	/* prepare socket data for the read operation */
+	setup_socket_data(socket_data, sock, &sock_data, buf, len, from, read_size);
+	prepare_read_command(socket_data, sendbuf, sizeof(sendbuf), sock, read_size);
+	HL78XX_LOG_DBG("%d socket_fd: %d, socket_id: %d, expected_data_len: %d", __LINE__,
+		       socket_data->current_sock_fd, socket_data->requested_socket_id,
+		       socket_data->expected_buf_len);
+	LOG_HEXDUMP_DBG(sendbuf, strlen(sendbuf), "sending");
+	trv = hl78xx_perform_receive_transaction(socket_data, sendbuf);
+	if (trv < 0) {
+		hl78xx_set_errno_from_code(trv);
+		ret = -1;
+		goto exit;
+	}
+	if (from && fromlen) {
+		*fromlen = sizeof(sock->dst);
+		memcpy(from, &sock->dst, *fromlen);
+	}
+	errno = 0;
+	ret = sock_data.recv_read_len;
+exit:
+	k_mutex_unlock(&socket_data->mdata_global->tx_lock);
+	modem_chat_attach(&socket_data->mdata_global->chat, socket_data->mdata_global->uart_pipe);
+	socket_data->expected_buf_len = 0;
+	check_tcp_state_if_needed(socket_data, sock);
+	return ret;
+}
+
+int check_if_any_socket_connected(const struct device *dev)
+{
+	struct hl78xx_data *data = dev->data;
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+	struct modem_socket_config *cfg = &socket_data->socket_config;
+
+	k_sem_take(&cfg->sem_lock, K_FOREVER);
+	for (int i = 0; i < cfg->sockets_len; i++) {
+		if (cfg->sockets[i].is_connected) {
+			/* if there is any socket  connected */
+			k_sem_give(&cfg->sem_lock);
+			return true;
+		}
+	}
+	k_sem_give(&cfg->sem_lock);
+	return false;
+}
+
+/* ===== Send / Receive helpers ========================================
+ * Helpers used by sendto/recv paths, preparing commands and transmitting
+ * data over the modem pipe.
+ */
+static int prepare_send_cmd(const struct modem_socket *sock, const struct sockaddr *dst_addr,
+			    size_t buf_len, char *cmd_buf, size_t cmd_buf_size)
+{
+	int ret = 0;
+
+	if (sock->ip_proto == IPPROTO_UDP) {
+		char ip_str[NET_IPV6_ADDR_LEN];
+		uint16_t dst_port = 0;
+
+		ret = modem_context_sprint_ip_addr(dst_addr, ip_str, sizeof(ip_str));
+		if (ret < 0) {
+			LOG_ERR("Error formatting IP string %d", ret);
+			return ret;
+		}
+		ret = modem_context_get_addr_port(dst_addr, &dst_port);
+		if (ret < 0) {
+			LOG_ERR("Error getting port from IP address %d", ret);
+			return ret;
+		}
+		snprintk(cmd_buf, cmd_buf_size, "AT+KUDPSND=%d,\"%s\",%u,%zu", sock->id, ip_str,
+			 dst_port, buf_len);
+		return 0;
+	}
+
+	/* Default to TCP-style send command */
+	snprintk(cmd_buf, cmd_buf_size, "AT+KTCPSND=%d,%zu", sock->id, buf_len);
+	return 0;
+}
+
+static int send_data_buffer(struct hl78xx_socket_data *socket_data, const char *buf,
+			    const size_t buf_len, int *sock_written)
+{
+	uint32_t offset = 0;
+	int len = buf_len;
+	int ret = 0;
+
+	if (len == 0) {
+		LOG_DBG("%d No data to send", __LINE__);
+		return 0;
+	}
+	while (len > 0) {
+		LOG_DBG("waiting for TX semaphore (offset=%u len=%d)", offset, len);
+		if (k_sem_take(&socket_data->mdata_global->script_stopped_sem_tx_int, K_FOREVER) <
+		    0) {
+			LOG_ERR("%s: k_sem_take(tx) failed", __func__);
+			return -1;
+		}
+		ret = modem_pipe_transmit(socket_data->mdata_global->uart_pipe,
+					  ((const uint8_t *)buf) + offset, len);
+		if (ret <= 0) {
+			LOG_ERR("Transmit error %d", ret);
+			return -1;
+		}
+		offset += ret;
+		len -= ret;
+		*sock_written += ret;
+	}
+	return 0;
+}
+
+static int validate_and_prepare(struct modem_socket *sock, const struct sockaddr **dst_addr,
+				size_t *buf_len, char *cmd_buf, size_t cmd_buf_len)
+{
+	/* Validate args and prepare send command */
+	if (!sock) {
+		errno = EINVAL;
+		return -1;
+	}
+	if (sock->type != SOCK_DGRAM && !sock->is_connected) {
+		errno = ENOTCONN;
+		return -1;
+	}
+	if (!*dst_addr && sock->ip_proto == IPPROTO_UDP) {
+		*dst_addr = &sock->dst;
+	}
+	if (*buf_len > MDM_MAX_DATA_LENGTH) {
+		if (sock->type == SOCK_DGRAM) {
+			errno = EMSGSIZE;
+			return -1;
+		}
+		*buf_len = MDM_MAX_DATA_LENGTH;
+	}
+	/* Consolidated send command helper handles UDP vs TCP formatting */
+	return prepare_send_cmd(sock, *dst_addr, *buf_len, cmd_buf, cmd_buf_len);
+}
+
+static int transmit_regular_data(struct hl78xx_socket_data *socket_data, const char *buf,
+				 size_t buf_len, int *sock_written)
+{
+	int ret;
+
+	ret = send_data_buffer(socket_data, buf, buf_len, sock_written);
+	if (ret < 0) {
+		return ret;
+	}
+	ret = k_sem_take(&socket_data->mdata_global->script_stopped_sem_tx_int, K_FOREVER);
+	if (ret < 0) {
+		LOG_ERR("%s: k_sem_take(tx) returned %d", __func__, ret);
+		return ret;
+	}
+	return modem_pipe_transmit(socket_data->mdata_global->uart_pipe,
+				   (uint8_t *)socket_data->mdata_global->buffers.eof_pattern,
+				   socket_data->mdata_global->buffers.eof_pattern_size);
+}
+
+/* send binary data via the +KUDPSND/+KTCPSND commands */
+static ssize_t send_socket_data(void *obj, struct hl78xx_socket_data *socket_data,
+				const struct sockaddr *dst_addr, const char *buf, size_t buf_len,
+				k_timeout_t timeout)
+{
+	struct modem_socket *sock = (struct modem_socket *)obj;
+	char cmd_buf[82] = {0}; /* AT+KUDPSND/KTCP=,IP,PORT,LENGTH */
+	int ret;
+	int sock_written = 0;
+
+	ret = validate_and_prepare(sock, &dst_addr, &buf_len, cmd_buf, sizeof(cmd_buf));
+	if (ret < 0) {
+		return ret;
+	}
+	socket_data->socket_data_error = false;
+	if (k_mutex_lock(&socket_data->mdata_global->tx_lock, K_SECONDS(1)) < 0) {
+		return -1;
+	}
+	ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf),
+				     (const struct modem_chat_match *)hl78xx_get_connect_matches(),
+				     hl78xx_get_connect_matches_size(), false);
+	if (ret < 0 || socket_data->socket_data_error) {
+		hl78xx_set_errno_from_code(ret);
+		ret = -1;
+		goto cleanup;
+	}
+	modem_pipe_attach(socket_data->mdata_global->chat.pipe, modem_pipe_callback,
+			  socket_data->mdata_global);
+	ret = transmit_regular_data(socket_data, buf, buf_len, &sock_written);
+	if (ret < 0) {
+		goto cleanup;
+	}
+	modem_chat_attach(&socket_data->mdata_global->chat, socket_data->mdata_global->uart_pipe);
+	ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, "", 0,
+				     hl78xx_get_sockets_ok_match(), 1, false);
+	if (ret < 0) {
+		LOG_ERR("Final confirmation failed: %d", ret);
+		goto cleanup;
+	}
+cleanup:
+	k_mutex_unlock(&socket_data->mdata_global->tx_lock);
+	return (ret < 0) ? -1 : sock_written;
+}
+
+#ifdef CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS
+/* ===== TLS implementation (conditional) ================================
+ * TLS credential upload and chipper settings helper implementations.
+ */
+static int handle_tls_sockopts(void *obj, int optname, const void *optval, socklen_t optlen)
+{
+	int ret;
+	struct modem_socket *sock = (struct modem_socket *)obj;
+	struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+
+	if (!sock || !socket_data || !socket_data->offload_dev) {
+		return -EINVAL;
+	}
+
+	switch (optname) {
+	case TLS_SEC_TAG_LIST:
+		ret = map_credentials(socket_data, optval, optlen);
+		return ret;
+
+	case TLS_HOSTNAME:
+		if (optlen >= MDM_MAX_HOSTNAME_LEN) {
+			return -EINVAL;
+		}
+		memset(socket_data->tls.hostname, 0, MDM_MAX_HOSTNAME_LEN);
+		memcpy(socket_data->tls.hostname, optval, optlen);
+		socket_data->tls.hostname[optlen] = '\0';
+		socket_data->tls.hostname_set = true;
+		ret = hl78xx_configure_chipper_suit(socket_data);
+		if (ret < 0) {
+			LOG_ERR("Failed to configure chipper suit: %d", ret);
+			return ret;
+		}
+		LOG_DBG("TLS hostname set to: %s", socket_data->tls.hostname);
+		return 0;
+
+	case TLS_PEER_VERIFY:
+		if (*(const uint32_t *)optval != TLS_PEER_VERIFY_REQUIRED) {
+			LOG_WRN("Disabling peer verification is not supported");
+		}
+		return 0;
+
+	case TLS_CERT_NOCOPY:
+		return 0; /* No-op, success */
+
+	default:
+		LOG_DBG("Unsupported TLS option: %d", optname);
+		return -EINVAL;
+	}
+}
+
+static int offload_setsockopt(void *obj, int level, int optname, const void *optval,
+			      socklen_t optlen)
+{
+	int ret = 0;
+
+	if (!IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) {
+		return -EINVAL;
+	}
+	if (level == SOL_TLS) {
+		ret = handle_tls_sockopts(obj, optname, optval, optlen);
+		if (ret < 0) {
+			hl78xx_set_errno_from_code(ret);
+			return -1;
+		}
+		return 0;
+	}
+	LOG_DBG("Unsupported socket option: %d", optname);
+	return -EINVAL;
+}
+#endif /* CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS */
+
+static ssize_t offload_sendto(void *obj, const void *buf, size_t len, int flags,
+			      const struct sockaddr *to, socklen_t tolen)
+{
+	int ret = 0;
+	struct modem_socket *sock = (struct modem_socket *)obj;
+	struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+
+	if (!sock || !socket_data || !socket_data->offload_dev) {
+		errno = EINVAL;
+		return -1;
+	}
+	if (!hl78xx_is_registered(socket_data->mdata_global)) {
+		LOG_ERR("Modem currently not attached to the network!");
+		return -EAGAIN;
+	}
+	/* Do some sanity checks. */
+	if (!buf || len == 0) {
+		errno = EINVAL;
+		return -1;
+	}
+	/* For stream sockets (TCP) the socket must be connected. For datagram
+	 * sockets (UDP) sendto can be used without a prior connect as long as a
+	 * destination address is provided or the socket has a stored dst. The
+	 * helper validate_and_prepare will supply sock->dst for UDP when needed.
+	 */
+	if (sock->type != SOCK_DGRAM && !sock->is_connected) {
+		errno = ENOTCONN;
+		return -1;
+	}
+	/* Only send up to MTU bytes. */
+	if (len > MDM_MAX_DATA_LENGTH) {
+		len = MDM_MAX_DATA_LENGTH;
+	}
+	ret = send_socket_data(obj, socket_data, to, buf, len, K_SECONDS(MDM_CMD_TIMEOUT));
+	if (ret < 0) {
+		/* Map internal negative return codes to positive errno values. Use EIO as
+		 * a conservative fallback when ret is non-negative (unexpected)
+		 */
+		hl78xx_set_errno_from_code(ret);
+		return -1;
+	}
+	errno = 0;
+	return ret;
+}
+
+static int offload_ioctl(void *obj, unsigned int request, va_list args)
+{
+	int ret = 0;
+	struct modem_socket *sock = (struct modem_socket *)obj;
+	struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+	struct zsock_pollfd *pfd;
+	struct k_poll_event **pev;
+	struct k_poll_event *pev_end;
+	/* sanity check: does parent == parent->offload_dev->data ? */
+	if (socket_data && socket_data->offload_dev &&
+	    socket_data->offload_dev->data != socket_data) {
+		LOG_WRN("parent mismatch: parent != offload_dev->data (%p != %p)", socket_data,
+			socket_data->offload_dev->data);
+	}
+	switch (request) {
+	case ZFD_IOCTL_POLL_PREPARE:
+		pfd = va_arg(args, struct zsock_pollfd *);
+		pev = va_arg(args, struct k_poll_event **);
+		pev_end = va_arg(args, struct k_poll_event *);
+		ret = modem_socket_poll_prepare(&socket_data->socket_config, obj, pfd, pev,
+						pev_end);
+
+		if (ret == -1 && errno == ENOTSUP && (pfd->events & ZSOCK_POLLOUT) &&
+		    sock->ip_proto == IPPROTO_UDP) {
+			/* Not Implemented */
+			/*
+			 *	You can implement this later when needed
+			 *	For now, just ignore it
+			 */
+			errno = ENOTSUP;
+			ret = 0;
+		}
+		return ret;
+
+	case ZFD_IOCTL_POLL_UPDATE:
+		pfd = va_arg(args, struct zsock_pollfd *);
+		pev = va_arg(args, struct k_poll_event **);
+		return modem_socket_poll_update(obj, pfd, pev);
+
+	case F_GETFL:
+		return 0;
+
+	case F_SETFL: {
+#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
+		int flags = va_arg(args, int);
+
+		LOG_DBG("F_SETFL called with flags=0x%x", flags);
+		ARG_UNUSED(flags);
+#endif
+		/* You can store flags if you want, but it's safe to just ignore them. */
+		return 0;
+	}
+
+	default:
+		errno = EINVAL;
+		return -1;
+	}
+}
+
+static ssize_t offload_read(void *obj, void *buffer, size_t count)
+{
+	return offload_recvfrom(obj, buffer, count, 0, NULL, 0);
+}
+
+static ssize_t offload_write(void *obj, const void *buffer, size_t count)
+{
+	return offload_sendto(obj, buffer, count, 0, NULL, 0);
+}
+
+static ssize_t offload_sendmsg(void *obj, const struct msghdr *msg, int flags)
+{
+	ssize_t sent = 0;
+	struct iovec bkp_iovec = {0};
+	struct msghdr crafted_msg = {.msg_name = msg->msg_name, .msg_namelen = msg->msg_namelen};
+	struct modem_socket *sock = (struct modem_socket *)obj;
+	struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock);
+	size_t full_len = 0;
+	int ret;
+
+	if (!sock || !socket_data || !socket_data->offload_dev) {
+		errno = EINVAL;
+		return -1;
+	}
+	/* Compute the full length to send and validate input */
+	for (int i = 0; i < msg->msg_iovlen; i++) {
+		if (!msg->msg_iov[i].iov_base || msg->msg_iov[i].iov_len == 0) {
+			errno = EINVAL;
+			return -1;
+		}
+		full_len += msg->msg_iov[i].iov_len;
+	}
+	while (full_len > sent) {
+		int removed = 0;
+		int i = 0;
+		int bkp_iovec_idx = -1;
+
+		crafted_msg.msg_iovlen = msg->msg_iovlen;
+		crafted_msg.msg_iov = &msg->msg_iov[0];
+
+		/* Adjust iovec to remove already sent bytes */
+		while (removed < sent) {
+			int to_remove = sent - removed;
+
+			if (to_remove >= msg->msg_iov[i].iov_len) {
+				crafted_msg.msg_iovlen -= 1;
+				crafted_msg.msg_iov = &msg->msg_iov[i + 1];
+				removed += msg->msg_iov[i].iov_len;
+			} else {
+				bkp_iovec_idx = i;
+				bkp_iovec = msg->msg_iov[i];
+
+				msg->msg_iov[i].iov_len -= to_remove;
+				msg->msg_iov[i].iov_base =
+					((uint8_t *)msg->msg_iov[i].iov_base) + to_remove;
+
+				removed += to_remove;
+			}
+			i++;
+		}
+		/* send_socket_data expects a buffer pointer and its byte length.
+		 * crafted_msg.msg_iovlen is the number of iovec entries and is
+		 * incorrect here (was causing sends of '2' bytes when two iovecs
+		 * were present). Use the first iovec's iov_len for the byte length.
+		 */
+		ret = send_socket_data(obj, socket_data, crafted_msg.msg_name,
+				       crafted_msg.msg_iov->iov_base, crafted_msg.msg_iov->iov_len,
+				       K_SECONDS(MDM_CMD_TIMEOUT));
+		if (bkp_iovec_idx != -1) {
+			msg->msg_iov[bkp_iovec_idx] = bkp_iovec;
+		}
+		if (ret < 0) {
+			/* Map negative internal return code to positive errno; fall back to
+			 * EIO
+			 */
+			hl78xx_set_errno_from_code(ret);
+			return -1;
+		}
+		sent += ret;
+	}
+	return sent;
+}
+/* clang-format off */
+static const struct socket_op_vtable offload_socket_fd_op_vtable = {
+	.fd_vtable = {
+			.read = offload_read,
+			.write = offload_write,
+			.close = offload_close,
+			.ioctl = offload_ioctl,
+		},
+	.bind = offload_bind,
+	.connect = offload_connect,
+	.sendto = offload_sendto,
+	.recvfrom = offload_recvfrom,
+	.listen = NULL,
+	.accept = NULL,
+	.sendmsg = offload_sendmsg,
+	.getsockopt = NULL,
+#if defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS)
+	.setsockopt = offload_setsockopt,
+#else
+	.setsockopt = NULL,
+#endif
+};
+/* clang-format on */
+static int hl78xx_init_sockets(const struct device *dev)
+{
+	int ret;
+	struct hl78xx_socket_data *socket_data = (struct hl78xx_socket_data *)dev->data;
+
+	socket_data->buf_pool = &mdm_recv_pool;
+	/* socket config */
+	ret = modem_socket_init(&socket_data->socket_config, &socket_data->sockets[0],
+				ARRAY_SIZE(socket_data->sockets), MDM_BASE_SOCKET_NUM, false,
+				&offload_socket_fd_op_vtable);
+	if (ret) {
+		goto error;
+	}
+	return 0;
+error:
+	return ret;
+}
+static void socket_notify_data(int socket_id, int new_total, void *user_data)
+{
+	int ret = 0;
+	struct modem_socket *sock;
+	struct hl78xx_data *data = (struct hl78xx_data *)user_data;
+	struct hl78xx_socket_data *socket_data =
+		(struct hl78xx_socket_data *)data->offload_dev->data;
+
+	if (!data || !socket_data) {
+		LOG_ERR("%s: invalid user_data", __func__);
+		return;
+	}
+	sock = modem_socket_from_id(&socket_data->socket_config, socket_id);
+	if (!sock) {
+		return;
+	}
+	/* Update the packet size */
+	ret = modem_socket_packet_size_update(&socket_data->socket_config, sock, new_total);
+	if (ret < 0) {
+		LOG_ERR("socket_id:%d left_bytes:%d err: %d", socket_id, new_total, ret);
+	}
+	if (new_total > 0) {
+		modem_socket_data_ready(&socket_data->socket_config, sock);
+	}
+	/* Duplicate/chat callback block removed; grouped versions live earlier */
+}
+
+#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS)
+static int hl78xx_configure_chipper_suit(struct hl78xx_socket_data *socket_data)
+{
+	const char *cmd_chipper_suit = "AT+KSSLCRYPTO=0,8,1,8192,4,4,3,0";
+
+	return modem_dynamic_cmd_send(
+		socket_data->mdata_global, NULL, cmd_chipper_suit, strlen(cmd_chipper_suit),
+		(const struct modem_chat_match *)hl78xx_get_ok_match(), 1, false);
+}
+/* send binary data via the K....STORE commands */
+static ssize_t hl78xx_send_cert(struct hl78xx_socket_data *socket_data, const char *cert_data,
+				size_t cert_len, enum tls_credential_type cert_type)
+{
+	int ret;
+	char send_buf[sizeof("AT+KPRIVKSTORE=#,####\r\n")];
+	int sock_written = 0;
+
+	if (!socket_data || !socket_data->mdata_global) {
+		return -EINVAL;
+	}
+
+	if (cert_len == 0 || !cert_data) {
+		LOG_ERR("Invalid certificate data or length");
+		return -EINVAL;
+	}
+	/** Certificate length exceeds maximum allowed size */
+	if (cert_len > MDM_MAX_CERT_LENGTH) {
+		return -EINVAL;
+	}
+
+	if (cert_type == TLS_CREDENTIAL_CA_CERTIFICATE ||
+	    cert_type == TLS_CREDENTIAL_SERVER_CERTIFICATE) {
+		snprintk(send_buf, sizeof(send_buf), "AT+KCERTSTORE=%d,%d", (cert_type - 1),
+			 cert_len);
+
+	} else if (cert_type == TLS_CREDENTIAL_PRIVATE_KEY) {
+		snprintk(send_buf, sizeof(send_buf), "AT+KPRIVKSTORE=0,%d", cert_len);
+
+	} else {
+		LOG_ERR("Unsupported certificate type: %d", cert_type);
+		return -EINVAL;
+	}
+	socket_data->socket_data_error = false;
+	if (k_mutex_lock(&socket_data->mdata_global->tx_lock, K_SECONDS(1)) < 0) {
+		errno = EBUSY;
+		return -1;
+	}
+	ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, send_buf, strlen(send_buf),
+				     (const struct modem_chat_match *)hl78xx_get_connect_matches(),
+				     hl78xx_get_connect_matches_size(), false);
+	if (ret < 0) {
+		LOG_ERR("Error sending AT command %d", ret);
+	}
+	if (socket_data->socket_data_error) {
+		ret = -ENODEV;
+		errno = ENODEV;
+		goto cleanup;
+	}
+	modem_pipe_attach(socket_data->mdata_global->chat.pipe, modem_pipe_callback,
+			  socket_data->mdata_global);
+	ret = send_data_buffer(socket_data, cert_data, cert_len, &sock_written);
+	if (ret < 0) {
+		goto cleanup;
+	}
+	ret = k_sem_take(&socket_data->mdata_global->script_stopped_sem_tx_int, K_FOREVER);
+	if (ret < 0) {
+		goto cleanup;
+	}
+	ret = modem_pipe_transmit(socket_data->mdata_global->uart_pipe,
+				  (uint8_t *)socket_data->mdata_global->buffers.eof_pattern,
+				  socket_data->mdata_global->buffers.eof_pattern_size);
+	if (ret < 0) {
+		LOG_ERR("Error sending EOF pattern: %d", ret);
+	}
+	modem_chat_attach(&socket_data->mdata_global->chat, socket_data->mdata_global->uart_pipe);
+	ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, "", 0,
+				     (const struct modem_chat_match *)hl78xx_get_ok_match(), 1,
+				     false);
+	if (ret < 0) {
+		LOG_ERR("Final confirmation failed: %d", ret);
+		goto cleanup;
+	}
+cleanup:
+	k_mutex_unlock(&socket_data->mdata_global->tx_lock);
+	return (ret < 0) ? -1 : sock_written;
+}
+
+static int map_credentials(struct hl78xx_socket_data *socket_data, const void *optval,
+			   socklen_t optlen)
+{
+	const sec_tag_t *sec_tags = (const sec_tag_t *)optval;
+	int ret = 0;
+	int tags_len;
+	sec_tag_t tag;
+	int i;
+	struct tls_credential *cert;
+
+	if ((optlen % sizeof(sec_tag_t)) != 0 || (optlen == 0)) {
+		return -EINVAL;
+	}
+	tags_len = optlen / sizeof(sec_tag_t);
+	/* For each tag, retrieve the credentials value and type: */
+	for (i = 0; i < tags_len; i++) {
+		tag = sec_tags[i];
+		cert = credential_next_get(tag, NULL);
+		while (cert != NULL) {
+			switch (cert->type) {
+			case TLS_CREDENTIAL_CA_CERTIFICATE:
+				LOG_DBG("TLS_CREDENTIAL_CA_CERTIFICATE tag: %d", tag);
+				break;
+
+			case TLS_CREDENTIAL_SERVER_CERTIFICATE:
+				LOG_DBG("TLS_CREDENTIAL_SERVER_CERTIFICATE tag: %d", tag);
+				break;
+
+			case TLS_CREDENTIAL_PRIVATE_KEY:
+				LOG_DBG("TLS_CREDENTIAL_PRIVATE_KEY tag: %d", tag);
+				break;
+
+			case TLS_CREDENTIAL_NONE:
+			case TLS_CREDENTIAL_PSK:
+			case TLS_CREDENTIAL_PSK_ID:
+			default:
+				/* Not handled */
+				return -EINVAL;
+			}
+			ret = hl78xx_send_cert(socket_data, cert->buf, cert->len, cert->type);
+			if (ret < 0) {
+				return ret;
+			}
+			cert = credential_next_get(tag, cert);
+		}
+	}
+
+	return 0;
+}
+#endif
+
+static int hl78xx_socket_init(const struct device *dev)
+{
+
+	struct hl78xx_socket_data *data = (struct hl78xx_socket_data *)dev->data;
+
+	data->offload_dev = dev;
+	/* Ensure the parent modem device pointer was set at static init time */
+	if (data->modem_dev == NULL) {
+		LOG_ERR("modem_dev not initialized for %s", dev->name);
+		return -EINVAL;
+	}
+	/* Ensure the modem device is ready before accessing its driver data */
+	if (!device_is_ready(data->modem_dev)) {
+		LOG_ERR("modem device %s not ready", data->modem_dev->name);
+		return -ENODEV;
+	}
+	if (data->modem_dev->data == NULL) {
+		LOG_ERR("modem device %s has no driver data yet", data->modem_dev->name);
+		return -EAGAIN;
+	}
+	data->mdata_global = (struct hl78xx_data *)data->modem_dev->data;
+	data->mdata_global->offload_dev = dev;
+	/* Keep original single global pointer usage but set via accessor. */
+	hl78xx_set_socket_global(data);
+	atomic_set(&data->mdata_global->state_leftover, 0);
+
+	return 0;
+}
+
+static void modem_net_iface_init(struct net_if *iface)
+{
+	const struct device *dev = net_if_get_device(iface);
+	struct hl78xx_socket_data *data = dev->data;
+
+	/* startup trace */
+	if (!data->mdata_global) {
+		LOG_WRN("mdata_global not set for net iface init on %s", dev->name);
+	}
+	net_if_set_link_addr(
+		iface,
+		modem_get_mac(data->mac_addr,
+			      data->mdata_global ? data->mdata_global->identity.imei : NULL),
+		sizeof(data->mac_addr), NET_LINK_ETHERNET);
+	data->net_iface = iface;
+	hl78xx_init_sockets(dev);
+	net_if_socket_offload_set(iface, offload_socket);
+}
+
+static struct offloaded_if_api api_funcs = {
+	.iface_api.init = modem_net_iface_init,
+};
+
+static bool offload_is_supported(int family, int type, int proto)
+{
+	bool fam_ok = false;
+
+#ifdef CONFIG_NET_IPV4
+	if (family == AF_INET) {
+		fam_ok = true;
+	}
+#endif
+#ifdef CONFIG_NET_IPV6
+	if (family == AF_INET6) {
+		fam_ok = true;
+	}
+#endif
+	if (!fam_ok) {
+		return false;
+	}
+	if (!(type == SOCK_DGRAM || type == SOCK_STREAM)) {
+		return false;
+	}
+	if (proto == IPPROTO_TCP || proto == IPPROTO_UDP) {
+		return true;
+	}
+#if defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS)
+	if (proto == IPPROTO_TLS_1_2) {
+		return true;
+	}
+#endif
+	return false;
+}
+
+#define MODEM_HL78XX_DEFINE_OFFLOAD_INSTANCE(inst)                                                 \
+	static struct hl78xx_socket_data hl78xx_socket_data_##inst = {                             \
+		.modem_dev = DEVICE_DT_GET(DT_PARENT(DT_DRV_INST(inst))),                          \
+	};                                                                                         \
+	NET_DEVICE_OFFLOAD_INIT(                                                                   \
+		inst, "hl78xx_dev", hl78xx_socket_init, NULL, &hl78xx_socket_data_##inst, NULL,    \
+		CONFIG_MODEM_HL78XX_OFFLOAD_INIT_PRIORITY, &api_funcs, MDM_MAX_DATA_LENGTH);       \
+                                                                                                   \
+	NET_SOCKET_OFFLOAD_REGISTER(inst, CONFIG_NET_SOCKETS_OFFLOAD_PRIORITY, AF_UNSPEC,          \
+				    offload_is_supported, offload_socket);
+
+#define MODEM_OFFLOAD_DEVICE_SWIR_HL78XX(inst) MODEM_HL78XX_DEFINE_OFFLOAD_INSTANCE(inst)
+
+#define DT_DRV_COMPAT swir_hl7812_offload
+DT_INST_FOREACH_STATUS_OKAY(MODEM_OFFLOAD_DEVICE_SWIR_HL78XX)
+#undef DT_DRV_COMPAT
+
+#define DT_DRV_COMPAT swir_hl7800_offload
+DT_INST_FOREACH_STATUS_OKAY(MODEM_OFFLOAD_DEVICE_SWIR_HL78XX)
+#undef DT_DRV_COMPAT
diff --git a/include/zephyr/drivers/modem/hl78xx_apis.h b/include/zephyr/drivers/modem/hl78xx_apis.h
new file mode 100644
index 0000000..bffd35d
--- /dev/null
+++ b/include/zephyr/drivers/modem/hl78xx_apis.h
@@ -0,0 +1,458 @@
+/*
+ * Copyright (c) 2025 Netfeasa Ltd.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef ZEPHYR_INCLUDE_DRIVERS_HL78XX_APIS_H_
+#define ZEPHYR_INCLUDE_DRIVERS_HL78XX_APIS_H_
+
+#include <zephyr/types.h>
+#include <zephyr/device.h>
+#include <errno.h>
+#include <stddef.h>
+#include <zephyr/modem/chat.h>
+#include <zephyr/drivers/cellular.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Magic constants */
+#define CSQ_RSSI_UNKNOWN        (99)
+#define CESQ_RSRP_UNKNOWN       (255)
+#define CESQ_RSRQ_UNKNOWN       (255)
+/* Magic numbers to units conversions */
+#define CSQ_RSSI_TO_DB(v)       (-113 + (2 * (v)))
+#define CESQ_RSRP_TO_DB(v)      (-140 + (v))
+#define CESQ_RSRQ_TO_DB(v)      (-20 + ((v) / 2))
+/** Monitor is paused. */
+#define PAUSED                  1
+/** Monitor is active, default */
+#define ACTIVE                  0
+#define MDM_MANUFACTURER_LENGTH 20
+#define MDM_MODEL_LENGTH        32
+#define MDM_REVISION_LENGTH     64
+#define MDM_IMEI_LENGTH         16
+#define MDM_IMSI_LENGTH         23
+#define MDM_ICCID_LENGTH        22
+#define MDM_APN_MAX_LENGTH      64
+#define MDM_MAX_CERT_LENGTH     4096
+#define MDM_MAX_HOSTNAME_LEN    128
+/**
+ * @brief Define an Event monitor to receive notifications in the system workqueue thread.
+ *
+ * @param name The monitor name.
+ * @param _handler The monitor callback.
+ * @param ... Optional monitor initial state (@c PAUSED or @c ACTIVE).
+ *	      The default initial state of a monitor is active.
+ */
+#define HL78XX_EVT_MONITOR(name, _handler, ...)                                                    \
+	static STRUCT_SECTION_ITERABLE(hl78xx_evt_monitor_entry, name) = {                         \
+		.handler = _handler,                                                               \
+		.next = NULL,                                                                      \
+		.flags.direct = false,                                                             \
+		COND_CODE_1(__VA_ARGS__, (.flags.paused = __VA_ARGS__,), ()) }
+
+/** Cellular radio access technologies */
+enum hl78xx_cell_rat_mode {
+	HL78XX_RAT_CAT_M1 = 0,
+	HL78XX_RAT_NB1,
+#ifdef CONFIG_MODEM_HL78XX_12
+	HL78XX_RAT_GSM,
+#ifdef CONFIG_MODEM_HL78XX_12_FW_R6
+	HL78XX_RAT_NBNTN,
+#endif
+#endif
+#ifdef CONFIG_MODEM_HL78XX_AUTORAT
+	HL78XX_RAT_MODE_AUTO,
+#endif
+	HL78XX_RAT_MODE_NONE,
+	HL78XX_RAT_COUNT = HL78XX_RAT_MODE_NONE
+};
+
+/** Phone functionality modes */
+enum hl78xx_phone_functionality {
+	HL78XX_SIM_POWER_OFF,
+	HL78XX_FULLY_FUNCTIONAL,
+	HL78XX_AIRPLANE = 4,
+};
+/** Module status codes */
+enum hl78xx_module_status {
+	HL78XX_MODULE_READY = 0,
+	HL78XX_MODULE_WAITING_FOR_ACCESS_CODE,
+	HL78XX_MODULE_SIM_NOT_PRESENT,
+	HL78XX_MODULE_SIMLOCK,
+	HL78XX_MODULE_UNRECOVERABLE_ERROR,
+	HL78XX_MODULE_UNKNOWN_STATE,
+	HL78XX_MODULE_INACTIVE_SIM
+};
+
+/** Cellular modem info type */
+enum hl78xx_modem_info_type {
+	/* <APN> Access Point Name */
+	HL78XX_MODEM_INFO_APN,
+	/* <Current RAT> */
+	HL78XX_MODEM_INFO_CURRENT_RAT,
+	/* <Network Operator> */
+	HL78XX_MODEM_INFO_NETWORK_OPERATOR,
+};
+
+/** Cellular network structure */
+struct hl78xx_network {
+	/** Cellular access technology */
+	enum hl78xx_cell_rat_mode technology;
+	/**
+	 * List of bands, as defined by the specified cellular access technology,
+	 * to enables. All supported bands are enabled if none are provided.
+	 */
+	uint16_t *bands;
+	/** Size of bands */
+	uint16_t size;
+};
+
+enum hl78xx_evt_type {
+	HL78XX_LTE_RAT_UPDATE,
+	HL78XX_LTE_REGISTRATION_STAT_UPDATE,
+	HL78XX_LTE_SIM_REGISTRATION,
+	HL78XX_LTE_MODEM_STARTUP,
+};
+
+struct hl78xx_evt {
+	enum hl78xx_evt_type type;
+
+	union {
+		enum cellular_registration_status reg_status;
+		enum hl78xx_cell_rat_mode rat_mode;
+		bool status;
+		int value;
+	} content;
+};
+/** API for configuring networks */
+typedef int (*hl78xx_api_configure_networks)(const struct device *dev,
+					     const struct hl78xx_network *networks, uint8_t size);
+
+/** API for getting supported networks */
+typedef int (*hl78xx_api_get_supported_networks)(const struct device *dev,
+						 const struct hl78xx_network **networks,
+						 uint8_t *size);
+
+/** API for getting network signal strength */
+typedef int (*hl78xx_api_get_signal)(const struct device *dev, const enum cellular_signal_type type,
+				     int16_t *value);
+
+/** API for getting modem information */
+typedef int (*hl78xx_api_get_modem_info)(const struct device *dev,
+					 const enum cellular_modem_info_type type, char *info,
+					 size_t size);
+
+/** API for getting registration status */
+typedef int (*hl78xx_api_get_registration_status)(const struct device *dev,
+						  enum cellular_access_technology tech,
+						  enum cellular_registration_status *status);
+
+/** API for setting apn */
+typedef int (*hl78xx_api_set_apn)(const struct device *dev, const char *apn, uint16_t size);
+
+/** API for set phone functionality */
+typedef int (*hl78xx_api_set_phone_functionality)(const struct device *dev,
+						  enum hl78xx_phone_functionality functionality,
+						  bool reset);
+
+/** API for get phone functionality */
+typedef int (*hl78xx_api_get_phone_functionality)(const struct device *dev,
+						  enum hl78xx_phone_functionality *functionality);
+
+/** API for get phone functionality */
+typedef int (*hl78xx_api_send_at_cmd)(const struct device *dev, const char *cmd, uint16_t cmd_size,
+				      const struct modem_chat_match *response_matches,
+				      uint16_t matches_size);
+
+/**< Event monitor entry */
+struct hl78xx_evt_monitor_entry; /* forward declaration */
+/* Event monitor dispatcher */
+typedef void (*hl78xx_evt_monitor_dispatcher_t)(struct hl78xx_evt *notif);
+/* Event monitor handler */
+typedef void (*hl78xx_evt_monitor_handler_t)(struct hl78xx_evt *notif,
+					     struct hl78xx_evt_monitor_entry *mon);
+
+struct hl78xx_evt_monitor_entry {
+	/** Monitor callback. */
+	const hl78xx_evt_monitor_handler_t handler;
+	/* link for runtime list */
+	struct hl78xx_evt_monitor_entry *next;
+	struct {
+		uint8_t paused: 1; /* Monitor is paused. */
+		uint8_t direct: 1; /* Dispatch in ISR. */
+	} flags;
+};
+/**
+ * @brief hl78xx_api_func_set_phone_functionality
+ * @param dev Cellular network device instance
+ * @param functionality phone functionality mode to set
+ * @param reset If true, the modem will be reset as part of applying the functionality change.
+ * @return 0 if successful.
+ */
+int hl78xx_api_func_set_phone_functionality(const struct device *dev,
+					    enum hl78xx_phone_functionality functionality,
+					    bool reset);
+/**
+ * @brief hl78xx_api_func_get_phone_functionality
+ * @param dev Cellular network device instance
+ * @param functionality Pointer to store the current phone functionality mode
+ * @return 0 if successful.
+ */
+int hl78xx_api_func_get_phone_functionality(const struct device *dev,
+					    enum hl78xx_phone_functionality *functionality);
+/**
+ * @brief hl78xx_api_func_get_signal - Brief description of the function.
+ * @param dev Cellular network device instance
+ * @param type Type of the signal to retrieve
+ * @param value Pointer to store the signal value
+ * @return 0 if successful.
+ */
+int hl78xx_api_func_get_signal(const struct device *dev, const enum cellular_signal_type type,
+			       int16_t *value);
+/**
+ * @brief hl78xx_api_func_get_modem_info_vendor - Brief description of the function.
+ * @param dev Cellular network device instance
+ * @param type Type of the modem info to retrieve
+ * @param info Pointer to store the modem info
+ * @param size Size of the info buffer
+ * @return 0 if successful.
+ */
+int hl78xx_api_func_get_modem_info_vendor(const struct device *dev,
+					  enum hl78xx_modem_info_type type, void *info,
+					  size_t size);
+/**
+ * @brief hl78xx_api_func_modem_dynamic_cmd_send - Brief description of the function.
+ * @param dev Cellular network device instance
+ * @param cmd AT command to send
+ * @param cmd_size Size of the AT command
+ * @param response_matches Expected response patterns
+ * @param matches_size Size of the response patterns
+ * @return 0 if successful.
+ */
+int hl78xx_api_func_modem_dynamic_cmd_send(const struct device *dev, const char *cmd,
+					   uint16_t cmd_size,
+					   const struct modem_chat_match *response_matches,
+					   uint16_t matches_size);
+/**
+ * @brief Get modem info for the device
+ *
+ * @param dev Cellular network device instance
+ * @param type Type of the modem info requested
+ * @param info Info string destination
+ * @param size Info string size
+ *
+ * @retval 0 if successful.
+ * @retval -ENOTSUP if API is not supported by cellular network device.
+ * @retval -ENODATA if modem does not provide info requested
+ * @retval Negative errno-code from chat module otherwise.
+ */
+static inline int hl78xx_get_modem_info(const struct device *dev,
+					const enum hl78xx_modem_info_type type, void *info,
+					size_t size)
+{
+	return hl78xx_api_func_get_modem_info_vendor(dev, type, info, size);
+}
+/**
+ * @brief Set the modem phone functionality mode.
+ *
+ * Configures the operational state of the modem (e.g., full, airplane, or minimum functionality).
+ * Optionally, the modem can be reset during this transition.
+ *
+ * @param dev Pointer to the modem device instance.
+ * @param functionality Desired phone functionality mode to be set.
+ *                      (e.g., full, airplane, minimum – see enum hl78xx_phone_functionality)
+ * @param reset If true, the modem will be reset as part of applying the functionality change.
+ *
+ * @retval 0 on success.
+ * @retval -EINVAL if an invalid parameter is passed.
+ * @retval -EIO on communication or command failure with the modem.
+ */
+static inline int hl78xx_set_phone_functionality(const struct device *dev,
+						 enum hl78xx_phone_functionality functionality,
+						 bool reset)
+{
+	return hl78xx_api_func_set_phone_functionality(dev, functionality, reset);
+}
+/**
+ * @brief Get the current phone functionality mode of the modem.
+ *
+ * Queries the modem to retrieve its current operational mode, such as
+ * full functionality, airplane mode, or minimum functionality.
+ *
+ * @param dev Pointer to the modem device instance.
+ * @param functionality Pointer to store the retrieved functionality mode.
+ *                      (see enum hl78xx_phone_functionality)
+ *
+ * @retval 0 on success.
+ * @retval -EINVAL if the input parameters are invalid.
+ * @retval -EIO if the modem fails to respond or returns an error.
+ */
+static inline int hl78xx_get_phone_functionality(const struct device *dev,
+						 enum hl78xx_phone_functionality *functionality)
+{
+	return hl78xx_api_func_get_phone_functionality(dev, functionality);
+}
+/**
+ * @brief Send an AT command to the modem and wait for a matched response.
+ *
+ * Transmits the specified AT command to the modem and waits for a response that matches
+ * one of the expected patterns defined in the response match table.
+ *
+ * @param dev Pointer to the modem device instance.
+ * @param cmd Pointer to the AT command string to be sent.
+ * @param cmd_size Length of the AT command string in bytes.
+ * @param response_matches Pointer to an array of expected response patterns.
+ *                         (see struct modem_chat_match)
+ * @param matches_size Number of response patterns in the array.
+ *
+ * @retval 0 on successful command transmission and response match.
+ * @retval -EINVAL if any parameter is invalid.
+ * @retval -ETIMEDOUT if the modem did not respond in the expected time.
+ * @retval -EIO on communication failure or if response did not match.
+ */
+static inline int hl78xx_modem_cmd_send(const struct device *dev, const char *cmd,
+					uint16_t cmd_size,
+					const struct modem_chat_match *response_matches,
+					uint16_t matches_size)
+{
+
+	return hl78xx_api_func_modem_dynamic_cmd_send(dev, cmd, cmd_size, response_matches,
+						      matches_size);
+}
+/**
+ * @brief Convert raw RSSI value from the modem to dBm.
+ *
+ * Parses the RSSI value reported by the modem (typically from an AT command response)
+ * and converts it to a corresponding signal strength in dBm, as defined by 3GPP TS 27.007.
+ *
+ * @param rssi Raw RSSI value (0–31 or 99 for not known or not detectable).
+ * @param value Pointer to store the converted RSSI in dBm.
+ *
+ * @retval 0 on successful conversion.
+ * @retval -EINVAL if the RSSI value is out of valid range or unsupported.
+ */
+static inline int hl78xx_parse_rssi(uint8_t rssi, int16_t *value)
+{
+	/* AT+CSQ returns a response +CSQ: <rssi>,<ber> where:
+	 * - rssi is a integer from 0 to 31 whose values describes a signal strength
+	 *   between -113 dBm for 0 and -51dbM for 31 or unknown for 99
+	 * - ber is an integer from 0 to 7 that describes the error rate, it can also
+	 *   be 99 for an unknown error rate
+	 */
+	if (rssi == CSQ_RSSI_UNKNOWN) {
+		return -EINVAL;
+	}
+
+	*value = (int16_t)CSQ_RSSI_TO_DB(rssi);
+	return 0;
+}
+/**
+ * @brief Convert raw RSRP value from the modem to dBm.
+ *
+ * Parses the Reference Signal Received Power (RSRP) value reported by the modem
+ * and converts it into a corresponding signal strength in dBm, typically based on
+ * 3GPP TS 36.133 specifications.
+ *
+ * @param rsrp Raw RSRP value (commonly in the range 0–97, or 255 if unknown).
+ * @param value Pointer to store the converted RSRP in dBm.
+ *
+ * @retval 0 on successful conversion.
+ * @retval -EINVAL if the RSRP value is out of range or represents an unknown value.
+ */
+static inline int hl78xx_parse_rsrp(uint8_t rsrp, int16_t *value)
+{
+	/* AT+CESQ returns a response
+	 * +CESQ: <rxlev>,<ber>,<rscp>,<ecn0>,<rsrq>,<rsrp> where:
+	 * rsrq is a integer from 0 to 34 whose values describes the Reference
+	 * Signal Receive Quality between -20 dB for 0 and -3 dB for 34
+	 * (0.5 dB steps), or unknown for 255
+	 * rsrp is an integer from 0 to 97 that describes the Reference Signal
+	 * Receive Power between -140 dBm for 0 and -44 dBm for 97 (1 dBm steps),
+	 * or unknown for 255
+	 */
+	if (rsrp == CESQ_RSRP_UNKNOWN) {
+		return -EINVAL;
+	}
+
+	*value = (int16_t)CESQ_RSRP_TO_DB(rsrp);
+	return 0;
+}
+/**
+ * @brief Convert raw RSRQ value from the modem to dB.
+ *
+ * Parses the Reference Signal Received Quality (RSRQ) value provided by the modem
+ * and converts it into a signal quality measurement in decibels (dB), as specified
+ * by 3GPP TS 36.133.
+ *
+ * @param rsrq Raw RSRQ value (typically 0–34, or 255 if unknown).
+ * @param value Pointer to store the converted RSRQ in dB.
+ *
+ * @retval 0 on successful conversion.
+ * @retval -EINVAL if the RSRQ value is out of valid range or indicates unknown.
+ */
+static inline int hl78xx_parse_rsrq(uint8_t rsrq, int16_t *value)
+{
+	if (rsrq == CESQ_RSRQ_UNKNOWN) {
+		return -EINVAL;
+	}
+
+	*value = (int16_t)CESQ_RSRQ_TO_DB(rsrq);
+	return 0;
+}
+/**
+ * @brief Pause monitor.
+ *
+ * Pause monitor @p mon from receiving notifications.
+ *
+ * @param mon The monitor to pause.
+ */
+static inline void hl78xx_evt_monitor_pause(struct hl78xx_evt_monitor_entry *mon)
+{
+	mon->flags.paused = true;
+}
+/**
+ * @brief Resume monitor.
+ *
+ * Resume forwarding notifications to monitor @p mon.
+ *
+ * @param mon The monitor to resume.
+ */
+static inline void hl78xx_evt_monitor_resume(struct hl78xx_evt_monitor_entry *mon)
+{
+	mon->flags.paused = false;
+}
+/**
+ * @brief Set the event notification handler for HL78xx modem events.
+ *
+ * Registers a callback handler to receive asynchronous event notifications
+ * from the HL78xx modem, such as network registration changes, GNSS updates,
+ * or other modem-generated events.
+ *
+ * @param handler Function pointer to the event monitor callback.
+ *                Pass NULL to clear the existing handler.
+ *
+ * @retval 0 on success.
+ * @retval -EINVAL if the handler parameter is invalid.
+ */
+int hl78xx_evt_notif_handler_set(hl78xx_evt_monitor_dispatcher_t handler);
+/**
+ * @brief Register an event monitor to receive HL78xx modem event notifications.
+ */
+int hl78xx_evt_monitor_register(struct hl78xx_evt_monitor_entry *mon);
+/**
+ * @brief Unregister an event monitor from receiving HL78xx modem event notifications.
+ */
+int hl78xx_evt_monitor_unregister(struct hl78xx_evt_monitor_entry *mon);
+/**
+ * @brief Convert HL78xx RAT mode to standard cellular API.
+ */
+enum cellular_access_technology hl78xx_rat_to_access_tech(enum hl78xx_cell_rat_mode rat_mode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZEPHYR_INCLUDE_DRIVERS_HL78XX_APIS_H_ */