18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * SPI driver for Micrel/Kendin KS8995M and KSZ8864RMN ethernet switches 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2008 Gabor Juhos <juhosg at openwrt.org> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * This file was based on: drivers/spi/at25.c 88c2ecf20Sopenharmony_ci * Copyright (C) 2006 David Brownell 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/types.h> 148c2ecf20Sopenharmony_ci#include <linux/kernel.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/delay.h> 178c2ecf20Sopenharmony_ci#include <linux/device.h> 188c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 198c2ecf20Sopenharmony_ci#include <linux/of.h> 208c2ecf20Sopenharmony_ci#include <linux/of_gpio.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#include <linux/spi/spi.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define DRV_VERSION "0.1.1" 258c2ecf20Sopenharmony_ci#define DRV_DESC "Micrel KS8995 Ethernet switch SPI driver" 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------------ */ 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define KS8995_REG_ID0 0x00 /* Chip ID0 */ 308c2ecf20Sopenharmony_ci#define KS8995_REG_ID1 0x01 /* Chip ID1 */ 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define KS8995_REG_GC0 0x02 /* Global Control 0 */ 338c2ecf20Sopenharmony_ci#define KS8995_REG_GC1 0x03 /* Global Control 1 */ 348c2ecf20Sopenharmony_ci#define KS8995_REG_GC2 0x04 /* Global Control 2 */ 358c2ecf20Sopenharmony_ci#define KS8995_REG_GC3 0x05 /* Global Control 3 */ 368c2ecf20Sopenharmony_ci#define KS8995_REG_GC4 0x06 /* Global Control 4 */ 378c2ecf20Sopenharmony_ci#define KS8995_REG_GC5 0x07 /* Global Control 5 */ 388c2ecf20Sopenharmony_ci#define KS8995_REG_GC6 0x08 /* Global Control 6 */ 398c2ecf20Sopenharmony_ci#define KS8995_REG_GC7 0x09 /* Global Control 7 */ 408c2ecf20Sopenharmony_ci#define KS8995_REG_GC8 0x0a /* Global Control 8 */ 418c2ecf20Sopenharmony_ci#define KS8995_REG_GC9 0x0b /* Global Control 9 */ 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci#define KS8995_REG_PC(p, r) ((0x10 * p) + r) /* Port Control */ 448c2ecf20Sopenharmony_ci#define KS8995_REG_PS(p, r) ((0x10 * p) + r + 0xe) /* Port Status */ 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#define KS8995_REG_TPC0 0x60 /* TOS Priority Control 0 */ 478c2ecf20Sopenharmony_ci#define KS8995_REG_TPC1 0x61 /* TOS Priority Control 1 */ 488c2ecf20Sopenharmony_ci#define KS8995_REG_TPC2 0x62 /* TOS Priority Control 2 */ 498c2ecf20Sopenharmony_ci#define KS8995_REG_TPC3 0x63 /* TOS Priority Control 3 */ 508c2ecf20Sopenharmony_ci#define KS8995_REG_TPC4 0x64 /* TOS Priority Control 4 */ 518c2ecf20Sopenharmony_ci#define KS8995_REG_TPC5 0x65 /* TOS Priority Control 5 */ 528c2ecf20Sopenharmony_ci#define KS8995_REG_TPC6 0x66 /* TOS Priority Control 6 */ 538c2ecf20Sopenharmony_ci#define KS8995_REG_TPC7 0x67 /* TOS Priority Control 7 */ 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci#define KS8995_REG_MAC0 0x68 /* MAC address 0 */ 568c2ecf20Sopenharmony_ci#define KS8995_REG_MAC1 0x69 /* MAC address 1 */ 578c2ecf20Sopenharmony_ci#define KS8995_REG_MAC2 0x6a /* MAC address 2 */ 588c2ecf20Sopenharmony_ci#define KS8995_REG_MAC3 0x6b /* MAC address 3 */ 598c2ecf20Sopenharmony_ci#define KS8995_REG_MAC4 0x6c /* MAC address 4 */ 608c2ecf20Sopenharmony_ci#define KS8995_REG_MAC5 0x6d /* MAC address 5 */ 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci#define KS8995_REG_IAC0 0x6e /* Indirect Access Control 0 */ 638c2ecf20Sopenharmony_ci#define KS8995_REG_IAC1 0x6f /* Indirect Access Control 0 */ 648c2ecf20Sopenharmony_ci#define KS8995_REG_IAD7 0x70 /* Indirect Access Data 7 */ 658c2ecf20Sopenharmony_ci#define KS8995_REG_IAD6 0x71 /* Indirect Access Data 6 */ 668c2ecf20Sopenharmony_ci#define KS8995_REG_IAD5 0x72 /* Indirect Access Data 5 */ 678c2ecf20Sopenharmony_ci#define KS8995_REG_IAD4 0x73 /* Indirect Access Data 4 */ 688c2ecf20Sopenharmony_ci#define KS8995_REG_IAD3 0x74 /* Indirect Access Data 3 */ 698c2ecf20Sopenharmony_ci#define KS8995_REG_IAD2 0x75 /* Indirect Access Data 2 */ 708c2ecf20Sopenharmony_ci#define KS8995_REG_IAD1 0x76 /* Indirect Access Data 1 */ 718c2ecf20Sopenharmony_ci#define KS8995_REG_IAD0 0x77 /* Indirect Access Data 0 */ 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci#define KSZ8864_REG_ID1 0xfe /* Chip ID in bit 7 */ 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci#define KS8995_REGS_SIZE 0x80 768c2ecf20Sopenharmony_ci#define KSZ8864_REGS_SIZE 0x100 778c2ecf20Sopenharmony_ci#define KSZ8795_REGS_SIZE 0x100 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci#define ID1_CHIPID_M 0xf 808c2ecf20Sopenharmony_ci#define ID1_CHIPID_S 4 818c2ecf20Sopenharmony_ci#define ID1_REVISION_M 0x7 828c2ecf20Sopenharmony_ci#define ID1_REVISION_S 1 838c2ecf20Sopenharmony_ci#define ID1_START_SW 1 /* start the switch */ 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci#define FAMILY_KS8995 0x95 868c2ecf20Sopenharmony_ci#define FAMILY_KSZ8795 0x87 878c2ecf20Sopenharmony_ci#define CHIPID_M 0 888c2ecf20Sopenharmony_ci#define KS8995_CHIP_ID 0x00 898c2ecf20Sopenharmony_ci#define KSZ8864_CHIP_ID 0x01 908c2ecf20Sopenharmony_ci#define KSZ8795_CHIP_ID 0x09 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci#define KS8995_CMD_WRITE 0x02U 938c2ecf20Sopenharmony_ci#define KS8995_CMD_READ 0x03U 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci#define KS8995_RESET_DELAY 10 /* usec */ 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cienum ks8995_chip_variant { 988c2ecf20Sopenharmony_ci ks8995, 998c2ecf20Sopenharmony_ci ksz8864, 1008c2ecf20Sopenharmony_ci ksz8795, 1018c2ecf20Sopenharmony_ci max_variant 1028c2ecf20Sopenharmony_ci}; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistruct ks8995_chip_params { 1058c2ecf20Sopenharmony_ci char *name; 1068c2ecf20Sopenharmony_ci int family_id; 1078c2ecf20Sopenharmony_ci int chip_id; 1088c2ecf20Sopenharmony_ci int regs_size; 1098c2ecf20Sopenharmony_ci int addr_width; 1108c2ecf20Sopenharmony_ci int addr_shift; 1118c2ecf20Sopenharmony_ci}; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic const struct ks8995_chip_params ks8995_chip[] = { 1148c2ecf20Sopenharmony_ci [ks8995] = { 1158c2ecf20Sopenharmony_ci .name = "KS8995MA", 1168c2ecf20Sopenharmony_ci .family_id = FAMILY_KS8995, 1178c2ecf20Sopenharmony_ci .chip_id = KS8995_CHIP_ID, 1188c2ecf20Sopenharmony_ci .regs_size = KS8995_REGS_SIZE, 1198c2ecf20Sopenharmony_ci .addr_width = 8, 1208c2ecf20Sopenharmony_ci .addr_shift = 0, 1218c2ecf20Sopenharmony_ci }, 1228c2ecf20Sopenharmony_ci [ksz8864] = { 1238c2ecf20Sopenharmony_ci .name = "KSZ8864RMN", 1248c2ecf20Sopenharmony_ci .family_id = FAMILY_KS8995, 1258c2ecf20Sopenharmony_ci .chip_id = KSZ8864_CHIP_ID, 1268c2ecf20Sopenharmony_ci .regs_size = KSZ8864_REGS_SIZE, 1278c2ecf20Sopenharmony_ci .addr_width = 8, 1288c2ecf20Sopenharmony_ci .addr_shift = 0, 1298c2ecf20Sopenharmony_ci }, 1308c2ecf20Sopenharmony_ci [ksz8795] = { 1318c2ecf20Sopenharmony_ci .name = "KSZ8795CLX", 1328c2ecf20Sopenharmony_ci .family_id = FAMILY_KSZ8795, 1338c2ecf20Sopenharmony_ci .chip_id = KSZ8795_CHIP_ID, 1348c2ecf20Sopenharmony_ci .regs_size = KSZ8795_REGS_SIZE, 1358c2ecf20Sopenharmony_ci .addr_width = 12, 1368c2ecf20Sopenharmony_ci .addr_shift = 1, 1378c2ecf20Sopenharmony_ci }, 1388c2ecf20Sopenharmony_ci}; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_cistruct ks8995_pdata { 1418c2ecf20Sopenharmony_ci int reset_gpio; 1428c2ecf20Sopenharmony_ci enum of_gpio_flags reset_gpio_flags; 1438c2ecf20Sopenharmony_ci}; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_cistruct ks8995_switch { 1468c2ecf20Sopenharmony_ci struct spi_device *spi; 1478c2ecf20Sopenharmony_ci struct mutex lock; 1488c2ecf20Sopenharmony_ci struct ks8995_pdata *pdata; 1498c2ecf20Sopenharmony_ci struct bin_attribute regs_attr; 1508c2ecf20Sopenharmony_ci const struct ks8995_chip_params *chip; 1518c2ecf20Sopenharmony_ci int revision_id; 1528c2ecf20Sopenharmony_ci}; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic const struct spi_device_id ks8995_id[] = { 1558c2ecf20Sopenharmony_ci {"ks8995", ks8995}, 1568c2ecf20Sopenharmony_ci {"ksz8864", ksz8864}, 1578c2ecf20Sopenharmony_ci {"ksz8795", ksz8795}, 1588c2ecf20Sopenharmony_ci { } 1598c2ecf20Sopenharmony_ci}; 1608c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(spi, ks8995_id); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cistatic const struct of_device_id ks8895_spi_of_match[] = { 1638c2ecf20Sopenharmony_ci { .compatible = "micrel,ks8995" }, 1648c2ecf20Sopenharmony_ci { .compatible = "micrel,ksz8864" }, 1658c2ecf20Sopenharmony_ci { .compatible = "micrel,ksz8795" }, 1668c2ecf20Sopenharmony_ci { }, 1678c2ecf20Sopenharmony_ci }; 1688c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ks8895_spi_of_match); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_cistatic inline u8 get_chip_id(u8 val) 1718c2ecf20Sopenharmony_ci{ 1728c2ecf20Sopenharmony_ci return (val >> ID1_CHIPID_S) & ID1_CHIPID_M; 1738c2ecf20Sopenharmony_ci} 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cistatic inline u8 get_chip_rev(u8 val) 1768c2ecf20Sopenharmony_ci{ 1778c2ecf20Sopenharmony_ci return (val >> ID1_REVISION_S) & ID1_REVISION_M; 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci/* create_spi_cmd - create a chip specific SPI command header 1818c2ecf20Sopenharmony_ci * @ks: pointer to switch instance 1828c2ecf20Sopenharmony_ci * @cmd: SPI command for switch 1838c2ecf20Sopenharmony_ci * @address: register address for command 1848c2ecf20Sopenharmony_ci * 1858c2ecf20Sopenharmony_ci * Different chip families use different bit pattern to address the switches 1868c2ecf20Sopenharmony_ci * registers: 1878c2ecf20Sopenharmony_ci * 1888c2ecf20Sopenharmony_ci * KS8995: 8bit command + 8bit address 1898c2ecf20Sopenharmony_ci * KSZ8795: 3bit command + 12bit address + 1bit TR (?) 1908c2ecf20Sopenharmony_ci */ 1918c2ecf20Sopenharmony_cistatic inline __be16 create_spi_cmd(struct ks8995_switch *ks, int cmd, 1928c2ecf20Sopenharmony_ci unsigned address) 1938c2ecf20Sopenharmony_ci{ 1948c2ecf20Sopenharmony_ci u16 result = cmd; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci /* make room for address (incl. address shift) */ 1978c2ecf20Sopenharmony_ci result <<= ks->chip->addr_width + ks->chip->addr_shift; 1988c2ecf20Sopenharmony_ci /* add address */ 1998c2ecf20Sopenharmony_ci result |= address << ks->chip->addr_shift; 2008c2ecf20Sopenharmony_ci /* SPI protocol needs big endian */ 2018c2ecf20Sopenharmony_ci return cpu_to_be16(result); 2028c2ecf20Sopenharmony_ci} 2038c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------------ */ 2048c2ecf20Sopenharmony_cistatic int ks8995_read(struct ks8995_switch *ks, char *buf, 2058c2ecf20Sopenharmony_ci unsigned offset, size_t count) 2068c2ecf20Sopenharmony_ci{ 2078c2ecf20Sopenharmony_ci __be16 cmd; 2088c2ecf20Sopenharmony_ci struct spi_transfer t[2]; 2098c2ecf20Sopenharmony_ci struct spi_message m; 2108c2ecf20Sopenharmony_ci int err; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci cmd = create_spi_cmd(ks, KS8995_CMD_READ, offset); 2138c2ecf20Sopenharmony_ci spi_message_init(&m); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci memset(&t, 0, sizeof(t)); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci t[0].tx_buf = &cmd; 2188c2ecf20Sopenharmony_ci t[0].len = sizeof(cmd); 2198c2ecf20Sopenharmony_ci spi_message_add_tail(&t[0], &m); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci t[1].rx_buf = buf; 2228c2ecf20Sopenharmony_ci t[1].len = count; 2238c2ecf20Sopenharmony_ci spi_message_add_tail(&t[1], &m); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci mutex_lock(&ks->lock); 2268c2ecf20Sopenharmony_ci err = spi_sync(ks->spi, &m); 2278c2ecf20Sopenharmony_ci mutex_unlock(&ks->lock); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci return err ? err : count; 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic int ks8995_write(struct ks8995_switch *ks, char *buf, 2338c2ecf20Sopenharmony_ci unsigned offset, size_t count) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci __be16 cmd; 2368c2ecf20Sopenharmony_ci struct spi_transfer t[2]; 2378c2ecf20Sopenharmony_ci struct spi_message m; 2388c2ecf20Sopenharmony_ci int err; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci cmd = create_spi_cmd(ks, KS8995_CMD_WRITE, offset); 2418c2ecf20Sopenharmony_ci spi_message_init(&m); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci memset(&t, 0, sizeof(t)); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci t[0].tx_buf = &cmd; 2468c2ecf20Sopenharmony_ci t[0].len = sizeof(cmd); 2478c2ecf20Sopenharmony_ci spi_message_add_tail(&t[0], &m); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci t[1].tx_buf = buf; 2508c2ecf20Sopenharmony_ci t[1].len = count; 2518c2ecf20Sopenharmony_ci spi_message_add_tail(&t[1], &m); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci mutex_lock(&ks->lock); 2548c2ecf20Sopenharmony_ci err = spi_sync(ks->spi, &m); 2558c2ecf20Sopenharmony_ci mutex_unlock(&ks->lock); 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci return err ? err : count; 2588c2ecf20Sopenharmony_ci} 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cistatic inline int ks8995_read_reg(struct ks8995_switch *ks, u8 addr, u8 *buf) 2618c2ecf20Sopenharmony_ci{ 2628c2ecf20Sopenharmony_ci return ks8995_read(ks, buf, addr, 1) != 1; 2638c2ecf20Sopenharmony_ci} 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_cistatic inline int ks8995_write_reg(struct ks8995_switch *ks, u8 addr, u8 val) 2668c2ecf20Sopenharmony_ci{ 2678c2ecf20Sopenharmony_ci char buf = val; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci return ks8995_write(ks, &buf, addr, 1) != 1; 2708c2ecf20Sopenharmony_ci} 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------------ */ 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_cistatic int ks8995_stop(struct ks8995_switch *ks) 2758c2ecf20Sopenharmony_ci{ 2768c2ecf20Sopenharmony_ci return ks8995_write_reg(ks, KS8995_REG_ID1, 0); 2778c2ecf20Sopenharmony_ci} 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_cistatic int ks8995_start(struct ks8995_switch *ks) 2808c2ecf20Sopenharmony_ci{ 2818c2ecf20Sopenharmony_ci return ks8995_write_reg(ks, KS8995_REG_ID1, 1); 2828c2ecf20Sopenharmony_ci} 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistatic int ks8995_reset(struct ks8995_switch *ks) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci int err; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci err = ks8995_stop(ks); 2898c2ecf20Sopenharmony_ci if (err) 2908c2ecf20Sopenharmony_ci return err; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci udelay(KS8995_RESET_DELAY); 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci return ks8995_start(ks); 2958c2ecf20Sopenharmony_ci} 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_cistatic ssize_t ks8995_registers_read(struct file *filp, struct kobject *kobj, 2988c2ecf20Sopenharmony_ci struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) 2998c2ecf20Sopenharmony_ci{ 3008c2ecf20Sopenharmony_ci struct device *dev; 3018c2ecf20Sopenharmony_ci struct ks8995_switch *ks8995; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci dev = kobj_to_dev(kobj); 3048c2ecf20Sopenharmony_ci ks8995 = dev_get_drvdata(dev); 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci return ks8995_read(ks8995, buf, off, count); 3078c2ecf20Sopenharmony_ci} 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_cistatic ssize_t ks8995_registers_write(struct file *filp, struct kobject *kobj, 3108c2ecf20Sopenharmony_ci struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) 3118c2ecf20Sopenharmony_ci{ 3128c2ecf20Sopenharmony_ci struct device *dev; 3138c2ecf20Sopenharmony_ci struct ks8995_switch *ks8995; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci dev = kobj_to_dev(kobj); 3168c2ecf20Sopenharmony_ci ks8995 = dev_get_drvdata(dev); 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci return ks8995_write(ks8995, buf, off, count); 3198c2ecf20Sopenharmony_ci} 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci/* ks8995_get_revision - get chip revision 3228c2ecf20Sopenharmony_ci * @ks: pointer to switch instance 3238c2ecf20Sopenharmony_ci * 3248c2ecf20Sopenharmony_ci * Verify chip family and id and get chip revision. 3258c2ecf20Sopenharmony_ci */ 3268c2ecf20Sopenharmony_cistatic int ks8995_get_revision(struct ks8995_switch *ks) 3278c2ecf20Sopenharmony_ci{ 3288c2ecf20Sopenharmony_ci int err; 3298c2ecf20Sopenharmony_ci u8 id0, id1, ksz8864_id; 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci /* read family id */ 3328c2ecf20Sopenharmony_ci err = ks8995_read_reg(ks, KS8995_REG_ID0, &id0); 3338c2ecf20Sopenharmony_ci if (err) { 3348c2ecf20Sopenharmony_ci err = -EIO; 3358c2ecf20Sopenharmony_ci goto err_out; 3368c2ecf20Sopenharmony_ci } 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci /* verify family id */ 3398c2ecf20Sopenharmony_ci if (id0 != ks->chip->family_id) { 3408c2ecf20Sopenharmony_ci dev_err(&ks->spi->dev, "chip family id mismatch: expected 0x%02x but 0x%02x read\n", 3418c2ecf20Sopenharmony_ci ks->chip->family_id, id0); 3428c2ecf20Sopenharmony_ci err = -ENODEV; 3438c2ecf20Sopenharmony_ci goto err_out; 3448c2ecf20Sopenharmony_ci } 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_ci switch (ks->chip->family_id) { 3478c2ecf20Sopenharmony_ci case FAMILY_KS8995: 3488c2ecf20Sopenharmony_ci /* try reading chip id at CHIP ID1 */ 3498c2ecf20Sopenharmony_ci err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1); 3508c2ecf20Sopenharmony_ci if (err) { 3518c2ecf20Sopenharmony_ci err = -EIO; 3528c2ecf20Sopenharmony_ci goto err_out; 3538c2ecf20Sopenharmony_ci } 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_ci /* verify chip id */ 3568c2ecf20Sopenharmony_ci if ((get_chip_id(id1) == CHIPID_M) && 3578c2ecf20Sopenharmony_ci (get_chip_id(id1) == ks->chip->chip_id)) { 3588c2ecf20Sopenharmony_ci /* KS8995MA */ 3598c2ecf20Sopenharmony_ci ks->revision_id = get_chip_rev(id1); 3608c2ecf20Sopenharmony_ci } else if (get_chip_id(id1) != CHIPID_M) { 3618c2ecf20Sopenharmony_ci /* KSZ8864RMN */ 3628c2ecf20Sopenharmony_ci err = ks8995_read_reg(ks, KS8995_REG_ID1, &ksz8864_id); 3638c2ecf20Sopenharmony_ci if (err) { 3648c2ecf20Sopenharmony_ci err = -EIO; 3658c2ecf20Sopenharmony_ci goto err_out; 3668c2ecf20Sopenharmony_ci } 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci if ((ksz8864_id & 0x80) && 3698c2ecf20Sopenharmony_ci (ks->chip->chip_id == KSZ8864_CHIP_ID)) { 3708c2ecf20Sopenharmony_ci ks->revision_id = get_chip_rev(id1); 3718c2ecf20Sopenharmony_ci } 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci } else { 3748c2ecf20Sopenharmony_ci dev_err(&ks->spi->dev, "unsupported chip id for KS8995 family: 0x%02x\n", 3758c2ecf20Sopenharmony_ci id1); 3768c2ecf20Sopenharmony_ci err = -ENODEV; 3778c2ecf20Sopenharmony_ci } 3788c2ecf20Sopenharmony_ci break; 3798c2ecf20Sopenharmony_ci case FAMILY_KSZ8795: 3808c2ecf20Sopenharmony_ci /* try reading chip id at CHIP ID1 */ 3818c2ecf20Sopenharmony_ci err = ks8995_read_reg(ks, KS8995_REG_ID1, &id1); 3828c2ecf20Sopenharmony_ci if (err) { 3838c2ecf20Sopenharmony_ci err = -EIO; 3848c2ecf20Sopenharmony_ci goto err_out; 3858c2ecf20Sopenharmony_ci } 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci if (get_chip_id(id1) == ks->chip->chip_id) { 3888c2ecf20Sopenharmony_ci ks->revision_id = get_chip_rev(id1); 3898c2ecf20Sopenharmony_ci } else { 3908c2ecf20Sopenharmony_ci dev_err(&ks->spi->dev, "unsupported chip id for KSZ8795 family: 0x%02x\n", 3918c2ecf20Sopenharmony_ci id1); 3928c2ecf20Sopenharmony_ci err = -ENODEV; 3938c2ecf20Sopenharmony_ci } 3948c2ecf20Sopenharmony_ci break; 3958c2ecf20Sopenharmony_ci default: 3968c2ecf20Sopenharmony_ci dev_err(&ks->spi->dev, "unsupported family id: 0x%02x\n", id0); 3978c2ecf20Sopenharmony_ci err = -ENODEV; 3988c2ecf20Sopenharmony_ci break; 3998c2ecf20Sopenharmony_ci } 4008c2ecf20Sopenharmony_cierr_out: 4018c2ecf20Sopenharmony_ci return err; 4028c2ecf20Sopenharmony_ci} 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci/* ks8995_parse_dt - setup platform data from devicetree 4058c2ecf20Sopenharmony_ci * @ks: pointer to switch instance 4068c2ecf20Sopenharmony_ci * 4078c2ecf20Sopenharmony_ci * Parses supported DT properties and sets up platform data 4088c2ecf20Sopenharmony_ci * accordingly. 4098c2ecf20Sopenharmony_ci */ 4108c2ecf20Sopenharmony_cistatic void ks8995_parse_dt(struct ks8995_switch *ks) 4118c2ecf20Sopenharmony_ci{ 4128c2ecf20Sopenharmony_ci struct device_node *np = ks->spi->dev.of_node; 4138c2ecf20Sopenharmony_ci struct ks8995_pdata *pdata = ks->pdata; 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ci if (!np) 4168c2ecf20Sopenharmony_ci return; 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci pdata->reset_gpio = of_get_named_gpio_flags(np, "reset-gpios", 0, 4198c2ecf20Sopenharmony_ci &pdata->reset_gpio_flags); 4208c2ecf20Sopenharmony_ci} 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_cistatic const struct bin_attribute ks8995_registers_attr = { 4238c2ecf20Sopenharmony_ci .attr = { 4248c2ecf20Sopenharmony_ci .name = "registers", 4258c2ecf20Sopenharmony_ci .mode = 0600, 4268c2ecf20Sopenharmony_ci }, 4278c2ecf20Sopenharmony_ci .size = KS8995_REGS_SIZE, 4288c2ecf20Sopenharmony_ci .read = ks8995_registers_read, 4298c2ecf20Sopenharmony_ci .write = ks8995_registers_write, 4308c2ecf20Sopenharmony_ci}; 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------------ */ 4338c2ecf20Sopenharmony_cistatic int ks8995_probe(struct spi_device *spi) 4348c2ecf20Sopenharmony_ci{ 4358c2ecf20Sopenharmony_ci struct ks8995_switch *ks; 4368c2ecf20Sopenharmony_ci int err; 4378c2ecf20Sopenharmony_ci int variant = spi_get_device_id(spi)->driver_data; 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci if (variant >= max_variant) { 4408c2ecf20Sopenharmony_ci dev_err(&spi->dev, "bad chip variant %d\n", variant); 4418c2ecf20Sopenharmony_ci return -ENODEV; 4428c2ecf20Sopenharmony_ci } 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci ks = devm_kzalloc(&spi->dev, sizeof(*ks), GFP_KERNEL); 4458c2ecf20Sopenharmony_ci if (!ks) 4468c2ecf20Sopenharmony_ci return -ENOMEM; 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci mutex_init(&ks->lock); 4498c2ecf20Sopenharmony_ci ks->spi = spi; 4508c2ecf20Sopenharmony_ci ks->chip = &ks8995_chip[variant]; 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci if (ks->spi->dev.of_node) { 4538c2ecf20Sopenharmony_ci ks->pdata = devm_kzalloc(&spi->dev, sizeof(*ks->pdata), 4548c2ecf20Sopenharmony_ci GFP_KERNEL); 4558c2ecf20Sopenharmony_ci if (!ks->pdata) 4568c2ecf20Sopenharmony_ci return -ENOMEM; 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci ks->pdata->reset_gpio = -1; 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_ci ks8995_parse_dt(ks); 4618c2ecf20Sopenharmony_ci } 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci if (!ks->pdata) 4648c2ecf20Sopenharmony_ci ks->pdata = spi->dev.platform_data; 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci /* de-assert switch reset */ 4678c2ecf20Sopenharmony_ci if (ks->pdata && gpio_is_valid(ks->pdata->reset_gpio)) { 4688c2ecf20Sopenharmony_ci unsigned long flags; 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci flags = (ks->pdata->reset_gpio_flags == OF_GPIO_ACTIVE_LOW ? 4718c2ecf20Sopenharmony_ci GPIOF_ACTIVE_LOW : 0); 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci err = devm_gpio_request_one(&spi->dev, 4748c2ecf20Sopenharmony_ci ks->pdata->reset_gpio, 4758c2ecf20Sopenharmony_ci flags, "switch-reset"); 4768c2ecf20Sopenharmony_ci if (err) { 4778c2ecf20Sopenharmony_ci dev_err(&spi->dev, 4788c2ecf20Sopenharmony_ci "failed to get reset-gpios: %d\n", err); 4798c2ecf20Sopenharmony_ci return -EIO; 4808c2ecf20Sopenharmony_ci } 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci gpiod_set_value(gpio_to_desc(ks->pdata->reset_gpio), 0); 4838c2ecf20Sopenharmony_ci } 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci spi_set_drvdata(spi, ks); 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_ci spi->mode = SPI_MODE_0; 4888c2ecf20Sopenharmony_ci spi->bits_per_word = 8; 4898c2ecf20Sopenharmony_ci err = spi_setup(spi); 4908c2ecf20Sopenharmony_ci if (err) { 4918c2ecf20Sopenharmony_ci dev_err(&spi->dev, "spi_setup failed, err=%d\n", err); 4928c2ecf20Sopenharmony_ci return err; 4938c2ecf20Sopenharmony_ci } 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci err = ks8995_get_revision(ks); 4968c2ecf20Sopenharmony_ci if (err) 4978c2ecf20Sopenharmony_ci return err; 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci memcpy(&ks->regs_attr, &ks8995_registers_attr, sizeof(ks->regs_attr)); 5008c2ecf20Sopenharmony_ci ks->regs_attr.size = ks->chip->regs_size; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci err = ks8995_reset(ks); 5038c2ecf20Sopenharmony_ci if (err) 5048c2ecf20Sopenharmony_ci return err; 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci sysfs_attr_init(&ks->regs_attr.attr); 5078c2ecf20Sopenharmony_ci err = sysfs_create_bin_file(&spi->dev.kobj, &ks->regs_attr); 5088c2ecf20Sopenharmony_ci if (err) { 5098c2ecf20Sopenharmony_ci dev_err(&spi->dev, "unable to create sysfs file, err=%d\n", 5108c2ecf20Sopenharmony_ci err); 5118c2ecf20Sopenharmony_ci return err; 5128c2ecf20Sopenharmony_ci } 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci dev_info(&spi->dev, "%s device found, Chip ID:%x, Revision:%x\n", 5158c2ecf20Sopenharmony_ci ks->chip->name, ks->chip->chip_id, ks->revision_id); 5168c2ecf20Sopenharmony_ci 5178c2ecf20Sopenharmony_ci return 0; 5188c2ecf20Sopenharmony_ci} 5198c2ecf20Sopenharmony_ci 5208c2ecf20Sopenharmony_cistatic int ks8995_remove(struct spi_device *spi) 5218c2ecf20Sopenharmony_ci{ 5228c2ecf20Sopenharmony_ci struct ks8995_switch *ks = spi_get_drvdata(spi); 5238c2ecf20Sopenharmony_ci 5248c2ecf20Sopenharmony_ci sysfs_remove_bin_file(&spi->dev.kobj, &ks->regs_attr); 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_ci /* assert reset */ 5278c2ecf20Sopenharmony_ci if (ks->pdata && gpio_is_valid(ks->pdata->reset_gpio)) 5288c2ecf20Sopenharmony_ci gpiod_set_value(gpio_to_desc(ks->pdata->reset_gpio), 1); 5298c2ecf20Sopenharmony_ci 5308c2ecf20Sopenharmony_ci return 0; 5318c2ecf20Sopenharmony_ci} 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------------ */ 5348c2ecf20Sopenharmony_cistatic struct spi_driver ks8995_driver = { 5358c2ecf20Sopenharmony_ci .driver = { 5368c2ecf20Sopenharmony_ci .name = "spi-ks8995", 5378c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(ks8895_spi_of_match), 5388c2ecf20Sopenharmony_ci }, 5398c2ecf20Sopenharmony_ci .probe = ks8995_probe, 5408c2ecf20Sopenharmony_ci .remove = ks8995_remove, 5418c2ecf20Sopenharmony_ci .id_table = ks8995_id, 5428c2ecf20Sopenharmony_ci}; 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_cimodule_spi_driver(ks8995_driver); 5458c2ecf20Sopenharmony_ci 5468c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRV_DESC); 5478c2ecf20Sopenharmony_ciMODULE_VERSION(DRV_VERSION); 5488c2ecf20Sopenharmony_ciMODULE_AUTHOR("Gabor Juhos <juhosg at openwrt.org>"); 5498c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 550