Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge branch 'nfc-s3fwrn5-support-a-uart-interface'

Bongsu Jeon says:

====================
nfc: s3fwrn5: Support a UART interface

S3FWRN82 is the Samsung's NFC chip that supports the UART communication.
Before adding the UART driver module, I did refactoring the s3fwrn5_i2c
module to reuse the common blocks.
====================

Link: https://lore.kernel.org/r/1606909661-3814-1-git-send-email-bongsu.jeon@samsung.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+390 -82
+28 -3
Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
··· 12 12 13 13 properties: 14 14 compatible: 15 - const: samsung,s3fwrn5-i2c 15 + enum: 16 + - samsung,s3fwrn5-i2c 17 + - samsung,s3fwrn82 16 18 17 19 en-gpios: 18 20 maxItems: 1 ··· 49 47 required: 50 48 - compatible 51 49 - en-gpios 52 - - interrupts 53 - - reg 54 50 - wake-gpios 51 + 52 + allOf: 53 + - if: 54 + properties: 55 + compatible: 56 + contains: 57 + const: samsung,s3fwrn5-i2c 58 + then: 59 + required: 60 + - interrupts 61 + - reg 55 62 56 63 examples: 57 64 - | ··· 80 69 81 70 en-gpios = <&gpf1 4 GPIO_ACTIVE_HIGH>; 82 71 wake-gpios = <&gpj0 2 GPIO_ACTIVE_HIGH>; 72 + }; 73 + }; 74 + # UART example on Raspberry Pi 75 + - | 76 + uart0 { 77 + status = "okay"; 78 + 79 + nfc { 80 + compatible = "samsung,s3fwrn82"; 81 + 82 + en-gpios = <&gpio 20 GPIO_ACTIVE_HIGH>; 83 + wake-gpios = <&gpio 16 GPIO_ACTIVE_HIGH>; 84 + 85 + status = "okay"; 83 86 }; 84 87 };
+12
drivers/nfc/s3fwrn5/Kconfig
··· 20 20 To compile this driver as a module, choose m here. The module will 21 21 be called s3fwrn5_i2c.ko. 22 22 Say N if unsure. 23 + 24 + config NFC_S3FWRN82_UART 25 + tristate "Samsung S3FWRN82 UART support" 26 + depends on NFC_NCI && SERIAL_DEV_BUS 27 + select NFC_S3FWRN5 28 + help 29 + This module adds support for a UART interface to the S3FWRN82 chip. 30 + Select this if your platform is using the UART bus. 31 + 32 + To compile this driver as a module, choose m here. The module will 33 + be called s3fwrn82_uart.ko. 34 + Say N if unsure.
+3 -1
drivers/nfc/s3fwrn5/Makefile
··· 3 3 # Makefile for Samsung S3FWRN5 NFC driver 4 4 # 5 5 6 - s3fwrn5-objs = core.o firmware.o nci.o 6 + s3fwrn5-objs = core.o firmware.o nci.o phy_common.o 7 7 s3fwrn5_i2c-objs = i2c.o 8 + s3fwrn82_uart-objs = uart.o 8 9 9 10 obj-$(CONFIG_NFC_S3FWRN5) += s3fwrn5.o 10 11 obj-$(CONFIG_NFC_S3FWRN5_I2C) += s3fwrn5_i2c.o 12 + obj-$(CONFIG_NFC_S3FWRN82_UART) += s3fwrn82_uart.o
+39 -78
drivers/nfc/s3fwrn5/i2c.c
··· 15 15 16 16 #include <net/nfc/nfc.h> 17 17 18 - #include "s3fwrn5.h" 18 + #include "phy_common.h" 19 19 20 20 #define S3FWRN5_I2C_DRIVER_NAME "s3fwrn5_i2c" 21 21 22 - #define S3FWRN5_EN_WAIT_TIME 150 23 - 24 22 struct s3fwrn5_i2c_phy { 23 + struct phy_common common; 25 24 struct i2c_client *i2c_dev; 26 - struct nci_dev *ndev; 27 25 28 - int gpio_en; 29 - int gpio_fw_wake; 30 - 31 - struct mutex mutex; 32 - 33 - enum s3fwrn5_mode mode; 34 26 unsigned int irq_skip:1; 35 27 }; 36 - 37 - static void s3fwrn5_i2c_set_wake(void *phy_id, bool wake) 38 - { 39 - struct s3fwrn5_i2c_phy *phy = phy_id; 40 - 41 - mutex_lock(&phy->mutex); 42 - gpio_set_value(phy->gpio_fw_wake, wake); 43 - msleep(S3FWRN5_EN_WAIT_TIME/2); 44 - mutex_unlock(&phy->mutex); 45 - } 46 28 47 29 static void s3fwrn5_i2c_set_mode(void *phy_id, enum s3fwrn5_mode mode) 48 30 { 49 31 struct s3fwrn5_i2c_phy *phy = phy_id; 50 32 51 - mutex_lock(&phy->mutex); 33 + mutex_lock(&phy->common.mutex); 52 34 53 - if (phy->mode == mode) 35 + if (s3fwrn5_phy_power_ctrl(&phy->common, mode) == false) 54 36 goto out; 55 - 56 - phy->mode = mode; 57 - 58 - gpio_set_value(phy->gpio_en, 1); 59 - gpio_set_value(phy->gpio_fw_wake, 0); 60 - if (mode == S3FWRN5_MODE_FW) 61 - gpio_set_value(phy->gpio_fw_wake, 1); 62 - 63 - if (mode != S3FWRN5_MODE_COLD) { 64 - msleep(S3FWRN5_EN_WAIT_TIME); 65 - gpio_set_value(phy->gpio_en, 0); 66 - msleep(S3FWRN5_EN_WAIT_TIME/2); 67 - } 68 37 69 38 phy->irq_skip = true; 70 39 71 40 out: 72 - mutex_unlock(&phy->mutex); 73 - } 74 - 75 - static enum s3fwrn5_mode s3fwrn5_i2c_get_mode(void *phy_id) 76 - { 77 - struct s3fwrn5_i2c_phy *phy = phy_id; 78 - enum s3fwrn5_mode mode; 79 - 80 - mutex_lock(&phy->mutex); 81 - 82 - mode = phy->mode; 83 - 84 - mutex_unlock(&phy->mutex); 85 - 86 - return mode; 41 + mutex_unlock(&phy->common.mutex); 87 42 } 88 43 89 44 static int s3fwrn5_i2c_write(void *phy_id, struct sk_buff *skb) ··· 46 91 struct s3fwrn5_i2c_phy *phy = phy_id; 47 92 int ret; 48 93 49 - mutex_lock(&phy->mutex); 94 + mutex_lock(&phy->common.mutex); 50 95 51 96 phy->irq_skip = false; 52 97 ··· 57 102 ret = i2c_master_send(phy->i2c_dev, skb->data, skb->len); 58 103 } 59 104 60 - mutex_unlock(&phy->mutex); 105 + mutex_unlock(&phy->common.mutex); 61 106 62 107 if (ret < 0) 63 108 return ret; ··· 69 114 } 70 115 71 116 static const struct s3fwrn5_phy_ops i2c_phy_ops = { 72 - .set_wake = s3fwrn5_i2c_set_wake, 117 + .set_wake = s3fwrn5_phy_set_wake, 73 118 .set_mode = s3fwrn5_i2c_set_mode, 74 - .get_mode = s3fwrn5_i2c_get_mode, 119 + .get_mode = s3fwrn5_phy_get_mode, 75 120 .write = s3fwrn5_i2c_write, 76 121 }; 77 122 ··· 83 128 char hdr[4]; 84 129 int ret; 85 130 86 - hdr_size = (phy->mode == S3FWRN5_MODE_NCI) ? 131 + hdr_size = (phy->common.mode == S3FWRN5_MODE_NCI) ? 87 132 NCI_CTRL_HDR_SIZE : S3FWRN5_FW_HDR_SIZE; 88 133 ret = i2c_master_recv(phy->i2c_dev, hdr, hdr_size); 89 134 if (ret < 0) ··· 92 137 if (ret < hdr_size) 93 138 return -EBADMSG; 94 139 95 - data_len = (phy->mode == S3FWRN5_MODE_NCI) ? 140 + data_len = (phy->common.mode == S3FWRN5_MODE_NCI) ? 96 141 ((struct nci_ctrl_hdr *)hdr)->plen : 97 142 ((struct s3fwrn5_fw_header *)hdr)->len; 98 143 ··· 112 157 } 113 158 114 159 out: 115 - return s3fwrn5_recv_frame(phy->ndev, skb, phy->mode); 160 + return s3fwrn5_recv_frame(phy->common.ndev, skb, phy->common.mode); 116 161 } 117 162 118 163 static irqreturn_t s3fwrn5_i2c_irq_thread_fn(int irq, void *phy_id) 119 164 { 120 165 struct s3fwrn5_i2c_phy *phy = phy_id; 121 166 122 - if (!phy || !phy->ndev) { 167 + if (!phy || !phy->common.ndev) { 123 168 WARN_ON_ONCE(1); 124 169 return IRQ_NONE; 125 170 } 126 171 127 - mutex_lock(&phy->mutex); 172 + mutex_lock(&phy->common.mutex); 128 173 129 174 if (phy->irq_skip) 130 175 goto out; 131 176 132 - switch (phy->mode) { 177 + switch (phy->common.mode) { 133 178 case S3FWRN5_MODE_NCI: 134 179 case S3FWRN5_MODE_FW: 135 180 s3fwrn5_i2c_read(phy); ··· 139 184 } 140 185 141 186 out: 142 - mutex_unlock(&phy->mutex); 187 + mutex_unlock(&phy->common.mutex); 143 188 144 189 return IRQ_HANDLED; 145 190 } ··· 152 197 if (!np) 153 198 return -ENODEV; 154 199 155 - phy->gpio_en = of_get_named_gpio(np, "en-gpios", 0); 156 - if (!gpio_is_valid(phy->gpio_en)) { 200 + phy->common.gpio_en = of_get_named_gpio(np, "en-gpios", 0); 201 + if (!gpio_is_valid(phy->common.gpio_en)) { 157 202 /* Support also deprecated property */ 158 - phy->gpio_en = of_get_named_gpio(np, "s3fwrn5,en-gpios", 0); 159 - if (!gpio_is_valid(phy->gpio_en)) 203 + phy->common.gpio_en = of_get_named_gpio(np, 204 + "s3fwrn5,en-gpios", 205 + 0); 206 + if (!gpio_is_valid(phy->common.gpio_en)) 160 207 return -ENODEV; 161 208 } 162 209 163 - phy->gpio_fw_wake = of_get_named_gpio(np, "wake-gpios", 0); 164 - if (!gpio_is_valid(phy->gpio_fw_wake)) { 210 + phy->common.gpio_fw_wake = of_get_named_gpio(np, "wake-gpios", 0); 211 + if (!gpio_is_valid(phy->common.gpio_fw_wake)) { 165 212 /* Support also deprecated property */ 166 - phy->gpio_fw_wake = of_get_named_gpio(np, "s3fwrn5,fw-gpios", 0); 167 - if (!gpio_is_valid(phy->gpio_fw_wake)) 213 + phy->common.gpio_fw_wake = of_get_named_gpio(np, 214 + "s3fwrn5,fw-gpios", 215 + 0); 216 + if (!gpio_is_valid(phy->common.gpio_fw_wake)) 168 217 return -ENODEV; 169 218 } 170 219 ··· 185 226 if (!phy) 186 227 return -ENOMEM; 187 228 188 - mutex_init(&phy->mutex); 189 - phy->mode = S3FWRN5_MODE_COLD; 229 + mutex_init(&phy->common.mutex); 230 + phy->common.mode = S3FWRN5_MODE_COLD; 190 231 phy->irq_skip = true; 191 232 192 233 phy->i2c_dev = client; ··· 196 237 if (ret < 0) 197 238 return ret; 198 239 199 - ret = devm_gpio_request_one(&phy->i2c_dev->dev, phy->gpio_en, 200 - GPIOF_OUT_INIT_HIGH, "s3fwrn5_en"); 240 + ret = devm_gpio_request_one(&phy->i2c_dev->dev, phy->common.gpio_en, 241 + GPIOF_OUT_INIT_HIGH, "s3fwrn5_en"); 201 242 if (ret < 0) 202 243 return ret; 203 244 204 - ret = devm_gpio_request_one(&phy->i2c_dev->dev, phy->gpio_fw_wake, 205 - GPIOF_OUT_INIT_LOW, "s3fwrn5_fw_wake"); 245 + ret = devm_gpio_request_one(&phy->i2c_dev->dev, 246 + phy->common.gpio_fw_wake, 247 + GPIOF_OUT_INIT_LOW, "s3fwrn5_fw_wake"); 206 248 if (ret < 0) 207 249 return ret; 208 250 209 - ret = s3fwrn5_probe(&phy->ndev, phy, &phy->i2c_dev->dev, &i2c_phy_ops); 251 + ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->i2c_dev->dev, 252 + &i2c_phy_ops); 210 253 if (ret < 0) 211 254 return ret; 212 255 ··· 216 255 s3fwrn5_i2c_irq_thread_fn, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, 217 256 S3FWRN5_I2C_DRIVER_NAME, phy); 218 257 if (ret) 219 - s3fwrn5_remove(phy->ndev); 258 + s3fwrn5_remove(phy->common.ndev); 220 259 221 260 return ret; 222 261 } ··· 225 264 { 226 265 struct s3fwrn5_i2c_phy *phy = i2c_get_clientdata(client); 227 266 228 - s3fwrn5_remove(phy->ndev); 267 + s3fwrn5_remove(phy->common.ndev); 229 268 230 269 return 0; 231 270 }
+75
drivers/nfc/s3fwrn5/phy_common.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * Link Layer for Samsung S3FWRN5 NCI based Driver 4 + * 5 + * Copyright (C) 2015 Samsung Electrnoics 6 + * Robert Baldyga <r.baldyga@samsung.com> 7 + * Copyright (C) 2020 Samsung Electrnoics 8 + * Bongsu Jeon <bongsu.jeon@samsung.com> 9 + */ 10 + 11 + #include <linux/gpio.h> 12 + #include <linux/delay.h> 13 + #include <linux/module.h> 14 + 15 + #include "phy_common.h" 16 + 17 + void s3fwrn5_phy_set_wake(void *phy_id, bool wake) 18 + { 19 + struct phy_common *phy = phy_id; 20 + 21 + mutex_lock(&phy->mutex); 22 + gpio_set_value(phy->gpio_fw_wake, wake); 23 + msleep(S3FWRN5_EN_WAIT_TIME); 24 + mutex_unlock(&phy->mutex); 25 + } 26 + EXPORT_SYMBOL(s3fwrn5_phy_set_wake); 27 + 28 + bool s3fwrn5_phy_power_ctrl(struct phy_common *phy, enum s3fwrn5_mode mode) 29 + { 30 + if (phy->mode == mode) 31 + return false; 32 + 33 + phy->mode = mode; 34 + 35 + gpio_set_value(phy->gpio_en, 1); 36 + gpio_set_value(phy->gpio_fw_wake, 0); 37 + if (mode == S3FWRN5_MODE_FW) 38 + gpio_set_value(phy->gpio_fw_wake, 1); 39 + 40 + if (mode != S3FWRN5_MODE_COLD) { 41 + msleep(S3FWRN5_EN_WAIT_TIME); 42 + gpio_set_value(phy->gpio_en, 0); 43 + msleep(S3FWRN5_EN_WAIT_TIME); 44 + } 45 + 46 + return true; 47 + } 48 + EXPORT_SYMBOL(s3fwrn5_phy_power_ctrl); 49 + 50 + void s3fwrn5_phy_set_mode(void *phy_id, enum s3fwrn5_mode mode) 51 + { 52 + struct phy_common *phy = phy_id; 53 + 54 + mutex_lock(&phy->mutex); 55 + 56 + s3fwrn5_phy_power_ctrl(phy, mode); 57 + 58 + mutex_unlock(&phy->mutex); 59 + } 60 + EXPORT_SYMBOL(s3fwrn5_phy_set_mode); 61 + 62 + enum s3fwrn5_mode s3fwrn5_phy_get_mode(void *phy_id) 63 + { 64 + struct phy_common *phy = phy_id; 65 + enum s3fwrn5_mode mode; 66 + 67 + mutex_lock(&phy->mutex); 68 + 69 + mode = phy->mode; 70 + 71 + mutex_unlock(&phy->mutex); 72 + 73 + return mode; 74 + } 75 + EXPORT_SYMBOL(s3fwrn5_phy_get_mode);
+37
drivers/nfc/s3fwrn5/phy_common.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-or-later 2 + * 3 + * Link Layer for Samsung S3FWRN5 NCI based Driver 4 + * 5 + * Copyright (C) 2015 Samsung Electrnoics 6 + * Robert Baldyga <r.baldyga@samsung.com> 7 + * Copyright (C) 2020 Samsung Electrnoics 8 + * Bongsu Jeon <bongsu.jeon@samsung.com> 9 + */ 10 + 11 + #ifndef __NFC_S3FWRN5_PHY_COMMON_H 12 + #define __NFC_S3FWRN5_PHY_COMMON_H 13 + 14 + #include <linux/mutex.h> 15 + #include <net/nfc/nci_core.h> 16 + 17 + #include "s3fwrn5.h" 18 + 19 + #define S3FWRN5_EN_WAIT_TIME 20 20 + 21 + struct phy_common { 22 + struct nci_dev *ndev; 23 + 24 + int gpio_en; 25 + int gpio_fw_wake; 26 + 27 + struct mutex mutex; 28 + 29 + enum s3fwrn5_mode mode; 30 + }; 31 + 32 + void s3fwrn5_phy_set_wake(void *phy_id, bool wake); 33 + bool s3fwrn5_phy_power_ctrl(struct phy_common *phy, enum s3fwrn5_mode mode); 34 + void s3fwrn5_phy_set_mode(void *phy_id, enum s3fwrn5_mode mode); 35 + enum s3fwrn5_mode s3fwrn5_phy_get_mode(void *phy_id); 36 + 37 + #endif /* __NFC_S3FWRN5_PHY_COMMON_H */
+196
drivers/nfc/s3fwrn5/uart.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + /* 3 + * UART Link Layer for S3FWRN82 NCI based Driver 4 + * 5 + * Copyright (C) 2015 Samsung Electronics 6 + * Robert Baldyga <r.baldyga@samsung.com> 7 + * Copyright (C) 2020 Samsung Electronics 8 + * Bongsu Jeon <bongsu.jeon@samsung.com> 9 + */ 10 + 11 + #include <linux/device.h> 12 + #include <linux/kernel.h> 13 + #include <linux/module.h> 14 + #include <linux/nfc.h> 15 + #include <linux/netdevice.h> 16 + #include <linux/of.h> 17 + #include <linux/serdev.h> 18 + #include <linux/gpio.h> 19 + #include <linux/of_gpio.h> 20 + 21 + #include "phy_common.h" 22 + 23 + #define S3FWRN82_NCI_HEADER 3 24 + #define S3FWRN82_NCI_IDX 2 25 + #define NCI_SKB_BUFF_LEN 258 26 + 27 + struct s3fwrn82_uart_phy { 28 + struct phy_common common; 29 + struct serdev_device *ser_dev; 30 + struct sk_buff *recv_skb; 31 + }; 32 + 33 + static int s3fwrn82_uart_write(void *phy_id, struct sk_buff *out) 34 + { 35 + struct s3fwrn82_uart_phy *phy = phy_id; 36 + int err; 37 + 38 + err = serdev_device_write(phy->ser_dev, 39 + out->data, out->len, 40 + MAX_SCHEDULE_TIMEOUT); 41 + if (err < 0) 42 + return err; 43 + 44 + return 0; 45 + } 46 + 47 + static const struct s3fwrn5_phy_ops uart_phy_ops = { 48 + .set_wake = s3fwrn5_phy_set_wake, 49 + .set_mode = s3fwrn5_phy_set_mode, 50 + .get_mode = s3fwrn5_phy_get_mode, 51 + .write = s3fwrn82_uart_write, 52 + }; 53 + 54 + static int s3fwrn82_uart_read(struct serdev_device *serdev, 55 + const unsigned char *data, 56 + size_t count) 57 + { 58 + struct s3fwrn82_uart_phy *phy = serdev_device_get_drvdata(serdev); 59 + size_t i; 60 + 61 + for (i = 0; i < count; i++) { 62 + skb_put_u8(phy->recv_skb, *data++); 63 + 64 + if (phy->recv_skb->len < S3FWRN82_NCI_HEADER) 65 + continue; 66 + 67 + if ((phy->recv_skb->len - S3FWRN82_NCI_HEADER) 68 + < phy->recv_skb->data[S3FWRN82_NCI_IDX]) 69 + continue; 70 + 71 + s3fwrn5_recv_frame(phy->common.ndev, phy->recv_skb, 72 + phy->common.mode); 73 + phy->recv_skb = alloc_skb(NCI_SKB_BUFF_LEN, GFP_KERNEL); 74 + if (!phy->recv_skb) 75 + return 0; 76 + } 77 + 78 + return i; 79 + } 80 + 81 + static const struct serdev_device_ops s3fwrn82_serdev_ops = { 82 + .receive_buf = s3fwrn82_uart_read, 83 + .write_wakeup = serdev_device_write_wakeup, 84 + }; 85 + 86 + static const struct of_device_id s3fwrn82_uart_of_match[] = { 87 + { .compatible = "samsung,s3fwrn82", }, 88 + {}, 89 + }; 90 + MODULE_DEVICE_TABLE(of, s3fwrn82_uart_of_match); 91 + 92 + static int s3fwrn82_uart_parse_dt(struct serdev_device *serdev) 93 + { 94 + struct s3fwrn82_uart_phy *phy = serdev_device_get_drvdata(serdev); 95 + struct device_node *np = serdev->dev.of_node; 96 + 97 + if (!np) 98 + return -ENODEV; 99 + 100 + phy->common.gpio_en = of_get_named_gpio(np, "en-gpios", 0); 101 + if (!gpio_is_valid(phy->common.gpio_en)) 102 + return -ENODEV; 103 + 104 + phy->common.gpio_fw_wake = of_get_named_gpio(np, "wake-gpios", 0); 105 + if (!gpio_is_valid(phy->common.gpio_fw_wake)) 106 + return -ENODEV; 107 + 108 + return 0; 109 + } 110 + 111 + static int s3fwrn82_uart_probe(struct serdev_device *serdev) 112 + { 113 + struct s3fwrn82_uart_phy *phy; 114 + int ret = -ENOMEM; 115 + 116 + phy = devm_kzalloc(&serdev->dev, sizeof(*phy), GFP_KERNEL); 117 + if (!phy) 118 + goto err_exit; 119 + 120 + phy->recv_skb = alloc_skb(NCI_SKB_BUFF_LEN, GFP_KERNEL); 121 + if (!phy->recv_skb) 122 + goto err_exit; 123 + 124 + mutex_init(&phy->common.mutex); 125 + phy->common.mode = S3FWRN5_MODE_COLD; 126 + 127 + phy->ser_dev = serdev; 128 + serdev_device_set_drvdata(serdev, phy); 129 + serdev_device_set_client_ops(serdev, &s3fwrn82_serdev_ops); 130 + ret = serdev_device_open(serdev); 131 + if (ret) { 132 + dev_err(&serdev->dev, "Unable to open device\n"); 133 + goto err_skb; 134 + } 135 + 136 + ret = serdev_device_set_baudrate(serdev, 115200); 137 + if (ret != 115200) { 138 + ret = -EINVAL; 139 + goto err_serdev; 140 + } 141 + 142 + serdev_device_set_flow_control(serdev, false); 143 + 144 + ret = s3fwrn82_uart_parse_dt(serdev); 145 + if (ret < 0) 146 + goto err_serdev; 147 + 148 + ret = devm_gpio_request_one(&phy->ser_dev->dev, phy->common.gpio_en, 149 + GPIOF_OUT_INIT_HIGH, "s3fwrn82_en"); 150 + if (ret < 0) 151 + goto err_serdev; 152 + 153 + ret = devm_gpio_request_one(&phy->ser_dev->dev, 154 + phy->common.gpio_fw_wake, 155 + GPIOF_OUT_INIT_LOW, "s3fwrn82_fw_wake"); 156 + if (ret < 0) 157 + goto err_serdev; 158 + 159 + ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->ser_dev->dev, 160 + &uart_phy_ops); 161 + if (ret < 0) 162 + goto err_serdev; 163 + 164 + return ret; 165 + 166 + err_serdev: 167 + serdev_device_close(serdev); 168 + err_skb: 169 + kfree_skb(phy->recv_skb); 170 + err_exit: 171 + return ret; 172 + } 173 + 174 + static void s3fwrn82_uart_remove(struct serdev_device *serdev) 175 + { 176 + struct s3fwrn82_uart_phy *phy = serdev_device_get_drvdata(serdev); 177 + 178 + s3fwrn5_remove(phy->common.ndev); 179 + serdev_device_close(serdev); 180 + kfree_skb(phy->recv_skb); 181 + } 182 + 183 + static struct serdev_device_driver s3fwrn82_uart_driver = { 184 + .probe = s3fwrn82_uart_probe, 185 + .remove = s3fwrn82_uart_remove, 186 + .driver = { 187 + .name = "s3fwrn82_uart", 188 + .of_match_table = s3fwrn82_uart_of_match, 189 + }, 190 + }; 191 + 192 + module_serdev_device_driver(s3fwrn82_uart_driver); 193 + 194 + MODULE_LICENSE("GPL"); 195 + MODULE_DESCRIPTION("UART driver for Samsung NFC"); 196 + MODULE_AUTHOR("Bongsu Jeon <bongsu.jeon@samsung.com>");