From patchwork Tue Oct 21 10:37:04 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jiri Prchal X-Patchwork-Id: 5112521 Return-Path: X-Original-To: patchwork-linux-spi@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id BDB029F349 for ; Tue, 21 Oct 2014 10:37:21 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 6D46A2012F for ; Tue, 21 Oct 2014 10:37:20 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 24EB120123 for ; Tue, 21 Oct 2014 10:37:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755084AbaJUKhS (ORCPT ); Tue, 21 Oct 2014 06:37:18 -0400 Received: from router.aksignal.cz ([188.175.113.102]:43427 "EHLO router.aksignal.cz" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755065AbaJUKhR (ORCPT ); Tue, 21 Oct 2014 06:37:17 -0400 Received: from localhost (localhost [127.0.0.1]) by router.aksignal.cz (Postfix) with ESMTP id E70A954C001; Tue, 21 Oct 2014 12:37:13 +0200 (CEST) X-Virus-Scanned: by amavisd-new at aksignal.cz Received: from router.aksignal.cz ([127.0.0.1]) by localhost (router.aksignal.cz [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id zrnXiFIjs2pU; Tue, 21 Oct 2014 12:37:10 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by router.aksignal.cz (Postfix) with ESMTP id 45CDD54C004; Tue, 21 Oct 2014 12:37:10 +0200 (CEST) Received: from localhost.localdomain (unknown [10.0.1.101]) by router.aksignal.cz (Postfix) with ESMTP id 3015B54C001; Tue, 21 Oct 2014 12:37:09 +0200 (CEST) From: Jiri Prchal To: linux-spi@vger.kernel.org, linux-i2c@vger.kernel.org Cc: broonie@kernel.org, nicolas.ferre@atmel.com, wsa@the-dreams.de, Jiri Prchal Subject: [PATCH] misc: eeprom: at25: add Cypress FRAM functionality Date: Tue, 21 Oct 2014 12:37:04 +0200 Message-Id: <7b9c6d95cd40e257db4f10f0a41c3d15ee4350ad.1413887159.git.jiri.prchal@aksignal.cz> X-Mailer: git-send-email 1.9.1 Sender: linux-spi-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-spi@vger.kernel.org X-Spam-Status: No, score=-8.3 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This patch adds functionality for Cypress FRAMs on SPI bus, such as FM25V05, FM25V10 etc. Added to at25 driver: - reading device ID and choose size and addr len from it - serial number reading and exporting it to sysfs - new compatible string Signed-off-by: Jiri Prchal --- Moved changes back to at25.c as Wolfram Sang coment "too much copied code". drivers/misc/eeprom/Kconfig | 5 +- drivers/misc/eeprom/at25.c | 205 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 189 insertions(+), 21 deletions(-) -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-spi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig index 9536852f..130a538 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig @@ -28,10 +28,11 @@ config EEPROM_AT24 will be called at24. config EEPROM_AT25 - tristate "SPI EEPROMs from most vendors" + tristate "SPI EEPROMs (FRAMs) from most vendors" depends on SPI && SYSFS help - Enable this driver to get read/write support to most SPI EEPROMs, + Enable this driver to get read/write support to most SPI EEPROMs + and Cypress FRAMs, after you configure the board init code to know about each eeprom on your target board. diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index 634f729..7bb9a6d 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -1,7 +1,9 @@ /* * at25.c -- support most SPI EEPROMs, such as Atmel AT25 models + * and Cypress FRAMs FM25 models * * Copyright (C) 2006 David Brownell + * 2014 Jiri Prchal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +21,7 @@ #include #include #include +#include /* * NOTE: this is an *EEPROM* driver. The vagaries of product naming @@ -34,6 +37,7 @@ struct at25_data { struct spi_eeprom chip; struct bin_attribute bin; unsigned addrlen; + int has_sernum; }; #define AT25_WREN 0x06 /* latch the write enable */ @@ -42,6 +46,9 @@ struct at25_data { #define AT25_WRSR 0x01 /* write status register */ #define AT25_READ 0x03 /* read byte(s) */ #define AT25_WRITE 0x02 /* write byte(s)/sector */ +#define FM25_SLEEP 0xb9 /* enter sleep mode */ +#define FM25_RDID 0x9f /* read device ID */ +#define FM25_RDSN 0xc3 /* read S/N */ #define AT25_SR_nRDY 0x01 /* nRDY = write-in-progress */ #define AT25_SR_WEN 0x02 /* write enable (latched) */ @@ -51,6 +58,9 @@ struct at25_data { #define AT25_INSTR_BIT3 0x08 /* Additional address bit in instr */ +#define FM25_ID_LEN 9 /* ID lenght */ +#define FM25_SN_LEN 8 /* serial number lenght */ + #define EE_MAXADDRLEN 3 /* 24 bit addresses, up to 2 MBytes */ /* Specs often allow 5 msec for a page write, sometimes 20 msec; @@ -58,6 +68,9 @@ struct at25_data { */ #define EE_TIMEOUT 25 +#define IS_EEPROM 0 +#define IS_FRAM 1 + /*-------------------------------------------------------------------------*/ #define io_limit PAGE_SIZE /* bytes */ @@ -132,6 +145,83 @@ at25_ee_read( } static ssize_t +fm25_id_read(struct at25_data *at25, char *buf) +{ + u8 command = FM25_RDID; + ssize_t status; + struct spi_transfer t[2]; + struct spi_message m; + + spi_message_init(&m); + memset(t, 0, sizeof t); + + t[0].tx_buf = &command; + t[0].len = 1; + spi_message_add_tail(&t[0], &m); + + t[1].rx_buf = buf; + t[1].len = FM25_ID_LEN; + spi_message_add_tail(&t[1], &m); + + mutex_lock(&at25->lock); + + status = spi_sync(at25->spi, &m); + dev_dbg(&at25->spi->dev, + "read %Zd bytes of ID --> %d\n", + FM25_ID_LEN, (int) status); + + mutex_unlock(&at25->lock); + return status ? status : FM25_ID_LEN; +} + +static ssize_t +fm25_sernum_read(struct at25_data *at25, char *buf) +{ + u8 command = FM25_RDSN; + ssize_t status; + struct spi_transfer t[2]; + struct spi_message m; + + spi_message_init(&m); + memset(t, 0, sizeof t); + + t[0].tx_buf = &command; + t[0].len = 1; + spi_message_add_tail(&t[0], &m); + + t[1].rx_buf = buf; + t[1].len = FM25_SN_LEN; + spi_message_add_tail(&t[1], &m); + + mutex_lock(&at25->lock); + + status = spi_sync(at25->spi, &m); + dev_dbg(&at25->spi->dev, + "read %Zd bytes of serial number --> %d\n", + FM25_SN_LEN, (int) status); + + mutex_unlock(&at25->lock); + return status ? status : FM25_SN_LEN; +} + +static ssize_t +sernum_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + char binbuf[FM25_SN_LEN]; + struct at25_data *at25; + int i; + char *pbuf = buf; + + at25 = dev_get_drvdata(dev); + fm25_sernum_read(at25, binbuf); + for (i = 0; i < FM25_SN_LEN; i++) + pbuf += sprintf(pbuf, "%02x ", binbuf[i]); + sprintf(--pbuf, "\n"); + return (3 * i); +} +static const DEVICE_ATTR_RO(sernum); + +static ssize_t at25_bin_read(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) @@ -303,13 +393,20 @@ static ssize_t at25_mem_write(struct memory_accessor *mem, const char *buf, static int at25_np_to_chip(struct device *dev, struct device_node *np, - struct spi_eeprom *chip) + struct spi_eeprom *chip, + int is_fram) { u32 val; memset(chip, 0, sizeof(*chip)); strncpy(chip->name, np->name, sizeof(chip->name)); + if (is_fram) { + if (of_find_property(np, "read-only", NULL)) + chip->flags |= EE_READONLY; + return 0; + } + if (of_property_read_u32(np, "size", &val) == 0 || of_property_read_u32(np, "at25,byte-len", &val) == 0) { chip->byte_len = val; @@ -356,6 +453,13 @@ static int at25_np_to_chip(struct device *dev, return 0; } +static const struct of_device_id at25_of_match[] = { + { .compatible = "atmel,at25", .data = (const void *)IS_EEPROM }, + { .compatible = "cypress,fm25", .data = (const void *)IS_FRAM }, + { } +}; +MODULE_DEVICE_TABLE(of, at25_of_match); + static int at25_probe(struct spi_device *spi) { struct at25_data *at25 = NULL; @@ -364,11 +468,18 @@ static int at25_probe(struct spi_device *spi) int err; int sr; int addrlen; + char id[FM25_ID_LEN]; + const struct of_device_id *match; + int is_fram = 0; + + match = of_match_device(of_match_ptr(at25_of_match), &spi->dev); + if (match) + is_fram = (int)(uintptr_t)match->data; /* Chip description */ if (!spi->dev.platform_data) { if (np) { - err = at25_np_to_chip(&spi->dev, np, &chip); + err = at25_np_to_chip(&spi->dev, np, &chip, is_fram); if (err) return err; } else { @@ -379,15 +490,17 @@ static int at25_probe(struct spi_device *spi) chip = *(struct spi_eeprom *)spi->dev.platform_data; /* For now we only support 8/16/24 bit addressing */ - if (chip.flags & EE_ADDR1) - addrlen = 1; - else if (chip.flags & EE_ADDR2) - addrlen = 2; - else if (chip.flags & EE_ADDR3) - addrlen = 3; - else { - dev_dbg(&spi->dev, "unsupported address type\n"); - return -EINVAL; + if (!is_fram) { + if (chip.flags & EE_ADDR1) + addrlen = 1; + else if (chip.flags & EE_ADDR2) + addrlen = 2; + else if (chip.flags & EE_ADDR3) + addrlen = 3; + else { + dev_dbg(&spi->dev, "unsupported address type\n"); + return -EINVAL; + } } /* Ping the chip ... the status register is pretty portable, @@ -410,6 +523,56 @@ static int at25_probe(struct spi_device *spi) spi_set_drvdata(spi, at25); at25->addrlen = addrlen; + if (is_fram) { + /* Get ID of chip */ + fm25_id_read(at25, id); + if (id[6] != 0xc2) { + dev_err(&spi->dev, + "Error: no Cypress FRAM (id %02x)\n", id[6]); + return -ENODEV; + } + /* set size found in ID */ + switch (id[7]) { + case 0x21: + at25->chip.byte_len = 16 * 1024; + break; + case 0x22: + at25->chip.byte_len = 32 * 1024; + break; + case 0x23: + at25->chip.byte_len = 64 * 1024; + break; + case 0x24: + at25->chip.byte_len = 128 * 1024; + break; + case 0x25: + at25->chip.byte_len = 256 * 1024; + break; + default: + dev_err(&spi->dev, + "Error: unsupported size (id %02x)\n", + id[7]); + return -ENODEV; + break; + } + + if (at25->chip.byte_len > 64 * 1024) { + at25->addrlen = 3; + at25->chip.flags |= EE_ADDR3; + } + else { + at25->addrlen = 2; + at25->chip.flags |= EE_ADDR2; + } + + if (id[8]) + at25->has_sernum = 1; + else + at25->has_sernum = 0; + + at25->chip.page_size = PAGE_SIZE; + } + /* Export the EEPROM bytes through sysfs, since that's convenient. * And maybe to other kernel code; it might hold a board's Ethernet * address, or board-specific calibration data generated on the @@ -420,7 +583,7 @@ static int at25_probe(struct spi_device *spi) * security codes, board-specific manufacturing calibrations, etc. */ sysfs_bin_attr_init(&at25->bin); - at25->bin.attr.name = "eeprom"; + at25->bin.attr.name = is_fram ? "fram" : "eeprom"; at25->bin.attr.mode = S_IRUSR; at25->bin.read = at25_bin_read; at25->mem.read = at25_mem_read; @@ -436,15 +599,23 @@ static int at25_probe(struct spi_device *spi) if (err) return err; + /* Export the FM25 serial number */ + if (at25->has_sernum) { + err = device_create_file(&spi->dev, &dev_attr_sernum); + if (err) + return err; + } + if (chip.setup) chip.setup(&at25->mem, chip.context); - dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n", + dev_info(&spi->dev, "%Zd %s %s %s%s, pagesize %u\n", (at25->bin.size < 1024) ? at25->bin.size : (at25->bin.size / 1024), (at25->bin.size < 1024) ? "Byte" : "KByte", at25->chip.name, + is_fram ? "fram" : "eeprom", (chip.flags & EE_READONLY) ? " (readonly)" : "", at25->chip.page_size); return 0; @@ -456,17 +627,13 @@ static int at25_remove(struct spi_device *spi) at25 = spi_get_drvdata(spi); sysfs_remove_bin_file(&spi->dev.kobj, &at25->bin); + if (at25->has_sernum) + device_remove_file(&spi->dev, &dev_attr_sernum); return 0; } /*-------------------------------------------------------------------------*/ -static const struct of_device_id at25_of_match[] = { - { .compatible = "atmel,at25", }, - { } -}; -MODULE_DEVICE_TABLE(of, at25_of_match); - static struct spi_driver at25_driver = { .driver = { .name = "at25",